summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engine/fragment.glsl7
-rw-r--r--engine/src/lib.rs7
-rw-r--r--engine/src/object.rs69
-rw-r--r--engine/src/renderer/mod.rs79
-rw-r--r--engine/src/renderer/vertex_array.rs62
-rw-r--r--engine/src/renderer/vertex_buffers.rs83
-rw-r--r--engine/src/shader.rs198
-rw-r--r--engine/vertex.glsl7
8 files changed, 507 insertions, 5 deletions
diff --git a/engine/fragment.glsl b/engine/fragment.glsl
new file mode 100644
index 0000000..0c56a32
--- /dev/null
+++ b/engine/fragment.glsl
@@ -0,0 +1,7 @@
+#version 330 core
+out vec4 FragColor;
+
+void main()
+{
+ FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
+}
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
index ea74fd1..28cd2da 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -3,10 +3,13 @@
use glfw::Window;
pub use glfw::WindowSize;
+use crate::object::Object;
use crate::vector::Vec2;
mod renderer;
+mod shader;
+pub mod object;
pub mod vector;
pub struct Engine
@@ -42,12 +45,12 @@ impl Engine
///
/// # Errors
/// Will return `Err` if updating the window fails.
- pub fn start(&self, mut func: impl FnMut()) -> Result<(), Error>
+ pub fn start(&self, objects: &[Object], mut func: impl FnMut()) -> Result<(), Error>
{
while !self.window.should_close() {
func();
- crate::renderer::render();
+ crate::renderer::render(objects.iter().map(Object::renderable));
self.window
.swap_buffers()
diff --git a/engine/src/object.rs b/engine/src/object.rs
new file mode 100644
index 0000000..ff84127
--- /dev/null
+++ b/engine/src/object.rs
@@ -0,0 +1,69 @@
+use crate::renderer::Renderable;
+use crate::shader::{Kind as ShaderKind, Program as ShaderProgram, Shader};
+
+pub struct Object
+{
+ renderable: Renderable,
+}
+
+impl Object
+{
+ /// Returns a new `Object`.
+ ///
+ /// # Errors
+ /// Will return `Err` if shader creation fails or if shader program linking fails.
+ pub fn new(vertices: &[f32]) -> Result<Self, Error>
+ {
+ let vertex_shader = Shader::new(ShaderKind::Vertex);
+
+ vertex_shader
+ .set_source(include_str!("../vertex.glsl"))
+ .map_err(Error::CreateVertexShaderFailed)?;
+
+ vertex_shader
+ .compile()
+ .map_err(Error::CreateVertexShaderFailed)?;
+
+ let fragment_shader = Shader::new(ShaderKind::Fragment);
+
+ fragment_shader
+ .set_source(include_str!("../fragment.glsl"))
+ .map_err(Error::CreateFragmentShaderFailed)?;
+
+ fragment_shader
+ .compile()
+ .map_err(Error::CreateFragmentShaderFailed)?;
+
+ let shader_program = ShaderProgram::new();
+
+ shader_program.attach(&vertex_shader);
+ shader_program.attach(&fragment_shader);
+
+ shader_program
+ .link()
+ .map_err(Error::LinkShaderProgramFailed)?;
+
+ let renderable = Renderable::new(shader_program, vertices);
+
+ Ok(Self { renderable })
+ }
+
+ pub(crate) fn renderable(&self) -> &Renderable
+ {
+ &self.renderable
+ }
+}
+
+/// Object error
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to create vertex shader")]
+ CreateVertexShaderFailed(#[source] crate::shader::Error),
+
+ #[error("Failed to create fragment shader")]
+ CreateFragmentShaderFailed(#[source] crate::shader::Error),
+
+ #[error("Failed to link shader program")]
+ LinkShaderProgramFailed(#[source] crate::shader::Error),
+}
diff --git a/engine/src/renderer/mod.rs b/engine/src/renderer/mod.rs
index 9cd379d..3c42f93 100644
--- a/engine/src/renderer/mod.rs
+++ b/engine/src/renderer/mod.rs
@@ -1,10 +1,17 @@
-use std::ffi::{c_void, CString};
+use std::ffi::{c_float, c_void, CString};
+use std::mem::size_of;
use std::process::abort;
+use std::ptr::null;
use glfw::WindowSize;
+use crate::renderer::vertex_array::{PrimitiveKind, VertexArray};
+use crate::renderer::vertex_buffers::{BufferUsage, VertexBuffers};
use crate::vector::Vec2;
+mod vertex_array;
+mod vertex_buffers;
+
pub fn initialize(window: &glfw::Window) -> Result<(), Error>
{
gl::load_with(|symbol| {
@@ -30,10 +37,51 @@ pub fn initialize(window: &glfw::Window) -> Result<(), Error>
Ok(())
}
-pub fn render()
+pub fn render<'renderable>(renderables: impl IntoIterator<Item = &'renderable Renderable>)
{
unsafe {
- gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
+ gl::Clear(gl::COLOR_BUFFER_BIT);
+ }
+
+ for renderable in renderables {
+ renderable.shader_program.activate();
+
+ renderable.vertex_arr.draw(PrimitiveKind::Triangles, 0, 3);
+ }
+}
+
+pub struct Renderable
+{
+ shader_program: crate::shader::Program,
+ vertex_arr: VertexArray,
+}
+
+impl Renderable
+{
+ pub fn new(shader_program: crate::shader::Program, vertices: &[f32]) -> Self
+ {
+ let vertex_arr = VertexArray::new();
+
+ vertex_arr.bind();
+
+ let vertex_buffer = VertexBuffers::<1>::new();
+
+ vertex_buffer
+ .store(0, vertices, BufferUsage::Static)
+ .unwrap();
+
+ vertex_attrib_ptr(0);
+ enable_vertex_attrib_array(0);
+
+ unsafe {
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindVertexArray(0);
+ }
+
+ Self {
+ shader_program,
+ vertex_arr,
+ }
}
}
@@ -57,3 +105,28 @@ pub enum Error
#[error("Failed to get window size")]
GetWindowSizeFailed(#[source] glfw::Error),
}
+
+fn vertex_attrib_ptr(index: usize)
+{
+ let stride = 3 * size_of::<c_float>();
+
+ unsafe {
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ gl::VertexAttribPointer(
+ index as gl::types::GLuint,
+ 3,
+ gl::FLOAT,
+ gl::FALSE,
+ stride as gl::types::GLsizei,
+ null(),
+ );
+ }
+}
+
+fn enable_vertex_attrib_array(index: usize)
+{
+ unsafe {
+ #[allow(clippy::cast_possible_truncation)]
+ gl::EnableVertexAttribArray(index as gl::types::GLuint);
+ }
+}
diff --git a/engine/src/renderer/vertex_array.rs b/engine/src/renderer/vertex_array.rs
new file mode 100644
index 0000000..e54f638
--- /dev/null
+++ b/engine/src/renderer/vertex_array.rs
@@ -0,0 +1,62 @@
+pub struct VertexArray
+{
+ array: gl::types::GLuint,
+}
+
+impl VertexArray
+{
+ pub fn new() -> Self
+ {
+ let mut array = 0;
+
+ unsafe {
+ gl::GenVertexArrays(1, &mut array);
+ }
+
+ Self { array }
+ }
+
+ pub fn draw(&self, primitive_kind: PrimitiveKind, start_index: u32, index_cnt: u32)
+ {
+ self.bind();
+
+ unsafe {
+ #[allow(clippy::cast_possible_wrap)]
+ gl::DrawArrays(
+ primitive_kind.into_gl(),
+ start_index as gl::types::GLint,
+ index_cnt as gl::types::GLsizei,
+ );
+ }
+ }
+
+ pub fn bind(&self)
+ {
+ unsafe { gl::BindVertexArray(self.array) }
+ }
+}
+
+impl Drop for VertexArray
+{
+ fn drop(&mut self)
+ {
+ unsafe {
+ gl::DeleteVertexArrays(1, &self.array);
+ }
+ }
+}
+
+pub enum PrimitiveKind
+{
+ Triangles,
+}
+
+impl PrimitiveKind
+{
+ fn into_gl(self) -> gl::types::GLenum
+ {
+ match self {
+ Self::Triangles => gl::TRIANGLES,
+ }
+ }
+}
diff --git a/engine/src/renderer/vertex_buffers.rs b/engine/src/renderer/vertex_buffers.rs
new file mode 100644
index 0000000..a6d1a2c
--- /dev/null
+++ b/engine/src/renderer/vertex_buffers.rs
@@ -0,0 +1,83 @@
+use std::mem::size_of_val;
+
+pub struct VertexBuffers<const CNT: usize>
+{
+ buffers: [gl::types::GLuint; CNT],
+}
+
+impl<const CNT: usize> VertexBuffers<CNT>
+{
+ pub fn new() -> Self
+ {
+ let mut buffers = [gl::types::GLuint::default(); CNT];
+
+ unsafe {
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ gl::GenBuffers(CNT as gl::types::GLsizei, buffers.as_mut_ptr());
+ };
+
+ Self { buffers }
+ }
+
+ pub fn store(
+ &self,
+ buffer_index: usize,
+ vertices: &[f32],
+ usage: BufferUsage,
+ ) -> Option<()>
+ {
+ let buffer = *self.buffers.get(buffer_index)?;
+
+ unsafe {
+ gl::BindBuffer(gl::ARRAY_BUFFER, buffer);
+ }
+
+ unsafe {
+ #[allow(clippy::cast_possible_wrap)]
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ size_of_val(vertices) as gl::types::GLsizeiptr,
+ vertices.as_ptr().cast(),
+ usage.into_gl(),
+ );
+ }
+
+ Some(())
+ }
+}
+
+impl<const CNT: usize> Drop for VertexBuffers<CNT>
+{
+ fn drop(&mut self)
+ {
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ unsafe {
+ gl::DeleteBuffers(CNT as gl::types::GLsizei, self.buffers.as_ptr());
+ }
+ }
+}
+
+#[allow(dead_code)]
+pub enum BufferUsage
+{
+ /// The buffer data is set only once and used by the GPU at most a few times.
+ Stream,
+
+ /// The buffer data is set only once and used many times.
+ Static,
+
+ /// The buffer data is changed a lot and used many times.
+ Dynamic,
+}
+
+impl BufferUsage
+{
+ fn into_gl(self) -> gl::types::GLenum
+ {
+ match self {
+ Self::Stream => gl::STREAM_DRAW,
+ Self::Static => gl::STATIC_DRAW,
+ Self::Dynamic => gl::DYNAMIC_DRAW,
+ }
+ }
+}
diff --git a/engine/src/shader.rs b/engine/src/shader.rs
new file mode 100644
index 0000000..0bbca77
--- /dev/null
+++ b/engine/src/shader.rs
@@ -0,0 +1,198 @@
+use std::ffi::CStr;
+use std::ptr::null_mut;
+
+#[derive(Debug)]
+pub struct Shader
+{
+ shader: gl::types::GLuint,
+}
+
+impl Shader
+{
+ pub fn new(kind: Kind) -> Self
+ {
+ let shader = unsafe { gl::CreateShader(kind.into_gl()) };
+
+ Self { shader }
+ }
+
+ pub fn set_source(&self, source: &str) -> Result<(), Error>
+ {
+ if !source.is_ascii() {
+ return Err(Error::SourceNotAscii);
+ }
+
+ unsafe {
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ gl::ShaderSource(
+ self.shader,
+ 1,
+ &source.as_ptr().cast(),
+ &(source.len() as gl::types::GLint),
+ );
+ }
+
+ Ok(())
+ }
+
+ pub fn compile(&self) -> Result<(), Error>
+ {
+ unsafe {
+ gl::CompileShader(self.shader);
+ }
+
+ let mut compile_success = gl::types::GLint::default();
+
+ unsafe {
+ gl::GetShaderiv(self.shader, gl::COMPILE_STATUS, &mut compile_success);
+ }
+
+ if compile_success == 0 {
+ let info_log = self.get_info_log();
+
+ return Err(Error::CompileFailed(info_log));
+ }
+
+ Ok(())
+ }
+
+ fn get_info_log(&self) -> String
+ {
+ let mut buf = vec![gl::types::GLchar::default(); 512];
+
+ unsafe {
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ gl::GetShaderInfoLog(
+ self.shader,
+ buf.len() as gl::types::GLsizei,
+ null_mut(),
+ buf.as_mut_ptr(),
+ );
+ }
+
+ let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
+
+ unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
+ }
+}
+
+impl Drop for Shader
+{
+ fn drop(&mut self)
+ {
+ unsafe {
+ gl::DeleteShader(self.shader);
+ }
+ }
+}
+
+/// Shader kind.
+#[derive(Debug, Clone, Copy)]
+pub enum Kind
+{
+ Vertex,
+ Fragment,
+}
+
+impl Kind
+{
+ fn into_gl(self) -> gl::types::GLenum
+ {
+ match self {
+ Self::Vertex => gl::VERTEX_SHADER,
+ Self::Fragment => gl::FRAGMENT_SHADER,
+ }
+ }
+}
+
+/// Shader program
+#[derive(Debug)]
+pub struct Program
+{
+ program: gl::types::GLuint,
+}
+
+impl Program
+{
+ pub fn new() -> Self
+ {
+ let program = unsafe { gl::CreateProgram() };
+
+ Self { program }
+ }
+
+ pub fn attach(&self, shader: &Shader)
+ {
+ unsafe {
+ gl::AttachShader(self.program, shader.shader);
+ }
+ }
+
+ pub fn link(&self) -> Result<(), Error>
+ {
+ unsafe {
+ gl::LinkProgram(self.program);
+ }
+
+ let mut link_success = gl::types::GLint::default();
+
+ unsafe {
+ gl::GetProgramiv(self.program, gl::LINK_STATUS, &mut link_success);
+ }
+
+ if link_success == 0 {
+ let info_log = self.get_info_log();
+
+ return Err(Error::CompileFailed(info_log));
+ }
+
+ Ok(())
+ }
+
+ pub fn activate(&self)
+ {
+ unsafe {
+ gl::UseProgram(self.program);
+ }
+ }
+
+ fn get_info_log(&self) -> String
+ {
+ let mut buf = vec![gl::types::GLchar::default(); 512];
+
+ unsafe {
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ gl::GetProgramInfoLog(
+ self.program,
+ buf.len() as gl::types::GLsizei,
+ null_mut(),
+ buf.as_mut_ptr(),
+ );
+ }
+
+ let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
+
+ unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
+ }
+}
+
+impl Drop for Program
+{
+ fn drop(&mut self)
+ {
+ unsafe {
+ gl::DeleteProgram(self.program);
+ }
+ }
+}
+
+/// Shader error.
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("All characters in source are not within the ASCII range")]
+ SourceNotAscii,
+
+ #[error("Failed to compile: {0}")]
+ CompileFailed(String),
+}
diff --git a/engine/vertex.glsl b/engine/vertex.glsl
new file mode 100644
index 0000000..c74ea10
--- /dev/null
+++ b/engine/vertex.glsl
@@ -0,0 +1,7 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+
+void main()
+{
+ gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
+}