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) {
431 let position = self
432 .overrides()
433 .binary_search_by(|(existing, _)| existing.cmp(&name));
434 if position.is_ok_and(|index| self.overrides()[index].1.to_bits() == value.to_bits()) {
435 return;
436 }
437 let specialization = self.specialization_mut();
438 specialization.overrides_hash.take();
439 let overrides = &mut specialization.overrides;
440 match position {
441 Ok(index) => overrides[index].1 = value,
442 Err(index) => overrides.insert(index, (name, value)),
443 }
444 }
445
446 pub fn clear_override(&mut self, name: &str) -> bool {
448 let Ok(index) = self
449 .overrides()
450 .binary_search_by(|(existing, _)| (*existing).cmp(name))
451 else {
452 return false;
453 };
454 let specialization = self.specialization_mut();
455 specialization.overrides_hash.take();
456 specialization.overrides.remove(index);
457 true
458 }
459
460 pub fn overrides(&self) -> &[(&'static str, f64)] {
463 &self.specialization().overrides
464 }
465
466 pub fn overrides_hash(&self) -> u64 {
468 let specialization = self.specialization();
469 if specialization.overrides.is_empty() {
470 return 0;
471 }
472 *specialization.overrides_hash.get_or_init(|| {
473 #[cfg(test)]
474 OVERRIDE_HASH_COMPUTATIONS.with(|count| count.set(count.get() + 1));
475 hash_shader_bytes(specialization.overrides.iter().flat_map(|(name, value)| {
476 name.bytes().chain([0]).chain(value.to_bits().to_le_bytes())
477 }))
478 })
479 }
480
481 pub fn set_input_padding(&mut self, padding: f32) {
485 self.input_padding = if padding.is_finite() {
486 padding.max(0.0)
487 } else {
488 0.0
489 };
490 }
491
492 pub fn input_padding(&self) -> f32 {
494 self.input_padding
495 }
496
497 pub fn set_output_padding(&mut self, padding: f32) {
502 self.output_padding = if padding.is_finite() {
503 padding.max(0.0)
504 } else {
505 0.0
506 };
507 }
508
509 pub fn output_padding(&self) -> f32 {
511 self.output_padding
512 }
513
514 pub fn set_output_support(&mut self, support: Option<Rect>) {
525 self.set_domains(ShaderDomains {
526 output_support: finite_rect(support),
527 sample_domain: self.sample_domain(),
528 });
529 }
530
531 pub fn output_support(&self) -> Option<Rect> {
533 self.domains
534 .as_ref()
535 .and_then(|domains| domains.output_support)
536 }
537
538 fn set_domains(&mut self, domains: ShaderDomains) {
539 self.domains = (domains != ShaderDomains::default()).then(|| Box::new(domains));
540 }
541
542 pub fn set_sample_domain(&mut self, domain: Option<Rect>) {
551 self.set_domains(ShaderDomains {
552 output_support: self.output_support(),
553 sample_domain: finite_rect(domain),
554 });
555 }
556
557 pub fn sample_domain(&self) -> Option<Rect> {
559 self.domains
560 .as_ref()
561 .and_then(|domains| domains.sample_domain)
562 }
563
564 pub fn set_float(&mut self, index: usize, value: f32) {
569 let _ = self.try_set_float(index, value);
570 }
571
572 pub fn try_set_float(
574 &mut self,
575 index: usize,
576 value: f32,
577 ) -> Result<(), RuntimeShaderUniformError> {
578 self.try_ensure_capacity(index, 1)?;
579 self.uniforms.set(index, value);
580 Ok(())
581 }
582
583 pub fn set_float2(&mut self, index: usize, x: f32, y: f32) {
588 let _ = self.try_set_float2(index, x, y);
589 }
590
591 pub fn try_set_float2(
593 &mut self,
594 index: usize,
595 x: f32,
596 y: f32,
597 ) -> Result<(), RuntimeShaderUniformError> {
598 self.try_ensure_capacity(index, 2)?;
599 self.uniforms.set(index, x);
600 self.uniforms.set(index + 1, y);
601 Ok(())
602 }
603
604 pub fn set_float4(&mut self, index: usize, x: f32, y: f32, z: f32, w: f32) {
609 let _ = self.try_set_float4(index, x, y, z, w);
610 }
611
612 pub fn try_set_float4(
614 &mut self,
615 index: usize,
616 x: f32,
617 y: f32,
618 z: f32,
619 w: f32,
620 ) -> Result<(), RuntimeShaderUniformError> {
621 self.try_ensure_capacity(index, 4)?;
622 self.uniforms.set(index, x);
623 self.uniforms.set(index + 1, y);
624 self.uniforms.set(index + 2, z);
625 self.uniforms.set(index + 3, w);
626 Ok(())
627 }
628
629 pub fn set_batched_source(&mut self, batched: bool) {
634 self.batched_source = batched;
635 }
636
637 pub fn batched_source(&self) -> bool {
640 self.batched_source
641 }
642
643 pub fn set_position_independent(&mut self, independent: bool) {
649 self.position_independent = independent;
650 }
651
652 pub fn position_independent(&self) -> bool {
654 self.position_independent
655 }
656
657 pub fn set_preserves_transparency(&mut self, preserves: bool) {
662 self.preserves_transparency = preserves;
663 }
664
665 pub fn preserves_transparency(&self) -> bool {
667 self.preserves_transparency
668 }
669
670 pub fn set_substrates(&mut self, substrates: &[SubstrateSpec]) {
679 assert!(
680 substrates.len() <= MAX_SUBSTRATES,
681 "a runtime shader declares at most {MAX_SUBSTRATES} substrates"
682 );
683 if self.substrates().len() == substrates.len()
684 && self
685 .substrates()
686 .iter()
687 .zip(substrates)
688 .all(|(existing, incoming)| existing.same_bits(incoming))
689 {
690 return;
691 }
692 self.specialization_mut().substrates = substrates.iter().copied().collect();
693 }
694
695 pub fn substrates(&self) -> &[SubstrateSpec] {
697 &self.specialization().substrates
698 }
699
700 pub fn hash_substrates<H: std::hash::Hasher>(&self, state: &mut H) {
702 use std::hash::Hash;
703 self.substrates().len().hash(state);
704 for substrate in self.substrates() {
705 substrate.hash_bits(state);
706 }
707 self.draw_split().hash(state);
708 }
709
710 pub fn set_draw_split(&mut self, override_name: Option<&'static str>) {
717 if self.draw_split() == override_name {
718 return;
719 }
720 self.specialization_mut().draw_split = override_name;
721 }
722
723 pub fn draw_split(&self) -> Option<&'static str> {
725 self.specialization().draw_split
726 }
727
728 pub fn set_specialization_exact(&mut self, exact: bool) {
736 if self.specialization_exact() == exact {
737 return;
738 }
739 self.specialization_mut().exact = exact;
740 }
741
742 pub fn specialization_exact(&self) -> bool {
744 self.specialization().exact
745 }
746
747 pub fn source(&self) -> &str {
749 &self.source
750 }
751
752 pub fn uniforms(&self) -> &[f32] {
754 self.uniforms.as_slice()
755 }
756
757 pub fn uniforms_padded(&self) -> [f32; Self::MAX_UNIFORMS] {
759 let mut padded = [0.0f32; Self::MAX_UNIFORMS];
760 let len = self.uniforms.len().min(Self::MAX_UNIFORMS);
761 padded[..len].copy_from_slice(&self.uniforms.as_slice()[..len]);
762 padded
763 }
764
765 pub fn source_hash(&self) -> u64 {
767 self.source_hash
768 }
769
770 fn try_ensure_capacity(
771 &mut self,
772 index: usize,
773 width: usize,
774 ) -> Result<(), RuntimeShaderUniformError> {
775 let min_len = index
776 .checked_add(width)
777 .ok_or_else(|| Self::uniform_range_error(index, width))?;
778 if min_len > Self::MAX_USER_UNIFORMS {
779 return Err(Self::uniform_range_error(index, width));
780 }
781 self.uniforms.ensure_len(min_len);
782 Ok(())
783 }
784
785 fn uniform_range_error(index: usize, width: usize) -> RuntimeShaderUniformError {
786 RuntimeShaderUniformError::OutOfUserRange {
787 index,
788 width,
789 max_user_uniforms: Self::MAX_USER_UNIFORMS,
790 reserved_start: Self::RESERVED_UNIFORM_START,
791 max_uniforms: Self::MAX_UNIFORMS,
792 }
793 }
794}
795
796#[cfg(test)]
797thread_local! {
798 static OVERRIDE_HASH_COMPUTATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
799}
800
801impl PartialEq for RuntimeShader {
802 fn eq(&self, other: &Self) -> bool {
803 self.source_hash == other.source_hash
804 && (Arc::ptr_eq(&self.source, &other.source)
805 || self.source.as_ref() == other.source.as_ref())
806 && self.uniforms == other.uniforms
807 && self.overrides().len() == other.overrides().len()
808 && self
809 .overrides()
810 .iter()
811 .zip(other.overrides())
812 .all(|(a, b)| a.0 == b.0 && a.1.to_bits() == b.1.to_bits())
813 && self.input_padding.to_bits() == other.input_padding.to_bits()
814 && self.output_padding.to_bits() == other.output_padding.to_bits()
815 && self.batched_source == other.batched_source
816 && self.position_independent == other.position_independent
817 && self.preserves_transparency == other.preserves_transparency
818 && self.substrates() == other.substrates()
819 && self.draw_split() == other.draw_split()
820 && self.domains == other.domains
821 }
822}
823
824fn hash_shader_source(source: &str) -> u64 {
825 hash_shader_bytes(source.bytes())
826}
827
828fn hash_shader_bytes(bytes: impl IntoIterator<Item = u8>) -> u64 {
829 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
830 const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
831
832 bytes.into_iter().fold(FNV_OFFSET_BASIS, |hash, byte| {
833 (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME)
834 })
835}
836
837#[derive(Clone, Copy, Debug, PartialEq, Eq)]
838struct ShaderSourceCallsite {
839 file: &'static str,
840 line: u32,
841 column: u32,
842}
843
844struct CachedShaderSource {
845 callsite: ShaderSourceCallsite,
846 source_hash: u64,
847 source: Arc<str>,
848}
849
850struct CachedSharedShaderSourceHash {
851 byte_ptr: usize,
852 len: usize,
853 source_hash: u64,
854 source: Weak<str>,
855}
856
857fn cached_shared_shader_source_hash(source: &Arc<str>) -> u64 {
858 static CACHE: OnceLock<Mutex<Vec<CachedSharedShaderSourceHash>>> = OnceLock::new();
859 let byte_ptr = source.as_ptr() as usize;
860 let len = source.len();
861 let mut cache = CACHE
862 .get_or_init(|| Mutex::new(Vec::new()))
863 .lock()
864 .unwrap_or_else(PoisonError::into_inner);
865
866 cache.retain(|entry| entry.source.strong_count() > 0);
867 if let Some(entry) = cache.iter().find(|entry| {
868 entry.byte_ptr == byte_ptr
869 && entry.len == len
870 && entry
871 .source
872 .upgrade()
873 .is_some_and(|cached| Arc::ptr_eq(&cached, source))
874 }) {
875 return entry.source_hash;
876 }
877
878 let source_hash = hash_shader_source(source);
879 cache.push(CachedSharedShaderSourceHash {
880 byte_ptr,
881 len,
882 source_hash,
883 source: Arc::downgrade(source),
884 });
885 source_hash
886}
887
888fn cached_shader_source(
889 location: &'static std::panic::Location<'static>,
890 source: &str,
891) -> (Arc<str>, u64) {
892 static CACHE: OnceLock<Mutex<Vec<CachedShaderSource>>> = OnceLock::new();
893 let callsite = ShaderSourceCallsite {
894 file: location.file(),
895 line: location.line(),
896 column: location.column(),
897 };
898 let mut cache = CACHE
899 .get_or_init(|| Mutex::new(Vec::new()))
900 .lock()
901 .unwrap_or_else(PoisonError::into_inner);
902
903 if let Some(entry) = cache.iter_mut().find(|entry| entry.callsite == callsite) {
904 if entry.source.as_ref() == source {
905 return (entry.source.clone(), entry.source_hash);
906 }
907 let source_hash = hash_shader_source(source);
908 entry.source_hash = source_hash;
909 entry.source = Arc::<str>::from(source);
910 return (entry.source.clone(), entry.source_hash);
911 }
912
913 let source_hash = hash_shader_source(source);
914 let shared = Arc::<str>::from(source);
915 cache.push(CachedShaderSource {
916 callsite,
917 source_hash,
918 source: shared.clone(),
919 });
920 (shared, source_hash)
921}
922
923#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
930pub enum ShaderTarget {
931 Page,
932 Layer,
933}
934
935#[derive(Clone, Debug, PartialEq)]
939pub struct ShaderWarmUp {
940 pub shader: RuntimeShader,
941 pub target: ShaderTarget,
942}
943
944#[derive(Clone, Debug, PartialEq)]
949pub enum RenderEffect {
950 Blur {
952 radius_x: f32,
953 radius_y: f32,
954 edge_treatment: TileMode,
955 },
956 Offset { offset_x: f32, offset_y: f32 },
958 Shader {
960 shader: Arc<RuntimeShader>,
962 },
963 Chain {
967 first: Arc<RenderEffect>,
968 second: Arc<RenderEffect>,
969 },
970}
971
972impl RenderEffect {
973 pub fn blur(radius: f32) -> Self {
975 Self::blur_with_edge_treatment(radius, TileMode::default())
976 }
977
978 pub fn blur_with_edge_treatment(radius: f32, edge_treatment: TileMode) -> Self {
981 Self::Blur {
982 radius_x: radius,
983 radius_y: radius,
984 edge_treatment,
985 }
986 }
987
988 pub fn blur_xy(radius_x: f32, radius_y: f32, edge_treatment: TileMode) -> Self {
990 Self::Blur {
991 radius_x,
992 radius_y,
993 edge_treatment,
994 }
995 }
996
997 pub fn offset(offset_x: f32, offset_y: f32) -> Self {
999 Self::Offset { offset_x, offset_y }
1000 }
1001
1002 pub fn runtime_shader(shader: RuntimeShader) -> Self {
1004 Self::Shader {
1005 shader: Arc::new(shader),
1006 }
1007 }
1008
1009 pub fn then(self, other: RenderEffect) -> Self {
1011 Self::Chain {
1012 first: Arc::new(self),
1013 second: Arc::new(other),
1014 }
1015 }
1016
1017 pub fn contains_runtime_shader(&self) -> bool {
1021 match self {
1022 RenderEffect::Shader { .. } => true,
1023 RenderEffect::Chain { first, second } => {
1024 first.contains_runtime_shader() || second.contains_runtime_shader()
1025 }
1026 _ => false,
1027 }
1028 }
1029
1030 pub fn preserves_transparency(&self) -> bool {
1034 match self {
1035 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => true,
1036 RenderEffect::Shader { shader } => shader.preserves_transparency(),
1037 RenderEffect::Chain { first, second } => {
1038 first.preserves_transparency() && second.preserves_transparency()
1039 }
1040 }
1041 }
1042
1043 pub fn input_padding(&self) -> f32 {
1045 match self {
1046 RenderEffect::Blur {
1047 radius_x, radius_y, ..
1048 } => radius_x.abs().max(radius_y.abs()),
1049 RenderEffect::Offset { offset_x, offset_y } => offset_x.abs().max(offset_y.abs()),
1050 RenderEffect::Shader { shader } => shader.input_padding(),
1051 RenderEffect::Chain { first, second } => first.input_padding() + second.input_padding(),
1052 }
1053 }
1054
1055 pub fn output_padding(&self) -> f32 {
1059 match self {
1060 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => 0.0,
1061 RenderEffect::Shader { shader } => shader.output_padding(),
1062 RenderEffect::Chain { first, second } => {
1063 first.output_padding() + second.output_padding()
1064 }
1065 }
1066 }
1067
1068 pub fn output_support(&self) -> Option<Rect> {
1073 match self {
1074 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
1075 RenderEffect::Shader { shader } => shader.output_support(),
1076 RenderEffect::Chain { second, .. } => second.output_support(),
1077 }
1078 }
1079
1080 pub fn sample_domain(&self) -> Option<Rect> {
1084 match self {
1085 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
1086 RenderEffect::Shader { shader } => shader.sample_domain(),
1087 RenderEffect::Chain { second, .. } => second.sample_domain(),
1088 }
1089 }
1090}
1091
1092#[cfg(test)]
1093#[path = "tests/render_effect_tests.rs"]
1094mod tests;