1use std::sync::{Arc, Mutex, OnceLock, PoisonError, Weak};
7
8use arrayvec::ArrayVec;
9
10use crate::{LayerShape, Rect};
11
12const RUNTIME_SHADER_INLINE_UNIFORMS: usize = 16;
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16pub enum TileMode {
17 #[default]
19 Clamp,
20 Repeated,
22 Mirror,
24 Decal,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq)]
34pub struct BlurredEdgeTreatment {
35 shape: Option<LayerShape>,
36}
37
38impl BlurredEdgeTreatment {
39 pub const RECTANGLE: Self = Self {
41 shape: Some(LayerShape::Rectangle),
42 };
43
44 pub const UNBOUNDED: Self = Self { shape: None };
46
47 pub const fn with_shape(shape: LayerShape) -> Self {
49 Self { shape: Some(shape) }
50 }
51
52 pub fn shape(self) -> Option<LayerShape> {
53 self.shape
54 }
55
56 pub fn clip(self) -> bool {
57 self.shape.is_some()
58 }
59
60 pub fn tile_mode(self) -> TileMode {
61 if self.clip() {
62 TileMode::Clamp
63 } else {
64 TileMode::Decal
65 }
66 }
67}
68
69impl Default for BlurredEdgeTreatment {
70 fn default() -> Self {
71 Self::RECTANGLE
72 }
73}
74
75pub const RUNTIME_SHADER_PRELUDE_WGSL: &str = concat!(
80 include_str!("../shaders/fullscreen_quad_vs.wgsl"),
81 include_str!("../shaders/runtime_shader_bindings.wgsl"),
82);
83
84#[derive(Clone, Debug)]
124pub struct RuntimeShader {
125 source: Arc<str>,
126 source_hash: u64,
127 uniforms: RuntimeShaderUniforms,
128 specialization: Option<Arc<ShaderSpecialization>>,
129 input_padding: f32,
130 output_padding: f32,
131 batched_source: bool,
132 position_independent: bool,
133 preserves_transparency: bool,
134 domains: Option<Box<ShaderDomains>>,
135}
136
137#[derive(Clone, Debug, Default)]
138struct ShaderSpecialization {
139 overrides: Vec<(&'static str, f64)>,
140 overrides_hash: OnceLock<u64>,
141 substrates: ArrayVec<SubstrateSpec, MAX_SUBSTRATES>,
142 draw_split: Option<&'static str>,
143 exact: bool,
144}
145
146pub(crate) struct ShaderSpecializationCache<K, const N: usize> {
147 entries: ArrayVec<CachedShaderSpecialization<K>, N>,
148}
149
150struct CachedShaderSpecialization<K> {
151 source: Option<Arc<ShaderSpecialization>>,
152 key: K,
153 result: Option<Arc<ShaderSpecialization>>,
154}
155
156impl<K: PartialEq, const N: usize> ShaderSpecializationCache<K, N> {
157 pub(crate) const fn new() -> Self {
158 assert!(N > 0);
159 Self {
160 entries: ArrayVec::new_const(),
161 }
162 }
163
164 pub(crate) fn apply(
165 &mut self,
166 shader: &mut RuntimeShader,
167 key: K,
168 specialize: impl FnOnce(&mut RuntimeShader, &K),
169 ) {
170 let hit = self.entries.iter().rposition(|entry| {
171 entry.key == key
172 && match (&entry.source, &shader.specialization) {
173 (Some(source), Some(current)) => Arc::ptr_eq(source, current),
174 (None, None) => true,
175 _ => false,
176 }
177 });
178 if let Some(index) = hit {
179 let entry = self.entries.remove(index);
180 shader.specialization.clone_from(&entry.result);
181 self.entries.push(entry);
182 return;
183 }
184 if shader
185 .specialization
186 .as_ref()
187 .is_some_and(|source| Arc::strong_count(source) == 1)
188 {
189 specialize(shader, &key);
190 return;
191 }
192 let source = shader.specialization.clone();
193 specialize(shader, &key);
194 if self.entries.is_full() {
195 self.entries.remove(0);
196 }
197 self.entries.push(CachedShaderSpecialization {
198 source,
199 key,
200 result: shader.specialization.clone(),
201 });
202 }
203}
204
205static DEFAULT_SHADER_SPECIALIZATION: ShaderSpecialization = ShaderSpecialization {
206 overrides: Vec::new(),
207 overrides_hash: OnceLock::new(),
208 substrates: ArrayVec::new_const(),
209 draw_split: None,
210 exact: false,
211};
212
213#[derive(Clone, Copy, Debug, Default, PartialEq)]
214struct ShaderDomains {
215 output_support: Option<Rect>,
216 sample_domain: Option<Rect>,
217}
218
219fn finite_rect(rect: Option<Rect>) -> Option<Rect> {
220 rect.filter(|rect| {
221 rect.x.is_finite()
222 && rect.y.is_finite()
223 && rect.width.is_finite()
224 && rect.height.is_finite()
225 })
226}
227
228pub const MAX_SUBSTRATES: usize = 3;
230
231#[derive(Clone, Copy, Debug, PartialEq)]
234pub enum SubstrateSpec {
235 Mean,
240 Average { block: u32 },
243 Blur { radius_px: f32 },
246}
247
248impl SubstrateSpec {
249 fn same_bits(&self, other: &Self) -> bool {
250 match (self, other) {
251 (Self::Mean, Self::Mean) => true,
252 (Self::Average { block: a }, Self::Average { block: b }) => a == b,
253 (Self::Blur { radius_px: a }, Self::Blur { radius_px: b }) => {
254 a.to_bits() == b.to_bits()
255 }
256 _ => false,
257 }
258 }
259
260 fn hash_bits<H: std::hash::Hasher>(&self, state: &mut H) {
261 use std::hash::Hash;
262 match self {
263 Self::Mean => 2u8.hash(state),
264 Self::Average { block } => {
265 0u8.hash(state);
266 block.hash(state);
267 }
268 Self::Blur { radius_px } => {
269 1u8.hash(state);
270 radius_px.to_bits().hash(state);
271 }
272 }
273 }
274}
275
276#[derive(Clone, Debug, PartialEq)]
277struct RuntimeShaderUniforms {
278 len: usize,
279 inline: [f32; RUNTIME_SHADER_INLINE_UNIFORMS],
280 heap: Option<Vec<f32>>,
281}
282
283impl RuntimeShaderUniforms {
284 fn new() -> Self {
285 Self {
286 len: 0,
287 inline: [0.0; RUNTIME_SHADER_INLINE_UNIFORMS],
288 heap: None,
289 }
290 }
291
292 fn as_slice(&self) -> &[f32] {
293 if let Some(heap) = &self.heap {
294 heap.as_slice()
295 } else {
296 &self.inline[..self.len]
297 }
298 }
299
300 fn len(&self) -> usize {
301 self.as_slice().len()
302 }
303
304 fn ensure_len(&mut self, min_len: usize) {
305 if let Some(heap) = &mut self.heap {
306 if heap.len() < min_len {
307 heap.resize(min_len, 0.0);
308 }
309 return;
310 }
311
312 if min_len <= RUNTIME_SHADER_INLINE_UNIFORMS {
313 self.len = self.len.max(min_len);
314 return;
315 }
316
317 let mut heap = Vec::with_capacity(min_len);
318 heap.extend_from_slice(&self.inline[..self.len]);
319 heap.resize(min_len, 0.0);
320 self.heap = Some(heap);
321 }
322
323 fn set(&mut self, index: usize, value: f32) {
324 if let Some(heap) = &mut self.heap {
325 heap[index] = value;
326 } else {
327 self.inline[index] = value;
328 }
329 }
330
331 #[cfg(test)]
332 fn is_inline(&self) -> bool {
333 self.heap.is_none()
334 }
335}
336
337#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
339pub enum RuntimeShaderUniformError {
340 #[error(
341 "uniform range starting at {index} with width {width} exceeds user uniform range 0..{max_user_uniforms}; slots {reserved_start}..{max_uniforms} are reserved for renderer data"
342 )]
343 OutOfUserRange {
344 index: usize,
345 width: usize,
346 max_user_uniforms: usize,
347 reserved_start: usize,
348 max_uniforms: usize,
349 },
350}
351
352impl RuntimeShader {
353 pub const MAX_UNIFORMS: usize = 256;
357 pub const RESERVED_UNIFORM_START: usize = 224;
359 pub const SUBSTRATE_REGION_UNIFORMS: [usize; MAX_SUBSTRATES] = [232, 228, 224];
362 pub const SOURCE_REGION_UNIFORM: usize = 236;
364 pub const MASK_RECT_UNIFORM: usize = 240;
366 pub const MASK_RADII_UNIFORM: usize = 244;
368 pub const EFFECT_RECT_UNIFORM: usize = 248;
370 pub const LOGICAL_SIZE_UNIFORM: usize = 252;
372 pub const ALPHA_UNIFORM: usize = 254;
374 pub const MAX_USER_UNIFORMS: usize = Self::RESERVED_UNIFORM_START;
376
377 #[track_caller]
379 pub fn new(wgsl_source: &str) -> Self {
380 let (source, source_hash) =
381 cached_shader_source(std::panic::Location::caller(), wgsl_source);
382 Self::with_source(source, source_hash)
383 }
384
385 pub fn from_shared_source(source: Arc<str>) -> Self {
390 let source_hash = cached_shared_shader_source_hash(&source);
391 Self::with_source(source, source_hash)
392 }
393
394 fn with_source(source: Arc<str>, source_hash: u64) -> Self {
395 Self {
396 source,
397 source_hash,
398 uniforms: RuntimeShaderUniforms::new(),
399 specialization: None,
400 input_padding: 0.0,
401 output_padding: 0.0,
402 batched_source: false,
403 position_independent: false,
404 preserves_transparency: false,
405 domains: None,
406 }
407 }
408
409 fn specialization(&self) -> &ShaderSpecialization {
410 self.specialization
411 .as_deref()
412 .unwrap_or(&DEFAULT_SHADER_SPECIALIZATION)
413 }
414
415 fn specialization_mut(&mut self) -> &mut ShaderSpecialization {
416 Arc::make_mut(self.specialization.get_or_insert_with(Arc::default))
417 }
418
419 pub fn set_override(&mut self, name: &'static str, value: f64) {
433 let position = self
434 .overrides()
435 .binary_search_by(|(existing, _)| existing.cmp(&name));
436 if position.is_ok_and(|index| self.overrides()[index].1.to_bits() == value.to_bits()) {
437 return;
438 }
439 let specialization = self.specialization_mut();
440 specialization.overrides_hash.take();
441 let overrides = &mut specialization.overrides;
442 match position {
443 Ok(index) => overrides[index].1 = value,
444 Err(index) => overrides.insert(index, (name, value)),
445 }
446 }
447
448 pub fn clear_override(&mut self, name: &str) -> bool {
450 let Ok(index) = self
451 .overrides()
452 .binary_search_by(|(existing, _)| (*existing).cmp(name))
453 else {
454 return false;
455 };
456 let specialization = self.specialization_mut();
457 specialization.overrides_hash.take();
458 specialization.overrides.remove(index);
459 true
460 }
461
462 pub fn overrides(&self) -> &[(&'static str, f64)] {
465 &self.specialization().overrides
466 }
467
468 pub fn overrides_hash(&self) -> u64 {
470 let specialization = self.specialization();
471 if specialization.overrides.is_empty() {
472 return 0;
473 }
474 *specialization.overrides_hash.get_or_init(|| {
475 #[cfg(test)]
476 OVERRIDE_HASH_COMPUTATIONS.with(|count| count.set(count.get() + 1));
477 hash_shader_bytes(specialization.overrides.iter().flat_map(|(name, value)| {
478 name.bytes().chain([0]).chain(value.to_bits().to_le_bytes())
479 }))
480 })
481 }
482
483 pub fn set_input_padding(&mut self, padding: f32) {
487 self.input_padding = if padding.is_finite() {
488 padding.max(0.0)
489 } else {
490 0.0
491 };
492 }
493
494 pub fn input_padding(&self) -> f32 {
496 self.input_padding
497 }
498
499 pub fn set_output_padding(&mut self, padding: f32) {
504 self.output_padding = if padding.is_finite() {
505 padding.max(0.0)
506 } else {
507 0.0
508 };
509 }
510
511 pub fn output_padding(&self) -> f32 {
513 self.output_padding
514 }
515
516 pub fn set_output_support(&mut self, support: Option<Rect>) {
527 self.set_domains(ShaderDomains {
528 output_support: finite_rect(support),
529 sample_domain: self.sample_domain(),
530 });
531 }
532
533 pub fn output_support(&self) -> Option<Rect> {
535 self.domains
536 .as_ref()
537 .and_then(|domains| domains.output_support)
538 }
539
540 fn set_domains(&mut self, domains: ShaderDomains) {
541 self.domains = (domains != ShaderDomains::default()).then(|| Box::new(domains));
542 }
543
544 pub fn set_sample_domain(&mut self, domain: Option<Rect>) {
553 self.set_domains(ShaderDomains {
554 output_support: self.output_support(),
555 sample_domain: finite_rect(domain),
556 });
557 }
558
559 pub fn sample_domain(&self) -> Option<Rect> {
561 self.domains
562 .as_ref()
563 .and_then(|domains| domains.sample_domain)
564 }
565
566 pub fn set_float(&mut self, index: usize, value: f32) {
571 let _ = self.try_set_float(index, value);
572 }
573
574 pub fn try_set_float(
576 &mut self,
577 index: usize,
578 value: f32,
579 ) -> Result<(), RuntimeShaderUniformError> {
580 self.try_ensure_capacity(index, 1)?;
581 self.uniforms.set(index, value);
582 Ok(())
583 }
584
585 pub fn set_float2(&mut self, index: usize, x: f32, y: f32) {
590 let _ = self.try_set_float2(index, x, y);
591 }
592
593 pub fn try_set_float2(
595 &mut self,
596 index: usize,
597 x: f32,
598 y: f32,
599 ) -> Result<(), RuntimeShaderUniformError> {
600 self.try_ensure_capacity(index, 2)?;
601 self.uniforms.set(index, x);
602 self.uniforms.set(index + 1, y);
603 Ok(())
604 }
605
606 pub fn set_float4(&mut self, index: usize, x: f32, y: f32, z: f32, w: f32) {
611 let _ = self.try_set_float4(index, x, y, z, w);
612 }
613
614 pub fn try_set_float4(
616 &mut self,
617 index: usize,
618 x: f32,
619 y: f32,
620 z: f32,
621 w: f32,
622 ) -> Result<(), RuntimeShaderUniformError> {
623 self.try_ensure_capacity(index, 4)?;
624 self.uniforms.set(index, x);
625 self.uniforms.set(index + 1, y);
626 self.uniforms.set(index + 2, z);
627 self.uniforms.set(index + 3, w);
628 Ok(())
629 }
630
631 pub fn set_batched_source(&mut self, batched: bool) {
636 self.batched_source = batched;
637 }
638
639 pub fn batched_source(&self) -> bool {
642 self.batched_source
643 }
644
645 pub fn set_position_independent(&mut self, independent: bool) {
652 self.position_independent = independent;
653 }
654
655 pub fn position_independent(&self) -> bool {
657 self.position_independent
658 }
659
660 pub fn set_preserves_transparency(&mut self, preserves: bool) {
665 self.preserves_transparency = preserves;
666 }
667
668 pub fn preserves_transparency(&self) -> bool {
670 self.preserves_transparency
671 }
672
673 pub fn set_substrates(&mut self, substrates: &[SubstrateSpec]) {
682 assert!(
683 substrates.len() <= MAX_SUBSTRATES,
684 "a runtime shader declares at most {MAX_SUBSTRATES} substrates"
685 );
686 if self.substrates().len() == substrates.len()
687 && self
688 .substrates()
689 .iter()
690 .zip(substrates)
691 .all(|(existing, incoming)| existing.same_bits(incoming))
692 {
693 return;
694 }
695 self.specialization_mut().substrates = substrates.iter().copied().collect();
696 }
697
698 pub fn substrates(&self) -> &[SubstrateSpec] {
700 &self.specialization().substrates
701 }
702
703 pub fn hash_substrates<H: std::hash::Hasher>(&self, state: &mut H) {
705 use std::hash::Hash;
706 self.substrates().len().hash(state);
707 for substrate in self.substrates() {
708 substrate.hash_bits(state);
709 }
710 self.draw_split().hash(state);
711 }
712
713 pub fn set_draw_split(&mut self, override_name: Option<&'static str>) {
720 if self.draw_split() == override_name {
721 return;
722 }
723 self.specialization_mut().draw_split = override_name;
724 }
725
726 pub fn draw_split(&self) -> Option<&'static str> {
728 self.specialization().draw_split
729 }
730
731 pub fn set_specialization_exact(&mut self, exact: bool) {
741 if self.specialization_exact() == exact {
742 return;
743 }
744 self.specialization_mut().exact = exact;
745 }
746
747 pub fn specialization_exact(&self) -> bool {
749 self.specialization().exact
750 }
751
752 pub fn source(&self) -> &str {
754 &self.source
755 }
756
757 pub fn uniforms(&self) -> &[f32] {
759 self.uniforms.as_slice()
760 }
761
762 pub fn uniforms_padded(&self) -> [f32; Self::MAX_UNIFORMS] {
764 let mut padded = [0.0f32; Self::MAX_UNIFORMS];
765 let len = self.uniforms.len().min(Self::MAX_UNIFORMS);
766 padded[..len].copy_from_slice(&self.uniforms.as_slice()[..len]);
767 padded
768 }
769
770 pub fn source_hash(&self) -> u64 {
772 self.source_hash
773 }
774
775 fn try_ensure_capacity(
776 &mut self,
777 index: usize,
778 width: usize,
779 ) -> Result<(), RuntimeShaderUniformError> {
780 let min_len = index
781 .checked_add(width)
782 .ok_or_else(|| Self::uniform_range_error(index, width))?;
783 if min_len > Self::MAX_USER_UNIFORMS {
784 return Err(Self::uniform_range_error(index, width));
785 }
786 self.uniforms.ensure_len(min_len);
787 Ok(())
788 }
789
790 fn uniform_range_error(index: usize, width: usize) -> RuntimeShaderUniformError {
791 RuntimeShaderUniformError::OutOfUserRange {
792 index,
793 width,
794 max_user_uniforms: Self::MAX_USER_UNIFORMS,
795 reserved_start: Self::RESERVED_UNIFORM_START,
796 max_uniforms: Self::MAX_UNIFORMS,
797 }
798 }
799}
800
801#[cfg(test)]
802thread_local! {
803 static OVERRIDE_HASH_COMPUTATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
804}
805
806impl PartialEq for RuntimeShader {
807 fn eq(&self, other: &Self) -> bool {
808 self.source_hash == other.source_hash
809 && (Arc::ptr_eq(&self.source, &other.source)
810 || self.source.as_ref() == other.source.as_ref())
811 && self.uniforms == other.uniforms
812 && self.overrides().len() == other.overrides().len()
813 && self
814 .overrides()
815 .iter()
816 .zip(other.overrides())
817 .all(|(a, b)| a.0 == b.0 && a.1.to_bits() == b.1.to_bits())
818 && self.input_padding.to_bits() == other.input_padding.to_bits()
819 && self.output_padding.to_bits() == other.output_padding.to_bits()
820 && self.batched_source == other.batched_source
821 && self.position_independent == other.position_independent
822 && self.preserves_transparency == other.preserves_transparency
823 && self.substrates() == other.substrates()
824 && self.draw_split() == other.draw_split()
825 && self.domains == other.domains
826 }
827}
828
829fn hash_shader_source(source: &str) -> u64 {
830 hash_shader_bytes(source.bytes())
831}
832
833fn hash_shader_bytes(bytes: impl IntoIterator<Item = u8>) -> u64 {
834 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
835 const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
836
837 bytes.into_iter().fold(FNV_OFFSET_BASIS, |hash, byte| {
838 (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME)
839 })
840}
841
842#[derive(Clone, Copy, Debug, PartialEq, Eq)]
843struct ShaderSourceCallsite {
844 file: &'static str,
845 line: u32,
846 column: u32,
847}
848
849struct CachedShaderSource {
850 callsite: ShaderSourceCallsite,
851 source_hash: u64,
852 source: Arc<str>,
853}
854
855struct CachedSharedShaderSourceHash {
856 byte_ptr: usize,
857 len: usize,
858 source_hash: u64,
859 source: Weak<str>,
860}
861
862fn cached_shared_shader_source_hash(source: &Arc<str>) -> u64 {
863 static CACHE: OnceLock<Mutex<Vec<CachedSharedShaderSourceHash>>> = OnceLock::new();
864 let byte_ptr = source.as_ptr() as usize;
865 let len = source.len();
866 let mut cache = CACHE
867 .get_or_init(|| Mutex::new(Vec::new()))
868 .lock()
869 .unwrap_or_else(PoisonError::into_inner);
870
871 cache.retain(|entry| entry.source.strong_count() > 0);
872 if let Some(entry) = cache.iter().find(|entry| {
873 entry.byte_ptr == byte_ptr
874 && entry.len == len
875 && entry
876 .source
877 .upgrade()
878 .is_some_and(|cached| Arc::ptr_eq(&cached, source))
879 }) {
880 return entry.source_hash;
881 }
882
883 let source_hash = hash_shader_source(source);
884 cache.push(CachedSharedShaderSourceHash {
885 byte_ptr,
886 len,
887 source_hash,
888 source: Arc::downgrade(source),
889 });
890 source_hash
891}
892
893fn cached_shader_source(
894 location: &'static std::panic::Location<'static>,
895 source: &str,
896) -> (Arc<str>, u64) {
897 static CACHE: OnceLock<Mutex<Vec<CachedShaderSource>>> = OnceLock::new();
898 let callsite = ShaderSourceCallsite {
899 file: location.file(),
900 line: location.line(),
901 column: location.column(),
902 };
903 let mut cache = CACHE
904 .get_or_init(|| Mutex::new(Vec::new()))
905 .lock()
906 .unwrap_or_else(PoisonError::into_inner);
907
908 if let Some(entry) = cache.iter_mut().find(|entry| entry.callsite == callsite) {
909 if entry.source.as_ref() == source {
910 return (entry.source.clone(), entry.source_hash);
911 }
912 let source_hash = hash_shader_source(source);
913 entry.source_hash = source_hash;
914 entry.source = Arc::<str>::from(source);
915 return (entry.source.clone(), entry.source_hash);
916 }
917
918 let source_hash = hash_shader_source(source);
919 let shared = Arc::<str>::from(source);
920 cache.push(CachedShaderSource {
921 callsite,
922 source_hash,
923 source: shared.clone(),
924 });
925 (shared, source_hash)
926}
927
928#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
935pub enum ShaderTarget {
936 Page,
937 Layer,
938}
939
940#[derive(Clone, Debug, PartialEq)]
944pub struct ShaderWarmUp {
945 pub shader: RuntimeShader,
946 pub target: ShaderTarget,
947}
948
949#[derive(Clone, Debug, PartialEq)]
954pub enum RenderEffect {
955 Blur {
957 radius_x: f32,
958 radius_y: f32,
959 edge_treatment: TileMode,
960 },
961 Offset { offset_x: f32, offset_y: f32 },
963 Shader {
965 shader: Arc<RuntimeShader>,
967 },
968 Chain {
972 first: Arc<RenderEffect>,
973 second: Arc<RenderEffect>,
974 },
975}
976
977impl RenderEffect {
978 pub fn blur(radius: f32) -> Self {
980 Self::blur_with_edge_treatment(radius, TileMode::default())
981 }
982
983 pub fn blur_with_edge_treatment(radius: f32, edge_treatment: TileMode) -> Self {
986 Self::Blur {
987 radius_x: radius,
988 radius_y: radius,
989 edge_treatment,
990 }
991 }
992
993 pub fn blur_xy(radius_x: f32, radius_y: f32, edge_treatment: TileMode) -> Self {
995 Self::Blur {
996 radius_x,
997 radius_y,
998 edge_treatment,
999 }
1000 }
1001
1002 pub fn offset(offset_x: f32, offset_y: f32) -> Self {
1004 Self::Offset { offset_x, offset_y }
1005 }
1006
1007 pub fn runtime_shader(shader: RuntimeShader) -> Self {
1009 Self::Shader {
1010 shader: Arc::new(shader),
1011 }
1012 }
1013
1014 pub fn then(self, other: RenderEffect) -> Self {
1016 Self::Chain {
1017 first: Arc::new(self),
1018 second: Arc::new(other),
1019 }
1020 }
1021
1022 pub fn contains_runtime_shader(&self) -> bool {
1026 match self {
1027 RenderEffect::Shader { .. } => true,
1028 RenderEffect::Chain { first, second } => {
1029 first.contains_runtime_shader() || second.contains_runtime_shader()
1030 }
1031 _ => false,
1032 }
1033 }
1034
1035 pub fn preserves_transparency(&self) -> bool {
1039 match self {
1040 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => true,
1041 RenderEffect::Shader { shader } => shader.preserves_transparency(),
1042 RenderEffect::Chain { first, second } => {
1043 first.preserves_transparency() && second.preserves_transparency()
1044 }
1045 }
1046 }
1047
1048 pub fn input_padding(&self) -> f32 {
1050 match self {
1051 RenderEffect::Blur {
1052 radius_x, radius_y, ..
1053 } => radius_x.abs().max(radius_y.abs()),
1054 RenderEffect::Offset { offset_x, offset_y } => offset_x.abs().max(offset_y.abs()),
1055 RenderEffect::Shader { shader } => shader.input_padding(),
1056 RenderEffect::Chain { first, second } => first.input_padding() + second.input_padding(),
1057 }
1058 }
1059
1060 pub fn output_padding(&self) -> f32 {
1064 match self {
1065 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => 0.0,
1066 RenderEffect::Shader { shader } => shader.output_padding(),
1067 RenderEffect::Chain { first, second } => {
1068 first.output_padding() + second.output_padding()
1069 }
1070 }
1071 }
1072
1073 pub fn output_support(&self) -> Option<Rect> {
1078 match self {
1079 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
1080 RenderEffect::Shader { shader } => shader.output_support(),
1081 RenderEffect::Chain { second, .. } => second.output_support(),
1082 }
1083 }
1084
1085 pub fn sample_domain(&self) -> Option<Rect> {
1089 match self {
1090 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
1091 RenderEffect::Shader { shader } => shader.sample_domain(),
1092 RenderEffect::Chain { second, .. } => second.sample_domain(),
1093 }
1094 }
1095}
1096
1097#[cfg(test)]
1098#[path = "tests/render_effect_tests.rs"]
1099mod tests;