solstice_2d/shared/
shader.rs

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/// A None value means we will use the default projections of the given type
20#[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}