Handle shader compilation errors
Description
We have the minimum needed to compile and link our shaders, but if there's an error in the shader syntax the application just won't render anything. As our shaders become more complicated, it's invaluable to have details about what went wrong when things aren't working.
This change checks the compile and link status of the shaders, and if they weren't successful it will fetch more details.
Since we're dealing with a wrapper over a C API, fetching the error details is a bit more involved than just returning a string from a function. We first fetch the size of the error message string, allocate a buffer to read it in, and then convert the C string to a Rust version.
Commands
git clone git@github.com:atsheehan/iridium
cd iridium
git checkout 858de625de7f85cc007d4dab9c52aae65d8fa1d0
cargo run --release
Code Changes
Modified src/render.rsGitHub
@@ -1,6 +1,6 @@1- use std::ffi::CString;
1+ use std::{ffi::CString, fmt::Display};
22
3- use gl::types::{GLenum, GLuint};
3+ use gl::types::{GLchar, GLenum, GLint, GLuint};
44 use glutin::{
55 config::ConfigTemplateBuilder,
66 context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext, Version},
@@ -62,7 +62,8 @@6262
6363 gl::load_with(|s| display.get_proc_address(&CString::new(s).unwrap()));
6464
65- let cube_program = Program::build(CUBE_VERTEX_SHADER_SRC, CUBE_FRAGMENT_SHADER_SRC);
65+ let cube_program =
66+ Program::build(CUBE_VERTEX_SHADER_SRC, CUBE_FRAGMENT_SHADER_SRC).unwrap();
6667
6768 unsafe {
6869 gl::UseProgram(cube_program.gl_id());
@@ -118,22 +119,52 @@118119 }
119120
120121 impl Program {
121- fn build(vertex_shader_src: &str, fragment_shader_src: &str) -> Self {
122- let vertex_shader = Shader::compile(vertex_shader_src, ShaderType::Vertex);
123- let fragment_shader = Shader::compile(fragment_shader_src, ShaderType::Fragment);
122+ fn build(vertex_shader_src: &str, fragment_shader_src: &str) -> Result<Self, ShaderError> {
123+ let vertex_shader = Shader::compile(vertex_shader_src, ShaderType::Vertex)?;
124+ let fragment_shader = Shader::compile(fragment_shader_src, ShaderType::Fragment)?;
124125
125126 let program_id = unsafe { gl::CreateProgram() };
126127
127- unsafe {
128+ let linking_was_successful: bool = unsafe {
128129 gl::AttachShader(program_id, vertex_shader.gl_id());
129130 gl::AttachShader(program_id, fragment_shader.gl_id());
130131 gl::LinkProgram(program_id);
131132 gl::DetachShader(program_id, vertex_shader.gl_id());
132133 gl::DetachShader(program_id, fragment_shader.gl_id());
133- }
134134
135- Self {
136- id: ProgramId(program_id),
135+ let mut linking_was_successful: GLint = gl::FALSE as GLint;
136+ gl::GetProgramiv(program_id, gl::LINK_STATUS, &mut linking_was_successful);
137+
138+ linking_was_successful == gl::TRUE as GLint
139+ };
140+
141+ if linking_was_successful {
142+ Ok(Self {
143+ id: ProgramId(program_id),
144+ })
145+ } else {
146+ let error_message_len: usize = unsafe {
147+ let mut error_message_len = 0;
148+ gl::GetProgramiv(program_id, gl::INFO_LOG_LENGTH, &mut error_message_len);
149+ error_message_len as usize
150+ };
151+
152+ let mut error_message_buffer: Vec<u8> = vec![b' '; error_message_len];
153+ let mut bytes_read = 0;
154+
155+ unsafe {
156+ gl::GetProgramInfoLog(
157+ program_id,
158+ error_message_len as GLint,
159+ &mut bytes_read,
160+ error_message_buffer.as_mut_ptr() as *mut GLchar,
161+ );
162+ }
163+
164+ let filled_buffer = &error_message_buffer[..(bytes_read as usize)];
165+ let error_message = CString::new(filled_buffer).unwrap().into_string().unwrap();
166+
167+ Err(ShaderError::Linking(error_message))
137168 }
138169 }
139170
@@ -149,16 +180,46 @@149180 }
150181
151182 impl Shader {
152- fn compile(source: &str, shader_type: ShaderType) -> Self {
183+ fn compile(source: &str, shader_type: ShaderType) -> Result<Self, ShaderError> {
153184 let source = CString::new(source).unwrap();
154185 let id = unsafe { gl::CreateShader(shader_type.gl_shader_type()) };
155186
156- unsafe {
187+ let compile_was_successful: bool = unsafe {
157188 gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null());
158189 gl::CompileShader(id);
159- }
160190
161- Self { id: ShaderId(id) }
191+ let mut compile_was_successful: GLint = gl::FALSE as GLint;
192+ gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut compile_was_successful);
193+
194+ compile_was_successful == gl::TRUE as GLint
195+ };
196+
197+ if compile_was_successful {
198+ Ok(Self { id: ShaderId(id) })
199+ } else {
200+ let error_message_len: usize = unsafe {
201+ let mut error_message_len = 0;
202+ gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut error_message_len);
203+ error_message_len as usize
204+ };
205+
206+ let mut error_message_buffer: Vec<u8> = vec![b' '; error_message_len];
207+ let mut bytes_read = 0;
208+
209+ unsafe {
210+ gl::GetShaderInfoLog(
211+ id,
212+ error_message_len as GLint,
213+ &mut bytes_read,
214+ error_message_buffer.as_mut_ptr() as *mut GLchar,
215+ );
216+ }
217+
218+ let filled_buffer = &error_message_buffer[..(bytes_read as usize)];
219+ let error_message = CString::new(filled_buffer).unwrap().into_string().unwrap();
220+
221+ Err(ShaderError::Compilation(error_message))
222+ }
162223 }
163224
164225 fn gl_id(&self) -> GLuint {
@@ -179,3 +240,18 @@179240 }
180241 }
181242 }
243+
244+ #[derive(Debug)]
245+ enum ShaderError {
246+ Compilation(String),
247+ Linking(String),
248+ }
249+
250+ impl Display for ShaderError {
251+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252+ match self {
253+ Self::Compilation(error) => write!(f, "shader compilation error: {}", error),
254+ Self::Linking(error) => write!(f, "shader linking error: {}", error),
255+ }
256+ }
257+ }
@@ -1,6 +1,6 @@1- use std::ffi::CString;
2
3- use gl::types::{GLenum, GLuint};
4 use glutin::{
5 config::ConfigTemplateBuilder,
6 context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext, Version},
@@ -62,7 +62,8 @@62
63 gl::load_with(|s| display.get_proc_address(&CString::new(s).unwrap()));
64
65- let cube_program = Program::build(CUBE_VERTEX_SHADER_SRC, CUBE_FRAGMENT_SHADER_SRC);
66
67 unsafe {
68 gl::UseProgram(cube_program.gl_id());
@@ -118,22 +119,52 @@118 }
119
120 impl Program {
121- fn build(vertex_shader_src: &str, fragment_shader_src: &str) -> Self {
122- let vertex_shader = Shader::compile(vertex_shader_src, ShaderType::Vertex);
123- let fragment_shader = Shader::compile(fragment_shader_src, ShaderType::Fragment);
124
125 let program_id = unsafe { gl::CreateProgram() };
126
127- unsafe {
128 gl::AttachShader(program_id, vertex_shader.gl_id());
129 gl::AttachShader(program_id, fragment_shader.gl_id());
130 gl::LinkProgram(program_id);
131 gl::DetachShader(program_id, vertex_shader.gl_id());
132 gl::DetachShader(program_id, fragment_shader.gl_id());
133- }
134
135- Self {
136- id: ProgramId(program_id),
137 }
138 }
139
@@ -149,16 +180,46 @@149 }
150
151 impl Shader {
152- fn compile(source: &str, shader_type: ShaderType) -> Self {
153 let source = CString::new(source).unwrap();
154 let id = unsafe { gl::CreateShader(shader_type.gl_shader_type()) };
155
156- unsafe {
157 gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null());
158 gl::CompileShader(id);
159- }
160
161- Self { id: ShaderId(id) }
162 }
163
164 fn gl_id(&self) -> GLuint {
@@ -179,3 +240,18 @@179 }
180 }
181 }
@@ -1,6 +1,6 @@1+ use std::{ffi::CString, fmt::Display};
2
3+ use gl::types::{GLchar, GLenum, GLint, GLuint};
4 use glutin::{
5 config::ConfigTemplateBuilder,
6 context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext, Version},
@@ -62,7 +62,8 @@62
63 gl::load_with(|s| display.get_proc_address(&CString::new(s).unwrap()));
64
65+ let cube_program =
66+ Program::build(CUBE_VERTEX_SHADER_SRC, CUBE_FRAGMENT_SHADER_SRC).unwrap();
67
68 unsafe {
69 gl::UseProgram(cube_program.gl_id());
@@ -118,22 +119,52 @@119 }
120
121 impl Program {
122+ fn build(vertex_shader_src: &str, fragment_shader_src: &str) -> Result<Self, ShaderError> {
123+ let vertex_shader = Shader::compile(vertex_shader_src, ShaderType::Vertex)?;
124+ let fragment_shader = Shader::compile(fragment_shader_src, ShaderType::Fragment)?;
125
126 let program_id = unsafe { gl::CreateProgram() };
127
128+ let linking_was_successful: bool = unsafe {
129 gl::AttachShader(program_id, vertex_shader.gl_id());
130 gl::AttachShader(program_id, fragment_shader.gl_id());
131 gl::LinkProgram(program_id);
132 gl::DetachShader(program_id, vertex_shader.gl_id());
133 gl::DetachShader(program_id, fragment_shader.gl_id());
134
135+ let mut linking_was_successful: GLint = gl::FALSE as GLint;
136+ gl::GetProgramiv(program_id, gl::LINK_STATUS, &mut linking_was_successful);
137+
138+ linking_was_successful == gl::TRUE as GLint
139+ };
140+
141+ if linking_was_successful {
142+ Ok(Self {
143+ id: ProgramId(program_id),
144+ })
145+ } else {
146+ let error_message_len: usize = unsafe {
147+ let mut error_message_len = 0;
148+ gl::GetProgramiv(program_id, gl::INFO_LOG_LENGTH, &mut error_message_len);
149+ error_message_len as usize
150+ };
151+
152+ let mut error_message_buffer: Vec<u8> = vec![b' '; error_message_len];
153+ let mut bytes_read = 0;
154+
155+ unsafe {
156+ gl::GetProgramInfoLog(
157+ program_id,
158+ error_message_len as GLint,
159+ &mut bytes_read,
160+ error_message_buffer.as_mut_ptr() as *mut GLchar,
161+ );
162+ }
163+
164+ let filled_buffer = &error_message_buffer[..(bytes_read as usize)];
165+ let error_message = CString::new(filled_buffer).unwrap().into_string().unwrap();
166+
167+ Err(ShaderError::Linking(error_message))
168 }
169 }
170
@@ -149,16 +180,46 @@180 }
181
182 impl Shader {
183+ fn compile(source: &str, shader_type: ShaderType) -> Result<Self, ShaderError> {
184 let source = CString::new(source).unwrap();
185 let id = unsafe { gl::CreateShader(shader_type.gl_shader_type()) };
186
187+ let compile_was_successful: bool = unsafe {
188 gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null());
189 gl::CompileShader(id);
190
191+ let mut compile_was_successful: GLint = gl::FALSE as GLint;
192+ gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut compile_was_successful);
193+
194+ compile_was_successful == gl::TRUE as GLint
195+ };
196+
197+ if compile_was_successful {
198+ Ok(Self { id: ShaderId(id) })
199+ } else {
200+ let error_message_len: usize = unsafe {
201+ let mut error_message_len = 0;
202+ gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut error_message_len);
203+ error_message_len as usize
204+ };
205+
206+ let mut error_message_buffer: Vec<u8> = vec![b' '; error_message_len];
207+ let mut bytes_read = 0;
208+
209+ unsafe {
210+ gl::GetShaderInfoLog(
211+ id,
212+ error_message_len as GLint,
213+ &mut bytes_read,
214+ error_message_buffer.as_mut_ptr() as *mut GLchar,
215+ );
216+ }
217+
218+ let filled_buffer = &error_message_buffer[..(bytes_read as usize)];
219+ let error_message = CString::new(filled_buffer).unwrap().into_string().unwrap();
220+
221+ Err(ShaderError::Compilation(error_message))
222+ }
223 }
224
225 fn gl_id(&self) -> GLuint {
@@ -179,3 +240,18 @@240 }
241 }
242 }
243+
244+ #[derive(Debug)]
245+ enum ShaderError {
246+ Compilation(String),
247+ Linking(String),
248+ }
249+
250+ impl Display for ShaderError {
251+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252+ match self {
253+ Self::Compilation(error) => write!(f, "shader compilation error: {}", error),
254+ Self::Linking(error) => write!(f, "shader linking error: {}", error),
255+ }
256+ }
257+ }