Skip to main content

freya_core/style/
shader.rs

1use std::sync::Arc;
2
3use freya_engine::prelude::*;
4use torin::prelude::Area;
5
6pub trait ShaderProvider {
7    fn prepare_shader(&self, effect: &RuntimeEffect, bounds: Area) -> Option<Shader>;
8}
9
10impl<F> ShaderProvider for F
11where
12    F: Fn(&RuntimeEffect, Area) -> Option<Shader>,
13{
14    fn prepare_shader(&self, effect: &RuntimeEffect, bounds: Area) -> Option<Shader> {
15        self(effect, bounds)
16    }
17}
18
19struct SharedRuntimeEffect(RuntimeEffect);
20
21// SAFETY: `RuntimeEffect` is immutable.
22unsafe impl Send for SharedRuntimeEffect {}
23unsafe impl Sync for SharedRuntimeEffect {}
24
25struct SharedShaderProvider(Box<dyn ShaderProvider>);
26
27// SAFETY: `ShaderProvider` is immutable.
28unsafe impl Send for SharedShaderProvider {}
29unsafe impl Sync for SharedShaderProvider {}
30
31/// A custom paint source backed by an SkSL shader.
32///
33/// Build it with [`ShaderFill::new`], passing the SkSL source, a compiled
34/// [`RuntimeEffect`] and a provider closure that supplies the shader's uniforms
35/// for a given bounds. Use it as a [`Fill`](crate::style::fill::Fill) for
36/// backgrounds or text.
37#[derive(Clone)]
38pub struct ShaderFill {
39    sksl: Arc<str>,
40    effect: Arc<SharedRuntimeEffect>,
41    provider: Arc<SharedShaderProvider>,
42}
43
44impl ShaderFill {
45    pub fn new<F>(sksl: impl Into<Arc<str>>, effect: RuntimeEffect, provider: F) -> Self
46    where
47        F: Fn(&RuntimeEffect, Area) -> Option<Shader> + 'static,
48    {
49        Self::from_provider(sksl, effect, provider)
50    }
51
52    pub fn from_provider<S>(sksl: impl Into<Arc<str>>, effect: RuntimeEffect, provider: S) -> Self
53    where
54        S: ShaderProvider + 'static,
55    {
56        Self {
57            sksl: sksl.into(),
58            effect: Arc::new(SharedRuntimeEffect(effect)),
59            provider: Arc::new(SharedShaderProvider(Box::new(provider))),
60        }
61    }
62
63    /// Prepare the shader for use by providing the necessary uniforms.
64    /// Returns [None] if the provider could not produce a [Shader], in which case the renderer will fallback to no fill.
65    pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
66        self.provider.0.prepare_shader(&self.effect.0, bounds)
67    }
68}
69
70impl std::fmt::Display for ShaderFill {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "shader({:p})", Arc::as_ptr(&self.provider))
73    }
74}
75
76impl std::fmt::Debug for ShaderFill {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        f.debug_struct("FillShader")
79            .field("sksl", &self.sksl)
80            .finish()
81    }
82}
83
84impl PartialEq for ShaderFill {
85    fn eq(&self, other: &Self) -> bool {
86        *self.sksl == *other.sksl
87            && Arc::ptr_eq(&self.effect, &other.effect)
88            && Arc::ptr_eq(&self.provider, &other.provider)
89    }
90}
91
92impl std::hash::Hash for ShaderFill {
93    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
94        (*self.sksl).hash(state);
95        Arc::as_ptr(&self.effect).hash(state);
96        Arc::as_ptr(&self.provider).hash(state);
97    }
98}
99
100#[cfg(feature = "serde")]
101impl serde::Serialize for ShaderFill {
102    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103    where
104        S: serde::Serializer,
105    {
106        serializer.serialize_str(&self.sksl)
107    }
108}
109
110#[cfg(feature = "serde")]
111impl<'de> serde::Deserialize<'de> for ShaderFill {
112    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113    where
114        D: serde::Deserializer<'de>,
115    {
116        let sksl = String::deserialize(deserializer)?;
117        let effect =
118            RuntimeEffect::make_for_shader(&sksl, None).map_err(serde::de::Error::custom)?;
119
120        Ok(Self {
121            sksl: sksl.into(),
122            effect: Arc::new(SharedRuntimeEffect(effect)),
123            provider: Arc::new(SharedShaderProvider(Box::new(
124                |_: &RuntimeEffect, _: Area| None,
125            ))),
126        })
127    }
128}