1#[derive(Copy, Clone, Debug, PartialEq)]
2pub struct Orthographic {
3 pub left: f32,
4 pub right: f32,
5 pub top: f32,
6 pub bottom: f32,
7 pub near: f32,
8 pub far: f32,
9}
10
11#[derive(Copy, Clone, Debug, PartialEq)]
12pub struct Perspective {
13 pub aspect: f32,
14 pub fovy: f32,
15 pub near: f32,
16 pub far: f32,
17}
18
19#[derive(Copy, Clone, Debug, PartialEq)]
21pub enum Projection {
22 Orthographic(Option<Orthographic>),
23 Perspective(Option<Perspective>),
24}
25
26#[derive(Debug)]
27pub enum ShaderError {
28 GraphicsError(solstice::GraphicsError),
29 UniformNotFound(String),
30}
31
32impl std::fmt::Display for ShaderError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
34 write!(f, "{:?}", self)
35 }
36}
37
38impl std::error::Error for ShaderError {}
39
40pub struct ShaderSource<'a> {
41 pub vertex: &'a str,
42 pub fragment: &'a str,
43}
44
45impl<'a> From<&'a String> for ShaderSource<'a> {
46 fn from(src: &'a String) -> Self {
47 Self {
48 vertex: src.as_str(),
49 fragment: src.as_str(),
50 }
51 }
52}
53
54impl<'a> From<&'a str> for ShaderSource<'a> {
55 fn from(src: &'a str) -> Self {
56 Self {
57 vertex: src,
58 fragment: src,
59 }
60 }
61}
62
63impl<'a> From<(&'a str, &'a str)> for ShaderSource<'a> {
64 fn from((vertex, fragment): (&'a str, &'a str)) -> Self {
65 Self { vertex, fragment }
66 }
67}
68
69use solstice::shader::{Attribute, DynamicShader, Uniform, UniformLocation};
70use solstice::{Context, ShaderKey};
71
72#[derive(Eq, PartialEq, Clone, Debug)]
73struct TextureCache {
74 ty: solstice::texture::TextureType,
75 key: solstice::TextureKey,
76 location: Option<UniformLocation>,
77}
78
79const MAX_TEXTURE_UNITS: usize = 8;
80
81#[derive(Debug, Clone, PartialEq)]
82pub struct Shader {
83 inner: solstice::shader::DynamicShader,
84
85 projection_location: Option<UniformLocation>,
86 projection_cache: mint::ColumnMatrix4<f32>,
87 view_location: Option<UniformLocation>,
88 view_cache: mint::ColumnMatrix4<f32>,
89 model_location: Option<UniformLocation>,
90 model_cache: mint::ColumnMatrix4<f32>,
91 normal_matrix_location: Option<UniformLocation>,
92 color_location: Option<UniformLocation>,
93 color_cache: mint::Vector4<f32>,
94 resolution_location: Option<UniformLocation>,
95 resolution_cache: mint::Vector4<f32>,
96
97 textures: [TextureCache; MAX_TEXTURE_UNITS],
98
99 other_uniforms: std::collections::HashMap<String, solstice::shader::RawUniformValue>,
100}
101
102const DEFAULT_VERT: &str = r#"
103vec4 pos(mat4 transform_projection, vec4 vertex_position) {
104 return transform_projection * vertex_position;
105}
106"#;
107
108const DEFAULT_FRAG: &str = r#"
109vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
110 return Texel(texture, texture_coords) * color;
111}
112"#;
113
114fn get_location(
115 shader: &solstice::shader::DynamicShader,
116 name: &str,
117) -> Result<UniformLocation, ShaderError> {
118 shader
119 .get_uniform_by_name(name)
120 .ok_or_else(|| ShaderError::UniformNotFound(name.to_owned()))
121 .map(|uniform| uniform.location.clone())
122}
123
124fn shader_src(src: ShaderSource) -> String {
125 format!(
126 "#define Image sampler2D
127#define ArrayImage sampler2DArray
128#define CubeImage samplerCube
129#define VolumeImage sampler3D
130
131varying vec4 vColor;
132varying vec2 vUV;
133
134uniform SOLSTICE_HIGHP_OR_MEDIUMP vec4 uResolution;
135
136#ifdef VERTEX
137attribute vec4 position;
138attribute vec4 color;
139attribute vec3 normal;
140attribute vec2 uv;
141
142uniform mat4 uProjection;
143uniform mat4 uView;
144uniform mat4 uModel;
145uniform mat4 uNormalMatrix;
146
147{vertex}
148
149void main() {{
150 vColor = color;
151 vUV = uv;
152 gl_Position = pos(uProjection * uView * uModel, position);
153}}
154#endif
155
156#ifdef FRAGMENT
157uniform sampler2D tex0;
158uniform vec4 uColor;
159
160{fragment}
161
162void main() {{
163 vec2 screen = vec2(gl_FragCoord.x, (gl_FragCoord.y * uResolution.z) + uResolution.w);
164 fragColor = effect(uColor * vColor, tex0, vUV, screen);
165}}
166#endif",
167 vertex = src.vertex,
168 fragment = src.fragment
169 )
170}
171
172impl Shader {
173 pub fn new(ctx: &mut Context) -> Result<Self, ShaderError> {
174 Self::with((DEFAULT_VERT, DEFAULT_FRAG), ctx)
175 }
176
177 pub fn with<'a, S>(src: S, ctx: &mut Context) -> Result<Self, ShaderError>
178 where
179 S: Into<ShaderSource<'a>>,
180 {
181 let src = shader_src(src.into());
182 let (vertex, fragment) =
183 solstice::shader::DynamicShader::create_source(src.as_str(), src.as_str());
184 let shader = DynamicShader::new(ctx, vertex.as_str(), fragment.as_str())
185 .map_err(ShaderError::GraphicsError)?;
186
187 let projection_location = get_location(&shader, "uProjection").ok();
188 let view_location = get_location(&shader, "uView").ok();
189 let model_location = get_location(&shader, "uModel").ok();
190 let normal_matrix_location = get_location(&shader, "uNormalMatrix").ok();
191 let color_location = get_location(&shader, "uColor").ok();
192 let resolution_location = get_location(&shader, "uResolution").ok();
193 let mut textures = (0..MAX_TEXTURE_UNITS).map(|i| {
194 let location = get_location(&shader, ("tex".to_owned() + &i.to_string()).as_str()).ok();
195 TextureCache {
196 ty: solstice::texture::TextureType::Tex2D,
197 key: Default::default(),
198 location,
199 }
200 });
201 let textures = [
202 textures.next().unwrap(),
203 textures.next().unwrap(),
204 textures.next().unwrap(),
205 textures.next().unwrap(),
206 textures.next().unwrap(),
207 textures.next().unwrap(),
208 textures.next().unwrap(),
209 textures.next().unwrap(),
210 ];
211
212 #[rustfmt::skip]
213 let identity: mint::ColumnMatrix4<f32> = [
214 1., 0., 0., 0.,
215 0., 1., 0., 0.,
216 0., 0., 1., 0.,
217 0., 0., 0., 1.,
218 ].into();
219 let white: mint::Vector4<f32> = [1., 1., 1., 1.].into();
220 let projection_cache = identity;
221
222 ctx.use_shader(Some(&shader));
223 if let Some(projection_location) = &projection_location {
224 ctx.set_uniform_by_location(
225 &projection_location,
226 &solstice::shader::RawUniformValue::Mat4(projection_cache),
227 );
228 }
229 if let Some(view_location) = &view_location {
230 ctx.set_uniform_by_location(
231 &view_location,
232 &solstice::shader::RawUniformValue::Mat4(identity),
233 );
234 }
235 if let Some(model_location) = &model_location {
236 ctx.set_uniform_by_location(
237 &model_location,
238 &solstice::shader::RawUniformValue::Mat4(identity),
239 );
240 }
241 if let Some(normal_location) = &normal_matrix_location {
242 ctx.set_uniform_by_location(
243 &normal_location,
244 &solstice::shader::RawUniformValue::Mat4(identity),
245 );
246 }
247 if let Some(color_location) = &color_location {
248 ctx.set_uniform_by_location(
249 color_location,
250 &solstice::shader::RawUniformValue::Vec4(white),
251 );
252 }
253
254 Ok(Self {
255 inner: shader,
256 projection_location,
257 projection_cache,
258 view_location,
259 view_cache: identity,
260 model_location,
261 model_cache: identity,
262 normal_matrix_location,
263 color_location,
264 color_cache: white,
265 resolution_location,
266 resolution_cache: mint::Vector4 {
267 x: 0f32,
268 y: 0.,
269 z: 0.,
270 w: 0.,
271 },
272 textures,
273 other_uniforms: Default::default(),
274 })
275 }
276
277 pub fn set_viewport(
278 &mut self,
279 projection: Projection,
280 default_projection_bounds: Option<crate::Rectangle>,
281 viewport: solstice::viewport::Viewport<i32>,
282 invert_y: bool,
283 ) {
284 let viewport = default_projection_bounds.unwrap_or_else(|| {
285 crate::Rectangle::new(
286 viewport.x() as _,
287 viewport.y() as _,
288 viewport.width() as _,
289 viewport.height() as _,
290 )
291 });
292 const FAR_PLANE: f32 = 1000.0;
293 let projection_cache: mint::ColumnMatrix4<f32> = match projection {
294 Projection::Orthographic(projection) => {
295 let (top, bottom) = if invert_y {
296 (viewport.y + viewport.height, -viewport.y)
297 } else {
298 (-viewport.y, viewport.y + viewport.height)
299 };
300 let Orthographic {
301 left,
302 right,
303 top,
304 bottom,
305 near,
306 far,
307 } = projection.unwrap_or(Orthographic {
308 left: viewport.x,
309 right: viewport.x + viewport.width,
310 top,
311 bottom,
312 near: 0.0,
313 far: FAR_PLANE,
314 });
315 ortho(left, right, bottom, top, near, far).into()
316 }
317 Projection::Perspective(projection) => {
318 let fovy = if invert_y {
319 -std::f32::consts::FRAC_PI_2
320 } else {
321 std::f32::consts::FRAC_PI_2
322 };
323 let Perspective {
324 aspect,
325 fovy,
326 near,
327 far,
328 } = projection.unwrap_or(Perspective {
329 aspect: viewport.width / viewport.height,
330 fovy,
331 near: 0.1,
332 far: FAR_PLANE,
333 });
334 nalgebra::Matrix4::new_perspective(aspect, fovy, near, far).into()
335 }
336 };
337
338 self.resolution_cache.x = viewport.width;
339 self.resolution_cache.y = viewport.height;
340 if invert_y {
341 self.resolution_cache.z = 1.;
342 self.resolution_cache.w = 0.;
343 } else {
344 self.resolution_cache.z = -1.;
345 self.resolution_cache.w = viewport.height;
346 }
347
348 self.projection_cache = projection_cache;
349 }
350
351 pub fn set_width_height(
352 &mut self,
353 projection: Projection,
354 width: f32,
355 height: f32,
356 invert_y: bool,
357 ) {
358 self.set_viewport(
359 projection,
360 None,
361 solstice::viewport::Viewport::new(0, 0, width as _, height as _),
362 invert_y,
363 )
364 }
365
366 pub fn set_color(&mut self, c: crate::Color) {
367 self.color_cache = c.into()
368 }
369
370 pub fn bind_texture<T: solstice::texture::Texture>(&mut self, texture: T) {
371 self.bind_texture_at_location(texture, 0);
372 }
373
374 pub fn bind_texture_at_location<T: solstice::texture::Texture>(
375 &mut self,
376 texture: T,
377 location: usize,
378 ) {
379 let cache = &mut self.textures[location];
380 cache.key = texture.get_texture_key();
381 cache.ty = texture.get_texture_type();
382 }
383
384 pub fn is_bound<T: solstice::texture::Texture>(&self, texture: T) -> bool {
385 self.textures[0].key == texture.get_texture_key()
386 }
387
388 pub fn is_dirty(&self) -> bool {
389 true
390 }
391
392 pub fn send_uniform<S, V>(&mut self, name: S, value: V)
393 where
394 S: AsRef<str>,
395 V: std::convert::TryInto<solstice::shader::RawUniformValue>,
396 {
397 if let Some(uniform) = self.inner.get_uniform_by_name(name.as_ref()) {
398 if let Some(data) = value.try_into().ok() {
399 self.other_uniforms.insert(uniform.name.clone(), data);
400 }
401 }
402 }
403
404 pub fn set_view<V: Into<mint::ColumnMatrix4<f32>>>(&mut self, view: V) {
405 self.view_cache = view.into();
406 }
407
408 pub fn set_model<M: Into<mint::ColumnMatrix4<f32>>>(&mut self, model: M) {
409 self.model_cache = model.into();
410 }
411
412 pub fn activate(&mut self, ctx: &mut Context) {
413 use solstice::shader::RawUniformValue::{Mat4, SignedInt, Vec4};
414 ctx.use_shader(Some(&self.inner));
415 for (index, texture) in self.textures.iter().enumerate() {
416 if let Some(location) = &texture.location {
417 ctx.bind_texture_to_unit(texture.ty, texture.key, index.into());
418 ctx.set_uniform_by_location(location, &SignedInt(index as _));
419 }
420 }
421 for (name, data) in self.other_uniforms.iter() {
422 let uniform = self.inner.get_uniform_by_name(name.as_str());
423 if let Some(uniform) = uniform {
424 ctx.set_uniform_by_location(&uniform.location, data);
425 }
426 }
427 if let Some(u) = self.color_location.as_ref() {
428 ctx.set_uniform_by_location(u, &Vec4(self.color_cache));
429 }
430 if let Some(u) = self.resolution_location.as_ref() {
431 ctx.set_uniform_by_location(
432 u,
433 &solstice::shader::RawUniformValue::Vec4(self.resolution_cache),
434 );
435 }
436 if let Some(projection_location) = &self.projection_location {
437 ctx.set_uniform_by_location(projection_location, &Mat4(self.projection_cache));
438 }
439 if let Some(view_location) = &self.view_location {
440 ctx.set_uniform_by_location(view_location, &Mat4(self.view_cache));
441 }
442 if let Some(model_location) = &self.model_location {
443 ctx.set_uniform_by_location(model_location, &Mat4(self.model_cache));
444 }
445 if let Some(normal_location) = &self.normal_matrix_location {
446 let v = nalgebra::Matrix4::from(self.view_cache)
447 * nalgebra::Matrix4::from(self.model_cache);
448 if let Some(v) = v.try_inverse() {
449 let v = v.transpose();
450 ctx.set_uniform_by_location(normal_location, &Mat4(v.into()))
451 }
452 }
453 }
454}
455
456impl solstice::shader::Shader for Shader {
457 fn handle(&self) -> ShaderKey {
458 self.inner.handle()
459 }
460
461 fn attributes(&self) -> &[Attribute] {
462 self.inner.attributes()
463 }
464
465 fn uniforms(&self) -> &[Uniform] {
466 self.inner.uniforms()
467 }
468}
469
470fn ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> [[f32; 4]; 4] {
471 let c0r0 = 2. / (right - left);
472 let c0r1 = 0.;
473 let c0r2 = 0.;
474 let c0r3 = 0.;
475
476 let c1r0 = 0.;
477 let c1r1 = 2. / (top - bottom);
478 let c1r2 = 0.;
479 let c1r3 = 0.;
480
481 let c2r0 = 0.;
482 let c2r1 = 0.;
483 let c2r2 = -2. / (far - near);
484 let c2r3 = 0.;
485
486 let c3r0 = -(right + left) / (right - left);
487 let c3r1 = -(top + bottom) / (top - bottom);
488 let c3r2 = -(far + near) / (far - near);
489 let c3r3 = 1.;
490
491 [
492 [c0r0, c0r1, c0r2, c0r3],
493 [c1r0, c1r1, c1r2, c1r3],
494 [c2r0, c2r1, c2r2, c2r3],
495 [c3r0, c3r1, c3r2, c3r3],
496 ]
497}