initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/.vscode
|
||||
1514
Cargo.lock
generated
Normal file
1514
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "gloom-rs"
|
||||
version = "0.1.0"
|
||||
authors = ["Michael H. Gimle <michael.gimle@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
glutin = "0.24.1"
|
||||
gl = "0.14.0"
|
||||
tobj = "2.0.2"
|
||||
image = "0.23.8"
|
||||
nalgebra-glm = "0.7.0"
|
||||
8
shaders/simple.frag
Normal file
8
shaders/simple.frag
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 460 core
|
||||
layout(binding = 0) uniform sampler2D t;
|
||||
in vec3 vColor;
|
||||
in vec2 vUv;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
color = vec4(vUv, 0, 1.0f);
|
||||
}
|
||||
16
shaders/simple.vert
Normal file
16
shaders/simple.vert
Normal file
@@ -0,0 +1,16 @@
|
||||
#version 460 core
|
||||
layout (location = 0) in vec3 pos;
|
||||
layout (location = 1) in vec3 iColor;
|
||||
layout (location = 2) in vec2 uv;
|
||||
layout (location = 0) uniform float val;
|
||||
// layout (location = 0) uniform mat4 P;
|
||||
// layout (location = 1) uniform mat4 V;
|
||||
// layout (location = 2) uniform mat4 M;
|
||||
out vec3 vColor;
|
||||
out vec2 vUv;
|
||||
void main() {
|
||||
// gl_Position = P * V * M * vec4(pos.xyz, 1.0f);
|
||||
gl_Position = vec4(pos.x, pos.y + val, pos.z, 1.0f);
|
||||
vColor = iColor;
|
||||
vUv = uv;
|
||||
}
|
||||
190
src/main.rs
Normal file
190
src/main.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
extern crate nalgebra_glm as glm;
|
||||
use gl::types::*;
|
||||
use std::{
|
||||
mem,
|
||||
ptr,
|
||||
str,
|
||||
os::raw::c_void,
|
||||
};
|
||||
use std::thread;
|
||||
use std::sync::{Mutex, Arc, RwLock};
|
||||
|
||||
mod shader;
|
||||
mod util;
|
||||
|
||||
use glutin::event::{Event, WindowEvent, KeyboardInput, ElementState::{Pressed, Released}, VirtualKeyCode::{self, *}};
|
||||
use glutin::event_loop::ControlFlow;
|
||||
|
||||
const SCREEN_W: u32 = 800;
|
||||
const SCREEN_H: u32 = 600;
|
||||
|
||||
// Helper functions to make interacting with OpenGL a little bit prettier. You will need these!
|
||||
// The names should be pretty self explanatory
|
||||
fn byte_size_of_array<T>(val: &[T]) -> isize {
|
||||
std::mem::size_of_val(&val[..]) as isize
|
||||
}
|
||||
|
||||
// Get the OpenGL-compatible pointer to an arbitrary array of numbers
|
||||
fn pointer_to_array<T>(val: &[T]) -> *const c_void {
|
||||
&val[0] as *const T as *const c_void
|
||||
}
|
||||
|
||||
// Get the size of the given type in bytes
|
||||
fn size_of<T>() -> i32 {
|
||||
mem::size_of::<T>() as i32
|
||||
}
|
||||
|
||||
// Get an offset in bytes for n units of type T
|
||||
fn offset<T>(n: u32) -> *const c_void {
|
||||
(n * mem::size_of::<T>() as u32) as *const T as *const c_void
|
||||
}
|
||||
|
||||
// == // Modify and complete the function below for the first task
|
||||
// unsafe fn FUNCTION_NAME(ARGUMENT_NAME: &Vec<f32>, ARGUMENT_NAME: &Vec<u32>) -> u32 { }
|
||||
|
||||
fn main() {
|
||||
// Set up the necessary objects to deal with windows and event handling
|
||||
let el = glutin::event_loop::EventLoop::new();
|
||||
let wb = glutin::window::WindowBuilder::new()
|
||||
.with_title("Gloom-rs")
|
||||
.with_resizable(false)
|
||||
.with_inner_size(glutin::dpi::LogicalSize::new(SCREEN_W, SCREEN_H));
|
||||
let cb = glutin::ContextBuilder::new()
|
||||
.with_vsync(true);
|
||||
let windowed_context = cb.build_windowed(wb, &el).unwrap();
|
||||
|
||||
// Set up a shared vector for keeping track of currently pressed keys
|
||||
let arc_pressed_keys = Arc::new(Mutex::new(Vec::<VirtualKeyCode>::with_capacity(10)));
|
||||
// Send a copy of this vector to send to the render thread
|
||||
let pressed_keys = Arc::clone(&arc_pressed_keys);
|
||||
|
||||
// Spawn a separate thread for rendering, so event handling doesn't block rendering
|
||||
let render_thread = thread::spawn(move || {
|
||||
// Acquire the OpenGL Context and load the function pointers. This has to be done inside of the renderin thread, because
|
||||
// an active OpenGL context cannot safely traverse a thread boundary
|
||||
let context = unsafe {
|
||||
let c = windowed_context.make_current().unwrap();
|
||||
gl::load_with(|symbol| c.get_proc_address(symbol) as *const _);
|
||||
c
|
||||
};
|
||||
|
||||
// Set up openGL
|
||||
unsafe {
|
||||
gl::Enable(gl::CULL_FACE);
|
||||
gl::Disable(gl::MULTISAMPLE);
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
gl::DebugMessageCallback(Some(util::debug_callback), ptr::null());
|
||||
}
|
||||
|
||||
// == // Set up your VAO here
|
||||
|
||||
|
||||
|
||||
|
||||
// Used to demonstrate keyboard handling -- feel free to remove
|
||||
let mut _arbitrary_number = 0.0;
|
||||
|
||||
let first_frame_time = std::time::Instant::now();
|
||||
let mut last_frame_time = first_frame_time;
|
||||
// The main rendering loop
|
||||
loop {
|
||||
let now = std::time::Instant::now();
|
||||
let elapsed = now.duration_since(first_frame_time).as_secs_f32();
|
||||
let delta_time = now.duration_since(last_frame_time).as_secs_f32();
|
||||
last_frame_time = now;
|
||||
|
||||
// Handle keyboard input
|
||||
if let Ok(keys) = pressed_keys.lock() {
|
||||
for key in keys.iter() {
|
||||
match key {
|
||||
VirtualKeyCode::A => {
|
||||
_arbitrary_number += delta_time;
|
||||
},
|
||||
VirtualKeyCode::D => {
|
||||
_arbitrary_number -= delta_time;
|
||||
},
|
||||
|
||||
|
||||
_ => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl::ClearColor(0.163, 0.163, 0.163, 1.0);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
// Issue the necessary commands to draw your scene here
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
context.swap_buffers().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// Keep track of the health of the rendering thread
|
||||
let render_thread_healthy = Arc::new(RwLock::new(true));
|
||||
let render_thread_watchdog = Arc::clone(&render_thread_healthy);
|
||||
thread::spawn(move || {
|
||||
if !render_thread.join().is_ok() {
|
||||
if let Ok(mut health) = render_thread_watchdog.write() {
|
||||
println!("Render thread panicked!");
|
||||
*health = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start the event loop -- This is where window events get handled
|
||||
el.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
// Terminate program if render thread panics
|
||||
if let Ok(health) = render_thread_healthy.read() {
|
||||
if *health == false {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
},
|
||||
// Keep track of currently pressed keys to send to the rendering thread
|
||||
Event::WindowEvent { event: WindowEvent::KeyboardInput {
|
||||
input: KeyboardInput { state: key_state, virtual_keycode: Some(keycode), .. }, .. }, .. } => {
|
||||
|
||||
if let Ok(mut keys) = arc_pressed_keys.lock() {
|
||||
match key_state {
|
||||
Released => {
|
||||
if keys.contains(&keycode) {
|
||||
let i = keys.iter().position(|&k| k == keycode).unwrap();
|
||||
keys.remove(i);
|
||||
}
|
||||
},
|
||||
Pressed => {
|
||||
if !keys.contains(&keycode) {
|
||||
keys.push(keycode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle escape separately
|
||||
match keycode {
|
||||
Escape => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
},
|
||||
_ => { }
|
||||
}
|
||||
},
|
||||
_ => { }
|
||||
}
|
||||
});
|
||||
}
|
||||
141
src/shader.rs
Normal file
141
src/shader.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use gl;
|
||||
use std::{
|
||||
ptr,
|
||||
str,
|
||||
ffi::CString,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub struct Shader {
|
||||
pub program_id: u32,
|
||||
}
|
||||
|
||||
pub struct ShaderBuilder {
|
||||
program_id: u32,
|
||||
shaders: Vec::<u32>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum ShaderType {
|
||||
Vertex,
|
||||
Fragment,
|
||||
TessellationControl,
|
||||
TessellationEvaluation,
|
||||
Geometry,
|
||||
}
|
||||
|
||||
impl Into<gl::types::GLenum> for ShaderType {
|
||||
fn into(self) -> gl::types::GLenum {
|
||||
match self {
|
||||
ShaderType::Vertex => { gl::VERTEX_SHADER },
|
||||
ShaderType::Fragment => { gl::FRAGMENT_SHADER },
|
||||
ShaderType::TessellationControl => { gl::TESS_CONTROL_SHADER },
|
||||
ShaderType::TessellationEvaluation => { gl::TESS_EVALUATION_SHADER } ,
|
||||
ShaderType::Geometry => { gl::GEOMETRY_SHADER },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShaderType {
|
||||
fn from_ext(ext: &std::ffi::OsStr) -> Result<ShaderType, String> {
|
||||
match ext.to_str().expect("Failed to read extension") {
|
||||
"vert" => { Ok(ShaderType::Vertex) },
|
||||
"frag" => { Ok(ShaderType::Fragment) },
|
||||
"tcs" => { Ok(ShaderType::TessellationControl) },
|
||||
"tes" => { Ok(ShaderType::TessellationEvaluation) },
|
||||
"geom" => { Ok(ShaderType::Geometry) },
|
||||
e => { Err(e.to_string()) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShaderBuilder {
|
||||
pub unsafe fn new() -> ShaderBuilder {
|
||||
ShaderBuilder {
|
||||
program_id: gl::CreateProgram(),
|
||||
shaders: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn attach_file(self, shader_path: &str) -> ShaderBuilder {
|
||||
let path = Path::new(shader_path);
|
||||
if let Some(extension) = path.extension() {
|
||||
let shader_type = ShaderType::from_ext(extension)
|
||||
.expect("Failed to parse file extension.");
|
||||
let shader_src = std::fs::read_to_string(path)
|
||||
.expect(&format!("Failed to read shader source. {}", shader_path));
|
||||
self.compile_shader(&shader_src, shader_type)
|
||||
} else {
|
||||
panic!("Failed to read extension of file with path: {}", shader_path);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn compile_shader(mut self, shader_src: &str, shader_type: ShaderType) -> ShaderBuilder {
|
||||
let shader = gl::CreateShader(shader_type.into());
|
||||
let c_str_shader = CString::new(shader_src.as_bytes()).unwrap();
|
||||
gl::ShaderSource(shader, 1, &c_str_shader.as_ptr(), ptr::null());
|
||||
gl::CompileShader(shader);
|
||||
|
||||
if !self.check_shader_errors(shader) {
|
||||
panic!("Shader failed to compile.");
|
||||
}
|
||||
|
||||
self.shaders.push(shader);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
unsafe fn check_shader_errors(&self, shader_id: u32) -> bool {
|
||||
let mut success = i32::from(gl::FALSE);
|
||||
let mut info_log = Vec::with_capacity(512);
|
||||
info_log.set_len(512 - 1);
|
||||
gl::GetShaderiv(shader_id, gl::COMPILE_STATUS, &mut success);
|
||||
if success != i32::from(gl::TRUE) {
|
||||
gl::GetShaderInfoLog(
|
||||
shader_id,
|
||||
512,
|
||||
ptr::null_mut(),
|
||||
info_log.as_mut_ptr() as *mut gl::types::GLchar,
|
||||
);
|
||||
println!("ERROR::Shader Compilation Failed!\n{}", String::from_utf8_lossy(&info_log));
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn check_linker_errors(&self) -> bool {
|
||||
let mut success = i32::from(gl::FALSE);
|
||||
let mut info_log = Vec::with_capacity(512);
|
||||
info_log.set_len(512 - 1);
|
||||
gl::GetProgramiv(self.program_id, gl::LINK_STATUS, &mut success);
|
||||
if success != i32::from(gl::TRUE) {
|
||||
gl::GetProgramInfoLog(
|
||||
self.program_id,
|
||||
512,
|
||||
ptr::null_mut(),
|
||||
info_log.as_mut_ptr() as *mut gl::types::GLchar,
|
||||
);
|
||||
println!("ERROR::SHADER::PROGRAM::COMPILATION_FAILED\n{}", String::from_utf8_lossy(&info_log));
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub unsafe fn link(self) -> Shader {
|
||||
for &shader in &self.shaders {
|
||||
gl::AttachShader(self.program_id, shader);
|
||||
}
|
||||
gl::LinkProgram(self.program_id);
|
||||
|
||||
// todo:: use this to make safer abstraction
|
||||
self.check_linker_errors();
|
||||
|
||||
for &shader in &self.shaders {
|
||||
gl::DeleteShader(shader);
|
||||
}
|
||||
|
||||
Shader {
|
||||
program_id: self.program_id
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/util.rs
Normal file
28
src/util.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use std::ffi::CString;
|
||||
|
||||
// Debug callback to panic upon enountering any OpenGL error
|
||||
pub extern "system" fn debug_callback(
|
||||
source: u32, e_type: u32, id: u32,
|
||||
severity: u32, _length: i32,
|
||||
msg: *const i8, _data: *mut std::ffi::c_void
|
||||
) {
|
||||
if e_type != gl::DEBUG_TYPE_ERROR { return }
|
||||
if severity == gl::DEBUG_SEVERITY_HIGH ||
|
||||
severity == gl::DEBUG_SEVERITY_MEDIUM ||
|
||||
severity == gl::DEBUG_SEVERITY_LOW
|
||||
{
|
||||
let severity_string = match severity {
|
||||
gl::DEBUG_SEVERITY_HIGH => "high",
|
||||
gl::DEBUG_SEVERITY_MEDIUM => "medium",
|
||||
gl::DEBUG_SEVERITY_LOW => "low",
|
||||
_ => "unknown",
|
||||
};
|
||||
unsafe {
|
||||
let string = CString::from_raw(msg as *mut i8);
|
||||
let error_message = String::from_utf8_lossy(string.as_bytes()).to_string();
|
||||
panic!("{}: Error of severity {} raised from {}: {}\n",
|
||||
id, severity_string, source, error_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user