1use crate::{
22 core::{
23 log::{Log, MessageKind},
24 ImmutableString,
25 },
26 error::FrameworkError,
27 gl::server::{GlGraphicsServer, GlKind},
28 gpu_program::{
29 GpuProgram, SamplerKind, ShaderPropertyKind, ShaderResourceDefinition, ShaderResourceKind,
30 UniformLocation,
31 },
32};
33use fxhash::FxHashMap;
34use glow::HasContext;
35use std::{
36 cell::RefCell,
37 marker::PhantomData,
38 ops::Deref,
39 rc::{Rc, Weak},
40};
41
42impl SamplerKind {
43 pub fn glsl_name(&self) -> &str {
44 match self {
45 SamplerKind::Sampler1D => "sampler1D",
46 SamplerKind::Sampler2D => "sampler2D",
47 SamplerKind::Sampler3D => "sampler3D",
48 SamplerKind::SamplerCube => "samplerCube",
49 SamplerKind::USampler1D => "usampler1D",
50 SamplerKind::USampler2D => "usampler2D",
51 SamplerKind::USampler3D => "usampler3D",
52 SamplerKind::USamplerCube => "usamplerCube",
53 }
54 }
55}
56
57unsafe fn create_shader(
58 server: &GlGraphicsServer,
59 name: String,
60 actual_type: u32,
61 source: &str,
62 gl_kind: GlKind,
63) -> Result<glow::Shader, FrameworkError> {
64 let merged_source = prepare_source_code(source, gl_kind);
65
66 let shader = server.gl.create_shader(actual_type)?;
67 server.gl.shader_source(shader, &merged_source);
68 server.gl.compile_shader(shader);
69
70 let status = server.gl.get_shader_compile_status(shader);
71 let compilation_message = server.gl.get_shader_info_log(shader);
72
73 if !status {
74 Log::writeln(
75 MessageKind::Error,
76 format!("Failed to compile {name} shader: {compilation_message}"),
77 );
78 Err(FrameworkError::ShaderCompilationFailed {
79 shader_name: name,
80 error_message: compilation_message,
81 })
82 } else {
83 let msg = if compilation_message.is_empty()
84 || compilation_message.chars().all(|c| c.is_whitespace())
85 {
86 format!("Shader {name} compiled successfully!")
87 } else {
88 format!("Shader {name} compiled successfully!\nAdditional info: {compilation_message}")
89 };
90
91 Log::writeln(MessageKind::Information, msg);
92
93 Ok(shader)
94 }
95}
96
97fn prepare_source_code(code: &str, gl_kind: GlKind) -> String {
98 let mut full_source_code = "#version 330 core\n// include 'shared.glsl'\n".to_owned();
99
100 if gl_kind == GlKind::OpenGLES {
101 full_source_code += r#"
102 precision highp float;
103 precision highp int;
104 precision highp usampler2D;
105 precision highp usampler3D;
106 precision highp usamplerCube;
107 precision highp sampler2D;
108 precision highp sampler3D;
109 precision highp samplerCube;
110 "#;
111 }
112
113 full_source_code += include_str!("shaders/shared.glsl");
114 full_source_code += "\n// end of include\n";
115 full_source_code += code;
116
117 if gl_kind == GlKind::OpenGLES {
118 full_source_code.replace("#version 330 core", "#version 300 es")
119 } else {
120 full_source_code
121 }
122}
123
124pub struct GlProgram {
125 state: Weak<GlGraphicsServer>,
126 pub id: glow::Program,
127 thread_mark: PhantomData<*const u8>,
129 uniform_locations: RefCell<FxHashMap<ImmutableString, Option<UniformLocation>>>,
130}
131
132#[inline]
133fn fetch_uniform_location(
134 server: &GlGraphicsServer,
135 program: glow::Program,
136 id: &str,
137) -> Option<UniformLocation> {
138 unsafe {
139 server
140 .gl
141 .get_uniform_location(program, id)
142 .map(|id| UniformLocation {
143 id,
144 thread_mark: PhantomData,
145 })
146 }
147}
148
149pub fn fetch_uniform_block_index(
150 server: &GlGraphicsServer,
151 program: glow::Program,
152 name: &str,
153) -> Option<usize> {
154 unsafe {
155 server
156 .gl
157 .get_uniform_block_index(program, name)
158 .map(|index| index as usize)
159 }
160}
161
162impl GlProgram {
163 pub fn from_source_and_properties(
164 server: &GlGraphicsServer,
165 program_name: &str,
166 vertex_source: &str,
167 fragment_source: &str,
168 resources: &[ShaderResourceDefinition],
169 ) -> Result<GlProgram, FrameworkError> {
170 let mut vertex_source = vertex_source.to_string();
171 let mut fragment_source = fragment_source.to_string();
172
173 for resource in resources {
175 for other_resource in resources {
176 if std::ptr::eq(resource, other_resource) {
177 continue;
178 }
179
180 if std::mem::discriminant(&resource.kind)
181 == std::mem::discriminant(&other_resource.kind)
182 {
183 if resource.binding == other_resource.binding {
184 return Err(FrameworkError::Custom(format!(
185 "Resource {} and {} using the same binding point {} \
186 in the {program_name} GPU program.",
187 resource.name, other_resource.name, resource.binding
188 )));
189 }
190
191 if resource.name == other_resource.name {
192 return Err(FrameworkError::Custom(format!(
193 "There are two or more resources with same name {} \
194 in the {program_name} GPU program.",
195 resource.name
196 )));
197 }
198 }
199 }
200 }
201
202 for initial_source in [&mut vertex_source, &mut fragment_source] {
204 let mut texture_bindings = String::new();
205
206 for property in resources {
207 let resource_name = &property.name;
208 match property.kind {
209 ShaderResourceKind::Texture { kind, .. } => {
210 let glsl_name = kind.glsl_name();
211 texture_bindings += &format!("uniform {glsl_name} {resource_name};\n");
212 }
213 ShaderResourceKind::PropertyGroup(ref fields) => {
214 if fields.is_empty() {
215 Log::warn(format!(
216 "Uniform block {resource_name} is empty and will be ignored!"
217 ));
218 continue;
219 }
220 let mut block = format!("struct T{resource_name}{{\n");
221 for field in fields {
222 let field_name = &field.name;
223 match field.kind {
224 ShaderPropertyKind::Float(_) => {
225 block += &format!("\tfloat {field_name};\n");
226 }
227 ShaderPropertyKind::FloatArray { max_len, .. } => {
228 block += &format!("\tfloat {field_name}[{max_len}];\n");
229 }
230 ShaderPropertyKind::Int(_) => {
231 block += &format!("\tint {field_name};\n");
232 }
233 ShaderPropertyKind::IntArray { max_len, .. } => {
234 block += &format!("\tint {field_name}[{max_len}];\n");
235 }
236 ShaderPropertyKind::UInt(_) => {
237 block += &format!("\tuint {field_name};\n");
238 }
239 ShaderPropertyKind::UIntArray { max_len, .. } => {
240 block += &format!("\tuint {field_name}[{max_len}];\n");
241 }
242 ShaderPropertyKind::Bool(_) => {
243 block += &format!("\tbool {field_name};\n");
244 }
245 ShaderPropertyKind::Vector2(_) => {
246 block += &format!("\tvec2 {field_name};\n");
247 }
248 ShaderPropertyKind::Vector2Array { max_len, .. } => {
249 block += &format!("\tvec2 {field_name}[{max_len}];\n");
250 }
251 ShaderPropertyKind::Vector3(_) => {
252 block += &format!("\tvec3 {field_name};\n");
253 }
254 ShaderPropertyKind::Vector3Array { max_len, .. } => {
255 block += &format!("\tvec3 {field_name}[{max_len}];\n");
256 }
257 ShaderPropertyKind::Vector4(_) => {
258 block += &format!("\tvec4 {field_name};\n");
259 }
260 ShaderPropertyKind::Vector4Array { max_len, .. } => {
261 block += &format!("\tvec4 {field_name}[{max_len}];\n");
262 }
263 ShaderPropertyKind::Matrix2(_) => {
264 block += &format!("\tmat2 {field_name};\n");
265 }
266 ShaderPropertyKind::Matrix2Array { max_len, .. } => {
267 block += &format!("\tmat2 {field_name}[{max_len}];\n");
268 }
269 ShaderPropertyKind::Matrix3(_) => {
270 block += &format!("\tmat3 {field_name};\n");
271 }
272 ShaderPropertyKind::Matrix3Array { max_len, .. } => {
273 block += &format!("\tmat3 {field_name}[{max_len}];\n");
274 }
275 ShaderPropertyKind::Matrix4(_) => {
276 block += &format!("\tmat4 {field_name};\n");
277 }
278 ShaderPropertyKind::Matrix4Array { max_len, .. } => {
279 block += &format!("\tmat4 {field_name}[{max_len}];\n");
280 }
281 ShaderPropertyKind::Color { .. } => {
282 block += &format!("\tvec4 {field_name};\n");
283 }
284 }
285 }
286 block += "};\n";
287 block += &format!("layout(std140) uniform U{resource_name} {{ T{resource_name} {resource_name}; }};\n");
288 initial_source.insert_str(0, &block);
289 }
290 }
291 }
292 initial_source.insert_str(0, &texture_bindings);
293 }
294
295 let program = Self::from_source(server, program_name, &vertex_source, &fragment_source)?;
296
297 unsafe {
298 server.set_program(Some(program.id));
299 for resource_definition in resources {
300 match resource_definition.kind {
301 ShaderResourceKind::Texture { .. } => {
302 if let Some(location) = server
303 .gl
304 .get_uniform_location(program.id, &resource_definition.name)
305 {
306 server
307 .gl
308 .uniform_1_i32(Some(&location), resource_definition.binding as i32);
309 }
310 }
311 ShaderResourceKind::PropertyGroup { .. } => {
312 if let Some(shader_block_index) = server.gl.get_uniform_block_index(
313 program.id,
314 &format!("U{}", resource_definition.name),
315 ) {
316 server.gl.uniform_block_binding(
317 program.id,
318 shader_block_index,
319 resource_definition.binding as u32,
320 )
321 } else {
322 Log::warn(format!(
323 "Couldn't find uniform block U{}",
324 resource_definition.name
325 ));
326 }
327 }
328 }
329 }
330 }
331
332 Ok(program)
333 }
334
335 pub fn from_source(
336 server: &GlGraphicsServer,
337 name: &str,
338 vertex_source: &str,
339 fragment_source: &str,
340 ) -> Result<GlProgram, FrameworkError> {
341 unsafe {
342 let vertex_shader = create_shader(
343 server,
344 format!("{name}_VertexShader"),
345 glow::VERTEX_SHADER,
346 vertex_source,
347 server.gl_kind(),
348 )?;
349 let fragment_shader = create_shader(
350 server,
351 format!("{name}_FragmentShader"),
352 glow::FRAGMENT_SHADER,
353 fragment_source,
354 server.gl_kind(),
355 )?;
356 let program = server.gl.create_program()?;
357 server.gl.attach_shader(program, vertex_shader);
358 server.gl.delete_shader(vertex_shader);
359 server.gl.attach_shader(program, fragment_shader);
360 server.gl.delete_shader(fragment_shader);
361 server.gl.link_program(program);
362 let status = server.gl.get_program_link_status(program);
363 let link_message = server.gl.get_program_info_log(program);
364
365 if !status {
366 Log::writeln(
367 MessageKind::Error,
368 format!("Failed to link {name} shader: {link_message}"),
369 );
370 Err(FrameworkError::ShaderLinkingFailed {
371 shader_name: name.to_owned(),
372 error_message: link_message,
373 })
374 } else {
375 let msg = if link_message.is_empty()
376 || link_message.chars().all(|c| c.is_whitespace())
377 {
378 format!("Shader {name} linked successfully!")
379 } else {
380 format!("Shader {name} linked successfully!\nAdditional info: {link_message}")
381 };
382
383 Log::writeln(MessageKind::Information, msg);
384
385 Ok(Self {
386 state: server.weak(),
387 id: program,
388 thread_mark: PhantomData,
389 uniform_locations: Default::default(),
390 })
391 }
392 }
393 }
394
395 pub fn uniform_location_internal(&self, name: &ImmutableString) -> Option<UniformLocation> {
396 let mut locations = self.uniform_locations.borrow_mut();
397 let server = self.state.upgrade().unwrap();
398 if let Some(cached_location) = locations.get(name) {
399 cached_location.clone()
400 } else {
401 let location = fetch_uniform_location(&server, self.id, name.deref());
402
403 locations.insert(name.clone(), location.clone());
404
405 location
406 }
407 }
408
409 fn bind(&self) -> TempBinding {
410 let server = self.state.upgrade().unwrap();
411 server.set_program(Some(self.id));
412 TempBinding { server }
413 }
414}
415
416struct TempBinding {
417 server: Rc<GlGraphicsServer>,
418}
419
420impl GpuProgram for GlProgram {
421 fn uniform_location(&self, name: &ImmutableString) -> Result<UniformLocation, FrameworkError> {
422 self.uniform_location_internal(name)
423 .ok_or_else(|| FrameworkError::UnableToFindShaderUniform(name.deref().to_owned()))
424 }
425
426 fn uniform_block_index(&self, name: &ImmutableString) -> Result<usize, FrameworkError> {
427 unsafe {
428 self.bind()
429 .server
430 .gl
431 .get_uniform_block_index(self.id, name)
432 .map(|index| index as usize)
433 .ok_or_else(|| {
434 FrameworkError::UnableToFindShaderUniformBlock(name.deref().to_owned())
435 })
436 }
437 }
438}
439
440impl Drop for GlProgram {
441 fn drop(&mut self) {
442 if let Some(state) = self.state.upgrade() {
443 unsafe {
444 state.gl.delete_program(self.id);
445 }
446 }
447 }
448}