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 preserves_transparency: bool,
133 domains: Option<Box<ShaderDomains>>,
134}
135
136#[derive(Clone, Debug, Default)]
137struct ShaderSpecialization {
138 overrides: Vec<(&'static str, f64)>,
139 overrides_hash: OnceLock<u64>,
140 substrates: ArrayVec<SubstrateSpec, MAX_SUBSTRATES>,
141 draw_split: Option<&'static str>,
142 exact: bool,
143}
144
145pub(crate) struct ShaderSpecializationCache<K, const N: usize> {
146 entries: ArrayVec<CachedShaderSpecialization<K>, N>,
147}
148
149struct CachedShaderSpecialization<K> {
150 source: Option<Arc<ShaderSpecialization>>,
151 key: K,
152 result: Option<Arc<ShaderSpecialization>>,
153}
154
155impl<K: PartialEq, const N: usize> ShaderSpecializationCache<K, N> {
156 pub(crate) const fn new() -> Self {
157 assert!(N > 0);
158 Self {
159 entries: ArrayVec::new_const(),
160 }
161 }
162
163 pub(crate) fn apply(
164 &mut self,
165 shader: &mut RuntimeShader,
166 key: K,
167 specialize: impl FnOnce(&mut RuntimeShader, &K),
168 ) {
169 let hit = self.entries.iter().rposition(|entry| {
170 entry.key == key
171 && match (&entry.source, &shader.specialization) {
172 (Some(source), Some(current)) => Arc::ptr_eq(source, current),
173 (None, None) => true,
174 _ => false,
175 }
176 });
177 if let Some(index) = hit {
178 let entry = self.entries.remove(index);
179 shader.specialization.clone_from(&entry.result);
180 self.entries.push(entry);
181 return;
182 }
183 if shader
184 .specialization
185 .as_ref()
186 .is_some_and(|source| Arc::strong_count(source) == 1)
187 {
188 specialize(shader, &key);
189 return;
190 }
191 let source = shader.specialization.clone();
192 specialize(shader, &key);
193 if self.entries.is_full() {
194 self.entries.remove(0);
195 }
196 self.entries.push(CachedShaderSpecialization {
197 source,
198 key,
199 result: shader.specialization.clone(),
200 });
201 }
202}
203
204static DEFAULT_SHADER_SPECIALIZATION: ShaderSpecialization = ShaderSpecialization {
205 overrides: Vec::new(),
206 overrides_hash: OnceLock::new(),
207 substrates: ArrayVec::new_const(),
208 draw_split: None,
209 exact: false,
210};
211
212#[derive(Clone, Copy, Debug, Default, PartialEq)]
213struct ShaderDomains {
214 output_support: Option<Rect>,
215 sample_domain: Option<Rect>,
216}
217
218fn finite_rect(rect: Option<Rect>) -> Option<Rect> {
219 rect.filter(|rect| {
220 rect.x.is_finite()
221 && rect.y.is_finite()
222 && rect.width.is_finite()
223 && rect.height.is_finite()
224 })
225}
226
227pub const MAX_SUBSTRATES: usize = 3;
229
230#[derive(Clone, Copy, Debug, PartialEq)]
233pub enum SubstrateSpec {
234 Mean,
239 Average { block: u32 },
242 Blur { radius_px: f32 },
245}
246
247impl SubstrateSpec {
248 fn same_bits(&self, other: &Self) -> bool {
249 match (self, other) {
250 (Self::Mean, Self::Mean) => true,
251 (Self::Average { block: a }, Self::Average { block: b }) => a == b,
252 (Self::Blur { radius_px: a }, Self::Blur { radius_px: b }) => {
253 a.to_bits() == b.to_bits()
254 }
255 _ => false,
256 }
257 }
258
259 fn hash_bits<H: std::hash::Hasher>(&self, state: &mut H) {
260 use std::hash::Hash;
261 match self {
262 Self::Mean => 2u8.hash(state),
263 Self::Average { block } => {
264 0u8.hash(state);
265 block.hash(state);
266 }
267 Self::Blur { radius_px } => {
268 1u8.hash(state);
269 radius_px.to_bits().hash(state);
270 }
271 }
272 }
273}
274
275#[derive(Clone, Debug, PartialEq)]
276struct RuntimeShaderUniforms {
277 len: usize,
278 inline: [f32; RUNTIME_SHADER_INLINE_UNIFORMS],
279 heap: Option<Vec<f32>>,
280}
281
282impl RuntimeShaderUniforms {
283 fn new() -> Self {
284 Self {
285 len: 0,
286 inline: [0.0; RUNTIME_SHADER_INLINE_UNIFORMS],
287 heap: None,
288 }
289 }
290
291 fn as_slice(&self) -> &[f32] {
292 if let Some(heap) = &self.heap {
293 heap.as_slice()
294 } else {
295 &self.inline[..self.len]
296 }
297 }
298
299 fn len(&self) -> usize {
300 self.as_slice().len()
301 }
302
303 fn ensure_len(&mut self, min_len: usize) {
304 if let Some(heap) = &mut self.heap {
305 if heap.len() < min_len {
306 heap.resize(min_len, 0.0);
307 }
308 return;
309 }
310
311 if min_len <= RUNTIME_SHADER_INLINE_UNIFORMS {
312 self.len = self.len.max(min_len);
313 return;
314 }
315
316 let mut heap = Vec::with_capacity(min_len);
317 heap.extend_from_slice(&self.inline[..self.len]);
318 heap.resize(min_len, 0.0);
319 self.heap = Some(heap);
320 }
321
322 fn set(&mut self, index: usize, value: f32) {
323 if let Some(heap) = &mut self.heap {
324 heap[index] = value;
325 } else {
326 self.inline[index] = value;
327 }
328 }
329
330 #[cfg(test)]
331 fn is_inline(&self) -> bool {
332 self.heap.is_none()
333 }
334}
335
336#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
338pub enum RuntimeShaderUniformError {
339 #[error(
340 "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"
341 )]
342 OutOfUserRange {
343 index: usize,
344 width: usize,
345 max_user_uniforms: usize,
346 reserved_start: usize,
347 max_uniforms: usize,
348 },
349}
350
351impl RuntimeShader {
352 pub const MAX_UNIFORMS: usize = 256;
356 pub const RESERVED_UNIFORM_START: usize = 224;
358 pub const SUBSTRATE_REGION_UNIFORMS: [usize; MAX_SUBSTRATES] = [232, 228, 224];
361 pub const SOURCE_REGION_UNIFORM: usize = 236;
363 pub const MASK_RECT_UNIFORM: usize = 240;
365 pub const MASK_RADII_UNIFORM: usize = 244;
367 pub const EFFECT_RECT_UNIFORM: usize = 248;
369 pub const LOGICAL_SIZE_UNIFORM: usize = 252;
371 pub const ALPHA_UNIFORM: usize = 254;
373 pub const MAX_USER_UNIFORMS: usize = Self::RESERVED_UNIFORM_START;
375
376 #[track_caller]
378 pub fn new(wgsl_source: &str) -> Self {
379 let (source, source_hash) =
380 cached_shader_source(std::panic::Location::caller(), wgsl_source);
381 Self::with_source(source, source_hash)
382 }
383
384 pub fn from_shared_source(source: Arc<str>) -> Self {
389 let source_hash = cached_shared_shader_source_hash(&source);
390 Self::with_source(source, source_hash)
391 }
392
393 fn with_source(source: Arc<str>, source_hash: u64) -> Self {
394 Self {
395 source,
396 source_hash,
397 uniforms: RuntimeShaderUniforms::new(),
398 specialization: None,
399 input_padding: 0.0,
400 output_padding: 0.0,
401 batched_source: false,
402 preserves_transparency: false,
403 domains: None,
404 }
405 }
406
407 fn specialization(&self) -> &ShaderSpecialization {
408 self.specialization
409 .as_deref()
410 .unwrap_or(&DEFAULT_SHADER_SPECIALIZATION)
411 }
412
413 fn specialization_mut(&mut self) -> &mut ShaderSpecialization {
414 Arc::make_mut(self.specialization.get_or_insert_with(Arc::default))
415 }
416
417 pub fn set_override(&mut self, name: &'static str, value: f64) {
429 let position = self
430 .overrides()
431 .binary_search_by(|(existing, _)| existing.cmp(&name));
432 if position.is_ok_and(|index| self.overrides()[index].1.to_bits() == value.to_bits()) {
433 return;
434 }
435 let specialization = self.specialization_mut();
436 specialization.overrides_hash.take();
437 let overrides = &mut specialization.overrides;
438 match position {
439 Ok(index) => overrides[index].1 = value,
440 Err(index) => overrides.insert(index, (name, value)),
441 }
442 }
443
444 pub fn clear_override(&mut self, name: &str) -> bool {
446 let Ok(index) = self
447 .overrides()
448 .binary_search_by(|(existing, _)| (*existing).cmp(name))
449 else {
450 return false;
451 };
452 let specialization = self.specialization_mut();
453 specialization.overrides_hash.take();
454 specialization.overrides.remove(index);
455 true
456 }
457
458 pub fn overrides(&self) -> &[(&'static str, f64)] {
461 &self.specialization().overrides
462 }
463
464 pub fn overrides_hash(&self) -> u64 {
466 let specialization = self.specialization();
467 if specialization.overrides.is_empty() {
468 return 0;
469 }
470 *specialization.overrides_hash.get_or_init(|| {
471 #[cfg(test)]
472 OVERRIDE_HASH_COMPUTATIONS.with(|count| count.set(count.get() + 1));
473 hash_shader_bytes(specialization.overrides.iter().flat_map(|(name, value)| {
474 name.bytes().chain([0]).chain(value.to_bits().to_le_bytes())
475 }))
476 })
477 }
478
479 pub fn set_input_padding(&mut self, padding: f32) {
483 self.input_padding = if padding.is_finite() {
484 padding.max(0.0)
485 } else {
486 0.0
487 };
488 }
489
490 pub fn input_padding(&self) -> f32 {
492 self.input_padding
493 }
494
495 pub fn set_output_padding(&mut self, padding: f32) {
500 self.output_padding = if padding.is_finite() {
501 padding.max(0.0)
502 } else {
503 0.0
504 };
505 }
506
507 pub fn output_padding(&self) -> f32 {
509 self.output_padding
510 }
511
512 pub fn set_output_support(&mut self, support: Option<Rect>) {
523 self.set_domains(ShaderDomains {
524 output_support: finite_rect(support),
525 sample_domain: self.sample_domain(),
526 });
527 }
528
529 pub fn output_support(&self) -> Option<Rect> {
531 self.domains
532 .as_ref()
533 .and_then(|domains| domains.output_support)
534 }
535
536 fn set_domains(&mut self, domains: ShaderDomains) {
537 self.domains = (domains != ShaderDomains::default()).then(|| Box::new(domains));
538 }
539
540 pub fn set_sample_domain(&mut self, domain: Option<Rect>) {
549 self.set_domains(ShaderDomains {
550 output_support: self.output_support(),
551 sample_domain: finite_rect(domain),
552 });
553 }
554
555 pub fn sample_domain(&self) -> Option<Rect> {
557 self.domains
558 .as_ref()
559 .and_then(|domains| domains.sample_domain)
560 }
561
562 pub fn set_float(&mut self, index: usize, value: f32) {
567 let _ = self.try_set_float(index, value);
568 }
569
570 pub fn try_set_float(
572 &mut self,
573 index: usize,
574 value: f32,
575 ) -> Result<(), RuntimeShaderUniformError> {
576 self.try_ensure_capacity(index, 1)?;
577 self.uniforms.set(index, value);
578 Ok(())
579 }
580
581 pub fn set_float2(&mut self, index: usize, x: f32, y: f32) {
586 let _ = self.try_set_float2(index, x, y);
587 }
588
589 pub fn try_set_float2(
591 &mut self,
592 index: usize,
593 x: f32,
594 y: f32,
595 ) -> Result<(), RuntimeShaderUniformError> {
596 self.try_ensure_capacity(index, 2)?;
597 self.uniforms.set(index, x);
598 self.uniforms.set(index + 1, y);
599 Ok(())
600 }
601
602 pub fn set_float4(&mut self, index: usize, x: f32, y: f32, z: f32, w: f32) {
607 let _ = self.try_set_float4(index, x, y, z, w);
608 }
609
610 pub fn try_set_float4(
612 &mut self,
613 index: usize,
614 x: f32,
615 y: f32,
616 z: f32,
617 w: f32,
618 ) -> Result<(), RuntimeShaderUniformError> {
619 self.try_ensure_capacity(index, 4)?;
620 self.uniforms.set(index, x);
621 self.uniforms.set(index + 1, y);
622 self.uniforms.set(index + 2, z);
623 self.uniforms.set(index + 3, w);
624 Ok(())
625 }
626
627 pub fn set_batched_source(&mut self, batched: bool) {
632 self.batched_source = batched;
633 }
634
635 pub fn batched_source(&self) -> bool {
638 self.batched_source
639 }
640
641 pub fn set_preserves_transparency(&mut self, preserves: bool) {
646 self.preserves_transparency = preserves;
647 }
648
649 pub fn preserves_transparency(&self) -> bool {
651 self.preserves_transparency
652 }
653
654 pub fn set_substrates(&mut self, substrates: &[SubstrateSpec]) {
663 assert!(
664 substrates.len() <= MAX_SUBSTRATES,
665 "a runtime shader declares at most {MAX_SUBSTRATES} substrates"
666 );
667 if self.substrates().len() == substrates.len()
668 && self
669 .substrates()
670 .iter()
671 .zip(substrates)
672 .all(|(existing, incoming)| existing.same_bits(incoming))
673 {
674 return;
675 }
676 self.specialization_mut().substrates = substrates.iter().copied().collect();
677 }
678
679 pub fn substrates(&self) -> &[SubstrateSpec] {
681 &self.specialization().substrates
682 }
683
684 pub fn hash_substrates<H: std::hash::Hasher>(&self, state: &mut H) {
686 use std::hash::Hash;
687 self.substrates().len().hash(state);
688 for substrate in self.substrates() {
689 substrate.hash_bits(state);
690 }
691 self.draw_split().hash(state);
692 }
693
694 pub fn set_draw_split(&mut self, override_name: Option<&'static str>) {
701 if self.draw_split() == override_name {
702 return;
703 }
704 self.specialization_mut().draw_split = override_name;
705 }
706
707 pub fn draw_split(&self) -> Option<&'static str> {
709 self.specialization().draw_split
710 }
711
712 pub fn set_specialization_exact(&mut self, exact: bool) {
720 if self.specialization_exact() == exact {
721 return;
722 }
723 self.specialization_mut().exact = exact;
724 }
725
726 pub fn specialization_exact(&self) -> bool {
728 self.specialization().exact
729 }
730
731 pub fn source(&self) -> &str {
733 &self.source
734 }
735
736 pub fn uniforms(&self) -> &[f32] {
738 self.uniforms.as_slice()
739 }
740
741 pub fn uniforms_padded(&self) -> [f32; Self::MAX_UNIFORMS] {
743 let mut padded = [0.0f32; Self::MAX_UNIFORMS];
744 let len = self.uniforms.len().min(Self::MAX_UNIFORMS);
745 padded[..len].copy_from_slice(&self.uniforms.as_slice()[..len]);
746 padded
747 }
748
749 pub fn source_hash(&self) -> u64 {
751 self.source_hash
752 }
753
754 fn try_ensure_capacity(
755 &mut self,
756 index: usize,
757 width: usize,
758 ) -> Result<(), RuntimeShaderUniformError> {
759 let min_len = index
760 .checked_add(width)
761 .ok_or_else(|| Self::uniform_range_error(index, width))?;
762 if min_len > Self::MAX_USER_UNIFORMS {
763 return Err(Self::uniform_range_error(index, width));
764 }
765 self.uniforms.ensure_len(min_len);
766 Ok(())
767 }
768
769 fn uniform_range_error(index: usize, width: usize) -> RuntimeShaderUniformError {
770 RuntimeShaderUniformError::OutOfUserRange {
771 index,
772 width,
773 max_user_uniforms: Self::MAX_USER_UNIFORMS,
774 reserved_start: Self::RESERVED_UNIFORM_START,
775 max_uniforms: Self::MAX_UNIFORMS,
776 }
777 }
778}
779
780#[cfg(test)]
781thread_local! {
782 static OVERRIDE_HASH_COMPUTATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
783}
784
785impl PartialEq for RuntimeShader {
786 fn eq(&self, other: &Self) -> bool {
787 self.source_hash == other.source_hash
788 && (Arc::ptr_eq(&self.source, &other.source)
789 || self.source.as_ref() == other.source.as_ref())
790 && self.uniforms == other.uniforms
791 && self.overrides().len() == other.overrides().len()
792 && self
793 .overrides()
794 .iter()
795 .zip(other.overrides())
796 .all(|(a, b)| a.0 == b.0 && a.1.to_bits() == b.1.to_bits())
797 && self.input_padding.to_bits() == other.input_padding.to_bits()
798 && self.output_padding.to_bits() == other.output_padding.to_bits()
799 && self.batched_source == other.batched_source
800 && self.preserves_transparency == other.preserves_transparency
801 && self.substrates() == other.substrates()
802 && self.draw_split() == other.draw_split()
803 && self.domains == other.domains
804 }
805}
806
807fn hash_shader_source(source: &str) -> u64 {
808 hash_shader_bytes(source.bytes())
809}
810
811fn hash_shader_bytes(bytes: impl IntoIterator<Item = u8>) -> u64 {
812 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
813 const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
814
815 bytes.into_iter().fold(FNV_OFFSET_BASIS, |hash, byte| {
816 (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME)
817 })
818}
819
820#[derive(Clone, Copy, Debug, PartialEq, Eq)]
821struct ShaderSourceCallsite {
822 file: &'static str,
823 line: u32,
824 column: u32,
825}
826
827struct CachedShaderSource {
828 callsite: ShaderSourceCallsite,
829 source_hash: u64,
830 source: Arc<str>,
831}
832
833struct CachedSharedShaderSourceHash {
834 byte_ptr: usize,
835 len: usize,
836 source_hash: u64,
837 source: Weak<str>,
838}
839
840fn cached_shared_shader_source_hash(source: &Arc<str>) -> u64 {
841 static CACHE: OnceLock<Mutex<Vec<CachedSharedShaderSourceHash>>> = OnceLock::new();
842 let byte_ptr = source.as_ptr() as usize;
843 let len = source.len();
844 let mut cache = CACHE
845 .get_or_init(|| Mutex::new(Vec::new()))
846 .lock()
847 .unwrap_or_else(PoisonError::into_inner);
848
849 cache.retain(|entry| entry.source.strong_count() > 0);
850 if let Some(entry) = cache.iter().find(|entry| {
851 entry.byte_ptr == byte_ptr
852 && entry.len == len
853 && entry
854 .source
855 .upgrade()
856 .is_some_and(|cached| Arc::ptr_eq(&cached, source))
857 }) {
858 return entry.source_hash;
859 }
860
861 let source_hash = hash_shader_source(source);
862 cache.push(CachedSharedShaderSourceHash {
863 byte_ptr,
864 len,
865 source_hash,
866 source: Arc::downgrade(source),
867 });
868 source_hash
869}
870
871fn cached_shader_source(
872 location: &'static std::panic::Location<'static>,
873 source: &str,
874) -> (Arc<str>, u64) {
875 static CACHE: OnceLock<Mutex<Vec<CachedShaderSource>>> = OnceLock::new();
876 let callsite = ShaderSourceCallsite {
877 file: location.file(),
878 line: location.line(),
879 column: location.column(),
880 };
881 let mut cache = CACHE
882 .get_or_init(|| Mutex::new(Vec::new()))
883 .lock()
884 .unwrap_or_else(PoisonError::into_inner);
885
886 if let Some(entry) = cache.iter_mut().find(|entry| entry.callsite == callsite) {
887 if entry.source.as_ref() == source {
888 return (entry.source.clone(), entry.source_hash);
889 }
890 let source_hash = hash_shader_source(source);
891 entry.source_hash = source_hash;
892 entry.source = Arc::<str>::from(source);
893 return (entry.source.clone(), entry.source_hash);
894 }
895
896 let source_hash = hash_shader_source(source);
897 let shared = Arc::<str>::from(source);
898 cache.push(CachedShaderSource {
899 callsite,
900 source_hash,
901 source: shared.clone(),
902 });
903 (shared, source_hash)
904}
905
906#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
913pub enum ShaderTarget {
914 Page,
915 Layer,
916}
917
918#[derive(Clone, Debug, PartialEq)]
922pub struct ShaderWarmUp {
923 pub shader: RuntimeShader,
924 pub target: ShaderTarget,
925}
926
927#[derive(Clone, Debug, PartialEq)]
932pub enum RenderEffect {
933 Blur {
935 radius_x: f32,
936 radius_y: f32,
937 edge_treatment: TileMode,
938 },
939 Offset { offset_x: f32, offset_y: f32 },
941 Shader {
943 shader: Arc<RuntimeShader>,
945 },
946 Chain {
950 first: Arc<RenderEffect>,
951 second: Arc<RenderEffect>,
952 },
953}
954
955impl RenderEffect {
956 pub fn blur(radius: f32) -> Self {
958 Self::blur_with_edge_treatment(radius, TileMode::default())
959 }
960
961 pub fn blur_with_edge_treatment(radius: f32, edge_treatment: TileMode) -> Self {
964 Self::Blur {
965 radius_x: radius,
966 radius_y: radius,
967 edge_treatment,
968 }
969 }
970
971 pub fn blur_xy(radius_x: f32, radius_y: f32, edge_treatment: TileMode) -> Self {
973 Self::Blur {
974 radius_x,
975 radius_y,
976 edge_treatment,
977 }
978 }
979
980 pub fn offset(offset_x: f32, offset_y: f32) -> Self {
982 Self::Offset { offset_x, offset_y }
983 }
984
985 pub fn runtime_shader(shader: RuntimeShader) -> Self {
987 Self::Shader {
988 shader: Arc::new(shader),
989 }
990 }
991
992 pub fn then(self, other: RenderEffect) -> Self {
994 Self::Chain {
995 first: Arc::new(self),
996 second: Arc::new(other),
997 }
998 }
999
1000 pub fn contains_runtime_shader(&self) -> bool {
1004 match self {
1005 RenderEffect::Shader { .. } => true,
1006 RenderEffect::Chain { first, second } => {
1007 first.contains_runtime_shader() || second.contains_runtime_shader()
1008 }
1009 _ => false,
1010 }
1011 }
1012
1013 pub fn preserves_transparency(&self) -> bool {
1017 match self {
1018 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => true,
1019 RenderEffect::Shader { shader } => shader.preserves_transparency(),
1020 RenderEffect::Chain { first, second } => {
1021 first.preserves_transparency() && second.preserves_transparency()
1022 }
1023 }
1024 }
1025
1026 pub fn input_padding(&self) -> f32 {
1028 match self {
1029 RenderEffect::Blur {
1030 radius_x, radius_y, ..
1031 } => radius_x.abs().max(radius_y.abs()),
1032 RenderEffect::Offset { offset_x, offset_y } => offset_x.abs().max(offset_y.abs()),
1033 RenderEffect::Shader { shader } => shader.input_padding(),
1034 RenderEffect::Chain { first, second } => first.input_padding() + second.input_padding(),
1035 }
1036 }
1037
1038 pub fn output_padding(&self) -> f32 {
1042 match self {
1043 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => 0.0,
1044 RenderEffect::Shader { shader } => shader.output_padding(),
1045 RenderEffect::Chain { first, second } => {
1046 first.output_padding() + second.output_padding()
1047 }
1048 }
1049 }
1050
1051 pub fn output_support(&self) -> Option<Rect> {
1056 match self {
1057 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
1058 RenderEffect::Shader { shader } => shader.output_support(),
1059 RenderEffect::Chain { second, .. } => second.output_support(),
1060 }
1061 }
1062
1063 pub fn sample_domain(&self) -> Option<Rect> {
1067 match self {
1068 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
1069 RenderEffect::Shader { shader } => shader.sample_domain(),
1070 RenderEffect::Chain { second, .. } => second.sample_domain(),
1071 }
1072 }
1073}
1074
1075#[cfg(test)]
1076#[path = "tests/render_effect_tests.rs"]
1077mod tests;