1use std::sync::{Arc, Mutex, OnceLock, 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(|poisoned| poisoned.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(|poisoned| poisoned.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)]
1076mod tests {
1077 use super::{RenderEffect, RuntimeShader};
1078
1079 #[test]
1080 fn a_shader_declares_it_preserves_transparency() {
1081 let mut shader = RuntimeShader::new("// preserves");
1082 assert!(!shader.preserves_transparency());
1083 let plain = shader.clone();
1084 shader.set_preserves_transparency(true);
1085 assert!(shader.preserves_transparency());
1086 assert_ne!(shader, plain);
1087 shader.set_preserves_transparency(false);
1088 assert_eq!(shader, plain);
1089 }
1090
1091 #[test]
1092 fn an_effect_preserves_transparency_when_every_step_does() {
1093 let mut declared = RuntimeShader::new("// declared");
1094 declared.set_preserves_transparency(true);
1095 let undeclared = RuntimeShader::new("// undeclared");
1096 assert!(RenderEffect::blur(3.0).preserves_transparency());
1097 assert!(RenderEffect::offset(2.0, 1.0).preserves_transparency());
1098 assert!(RenderEffect::runtime_shader(declared.clone()).preserves_transparency());
1099 assert!(!RenderEffect::runtime_shader(undeclared.clone()).preserves_transparency());
1100 assert!(
1101 RenderEffect::blur(3.0)
1102 .then(RenderEffect::runtime_shader(declared))
1103 .preserves_transparency()
1104 );
1105 assert!(
1106 !RenderEffect::blur(3.0)
1107 .then(RenderEffect::runtime_shader(undeclared))
1108 .preserves_transparency()
1109 );
1110 }
1111
1112 #[test]
1113 fn overrides_stay_sorted_and_replace_by_name() {
1114 let mut shader = super::RuntimeShader::new("// overrides");
1115 shader.set_override("ZETA", 1.0);
1116 shader.set_override("ALPHA", 0.0);
1117 shader.set_override("ZETA", 2.0);
1118 assert_eq!(shader.overrides(), &[("ALPHA", 0.0), ("ZETA", 2.0)]);
1119 }
1120
1121 #[test]
1122 fn clear_override_removes_present_name_and_preserves_remaining_set() {
1123 let mut shader = super::RuntimeShader::new("");
1124 shader.set_override("ZETA", 1.0);
1125 shader.set_override("ALPHA", 2.0);
1126 let mut expected = super::RuntimeShader::new("");
1127 expected.set_override("ALPHA", 2.0);
1128 assert!(shader.clear_override("ZETA"));
1129 assert!(!shader.clear_override("MISSING"));
1130 assert_eq!(shader.overrides(), &[("ALPHA", 2.0)]);
1131 assert_eq!(shader.overrides_hash(), expected.overrides_hash());
1132 assert!(shader.clear_override("ALPHA"));
1133 assert!(shader.overrides().is_empty());
1134 assert_eq!(shader.overrides_hash(), 0);
1135 }
1136
1137 #[test]
1138 fn overrides_distinguish_otherwise_equal_shaders() {
1139 let plain = super::RuntimeShader::new("// overrides-eq");
1140 let mut raised = plain.clone();
1141 raised.set_override("FLAG", 1.0);
1142 assert_eq!(plain.overrides_hash(), 0);
1143 assert_ne!(plain.overrides_hash(), raised.overrides_hash());
1144 assert_ne!(plain, raised);
1145 let mut lowered = raised.clone();
1146 lowered.set_override("FLAG", 0.0);
1147 assert_ne!(raised.overrides_hash(), lowered.overrides_hash());
1148 assert_ne!(raised, lowered);
1149 }
1150
1151 #[test]
1152 fn unchanged_override_lookups_share_one_hash_computation() {
1153 let mut shader = RuntimeShader::new("");
1154 shader.set_override("FLAG", 1.0);
1155 shader.set_override("SCALE", 0.5);
1156 OVERRIDE_HASH_COMPUTATIONS.with(|count| count.set(0));
1157 let expected = shader.overrides_hash();
1158 for _ in 0..24 {
1159 let mut copy = shader.clone();
1160 copy.set_float(0, 7.0);
1161 copy.set_override("FLAG", 1.0);
1162 assert!(!copy.clear_override("ABSENT"));
1163 assert_eq!(copy.overrides_hash(), expected);
1164 }
1165 assert_eq!(OVERRIDE_HASH_COMPUTATIONS.with(std::cell::Cell::get), 1);
1166 }
1167
1168 #[test]
1169 fn override_hash_tracks_clone_mutations_and_float_bits() {
1170 fn independent_hash(shader: &RuntimeShader) -> u64 {
1171 let bytes: Vec<u8> = shader
1172 .overrides()
1173 .iter()
1174 .flat_map(|(name, value)| {
1175 name.bytes().chain([0]).chain(value.to_bits().to_le_bytes())
1176 })
1177 .collect();
1178 if bytes.is_empty() {
1179 0
1180 } else {
1181 hash_shader_bytes(bytes)
1182 }
1183 }
1184
1185 let mut original = RuntimeShader::new("");
1186 original.set_override("VALUE", 0.0);
1187 let first = original.overrides_hash();
1188 assert_eq!(first, independent_hash(&original));
1189 for value in [
1190 -0.0,
1191 0.5,
1192 f64::INFINITY,
1193 f64::from_bits(0x7ff8_0000_0000_0001),
1194 ] {
1195 let mut changed = original.clone();
1196 changed.set_override("VALUE", value);
1197 assert_eq!(changed.overrides_hash(), independent_hash(&changed));
1198 assert_ne!(changed.overrides_hash(), first);
1199 changed.set_override("ADDED", 1.0);
1200 assert_eq!(changed.overrides_hash(), independent_hash(&changed));
1201 assert!(changed.clear_override("VALUE"));
1202 assert_eq!(changed.overrides_hash(), independent_hash(&changed));
1203 assert!(changed.clear_override("ADDED"));
1204 assert_eq!(changed.overrides_hash(), 0);
1205 assert_eq!(original.overrides_hash(), first);
1206 }
1207 }
1208
1209 #[test]
1210 fn shader_clones_share_declarations_and_isolate_mutation() {
1211 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1212 shader.set_override("FLAG", 1.0);
1213 shader.set_substrates(&[SubstrateSpec::Average { block: 4 }]);
1214 shader.set_draw_split(Some("SPLIT"));
1215 let support = Rect {
1216 x: 1.0,
1217 y: 2.0,
1218 width: 30.0,
1219 height: 40.0,
1220 };
1221 shader.set_output_support(Some(support));
1222 let mut cloned = shader.clone();
1223 assert_eq!(cloned.overrides().as_ptr(), shader.overrides().as_ptr());
1224 assert_eq!(cloned.substrates().as_ptr(), shader.substrates().as_ptr());
1225 cloned.set_float(0, 2.0);
1226 assert!(shader.uniforms().is_empty());
1227 assert_eq!(cloned.overrides().as_ptr(), shader.overrides().as_ptr());
1228 cloned.set_override("FLAG", 2.0);
1229 assert_eq!(cloned.substrates(), shader.substrates());
1230 assert_eq!(cloned.draw_split(), shader.draw_split());
1231 cloned.set_substrates(&[SubstrateSpec::Average { block: 8 }]);
1232 cloned.set_draw_split(None);
1233 cloned.set_output_support(None);
1234 assert_eq!(shader.overrides(), &[("FLAG", 1.0)]);
1235 assert_eq!(shader.substrates(), &[SubstrateSpec::Average { block: 4 }]);
1236 assert_eq!(shader.draw_split(), Some("SPLIT"));
1237 assert_eq!(shader.output_support(), Some(support));
1238 assert_ne!(cloned, shader);
1239 }
1240
1241 use super::*;
1242 use crate::RoundedCornerShape;
1243
1244 #[test]
1245 fn cloned_effect_chains_keep_order_and_isolate_nested_edits() {
1246 let original = RenderEffect::offset(2.0, 7.0)
1247 .then(RenderEffect::blur(3.0))
1248 .then(RenderEffect::offset(-4.0, 1.0));
1249 let mut edited = original.clone();
1250 assert_eq!(edited, original);
1251 let RenderEffect::Chain {
1252 first: original_first,
1253 second: original_second,
1254 } = &original
1255 else {
1256 panic!("chain effect")
1257 };
1258 let RenderEffect::Chain {
1259 first: edited_first,
1260 second: edited_second,
1261 } = &mut edited
1262 else {
1263 panic!("chain effect")
1264 };
1265 assert!(Arc::ptr_eq(original_first, edited_first));
1266 assert!(Arc::ptr_eq(original_second, edited_second));
1267 assert_eq!(original_second.as_ref(), &RenderEffect::offset(-4.0, 1.0));
1268 let RenderEffect::Chain { first, second } = Arc::make_mut(edited_first) else {
1269 panic!("nested chain")
1270 };
1271 assert_eq!(first.as_ref(), &RenderEffect::offset(2.0, 7.0));
1272 assert_eq!(second.as_ref(), &RenderEffect::blur(3.0));
1273 *Arc::make_mut(first) = RenderEffect::offset(12.0, 17.0);
1274 *Arc::make_mut(edited_second) = RenderEffect::blur(11.0);
1275 assert_eq!(
1276 original,
1277 RenderEffect::offset(2.0, 7.0)
1278 .then(RenderEffect::blur(3.0))
1279 .then(RenderEffect::offset(-4.0, 1.0))
1280 );
1281 assert_eq!(
1282 edited,
1283 RenderEffect::offset(12.0, 17.0)
1284 .then(RenderEffect::blur(3.0))
1285 .then(RenderEffect::blur(11.0))
1286 );
1287 }
1288
1289 #[test]
1290 fn cloned_shader_effects_preserve_configuration_and_isolate_edits() {
1291 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1292 shader.set_float(20, 3.0);
1293 shader.set_override("FEATURE", -0.0);
1294 shader.set_input_padding(7.0);
1295 shader.set_substrates(&[SubstrateSpec::Blur { radius_px: 12.0 }]);
1296 shader.set_draw_split(Some("SPLIT"));
1297 let original = RenderEffect::runtime_shader(shader.clone());
1298 let mut edited = original.clone();
1299 assert_eq!(edited, original);
1300 let RenderEffect::Shader {
1301 shader: original_shader,
1302 } = &original
1303 else {
1304 panic!("shader effect")
1305 };
1306 assert_eq!(original_shader.as_ref(), &shader);
1307 let RenderEffect::Shader {
1308 shader: edited_shader,
1309 } = &mut edited
1310 else {
1311 panic!("shader effect")
1312 };
1313 assert!(Arc::ptr_eq(original_shader, edited_shader));
1314 let changed = Arc::make_mut(edited_shader);
1315 changed.set_float(20, 9.0);
1316 changed.set_override("FEATURE", 1.0);
1317 changed.set_substrates(&[]);
1318 changed.set_draw_split(None);
1319 assert_eq!(original_shader.as_ref(), &shader);
1320 assert_eq!(edited_shader.uniforms()[20], 9.0);
1321 assert_eq!(edited_shader.overrides(), &[("FEATURE", 1.0)]);
1322 assert!(edited_shader.substrates().is_empty());
1323 assert_eq!(edited_shader.draw_split(), None);
1324 assert_ne!(original, edited);
1325 }
1326
1327 #[test]
1328 fn runtime_shader_set_uniforms() {
1329 let mut shader = RuntimeShader::new("// test");
1330 shader.set_float(0, 1.0);
1331 shader.set_float2(2, 3.0, 4.0);
1332 shader.set_float4(4, 5.0, 6.0, 7.0, 8.0);
1333
1334 assert_eq!(shader.uniforms()[0], 1.0);
1335 assert_eq!(shader.uniforms()[1], 0.0);
1336 assert_eq!(shader.uniforms()[2], 3.0);
1337 assert_eq!(shader.uniforms()[3], 4.0);
1338 assert_eq!(shader.uniforms()[4], 5.0);
1339 assert_eq!(shader.uniforms()[5], 6.0);
1340 assert_eq!(shader.uniforms()[6], 7.0);
1341 assert_eq!(shader.uniforms()[7], 8.0);
1342 }
1343
1344 #[test]
1345 fn runtime_shader_padded() {
1346 let mut shader = RuntimeShader::new("// test");
1347 shader.set_float(0, 42.0);
1348 let padded = shader.uniforms_padded();
1349 assert_eq!(padded[0], 42.0);
1350 assert_eq!(padded[1], 0.0);
1351 assert_eq!(padded[255], 0.0);
1352 }
1353
1354 #[test]
1355 fn blur_and_offset_declare_input_padding() {
1356 assert_eq!(
1357 RenderEffect::blur_xy(6.0, 12.0, TileMode::Clamp).input_padding(),
1358 12.0
1359 );
1360 assert_eq!(RenderEffect::offset(-8.0, 3.0).input_padding(), 8.0);
1361 }
1362
1363 #[test]
1364 fn chained_effect_padding_accumulates_sampling_ranges() {
1365 let mut shader = RuntimeShader::new("// test");
1366 shader.set_input_padding(9.0);
1367 let effect = RenderEffect::blur_xy(4.0, 6.0, TileMode::Clamp)
1368 .then(RenderEffect::runtime_shader(shader))
1369 .then(RenderEffect::offset(2.0, -5.0));
1370
1371 assert_eq!(effect.input_padding(), 20.0);
1372 }
1373
1374 #[test]
1375 fn runtime_shader_keeps_common_uniform_payload_inline() {
1376 let mut shader = RuntimeShader::new("// test");
1377 shader.set_float4(0, 1.0, 2.0, 3.0, 4.0);
1378 shader.set_float4(4, 5.0, 6.0, 7.0, 8.0);
1379 shader.set_float4(8, 9.0, 10.0, 11.0, 12.0);
1380 shader.set_float4(12, 13.0, 14.0, 15.0, 16.0);
1381
1382 assert!(shader.uniforms.is_inline());
1383 assert_eq!(shader.uniforms().len(), 16);
1384
1385 shader.set_float(16, 17.0);
1386 assert!(!shader.uniforms.is_inline());
1387 assert_eq!(shader.uniforms()[16], 17.0);
1388 }
1389
1390 #[test]
1391 fn runtime_shader_try_set_reports_reserved_uniform_slots() {
1392 let mut shader = RuntimeShader::new("// test");
1393
1394 let err = shader
1395 .try_set_float(RuntimeShader::RESERVED_UNIFORM_START, 1.0)
1396 .unwrap_err();
1397 assert_eq!(
1398 err,
1399 RuntimeShaderUniformError::OutOfUserRange {
1400 index: RuntimeShader::RESERVED_UNIFORM_START,
1401 width: 1,
1402 max_user_uniforms: RuntimeShader::MAX_USER_UNIFORMS,
1403 reserved_start: RuntimeShader::RESERVED_UNIFORM_START,
1404 max_uniforms: RuntimeShader::MAX_UNIFORMS,
1405 }
1406 );
1407 assert!(shader.uniforms().is_empty());
1408
1409 let err = shader
1410 .try_set_float4(RuntimeShader::MAX_USER_UNIFORMS - 3, 1.0, 2.0, 3.0, 4.0)
1411 .unwrap_err();
1412 assert_eq!(
1413 err,
1414 RuntimeShaderUniformError::OutOfUserRange {
1415 index: RuntimeShader::MAX_USER_UNIFORMS - 3,
1416 width: 4,
1417 max_user_uniforms: RuntimeShader::MAX_USER_UNIFORMS,
1418 reserved_start: RuntimeShader::RESERVED_UNIFORM_START,
1419 max_uniforms: RuntimeShader::MAX_UNIFORMS,
1420 }
1421 );
1422 }
1423
1424 #[test]
1425 fn runtime_shader_setters_ignore_invalid_uniform_slots_without_panicking() {
1426 let mut shader = RuntimeShader::new("// test");
1427 shader.set_float(0, 7.0);
1428
1429 shader.set_float(RuntimeShader::RESERVED_UNIFORM_START, 1.0);
1430 shader.set_float4(RuntimeShader::MAX_USER_UNIFORMS - 3, 1.0, 2.0, 3.0, 4.0);
1431
1432 assert_eq!(shader.uniforms(), &[7.0]);
1433 }
1434
1435 #[test]
1436 fn render_effect_chaining() {
1437 let blur = RenderEffect::blur(10.0);
1438 let offset = RenderEffect::offset(5.0, 5.0);
1439 let chained = blur.then(offset);
1440 match chained {
1441 RenderEffect::Chain { first, second } => {
1442 assert!(matches!(*first, RenderEffect::Blur { .. }));
1443 assert!(matches!(*second, RenderEffect::Offset { .. }));
1444 }
1445 _ => panic!("expected Chain"),
1446 }
1447 }
1448
1449 #[test]
1450 fn blur_convenience() {
1451 let effect = RenderEffect::blur(15.0);
1452 match effect {
1453 RenderEffect::Blur {
1454 radius_x,
1455 radius_y,
1456 edge_treatment,
1457 } => {
1458 assert_eq!(radius_x, 15.0);
1459 assert_eq!(radius_y, 15.0);
1460 assert_eq!(edge_treatment, TileMode::Clamp);
1461 }
1462 _ => panic!("expected Blur"),
1463 }
1464 }
1465
1466 #[test]
1467 fn blur_with_edge_treatment_uses_explicit_mode() {
1468 let effect = RenderEffect::blur_with_edge_treatment(6.0, TileMode::Decal);
1469 match effect {
1470 RenderEffect::Blur {
1471 radius_x,
1472 radius_y,
1473 edge_treatment,
1474 } => {
1475 assert_eq!(radius_x, 6.0);
1476 assert_eq!(radius_y, 6.0);
1477 assert_eq!(edge_treatment, TileMode::Decal);
1478 }
1479 _ => panic!("expected Blur"),
1480 }
1481 }
1482
1483 #[test]
1484 fn source_hash_consistent() {
1485 let s1 = RuntimeShader::new("fn main() {}");
1486 let s2 = RuntimeShader::new("fn main() {}");
1487 assert_eq!(s1.source_hash(), s2.source_hash());
1488 }
1489
1490 #[test]
1491 fn runtime_shader_from_shared_source_reuses_shared_source() {
1492 let source = Arc::<str>::from("fn fragment() -> vec4<f32> { return vec4<f32>(1.0); }");
1493 let s1 = RuntimeShader::from_shared_source(source.clone());
1494 let s2 = RuntimeShader::from_shared_source(source);
1495
1496 assert!(Arc::ptr_eq(&s1.source, &s2.source));
1497 assert_eq!(s1.source_hash(), s2.source_hash());
1498 }
1499
1500 fn runtime_shader_from_reuse_callsite(source: &str) -> RuntimeShader {
1501 RuntimeShader::new(source)
1502 }
1503
1504 fn runtime_shader_from_replacement_callsite(source: &str) -> RuntimeShader {
1505 RuntimeShader::new(source)
1506 }
1507
1508 #[test]
1509 fn runtime_shader_new_reuses_same_callsite_source() {
1510 let source = "fn fragment() -> vec4<f32> { return vec4<f32>(1.0); }";
1511 let s1 = runtime_shader_from_reuse_callsite(source);
1512 let s2 = runtime_shader_from_reuse_callsite(source);
1513
1514 assert!(Arc::ptr_eq(&s1.source, &s2.source));
1515 assert_eq!(s1.source_hash(), s2.source_hash());
1516 }
1517
1518 #[test]
1519 fn runtime_shader_new_replaces_changed_callsite_source() {
1520 let s1 = runtime_shader_from_replacement_callsite("fn a() {}");
1521 let s2 = runtime_shader_from_replacement_callsite("fn b() {}");
1522
1523 assert!(!Arc::ptr_eq(&s1.source, &s2.source));
1524 assert_ne!(s1.source_hash(), s2.source_hash());
1525 assert_eq!(s2.source(), "fn b() {}");
1526 }
1527
1528 #[test]
1529 fn runtime_shader_source_storage_has_no_process_global_interner() {
1530 let source = include_str!("render_effect.rs");
1531 let blocked_static = ["static ", "INTERNER"].concat();
1532 let blocked_type = ["ShaderSource", "Interner"].concat();
1533
1534 assert!(
1535 !source.contains(&blocked_static) && !source.contains(&blocked_type),
1536 "RuntimeShader source sharing must be explicit via from_shared_source, not a process-global interner"
1537 );
1538 }
1539
1540 #[test]
1541 fn blur_xy_preserves_tile_mode() {
1542 let effect = RenderEffect::blur_xy(3.0, 7.0, TileMode::Clamp);
1543 match effect {
1544 RenderEffect::Blur {
1545 radius_x,
1546 radius_y,
1547 edge_treatment,
1548 } => {
1549 assert_eq!(radius_x, 3.0);
1550 assert_eq!(radius_y, 7.0);
1551 assert_eq!(edge_treatment, TileMode::Clamp);
1552 }
1553 _ => panic!("expected Blur"),
1554 }
1555 }
1556
1557 #[test]
1558 fn offset_constructor_sets_components() {
1559 let effect = RenderEffect::offset(11.0, -5.0);
1560 match effect {
1561 RenderEffect::Offset { offset_x, offset_y } => {
1562 assert_eq!(offset_x, 11.0);
1563 assert_eq!(offset_y, -5.0);
1564 }
1565 _ => panic!("expected Offset"),
1566 }
1567 }
1568
1569 #[test]
1570 fn runtime_shader_equality_is_source_value_based() {
1571 let mut s1 = RuntimeShader::new("fn main() {}");
1572 let mut s2 = RuntimeShader::new("fn main() {}");
1573 s1.set_float(0, 1.0);
1574 s2.set_float(0, 1.0);
1575 assert_eq!(s1, s2);
1576 }
1577
1578 #[test]
1579 fn blurred_edge_treatment_defaults_to_bounded_rectangle() {
1580 let treatment = BlurredEdgeTreatment::default();
1581 assert_eq!(treatment.shape(), Some(LayerShape::Rectangle));
1582 assert!(treatment.clip());
1583 assert_eq!(treatment.tile_mode(), TileMode::Clamp);
1584 }
1585
1586 #[test]
1587 fn blurred_edge_treatment_unbounded_uses_decal_and_no_clip() {
1588 let treatment = BlurredEdgeTreatment::UNBOUNDED;
1589 assert_eq!(treatment.shape(), None);
1590 assert!(!treatment.clip());
1591 assert_eq!(treatment.tile_mode(), TileMode::Decal);
1592 }
1593
1594 #[test]
1595 fn blurred_edge_treatment_with_shape_uses_bounded_mode() {
1596 let rounded = LayerShape::Rounded(RoundedCornerShape::uniform(8.0));
1597 let treatment = BlurredEdgeTreatment::with_shape(rounded);
1598 assert_eq!(treatment.shape(), Some(rounded));
1599 assert!(treatment.clip());
1600 assert_eq!(treatment.tile_mode(), TileMode::Clamp);
1601 }
1602
1603 #[test]
1604 fn an_effect_chains_output_support_is_the_support_of_the_stage_that_writes_its_output() {
1605 let mut shader = RuntimeShader::new("fn glass_fs() {}");
1606 assert_eq!(shader.output_support(), None);
1607 let support = Rect {
1608 x: 4.0,
1609 y: 6.0,
1610 width: 30.0,
1611 height: 12.0,
1612 };
1613 shader.set_output_support(Some(support));
1614 assert_eq!(shader.output_support(), Some(support));
1615 let effect = RenderEffect::blur(3.0).then(RenderEffect::runtime_shader(shader.clone()));
1616 assert_eq!(effect.output_support(), Some(support));
1617 let effect = RenderEffect::runtime_shader(shader.clone()).then(RenderEffect::blur(3.0));
1618 assert_eq!(effect.output_support(), None);
1619 assert_eq!(RenderEffect::blur(3.0).output_support(), None);
1620 }
1621
1622 #[test]
1623 fn a_sample_domain_is_the_writers_and_a_blur_declares_none() {
1624 let mut shader = RuntimeShader::new("fn glass_fs() {}");
1625 let domain = Rect {
1626 x: -2.0,
1627 y: -2.0,
1628 width: 20.0,
1629 height: 12.0,
1630 };
1631 let plain = shader.clone();
1632 shader.set_sample_domain(Some(domain));
1633 assert_ne!(shader, plain);
1634 assert_eq!(shader.sample_domain(), Some(domain));
1635 let effect = RenderEffect::blur(3.0).then(RenderEffect::runtime_shader(shader.clone()));
1636 assert_eq!(effect.sample_domain(), Some(domain));
1637 assert_eq!(RenderEffect::blur(3.0).output_support(), None);
1638 assert_eq!(RenderEffect::blur(3.0).sample_domain(), None);
1639 shader.set_sample_domain(Some(Rect {
1640 x: f32::INFINITY,
1641 ..domain
1642 }));
1643 assert_eq!(shader.sample_domain(), None);
1644 }
1645
1646 #[test]
1647 fn a_non_finite_output_support_clears_the_declaration_and_a_support_tells_shaders_apart() {
1648 let mut shader = RuntimeShader::new("fn glass_fs() {}");
1649 let plain = shader.clone();
1650 shader.set_output_support(Some(Rect {
1651 x: 0.0,
1652 y: 0.0,
1653 width: 10.0,
1654 height: 10.0,
1655 }));
1656 assert_ne!(shader, plain);
1657 shader.set_output_support(Some(Rect {
1658 x: 0.0,
1659 y: 0.0,
1660 width: f32::NAN,
1661 height: 10.0,
1662 }));
1663 assert_eq!(shader.output_support(), None);
1664 assert_eq!(shader, plain);
1665 }
1666 #[test]
1667 fn specialization_cache_preserves_source_identity_and_shader_values() {
1668 let mut cache = ShaderSpecializationCache::<u32, 2>::new();
1669 let mut first = RuntimeShader::new("fn effect_fs() {}");
1670 first.set_override("CALLER", -0.0);
1671 let mut second = first.clone();
1672 second.set_override("CALLER", f64::from_bits(0x7ff8_0000_0000_0001));
1673 let sources = [first, second];
1674 for key in [1, 1, 2, 3, 1] {
1675 for source in &sources {
1676 let mut shader = source.clone();
1677 shader.set_float(0, key as f32);
1678 shader.set_input_padding(key as f32);
1679 cache.apply(&mut shader, key, |shader, &key| {
1680 shader.set_override("FEATURE", f64::from(key));
1681 shader.set_draw_split(Some("SPLIT"));
1682 shader.set_substrates(&[SubstrateSpec::Average { block: key }]);
1683 });
1684 assert_eq!(
1685 shader.overrides()[0].1.to_bits(),
1686 source.overrides()[0].1.to_bits()
1687 );
1688 assert_eq!(shader.overrides()[1], ("FEATURE", f64::from(key)));
1689 assert_eq!(
1690 shader.substrates(),
1691 &[SubstrateSpec::Average { block: key }]
1692 );
1693 assert_eq!(shader.draw_split(), Some("SPLIT"));
1694 assert_eq!(shader.uniforms(), &[key as f32]);
1695 assert_eq!(shader.input_padding(), key as f32);
1696 assert_eq!(source.overrides().len(), 1);
1697 assert!(source.substrates().is_empty());
1698 assert_eq!(source.draw_split(), None);
1699 let mut repeated = source.clone();
1700 cache.apply(&mut repeated, key, |_, _| {
1701 panic!("shared specialization missed")
1702 });
1703 assert_eq!(repeated.overrides_hash(), shader.overrides_hash());
1704 assert!(Arc::ptr_eq(
1705 repeated.specialization.as_ref().unwrap(),
1706 shader.specialization.as_ref().unwrap(),
1707 ));
1708 assert!(cache.entries.len() <= 2);
1709 }
1710 }
1711 }
1712
1713 #[test]
1714 fn specialization_cache_mutates_unique_state_without_retaining_it() {
1715 let mut cache = ShaderSpecializationCache::<(), 2>::new();
1716 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1717 shader.set_override("VALUE", 1.0);
1718 let allocation = Arc::as_ptr(shader.specialization.as_ref().unwrap());
1719 cache.apply(&mut shader, (), |shader, ()| {
1720 shader.set_override("VALUE", 2.0)
1721 });
1722 assert_eq!(shader.overrides(), &[("VALUE", 2.0)]);
1723 assert_eq!(
1724 Arc::as_ptr(shader.specialization.as_ref().unwrap()),
1725 allocation
1726 );
1727 assert!(cache.entries.is_empty());
1728 }
1729
1730 #[test]
1731 fn unchanged_shader_declarations_keep_their_storage() {
1732 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1733 shader.set_substrates(&[]);
1734 shader.set_draw_split(None);
1735 assert!(!shader.clear_override("MISSING"));
1736 assert!(shader.specialization.is_none());
1737 shader.set_override("FLAG", 1.0);
1738 let mut cloned = shader.clone();
1739 cloned.set_override("FLAG", 1.0);
1740 cloned.set_substrates(&[]);
1741 cloned.set_draw_split(None);
1742 assert!(!cloned.clear_override("MISSING"));
1743 assert_eq!(cloned.overrides().as_ptr(), shader.overrides().as_ptr());
1744 }
1745
1746 #[test]
1747 fn mean_substrates_have_distinct_stable_identity() {
1748 use std::hash::{DefaultHasher, Hasher};
1749 let hash = |spec: SubstrateSpec| {
1750 let mut h = DefaultHasher::new();
1751 spec.hash_bits(&mut h);
1752 h.finish()
1753 };
1754 let mean = SubstrateSpec::Mean;
1755 assert!(mean.same_bits(&mean));
1756 for other in [
1757 SubstrateSpec::Average { block: 4 },
1758 SubstrateSpec::Blur { radius_px: 12.0 },
1759 ] {
1760 assert!(!mean.same_bits(&other));
1761 assert_ne!(hash(mean), hash(other));
1762 }
1763 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1764 shader.set_substrates(&[mean]);
1765 let mut cloned = shader.clone();
1766 cloned.set_substrates(&[SubstrateSpec::Average { block: 4 }]);
1767 assert_eq!(shader.substrates(), &[mean]);
1768 assert_eq!(cloned.substrates(), &[SubstrateSpec::Average { block: 4 }]);
1769 }
1770
1771 #[test]
1772 fn shader_substrates_preserve_order_and_ownership_across_size_changes() {
1773 let declared = [
1774 SubstrateSpec::Blur { radius_px: 12.0 },
1775 SubstrateSpec::Average { block: 4 },
1776 SubstrateSpec::Blur { radius_px: -0.0 },
1777 ];
1778 let mut source = declared;
1779 let mut original = RuntimeShader::new("fn effect_fs() {}");
1780 original.set_substrates(&source);
1781 source[0] = SubstrateSpec::Average { block: 16 };
1782 let mut changed = original.clone();
1783 for replacement in [&source[..1], &source[..2], &source[..0], &source[..]] {
1784 changed.set_substrates(replacement);
1785 assert_eq!(changed.substrates().len(), replacement.len());
1786 assert!(
1787 changed
1788 .substrates()
1789 .iter()
1790 .zip(replacement)
1791 .all(|(actual, expected)| actual.same_bits(expected))
1792 );
1793 assert_eq!(original.substrates().len(), declared.len());
1794 assert!(
1795 original
1796 .substrates()
1797 .iter()
1798 .zip(&declared)
1799 .all(|(actual, expected)| actual.same_bits(expected))
1800 );
1801 }
1802 }
1803
1804 #[test]
1805 fn shader_substrate_setters_preserve_float_bits_when_detaching() {
1806 let mut original = RuntimeShader::new("fn effect_fs() {}");
1807 original.set_substrates(&[SubstrateSpec::Blur { radius_px: 0.0 }]);
1808 let mut cloned = original.clone();
1809 cloned.set_substrates(&[SubstrateSpec::Blur { radius_px: 0.0 }]);
1810 assert_eq!(cloned.substrates().as_ptr(), original.substrates().as_ptr());
1811 cloned.set_substrates(&[SubstrateSpec::Blur { radius_px: -0.0 }]);
1812 let [SubstrateSpec::Blur { radius_px }] = cloned.substrates() else {
1813 panic!("one blur substrate");
1814 };
1815 assert_eq!(radius_px.to_bits(), (-0.0_f32).to_bits());
1816 let [SubstrateSpec::Blur { radius_px }] = original.substrates() else {
1817 panic!("original blur substrate");
1818 };
1819 assert_eq!(radius_px.to_bits(), 0.0_f32.to_bits());
1820 }
1821}