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 domains: Option<Box<ShaderDomains>>,
133}
134
135#[derive(Clone, Debug, Default)]
136struct ShaderSpecialization {
137 overrides: Vec<(&'static str, f64)>,
138 overrides_hash: OnceLock<u64>,
139 substrates: ArrayVec<SubstrateSpec, MAX_SUBSTRATES>,
140 draw_split: Option<&'static str>,
141}
142
143pub(crate) struct ShaderSpecializationCache<K, const N: usize> {
144 entries: ArrayVec<CachedShaderSpecialization<K>, N>,
145}
146
147struct CachedShaderSpecialization<K> {
148 source: Option<Arc<ShaderSpecialization>>,
149 key: K,
150 result: Option<Arc<ShaderSpecialization>>,
151}
152
153impl<K: PartialEq, const N: usize> ShaderSpecializationCache<K, N> {
154 pub(crate) const fn new() -> Self {
155 assert!(N > 0);
156 Self {
157 entries: ArrayVec::new_const(),
158 }
159 }
160
161 pub(crate) fn apply(
162 &mut self,
163 shader: &mut RuntimeShader,
164 key: K,
165 specialize: impl FnOnce(&mut RuntimeShader, &K),
166 ) {
167 let hit = self.entries.iter().rposition(|entry| {
168 entry.key == key
169 && match (&entry.source, &shader.specialization) {
170 (Some(source), Some(current)) => Arc::ptr_eq(source, current),
171 (None, None) => true,
172 _ => false,
173 }
174 });
175 if let Some(index) = hit {
176 let entry = self.entries.remove(index);
177 shader.specialization.clone_from(&entry.result);
178 self.entries.push(entry);
179 return;
180 }
181 if shader
182 .specialization
183 .as_ref()
184 .is_some_and(|source| Arc::strong_count(source) == 1)
185 {
186 specialize(shader, &key);
187 return;
188 }
189 let source = shader.specialization.clone();
190 specialize(shader, &key);
191 if self.entries.is_full() {
192 self.entries.remove(0);
193 }
194 self.entries.push(CachedShaderSpecialization {
195 source,
196 key,
197 result: shader.specialization.clone(),
198 });
199 }
200}
201
202static DEFAULT_SHADER_SPECIALIZATION: ShaderSpecialization = ShaderSpecialization {
203 overrides: Vec::new(),
204 overrides_hash: OnceLock::new(),
205 substrates: ArrayVec::new_const(),
206 draw_split: None,
207};
208
209#[derive(Clone, Copy, Debug, Default, PartialEq)]
210struct ShaderDomains {
211 output_support: Option<Rect>,
212 sample_domain: Option<Rect>,
213}
214
215fn finite_rect(rect: Option<Rect>) -> Option<Rect> {
216 rect.filter(|rect| {
217 rect.x.is_finite()
218 && rect.y.is_finite()
219 && rect.width.is_finite()
220 && rect.height.is_finite()
221 })
222}
223
224pub const MAX_SUBSTRATES: usize = 3;
226
227#[derive(Clone, Copy, Debug, PartialEq)]
230pub enum SubstrateSpec {
231 Mean,
236 Average { block: u32 },
239 Blur { radius_px: f32 },
242}
243
244impl SubstrateSpec {
245 fn same_bits(&self, other: &Self) -> bool {
246 match (self, other) {
247 (Self::Mean, Self::Mean) => true,
248 (Self::Average { block: a }, Self::Average { block: b }) => a == b,
249 (Self::Blur { radius_px: a }, Self::Blur { radius_px: b }) => {
250 a.to_bits() == b.to_bits()
251 }
252 _ => false,
253 }
254 }
255
256 fn hash_bits<H: std::hash::Hasher>(&self, state: &mut H) {
257 use std::hash::Hash;
258 match self {
259 Self::Mean => 2u8.hash(state),
260 Self::Average { block } => {
261 0u8.hash(state);
262 block.hash(state);
263 }
264 Self::Blur { radius_px } => {
265 1u8.hash(state);
266 radius_px.to_bits().hash(state);
267 }
268 }
269 }
270}
271
272#[derive(Clone, Debug, PartialEq)]
273struct RuntimeShaderUniforms {
274 len: usize,
275 inline: [f32; RUNTIME_SHADER_INLINE_UNIFORMS],
276 heap: Option<Vec<f32>>,
277}
278
279impl RuntimeShaderUniforms {
280 fn new() -> Self {
281 Self {
282 len: 0,
283 inline: [0.0; RUNTIME_SHADER_INLINE_UNIFORMS],
284 heap: None,
285 }
286 }
287
288 fn as_slice(&self) -> &[f32] {
289 if let Some(heap) = &self.heap {
290 heap.as_slice()
291 } else {
292 &self.inline[..self.len]
293 }
294 }
295
296 fn len(&self) -> usize {
297 self.as_slice().len()
298 }
299
300 fn ensure_len(&mut self, min_len: usize) {
301 if let Some(heap) = &mut self.heap {
302 if heap.len() < min_len {
303 heap.resize(min_len, 0.0);
304 }
305 return;
306 }
307
308 if min_len <= RUNTIME_SHADER_INLINE_UNIFORMS {
309 self.len = self.len.max(min_len);
310 return;
311 }
312
313 let mut heap = Vec::with_capacity(min_len);
314 heap.extend_from_slice(&self.inline[..self.len]);
315 heap.resize(min_len, 0.0);
316 self.heap = Some(heap);
317 }
318
319 fn set(&mut self, index: usize, value: f32) {
320 if let Some(heap) = &mut self.heap {
321 heap[index] = value;
322 } else {
323 self.inline[index] = value;
324 }
325 }
326
327 #[cfg(test)]
328 fn is_inline(&self) -> bool {
329 self.heap.is_none()
330 }
331}
332
333#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
335pub enum RuntimeShaderUniformError {
336 #[error(
337 "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"
338 )]
339 OutOfUserRange {
340 index: usize,
341 width: usize,
342 max_user_uniforms: usize,
343 reserved_start: usize,
344 max_uniforms: usize,
345 },
346}
347
348impl RuntimeShader {
349 pub const MAX_UNIFORMS: usize = 256;
353 pub const RESERVED_UNIFORM_START: usize = 224;
355 pub const SUBSTRATE_REGION_UNIFORMS: [usize; MAX_SUBSTRATES] = [232, 228, 224];
358 pub const SOURCE_REGION_UNIFORM: usize = 236;
360 pub const MASK_RECT_UNIFORM: usize = 240;
362 pub const MASK_RADII_UNIFORM: usize = 244;
364 pub const EFFECT_RECT_UNIFORM: usize = 248;
366 pub const LOGICAL_SIZE_UNIFORM: usize = 252;
368 pub const ALPHA_UNIFORM: usize = 254;
370 pub const MAX_USER_UNIFORMS: usize = Self::RESERVED_UNIFORM_START;
372
373 #[track_caller]
375 pub fn new(wgsl_source: &str) -> Self {
376 let (source, source_hash) =
377 cached_shader_source(std::panic::Location::caller(), wgsl_source);
378 Self::with_source(source, source_hash)
379 }
380
381 pub fn from_shared_source(source: Arc<str>) -> Self {
386 let source_hash = cached_shared_shader_source_hash(&source);
387 Self::with_source(source, source_hash)
388 }
389
390 fn with_source(source: Arc<str>, source_hash: u64) -> Self {
391 Self {
392 source,
393 source_hash,
394 uniforms: RuntimeShaderUniforms::new(),
395 specialization: None,
396 input_padding: 0.0,
397 output_padding: 0.0,
398 batched_source: false,
399 domains: None,
400 }
401 }
402
403 fn specialization(&self) -> &ShaderSpecialization {
404 self.specialization
405 .as_deref()
406 .unwrap_or(&DEFAULT_SHADER_SPECIALIZATION)
407 }
408
409 fn specialization_mut(&mut self) -> &mut ShaderSpecialization {
410 Arc::make_mut(self.specialization.get_or_insert_with(Arc::default))
411 }
412
413 pub fn set_override(&mut self, name: &'static str, value: f64) {
420 let position = self
421 .overrides()
422 .binary_search_by(|(existing, _)| existing.cmp(&name));
423 if position.is_ok_and(|index| self.overrides()[index].1.to_bits() == value.to_bits()) {
424 return;
425 }
426 let specialization = self.specialization_mut();
427 specialization.overrides_hash.take();
428 let overrides = &mut specialization.overrides;
429 match position {
430 Ok(index) => overrides[index].1 = value,
431 Err(index) => overrides.insert(index, (name, value)),
432 }
433 }
434
435 pub fn clear_override(&mut self, name: &str) -> bool {
437 let Ok(index) = self
438 .overrides()
439 .binary_search_by(|(existing, _)| (*existing).cmp(name))
440 else {
441 return false;
442 };
443 let specialization = self.specialization_mut();
444 specialization.overrides_hash.take();
445 specialization.overrides.remove(index);
446 true
447 }
448
449 pub fn overrides(&self) -> &[(&'static str, f64)] {
452 &self.specialization().overrides
453 }
454
455 pub fn overrides_hash(&self) -> u64 {
457 let specialization = self.specialization();
458 if specialization.overrides.is_empty() {
459 return 0;
460 }
461 *specialization.overrides_hash.get_or_init(|| {
462 #[cfg(test)]
463 OVERRIDE_HASH_COMPUTATIONS.with(|count| count.set(count.get() + 1));
464 hash_shader_bytes(specialization.overrides.iter().flat_map(|(name, value)| {
465 name.bytes().chain([0]).chain(value.to_bits().to_le_bytes())
466 }))
467 })
468 }
469
470 pub fn set_input_padding(&mut self, padding: f32) {
474 self.input_padding = if padding.is_finite() {
475 padding.max(0.0)
476 } else {
477 0.0
478 };
479 }
480
481 pub fn input_padding(&self) -> f32 {
483 self.input_padding
484 }
485
486 pub fn set_output_padding(&mut self, padding: f32) {
491 self.output_padding = if padding.is_finite() {
492 padding.max(0.0)
493 } else {
494 0.0
495 };
496 }
497
498 pub fn output_padding(&self) -> f32 {
500 self.output_padding
501 }
502
503 pub fn set_output_support(&mut self, support: Option<Rect>) {
514 self.set_domains(ShaderDomains {
515 output_support: finite_rect(support),
516 sample_domain: self.sample_domain(),
517 });
518 }
519
520 pub fn output_support(&self) -> Option<Rect> {
522 self.domains
523 .as_ref()
524 .and_then(|domains| domains.output_support)
525 }
526
527 fn set_domains(&mut self, domains: ShaderDomains) {
528 self.domains = (domains != ShaderDomains::default()).then(|| Box::new(domains));
529 }
530
531 pub fn set_sample_domain(&mut self, domain: Option<Rect>) {
540 self.set_domains(ShaderDomains {
541 output_support: self.output_support(),
542 sample_domain: finite_rect(domain),
543 });
544 }
545
546 pub fn sample_domain(&self) -> Option<Rect> {
548 self.domains
549 .as_ref()
550 .and_then(|domains| domains.sample_domain)
551 }
552
553 pub fn set_float(&mut self, index: usize, value: f32) {
558 let _ = self.try_set_float(index, value);
559 }
560
561 pub fn try_set_float(
563 &mut self,
564 index: usize,
565 value: f32,
566 ) -> Result<(), RuntimeShaderUniformError> {
567 self.try_ensure_capacity(index, 1)?;
568 self.uniforms.set(index, value);
569 Ok(())
570 }
571
572 pub fn set_float2(&mut self, index: usize, x: f32, y: f32) {
577 let _ = self.try_set_float2(index, x, y);
578 }
579
580 pub fn try_set_float2(
582 &mut self,
583 index: usize,
584 x: f32,
585 y: f32,
586 ) -> Result<(), RuntimeShaderUniformError> {
587 self.try_ensure_capacity(index, 2)?;
588 self.uniforms.set(index, x);
589 self.uniforms.set(index + 1, y);
590 Ok(())
591 }
592
593 pub fn set_float4(&mut self, index: usize, x: f32, y: f32, z: f32, w: f32) {
598 let _ = self.try_set_float4(index, x, y, z, w);
599 }
600
601 pub fn try_set_float4(
603 &mut self,
604 index: usize,
605 x: f32,
606 y: f32,
607 z: f32,
608 w: f32,
609 ) -> Result<(), RuntimeShaderUniformError> {
610 self.try_ensure_capacity(index, 4)?;
611 self.uniforms.set(index, x);
612 self.uniforms.set(index + 1, y);
613 self.uniforms.set(index + 2, z);
614 self.uniforms.set(index + 3, w);
615 Ok(())
616 }
617
618 pub fn set_batched_source(&mut self, batched: bool) {
623 self.batched_source = batched;
624 }
625
626 pub fn batched_source(&self) -> bool {
629 self.batched_source
630 }
631
632 pub fn set_substrates(&mut self, substrates: &[SubstrateSpec]) {
641 assert!(
642 substrates.len() <= MAX_SUBSTRATES,
643 "a runtime shader declares at most {MAX_SUBSTRATES} substrates"
644 );
645 if self.substrates().len() == substrates.len()
646 && self
647 .substrates()
648 .iter()
649 .zip(substrates)
650 .all(|(existing, incoming)| existing.same_bits(incoming))
651 {
652 return;
653 }
654 self.specialization_mut().substrates = substrates.iter().copied().collect();
655 }
656
657 pub fn substrates(&self) -> &[SubstrateSpec] {
659 &self.specialization().substrates
660 }
661
662 pub fn hash_substrates<H: std::hash::Hasher>(&self, state: &mut H) {
664 use std::hash::Hash;
665 self.substrates().len().hash(state);
666 for substrate in self.substrates() {
667 substrate.hash_bits(state);
668 }
669 self.draw_split().hash(state);
670 }
671
672 pub fn set_draw_split(&mut self, override_name: Option<&'static str>) {
679 if self.draw_split() == override_name {
680 return;
681 }
682 self.specialization_mut().draw_split = override_name;
683 }
684
685 pub fn draw_split(&self) -> Option<&'static str> {
687 self.specialization().draw_split
688 }
689
690 pub fn source(&self) -> &str {
692 &self.source
693 }
694
695 pub fn uniforms(&self) -> &[f32] {
697 self.uniforms.as_slice()
698 }
699
700 pub fn uniforms_padded(&self) -> [f32; Self::MAX_UNIFORMS] {
702 let mut padded = [0.0f32; Self::MAX_UNIFORMS];
703 let len = self.uniforms.len().min(Self::MAX_UNIFORMS);
704 padded[..len].copy_from_slice(&self.uniforms.as_slice()[..len]);
705 padded
706 }
707
708 pub fn source_hash(&self) -> u64 {
710 self.source_hash
711 }
712
713 fn try_ensure_capacity(
714 &mut self,
715 index: usize,
716 width: usize,
717 ) -> Result<(), RuntimeShaderUniformError> {
718 let min_len = index
719 .checked_add(width)
720 .ok_or_else(|| Self::uniform_range_error(index, width))?;
721 if min_len > Self::MAX_USER_UNIFORMS {
722 return Err(Self::uniform_range_error(index, width));
723 }
724 self.uniforms.ensure_len(min_len);
725 Ok(())
726 }
727
728 fn uniform_range_error(index: usize, width: usize) -> RuntimeShaderUniformError {
729 RuntimeShaderUniformError::OutOfUserRange {
730 index,
731 width,
732 max_user_uniforms: Self::MAX_USER_UNIFORMS,
733 reserved_start: Self::RESERVED_UNIFORM_START,
734 max_uniforms: Self::MAX_UNIFORMS,
735 }
736 }
737}
738
739#[cfg(test)]
740thread_local! {
741 static OVERRIDE_HASH_COMPUTATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
742}
743
744impl PartialEq for RuntimeShader {
745 fn eq(&self, other: &Self) -> bool {
746 self.source_hash == other.source_hash
747 && (Arc::ptr_eq(&self.source, &other.source)
748 || self.source.as_ref() == other.source.as_ref())
749 && self.uniforms == other.uniforms
750 && self.overrides().len() == other.overrides().len()
751 && self
752 .overrides()
753 .iter()
754 .zip(other.overrides())
755 .all(|(a, b)| a.0 == b.0 && a.1.to_bits() == b.1.to_bits())
756 && self.input_padding.to_bits() == other.input_padding.to_bits()
757 && self.output_padding.to_bits() == other.output_padding.to_bits()
758 && self.batched_source == other.batched_source
759 && self.substrates() == other.substrates()
760 && self.draw_split() == other.draw_split()
761 && self.domains == other.domains
762 }
763}
764
765fn hash_shader_source(source: &str) -> u64 {
766 hash_shader_bytes(source.bytes())
767}
768
769fn hash_shader_bytes(bytes: impl IntoIterator<Item = u8>) -> u64 {
770 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
771 const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
772
773 bytes.into_iter().fold(FNV_OFFSET_BASIS, |hash, byte| {
774 (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME)
775 })
776}
777
778#[derive(Clone, Copy, Debug, PartialEq, Eq)]
779struct ShaderSourceCallsite {
780 file: &'static str,
781 line: u32,
782 column: u32,
783}
784
785struct CachedShaderSource {
786 callsite: ShaderSourceCallsite,
787 source_hash: u64,
788 source: Arc<str>,
789}
790
791struct CachedSharedShaderSourceHash {
792 byte_ptr: usize,
793 len: usize,
794 source_hash: u64,
795 source: Weak<str>,
796}
797
798fn cached_shared_shader_source_hash(source: &Arc<str>) -> u64 {
799 static CACHE: OnceLock<Mutex<Vec<CachedSharedShaderSourceHash>>> = OnceLock::new();
800 let byte_ptr = source.as_ptr() as usize;
801 let len = source.len();
802 let mut cache = CACHE
803 .get_or_init(|| Mutex::new(Vec::new()))
804 .lock()
805 .unwrap_or_else(|poisoned| poisoned.into_inner());
806
807 cache.retain(|entry| entry.source.strong_count() > 0);
808 if let Some(entry) = cache.iter().find(|entry| {
809 entry.byte_ptr == byte_ptr
810 && entry.len == len
811 && entry
812 .source
813 .upgrade()
814 .is_some_and(|cached| Arc::ptr_eq(&cached, source))
815 }) {
816 return entry.source_hash;
817 }
818
819 let source_hash = hash_shader_source(source);
820 cache.push(CachedSharedShaderSourceHash {
821 byte_ptr,
822 len,
823 source_hash,
824 source: Arc::downgrade(source),
825 });
826 source_hash
827}
828
829fn cached_shader_source(
830 location: &'static std::panic::Location<'static>,
831 source: &str,
832) -> (Arc<str>, u64) {
833 static CACHE: OnceLock<Mutex<Vec<CachedShaderSource>>> = OnceLock::new();
834 let callsite = ShaderSourceCallsite {
835 file: location.file(),
836 line: location.line(),
837 column: location.column(),
838 };
839 let mut cache = CACHE
840 .get_or_init(|| Mutex::new(Vec::new()))
841 .lock()
842 .unwrap_or_else(|poisoned| poisoned.into_inner());
843
844 if let Some(entry) = cache.iter_mut().find(|entry| entry.callsite == callsite) {
845 if entry.source.as_ref() == source {
846 return (entry.source.clone(), entry.source_hash);
847 }
848 let source_hash = hash_shader_source(source);
849 entry.source_hash = source_hash;
850 entry.source = Arc::<str>::from(source);
851 return (entry.source.clone(), entry.source_hash);
852 }
853
854 let source_hash = hash_shader_source(source);
855 let shared = Arc::<str>::from(source);
856 cache.push(CachedShaderSource {
857 callsite,
858 source_hash,
859 source: shared.clone(),
860 });
861 (shared, source_hash)
862}
863
864#[derive(Clone, Debug, PartialEq)]
869pub enum RenderEffect {
870 Blur {
872 radius_x: f32,
873 radius_y: f32,
874 edge_treatment: TileMode,
875 },
876 Offset { offset_x: f32, offset_y: f32 },
878 Shader {
880 shader: Arc<RuntimeShader>,
882 },
883 Chain {
887 first: Arc<RenderEffect>,
888 second: Arc<RenderEffect>,
889 },
890}
891
892impl RenderEffect {
893 pub fn blur(radius: f32) -> Self {
895 Self::blur_with_edge_treatment(radius, TileMode::default())
896 }
897
898 pub fn blur_with_edge_treatment(radius: f32, edge_treatment: TileMode) -> Self {
901 Self::Blur {
902 radius_x: radius,
903 radius_y: radius,
904 edge_treatment,
905 }
906 }
907
908 pub fn blur_xy(radius_x: f32, radius_y: f32, edge_treatment: TileMode) -> Self {
910 Self::Blur {
911 radius_x,
912 radius_y,
913 edge_treatment,
914 }
915 }
916
917 pub fn offset(offset_x: f32, offset_y: f32) -> Self {
919 Self::Offset { offset_x, offset_y }
920 }
921
922 pub fn runtime_shader(shader: RuntimeShader) -> Self {
924 Self::Shader {
925 shader: Arc::new(shader),
926 }
927 }
928
929 pub fn then(self, other: RenderEffect) -> Self {
931 Self::Chain {
932 first: Arc::new(self),
933 second: Arc::new(other),
934 }
935 }
936
937 pub fn contains_runtime_shader(&self) -> bool {
941 match self {
942 RenderEffect::Shader { .. } => true,
943 RenderEffect::Chain { first, second } => {
944 first.contains_runtime_shader() || second.contains_runtime_shader()
945 }
946 _ => false,
947 }
948 }
949
950 pub fn input_padding(&self) -> f32 {
952 match self {
953 RenderEffect::Blur {
954 radius_x, radius_y, ..
955 } => radius_x.abs().max(radius_y.abs()),
956 RenderEffect::Offset { offset_x, offset_y } => offset_x.abs().max(offset_y.abs()),
957 RenderEffect::Shader { shader } => shader.input_padding(),
958 RenderEffect::Chain { first, second } => first.input_padding() + second.input_padding(),
959 }
960 }
961
962 pub fn output_padding(&self) -> f32 {
966 match self {
967 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => 0.0,
968 RenderEffect::Shader { shader } => shader.output_padding(),
969 RenderEffect::Chain { first, second } => {
970 first.output_padding() + second.output_padding()
971 }
972 }
973 }
974
975 pub fn output_support(&self) -> Option<Rect> {
980 match self {
981 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
982 RenderEffect::Shader { shader } => shader.output_support(),
983 RenderEffect::Chain { second, .. } => second.output_support(),
984 }
985 }
986
987 pub fn sample_domain(&self) -> Option<Rect> {
991 match self {
992 RenderEffect::Blur { .. } | RenderEffect::Offset { .. } => None,
993 RenderEffect::Shader { shader } => shader.sample_domain(),
994 RenderEffect::Chain { second, .. } => second.sample_domain(),
995 }
996 }
997}
998
999#[cfg(test)]
1000mod tests {
1001 #[test]
1002 fn overrides_stay_sorted_and_replace_by_name() {
1003 let mut shader = super::RuntimeShader::new("// overrides");
1004 shader.set_override("ZETA", 1.0);
1005 shader.set_override("ALPHA", 0.0);
1006 shader.set_override("ZETA", 2.0);
1007 assert_eq!(shader.overrides(), &[("ALPHA", 0.0), ("ZETA", 2.0)]);
1008 }
1009
1010 #[test]
1011 fn clear_override_removes_present_name_and_preserves_remaining_set() {
1012 let mut shader = super::RuntimeShader::new("");
1013 shader.set_override("ZETA", 1.0);
1014 shader.set_override("ALPHA", 2.0);
1015 let mut expected = super::RuntimeShader::new("");
1016 expected.set_override("ALPHA", 2.0);
1017 assert!(shader.clear_override("ZETA"));
1018 assert!(!shader.clear_override("MISSING"));
1019 assert_eq!(shader.overrides(), &[("ALPHA", 2.0)]);
1020 assert_eq!(shader.overrides_hash(), expected.overrides_hash());
1021 assert!(shader.clear_override("ALPHA"));
1022 assert!(shader.overrides().is_empty());
1023 assert_eq!(shader.overrides_hash(), 0);
1024 }
1025
1026 #[test]
1027 fn overrides_distinguish_otherwise_equal_shaders() {
1028 let plain = super::RuntimeShader::new("// overrides-eq");
1029 let mut raised = plain.clone();
1030 raised.set_override("FLAG", 1.0);
1031 assert_eq!(plain.overrides_hash(), 0);
1032 assert_ne!(plain.overrides_hash(), raised.overrides_hash());
1033 assert_ne!(plain, raised);
1034 let mut lowered = raised.clone();
1035 lowered.set_override("FLAG", 0.0);
1036 assert_ne!(raised.overrides_hash(), lowered.overrides_hash());
1037 assert_ne!(raised, lowered);
1038 }
1039
1040 #[test]
1041 fn unchanged_override_lookups_share_one_hash_computation() {
1042 let mut shader = RuntimeShader::new("");
1043 shader.set_override("FLAG", 1.0);
1044 shader.set_override("SCALE", 0.5);
1045 OVERRIDE_HASH_COMPUTATIONS.with(|count| count.set(0));
1046 let expected = shader.overrides_hash();
1047 for _ in 0..24 {
1048 let mut copy = shader.clone();
1049 copy.set_float(0, 7.0);
1050 copy.set_override("FLAG", 1.0);
1051 assert!(!copy.clear_override("ABSENT"));
1052 assert_eq!(copy.overrides_hash(), expected);
1053 }
1054 assert_eq!(OVERRIDE_HASH_COMPUTATIONS.with(std::cell::Cell::get), 1);
1055 }
1056
1057 #[test]
1058 fn override_hash_tracks_clone_mutations_and_float_bits() {
1059 fn independent_hash(shader: &RuntimeShader) -> u64 {
1060 let bytes: Vec<u8> = shader
1061 .overrides()
1062 .iter()
1063 .flat_map(|(name, value)| {
1064 name.bytes().chain([0]).chain(value.to_bits().to_le_bytes())
1065 })
1066 .collect();
1067 if bytes.is_empty() {
1068 0
1069 } else {
1070 hash_shader_bytes(bytes)
1071 }
1072 }
1073
1074 let mut original = RuntimeShader::new("");
1075 original.set_override("VALUE", 0.0);
1076 let first = original.overrides_hash();
1077 assert_eq!(first, independent_hash(&original));
1078 for value in [
1079 -0.0,
1080 0.5,
1081 f64::INFINITY,
1082 f64::from_bits(0x7ff8_0000_0000_0001),
1083 ] {
1084 let mut changed = original.clone();
1085 changed.set_override("VALUE", value);
1086 assert_eq!(changed.overrides_hash(), independent_hash(&changed));
1087 assert_ne!(changed.overrides_hash(), first);
1088 changed.set_override("ADDED", 1.0);
1089 assert_eq!(changed.overrides_hash(), independent_hash(&changed));
1090 assert!(changed.clear_override("VALUE"));
1091 assert_eq!(changed.overrides_hash(), independent_hash(&changed));
1092 assert!(changed.clear_override("ADDED"));
1093 assert_eq!(changed.overrides_hash(), 0);
1094 assert_eq!(original.overrides_hash(), first);
1095 }
1096 }
1097
1098 #[test]
1099 fn shader_clones_share_declarations_and_isolate_mutation() {
1100 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1101 shader.set_override("FLAG", 1.0);
1102 shader.set_substrates(&[SubstrateSpec::Average { block: 4 }]);
1103 shader.set_draw_split(Some("SPLIT"));
1104 let support = Rect {
1105 x: 1.0,
1106 y: 2.0,
1107 width: 30.0,
1108 height: 40.0,
1109 };
1110 shader.set_output_support(Some(support));
1111 let mut cloned = shader.clone();
1112 assert_eq!(cloned.overrides().as_ptr(), shader.overrides().as_ptr());
1113 assert_eq!(cloned.substrates().as_ptr(), shader.substrates().as_ptr());
1114 cloned.set_float(0, 2.0);
1115 assert!(shader.uniforms().is_empty());
1116 assert_eq!(cloned.overrides().as_ptr(), shader.overrides().as_ptr());
1117 cloned.set_override("FLAG", 2.0);
1118 assert_eq!(cloned.substrates(), shader.substrates());
1119 assert_eq!(cloned.draw_split(), shader.draw_split());
1120 cloned.set_substrates(&[SubstrateSpec::Average { block: 8 }]);
1121 cloned.set_draw_split(None);
1122 cloned.set_output_support(None);
1123 assert_eq!(shader.overrides(), &[("FLAG", 1.0)]);
1124 assert_eq!(shader.substrates(), &[SubstrateSpec::Average { block: 4 }]);
1125 assert_eq!(shader.draw_split(), Some("SPLIT"));
1126 assert_eq!(shader.output_support(), Some(support));
1127 assert_ne!(cloned, shader);
1128 }
1129
1130 use super::*;
1131 use crate::RoundedCornerShape;
1132
1133 #[test]
1134 fn cloned_effect_chains_keep_order_and_isolate_nested_edits() {
1135 let original = RenderEffect::offset(2.0, 7.0)
1136 .then(RenderEffect::blur(3.0))
1137 .then(RenderEffect::offset(-4.0, 1.0));
1138 let mut edited = original.clone();
1139 assert_eq!(edited, original);
1140 let RenderEffect::Chain {
1141 first: original_first,
1142 second: original_second,
1143 } = &original
1144 else {
1145 panic!("chain effect")
1146 };
1147 let RenderEffect::Chain {
1148 first: edited_first,
1149 second: edited_second,
1150 } = &mut edited
1151 else {
1152 panic!("chain effect")
1153 };
1154 assert!(Arc::ptr_eq(original_first, edited_first));
1155 assert!(Arc::ptr_eq(original_second, edited_second));
1156 assert_eq!(original_second.as_ref(), &RenderEffect::offset(-4.0, 1.0));
1157 let RenderEffect::Chain { first, second } = Arc::make_mut(edited_first) else {
1158 panic!("nested chain")
1159 };
1160 assert_eq!(first.as_ref(), &RenderEffect::offset(2.0, 7.0));
1161 assert_eq!(second.as_ref(), &RenderEffect::blur(3.0));
1162 *Arc::make_mut(first) = RenderEffect::offset(12.0, 17.0);
1163 *Arc::make_mut(edited_second) = RenderEffect::blur(11.0);
1164 assert_eq!(
1165 original,
1166 RenderEffect::offset(2.0, 7.0)
1167 .then(RenderEffect::blur(3.0))
1168 .then(RenderEffect::offset(-4.0, 1.0))
1169 );
1170 assert_eq!(
1171 edited,
1172 RenderEffect::offset(12.0, 17.0)
1173 .then(RenderEffect::blur(3.0))
1174 .then(RenderEffect::blur(11.0))
1175 );
1176 }
1177
1178 #[test]
1179 fn cloned_shader_effects_preserve_configuration_and_isolate_edits() {
1180 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1181 shader.set_float(20, 3.0);
1182 shader.set_override("FEATURE", -0.0);
1183 shader.set_input_padding(7.0);
1184 shader.set_substrates(&[SubstrateSpec::Blur { radius_px: 12.0 }]);
1185 shader.set_draw_split(Some("SPLIT"));
1186 let original = RenderEffect::runtime_shader(shader.clone());
1187 let mut edited = original.clone();
1188 assert_eq!(edited, original);
1189 let RenderEffect::Shader {
1190 shader: original_shader,
1191 } = &original
1192 else {
1193 panic!("shader effect")
1194 };
1195 assert_eq!(original_shader.as_ref(), &shader);
1196 let RenderEffect::Shader {
1197 shader: edited_shader,
1198 } = &mut edited
1199 else {
1200 panic!("shader effect")
1201 };
1202 assert!(Arc::ptr_eq(original_shader, edited_shader));
1203 let changed = Arc::make_mut(edited_shader);
1204 changed.set_float(20, 9.0);
1205 changed.set_override("FEATURE", 1.0);
1206 changed.set_substrates(&[]);
1207 changed.set_draw_split(None);
1208 assert_eq!(original_shader.as_ref(), &shader);
1209 assert_eq!(edited_shader.uniforms()[20], 9.0);
1210 assert_eq!(edited_shader.overrides(), &[("FEATURE", 1.0)]);
1211 assert!(edited_shader.substrates().is_empty());
1212 assert_eq!(edited_shader.draw_split(), None);
1213 assert_ne!(original, edited);
1214 }
1215
1216 #[test]
1217 fn runtime_shader_set_uniforms() {
1218 let mut shader = RuntimeShader::new("// test");
1219 shader.set_float(0, 1.0);
1220 shader.set_float2(2, 3.0, 4.0);
1221 shader.set_float4(4, 5.0, 6.0, 7.0, 8.0);
1222
1223 assert_eq!(shader.uniforms()[0], 1.0);
1224 assert_eq!(shader.uniforms()[1], 0.0);
1225 assert_eq!(shader.uniforms()[2], 3.0);
1226 assert_eq!(shader.uniforms()[3], 4.0);
1227 assert_eq!(shader.uniforms()[4], 5.0);
1228 assert_eq!(shader.uniforms()[5], 6.0);
1229 assert_eq!(shader.uniforms()[6], 7.0);
1230 assert_eq!(shader.uniforms()[7], 8.0);
1231 }
1232
1233 #[test]
1234 fn runtime_shader_padded() {
1235 let mut shader = RuntimeShader::new("// test");
1236 shader.set_float(0, 42.0);
1237 let padded = shader.uniforms_padded();
1238 assert_eq!(padded[0], 42.0);
1239 assert_eq!(padded[1], 0.0);
1240 assert_eq!(padded[255], 0.0);
1241 }
1242
1243 #[test]
1244 fn blur_and_offset_declare_input_padding() {
1245 assert_eq!(
1246 RenderEffect::blur_xy(6.0, 12.0, TileMode::Clamp).input_padding(),
1247 12.0
1248 );
1249 assert_eq!(RenderEffect::offset(-8.0, 3.0).input_padding(), 8.0);
1250 }
1251
1252 #[test]
1253 fn chained_effect_padding_accumulates_sampling_ranges() {
1254 let mut shader = RuntimeShader::new("// test");
1255 shader.set_input_padding(9.0);
1256 let effect = RenderEffect::blur_xy(4.0, 6.0, TileMode::Clamp)
1257 .then(RenderEffect::runtime_shader(shader))
1258 .then(RenderEffect::offset(2.0, -5.0));
1259
1260 assert_eq!(effect.input_padding(), 20.0);
1261 }
1262
1263 #[test]
1264 fn runtime_shader_keeps_common_uniform_payload_inline() {
1265 let mut shader = RuntimeShader::new("// test");
1266 shader.set_float4(0, 1.0, 2.0, 3.0, 4.0);
1267 shader.set_float4(4, 5.0, 6.0, 7.0, 8.0);
1268 shader.set_float4(8, 9.0, 10.0, 11.0, 12.0);
1269 shader.set_float4(12, 13.0, 14.0, 15.0, 16.0);
1270
1271 assert!(shader.uniforms.is_inline());
1272 assert_eq!(shader.uniforms().len(), 16);
1273
1274 shader.set_float(16, 17.0);
1275 assert!(!shader.uniforms.is_inline());
1276 assert_eq!(shader.uniforms()[16], 17.0);
1277 }
1278
1279 #[test]
1280 fn runtime_shader_try_set_reports_reserved_uniform_slots() {
1281 let mut shader = RuntimeShader::new("// test");
1282
1283 let err = shader
1284 .try_set_float(RuntimeShader::RESERVED_UNIFORM_START, 1.0)
1285 .unwrap_err();
1286 assert_eq!(
1287 err,
1288 RuntimeShaderUniformError::OutOfUserRange {
1289 index: RuntimeShader::RESERVED_UNIFORM_START,
1290 width: 1,
1291 max_user_uniforms: RuntimeShader::MAX_USER_UNIFORMS,
1292 reserved_start: RuntimeShader::RESERVED_UNIFORM_START,
1293 max_uniforms: RuntimeShader::MAX_UNIFORMS,
1294 }
1295 );
1296 assert!(shader.uniforms().is_empty());
1297
1298 let err = shader
1299 .try_set_float4(RuntimeShader::MAX_USER_UNIFORMS - 3, 1.0, 2.0, 3.0, 4.0)
1300 .unwrap_err();
1301 assert_eq!(
1302 err,
1303 RuntimeShaderUniformError::OutOfUserRange {
1304 index: RuntimeShader::MAX_USER_UNIFORMS - 3,
1305 width: 4,
1306 max_user_uniforms: RuntimeShader::MAX_USER_UNIFORMS,
1307 reserved_start: RuntimeShader::RESERVED_UNIFORM_START,
1308 max_uniforms: RuntimeShader::MAX_UNIFORMS,
1309 }
1310 );
1311 }
1312
1313 #[test]
1314 fn runtime_shader_setters_ignore_invalid_uniform_slots_without_panicking() {
1315 let mut shader = RuntimeShader::new("// test");
1316 shader.set_float(0, 7.0);
1317
1318 shader.set_float(RuntimeShader::RESERVED_UNIFORM_START, 1.0);
1319 shader.set_float4(RuntimeShader::MAX_USER_UNIFORMS - 3, 1.0, 2.0, 3.0, 4.0);
1320
1321 assert_eq!(shader.uniforms(), &[7.0]);
1322 }
1323
1324 #[test]
1325 fn render_effect_chaining() {
1326 let blur = RenderEffect::blur(10.0);
1327 let offset = RenderEffect::offset(5.0, 5.0);
1328 let chained = blur.then(offset);
1329 match chained {
1330 RenderEffect::Chain { first, second } => {
1331 assert!(matches!(*first, RenderEffect::Blur { .. }));
1332 assert!(matches!(*second, RenderEffect::Offset { .. }));
1333 }
1334 _ => panic!("expected Chain"),
1335 }
1336 }
1337
1338 #[test]
1339 fn blur_convenience() {
1340 let effect = RenderEffect::blur(15.0);
1341 match effect {
1342 RenderEffect::Blur {
1343 radius_x,
1344 radius_y,
1345 edge_treatment,
1346 } => {
1347 assert_eq!(radius_x, 15.0);
1348 assert_eq!(radius_y, 15.0);
1349 assert_eq!(edge_treatment, TileMode::Clamp);
1350 }
1351 _ => panic!("expected Blur"),
1352 }
1353 }
1354
1355 #[test]
1356 fn blur_with_edge_treatment_uses_explicit_mode() {
1357 let effect = RenderEffect::blur_with_edge_treatment(6.0, TileMode::Decal);
1358 match effect {
1359 RenderEffect::Blur {
1360 radius_x,
1361 radius_y,
1362 edge_treatment,
1363 } => {
1364 assert_eq!(radius_x, 6.0);
1365 assert_eq!(radius_y, 6.0);
1366 assert_eq!(edge_treatment, TileMode::Decal);
1367 }
1368 _ => panic!("expected Blur"),
1369 }
1370 }
1371
1372 #[test]
1373 fn source_hash_consistent() {
1374 let s1 = RuntimeShader::new("fn main() {}");
1375 let s2 = RuntimeShader::new("fn main() {}");
1376 assert_eq!(s1.source_hash(), s2.source_hash());
1377 }
1378
1379 #[test]
1380 fn runtime_shader_from_shared_source_reuses_shared_source() {
1381 let source = Arc::<str>::from("fn fragment() -> vec4<f32> { return vec4<f32>(1.0); }");
1382 let s1 = RuntimeShader::from_shared_source(source.clone());
1383 let s2 = RuntimeShader::from_shared_source(source);
1384
1385 assert!(Arc::ptr_eq(&s1.source, &s2.source));
1386 assert_eq!(s1.source_hash(), s2.source_hash());
1387 }
1388
1389 fn runtime_shader_from_reuse_callsite(source: &str) -> RuntimeShader {
1390 RuntimeShader::new(source)
1391 }
1392
1393 fn runtime_shader_from_replacement_callsite(source: &str) -> RuntimeShader {
1394 RuntimeShader::new(source)
1395 }
1396
1397 #[test]
1398 fn runtime_shader_new_reuses_same_callsite_source() {
1399 let source = "fn fragment() -> vec4<f32> { return vec4<f32>(1.0); }";
1400 let s1 = runtime_shader_from_reuse_callsite(source);
1401 let s2 = runtime_shader_from_reuse_callsite(source);
1402
1403 assert!(Arc::ptr_eq(&s1.source, &s2.source));
1404 assert_eq!(s1.source_hash(), s2.source_hash());
1405 }
1406
1407 #[test]
1408 fn runtime_shader_new_replaces_changed_callsite_source() {
1409 let s1 = runtime_shader_from_replacement_callsite("fn a() {}");
1410 let s2 = runtime_shader_from_replacement_callsite("fn b() {}");
1411
1412 assert!(!Arc::ptr_eq(&s1.source, &s2.source));
1413 assert_ne!(s1.source_hash(), s2.source_hash());
1414 assert_eq!(s2.source(), "fn b() {}");
1415 }
1416
1417 #[test]
1418 fn runtime_shader_source_storage_has_no_process_global_interner() {
1419 let source = include_str!("render_effect.rs");
1420 let blocked_static = ["static ", "INTERNER"].concat();
1421 let blocked_type = ["ShaderSource", "Interner"].concat();
1422
1423 assert!(
1424 !source.contains(&blocked_static) && !source.contains(&blocked_type),
1425 "RuntimeShader source sharing must be explicit via from_shared_source, not a process-global interner"
1426 );
1427 }
1428
1429 #[test]
1430 fn blur_xy_preserves_tile_mode() {
1431 let effect = RenderEffect::blur_xy(3.0, 7.0, TileMode::Clamp);
1432 match effect {
1433 RenderEffect::Blur {
1434 radius_x,
1435 radius_y,
1436 edge_treatment,
1437 } => {
1438 assert_eq!(radius_x, 3.0);
1439 assert_eq!(radius_y, 7.0);
1440 assert_eq!(edge_treatment, TileMode::Clamp);
1441 }
1442 _ => panic!("expected Blur"),
1443 }
1444 }
1445
1446 #[test]
1447 fn offset_constructor_sets_components() {
1448 let effect = RenderEffect::offset(11.0, -5.0);
1449 match effect {
1450 RenderEffect::Offset { offset_x, offset_y } => {
1451 assert_eq!(offset_x, 11.0);
1452 assert_eq!(offset_y, -5.0);
1453 }
1454 _ => panic!("expected Offset"),
1455 }
1456 }
1457
1458 #[test]
1459 fn runtime_shader_equality_is_source_value_based() {
1460 let mut s1 = RuntimeShader::new("fn main() {}");
1461 let mut s2 = RuntimeShader::new("fn main() {}");
1462 s1.set_float(0, 1.0);
1463 s2.set_float(0, 1.0);
1464 assert_eq!(s1, s2);
1465 }
1466
1467 #[test]
1468 fn blurred_edge_treatment_defaults_to_bounded_rectangle() {
1469 let treatment = BlurredEdgeTreatment::default();
1470 assert_eq!(treatment.shape(), Some(LayerShape::Rectangle));
1471 assert!(treatment.clip());
1472 assert_eq!(treatment.tile_mode(), TileMode::Clamp);
1473 }
1474
1475 #[test]
1476 fn blurred_edge_treatment_unbounded_uses_decal_and_no_clip() {
1477 let treatment = BlurredEdgeTreatment::UNBOUNDED;
1478 assert_eq!(treatment.shape(), None);
1479 assert!(!treatment.clip());
1480 assert_eq!(treatment.tile_mode(), TileMode::Decal);
1481 }
1482
1483 #[test]
1484 fn blurred_edge_treatment_with_shape_uses_bounded_mode() {
1485 let rounded = LayerShape::Rounded(RoundedCornerShape::uniform(8.0));
1486 let treatment = BlurredEdgeTreatment::with_shape(rounded);
1487 assert_eq!(treatment.shape(), Some(rounded));
1488 assert!(treatment.clip());
1489 assert_eq!(treatment.tile_mode(), TileMode::Clamp);
1490 }
1491
1492 #[test]
1493 fn an_effect_chains_output_support_is_the_support_of_the_stage_that_writes_its_output() {
1494 let mut shader = RuntimeShader::new("fn glass_fs() {}");
1495 assert_eq!(shader.output_support(), None);
1496 let support = Rect {
1497 x: 4.0,
1498 y: 6.0,
1499 width: 30.0,
1500 height: 12.0,
1501 };
1502 shader.set_output_support(Some(support));
1503 assert_eq!(shader.output_support(), Some(support));
1504 let effect = RenderEffect::blur(3.0).then(RenderEffect::runtime_shader(shader.clone()));
1505 assert_eq!(effect.output_support(), Some(support));
1506 let effect = RenderEffect::runtime_shader(shader.clone()).then(RenderEffect::blur(3.0));
1507 assert_eq!(effect.output_support(), None);
1508 assert_eq!(RenderEffect::blur(3.0).output_support(), None);
1509 }
1510
1511 #[test]
1512 fn a_sample_domain_is_the_writers_and_a_blur_declares_none() {
1513 let mut shader = RuntimeShader::new("fn glass_fs() {}");
1514 let domain = Rect {
1515 x: -2.0,
1516 y: -2.0,
1517 width: 20.0,
1518 height: 12.0,
1519 };
1520 let plain = shader.clone();
1521 shader.set_sample_domain(Some(domain));
1522 assert_ne!(shader, plain);
1523 assert_eq!(shader.sample_domain(), Some(domain));
1524 let effect = RenderEffect::blur(3.0).then(RenderEffect::runtime_shader(shader.clone()));
1525 assert_eq!(effect.sample_domain(), Some(domain));
1526 assert_eq!(RenderEffect::blur(3.0).output_support(), None);
1527 assert_eq!(RenderEffect::blur(3.0).sample_domain(), None);
1528 shader.set_sample_domain(Some(Rect {
1529 x: f32::INFINITY,
1530 ..domain
1531 }));
1532 assert_eq!(shader.sample_domain(), None);
1533 }
1534
1535 #[test]
1536 fn a_non_finite_output_support_clears_the_declaration_and_a_support_tells_shaders_apart() {
1537 let mut shader = RuntimeShader::new("fn glass_fs() {}");
1538 let plain = shader.clone();
1539 shader.set_output_support(Some(Rect {
1540 x: 0.0,
1541 y: 0.0,
1542 width: 10.0,
1543 height: 10.0,
1544 }));
1545 assert_ne!(shader, plain);
1546 shader.set_output_support(Some(Rect {
1547 x: 0.0,
1548 y: 0.0,
1549 width: f32::NAN,
1550 height: 10.0,
1551 }));
1552 assert_eq!(shader.output_support(), None);
1553 assert_eq!(shader, plain);
1554 }
1555 #[test]
1556 fn specialization_cache_preserves_source_identity_and_shader_values() {
1557 let mut cache = ShaderSpecializationCache::<u32, 2>::new();
1558 let mut first = RuntimeShader::new("fn effect_fs() {}");
1559 first.set_override("CALLER", -0.0);
1560 let mut second = first.clone();
1561 second.set_override("CALLER", f64::from_bits(0x7ff8_0000_0000_0001));
1562 let sources = [first, second];
1563 for key in [1, 1, 2, 3, 1] {
1564 for source in &sources {
1565 let mut shader = source.clone();
1566 shader.set_float(0, key as f32);
1567 shader.set_input_padding(key as f32);
1568 cache.apply(&mut shader, key, |shader, &key| {
1569 shader.set_override("FEATURE", f64::from(key));
1570 shader.set_draw_split(Some("SPLIT"));
1571 shader.set_substrates(&[SubstrateSpec::Average { block: key }]);
1572 });
1573 assert_eq!(
1574 shader.overrides()[0].1.to_bits(),
1575 source.overrides()[0].1.to_bits()
1576 );
1577 assert_eq!(shader.overrides()[1], ("FEATURE", f64::from(key)));
1578 assert_eq!(
1579 shader.substrates(),
1580 &[SubstrateSpec::Average { block: key }]
1581 );
1582 assert_eq!(shader.draw_split(), Some("SPLIT"));
1583 assert_eq!(shader.uniforms(), &[key as f32]);
1584 assert_eq!(shader.input_padding(), key as f32);
1585 assert_eq!(source.overrides().len(), 1);
1586 assert!(source.substrates().is_empty());
1587 assert_eq!(source.draw_split(), None);
1588 let mut repeated = source.clone();
1589 cache.apply(&mut repeated, key, |_, _| {
1590 panic!("shared specialization missed")
1591 });
1592 assert_eq!(repeated.overrides_hash(), shader.overrides_hash());
1593 assert!(Arc::ptr_eq(
1594 repeated.specialization.as_ref().unwrap(),
1595 shader.specialization.as_ref().unwrap(),
1596 ));
1597 assert!(cache.entries.len() <= 2);
1598 }
1599 }
1600 }
1601
1602 #[test]
1603 fn specialization_cache_mutates_unique_state_without_retaining_it() {
1604 let mut cache = ShaderSpecializationCache::<(), 2>::new();
1605 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1606 shader.set_override("VALUE", 1.0);
1607 let allocation = Arc::as_ptr(shader.specialization.as_ref().unwrap());
1608 cache.apply(&mut shader, (), |shader, ()| {
1609 shader.set_override("VALUE", 2.0)
1610 });
1611 assert_eq!(shader.overrides(), &[("VALUE", 2.0)]);
1612 assert_eq!(
1613 Arc::as_ptr(shader.specialization.as_ref().unwrap()),
1614 allocation
1615 );
1616 assert!(cache.entries.is_empty());
1617 }
1618
1619 #[test]
1620 fn unchanged_shader_declarations_keep_their_storage() {
1621 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1622 shader.set_substrates(&[]);
1623 shader.set_draw_split(None);
1624 assert!(!shader.clear_override("MISSING"));
1625 assert!(shader.specialization.is_none());
1626 shader.set_override("FLAG", 1.0);
1627 let mut cloned = shader.clone();
1628 cloned.set_override("FLAG", 1.0);
1629 cloned.set_substrates(&[]);
1630 cloned.set_draw_split(None);
1631 assert!(!cloned.clear_override("MISSING"));
1632 assert_eq!(cloned.overrides().as_ptr(), shader.overrides().as_ptr());
1633 }
1634
1635 #[test]
1636 fn mean_substrates_have_distinct_stable_identity() {
1637 use std::hash::{DefaultHasher, Hasher};
1638 let hash = |spec: SubstrateSpec| {
1639 let mut h = DefaultHasher::new();
1640 spec.hash_bits(&mut h);
1641 h.finish()
1642 };
1643 let mean = SubstrateSpec::Mean;
1644 assert!(mean.same_bits(&mean));
1645 for other in [
1646 SubstrateSpec::Average { block: 4 },
1647 SubstrateSpec::Blur { radius_px: 12.0 },
1648 ] {
1649 assert!(!mean.same_bits(&other));
1650 assert_ne!(hash(mean), hash(other));
1651 }
1652 let mut shader = RuntimeShader::new("fn effect_fs() {}");
1653 shader.set_substrates(&[mean]);
1654 let mut cloned = shader.clone();
1655 cloned.set_substrates(&[SubstrateSpec::Average { block: 4 }]);
1656 assert_eq!(shader.substrates(), &[mean]);
1657 assert_eq!(cloned.substrates(), &[SubstrateSpec::Average { block: 4 }]);
1658 }
1659
1660 #[test]
1661 fn shader_substrates_preserve_order_and_ownership_across_size_changes() {
1662 let declared = [
1663 SubstrateSpec::Blur { radius_px: 12.0 },
1664 SubstrateSpec::Average { block: 4 },
1665 SubstrateSpec::Blur { radius_px: -0.0 },
1666 ];
1667 let mut source = declared;
1668 let mut original = RuntimeShader::new("fn effect_fs() {}");
1669 original.set_substrates(&source);
1670 source[0] = SubstrateSpec::Average { block: 16 };
1671 let mut changed = original.clone();
1672 for replacement in [&source[..1], &source[..2], &source[..0], &source[..]] {
1673 changed.set_substrates(replacement);
1674 assert_eq!(changed.substrates().len(), replacement.len());
1675 assert!(
1676 changed
1677 .substrates()
1678 .iter()
1679 .zip(replacement)
1680 .all(|(actual, expected)| actual.same_bits(expected))
1681 );
1682 assert_eq!(original.substrates().len(), declared.len());
1683 assert!(
1684 original
1685 .substrates()
1686 .iter()
1687 .zip(&declared)
1688 .all(|(actual, expected)| actual.same_bits(expected))
1689 );
1690 }
1691 }
1692
1693 #[test]
1694 fn shader_substrate_setters_preserve_float_bits_when_detaching() {
1695 let mut original = RuntimeShader::new("fn effect_fs() {}");
1696 original.set_substrates(&[SubstrateSpec::Blur { radius_px: 0.0 }]);
1697 let mut cloned = original.clone();
1698 cloned.set_substrates(&[SubstrateSpec::Blur { radius_px: 0.0 }]);
1699 assert_eq!(cloned.substrates().as_ptr(), original.substrates().as_ptr());
1700 cloned.set_substrates(&[SubstrateSpec::Blur { radius_px: -0.0 }]);
1701 let [SubstrateSpec::Blur { radius_px }] = cloned.substrates() else {
1702 panic!("one blur substrate");
1703 };
1704 assert_eq!(radius_px.to_bits(), (-0.0_f32).to_bits());
1705 let [SubstrateSpec::Blur { radius_px }] = original.substrates() else {
1706 panic!("original blur substrate");
1707 };
1708 assert_eq!(radius_px.to_bits(), 0.0_f32.to_bits());
1709 }
1710}