1use super::{
43 impl_server_render_resource, CompositionProperty, IAnimatedServerObject, IServerObject, IServerRenderResource,
44 IServerRenderResourceObserver, ServerCompositionVisual, ServerCompositor, ServerLayerVisual, ServerRenderResource,
45};
46use crate::media::imaging::BitmapBlendingMode;
47use crate::media::{Color, RenderOptions};
48use crate::platform::{AlphaFormat, IDrawingContextImpl, MultiplyLayerFactors, PixelFormats, SharedBitmapImpl};
49use crate::rendering::composition::generated::{
50 ServerAmbientLightHooks, ServerAmbientLightProps, ServerCompositionLightHooks, ServerCompositionLightProps,
51 ServerDistantLightHooks, ServerDistantLightProps, ServerPointLightHooks, ServerPointLightProps,
52 ServerSpotLightHooks, ServerSpotLightProps, ServerVisualUnorderedCollectionHooks,
53 ServerVisualUnorderedCollectionProps,
54};
55use crate::rendering::composition::transport::BatchStreamReader;
56use crate::{Matrix, PixelSize, Point, Rect, Vector, Vector3D};
57use std::any::{Any, TypeId};
58use std::cell::RefCell;
59use std::rc::{Rc, Weak};
60use std::sync::Arc;
61use std::time::Duration;
62
63pub const LIGHT_MAP_CELL: f64 = 4.0;
67
68pub const LIGHT_MAP_MAX_CELLS: i32 = 128;
71
72pub const LIGHT_MAP_SAMPLE_SPACING: f64 = 2.0;
74
75pub const LIGHT_MAP_MAX_SAMPLES: i32 = 1024;
77
78pub const LIGHT_MAP_MAX_SCALE: f64 = 16.0;
81
82const MAX_LIGHT_DEPTH: usize = 4096;
85
86#[derive(Clone, Copy, Debug, Default, PartialEq)]
90pub struct LightColor {
91 pub r: f64,
93 pub g: f64,
95 pub b: f64,
97}
98
99impl LightColor {
100 pub const BLACK: LightColor = LightColor { r: 0.0, g: 0.0, b: 0.0 };
102
103 pub const WHITE: LightColor = LightColor { r: 1.0, g: 1.0, b: 1.0 };
105
106 pub const fn new(r: f64, g: f64, b: f64) -> LightColor {
108 LightColor { r, g, b }
109 }
110
111 pub fn from_color(color: Color, intensity: f64) -> LightColor {
117 let scale = intensity.max(0.0) * color.a as f64 / 255.0;
118 LightColor {
119 r: color.r as f64 / 255.0 * scale,
120 g: color.g as f64 / 255.0 * scale,
121 b: color.b as f64 / 255.0 * scale,
122 }
123 }
124
125 pub fn scale(self, factor: f64) -> LightColor {
127 LightColor { r: self.r * factor, g: self.g * factor, b: self.b * factor }
128 }
129
130 pub fn add(self, other: LightColor) -> LightColor {
132 LightColor { r: self.r + other.r, g: self.g + other.g, b: self.b + other.b }
133 }
134
135 pub fn lerp(self, other: LightColor, t: f64) -> LightColor {
137 LightColor {
138 r: self.r + (other.r - self.r) * t,
139 g: self.g + (other.g - self.g) * t,
140 b: self.b + (other.b - self.b) * t,
141 }
142 }
143
144 pub fn clamped(self) -> LightColor {
146 LightColor { r: self.r.clamp(0.0, 1.0), g: self.g.clamp(0.0, 1.0), b: self.b.clamp(0.0, 1.0) }
147 }
148
149 pub fn atop_pixel(self) -> [u8; 4] {
153 let light = self.clamped();
154 let m = light.r.min(light.g).min(light.b);
155 let byte = |value: f64| (value * 255.0).round().clamp(0.0, 255.0) as u8;
156 [byte(light.r - m), byte(light.g - m), byte(light.b - m), byte(1.0 - m)]
157 }
158}
159
160#[derive(Clone, Copy, Debug, PartialEq)]
162pub struct LightAttenuation {
163 pub constant: f64,
165 pub linear: f64,
167 pub quadratic: f64,
169 pub min_cutoff: f64,
171 pub max_cutoff: f64,
173}
174
175impl LightAttenuation {
176 pub const NONE: LightAttenuation =
178 LightAttenuation { constant: 1.0, linear: 0.0, quadratic: 0.0, min_cutoff: 0.0, max_cutoff: f64::MAX };
179
180 pub fn factor(&self, distance: f64) -> f64 {
189 if distance > self.max_cutoff {
190 return 0.0;
191 }
192 let d = distance.max(self.min_cutoff).max(0.0);
193 let denominator = self.constant + self.linear * d + self.quadratic * d * d;
194 if denominator <= 0.0 {
195 1.0
196 } else {
197 1.0 / denominator
198 }
199 }
200}
201
202fn length(v: Vector3D) -> f64 {
203 (v.x * v.x + v.y * v.y + v.z * v.z).sqrt()
204}
205
206pub fn point_light_incidence(position: Vector3D, point: Point) -> (f64, f64) {
209 let to_light = Vector3D::new(position.x - point.x, position.y - point.y, position.z);
210 let distance = length(to_light);
211 if distance <= 0.0 || position.z <= 0.0 {
212 return (0.0, distance);
213 }
214 (position.z / distance, distance)
215}
216
217pub fn distant_light_incidence(direction: Vector3D) -> f64 {
221 let len = length(direction);
222 if len <= 0.0 {
223 return 0.0;
224 }
225 (-direction.z / len).max(0.0)
226}
227
228fn smoothstep(t: f64) -> f64 {
229 let t = t.clamp(0.0, 1.0);
230 t * t * (3.0 - 2.0 * t)
231}
232
233pub fn spot_light_cone(
238 position: Vector3D,
239 direction: Vector3D,
240 inner_cone_angle: f64,
241 outer_cone_angle: f64,
242 point: Point,
243) -> f64 {
244 let to_point = Vector3D::new(point.x - position.x, point.y - position.y, -position.z);
245 let (to_point_length, direction_length) = (length(to_point), length(direction));
246 if to_point_length <= 0.0 || direction_length <= 0.0 {
247 return 0.0;
248 }
249 let cos_angle = (to_point.x * direction.x + to_point.y * direction.y + to_point.z * direction.z)
250 / (to_point_length * direction_length);
251 let outer = outer_cone_angle.clamp(0.0, std::f64::consts::PI);
252 let inner = inner_cone_angle.clamp(0.0, outer);
253 let (cos_inner, cos_outer) = (inner.cos(), outer.cos());
254 if cos_angle >= cos_inner {
255 return 1.0;
256 }
257 if cos_angle <= cos_outer || cos_inner <= cos_outer {
258 return 0.0;
259 }
260 smoothstep((cos_angle - cos_outer) / (cos_inner - cos_outer))
261}
262
263#[derive(Clone, Copy, Debug, PartialEq)]
265pub enum LightSource {
266 Ambient { color: LightColor },
268 Distant { color: LightColor, direction: Vector3D },
270 Point { color: LightColor, position: Vector3D, attenuation: LightAttenuation },
272 Spot {
274 inner_color: LightColor,
275 outer_color: LightColor,
276 position: Vector3D,
277 direction: Vector3D,
278 inner_cone_angle: f64,
279 outer_cone_angle: f64,
280 attenuation: LightAttenuation,
281 },
282}
283
284impl LightSource {
285 pub fn light_at(&self, point: Point) -> LightColor {
287 match *self {
288 LightSource::Ambient { color } => color,
289 LightSource::Distant { color, direction } => color.scale(distant_light_incidence(direction)),
290 LightSource::Point { color, position, attenuation } => {
291 let (incidence, distance) = point_light_incidence(position, point);
292 color.scale(incidence * attenuation.factor(distance))
293 }
294 LightSource::Spot {
295 inner_color,
296 outer_color,
297 position,
298 direction,
299 inner_cone_angle,
300 outer_cone_angle,
301 attenuation,
302 } => {
303 let cone = spot_light_cone(position, direction, inner_cone_angle, outer_cone_angle, point);
304 if cone <= 0.0 {
305 return LightColor::BLACK;
306 }
307 let (incidence, distance) = point_light_incidence(position, point);
308 outer_color.lerp(inner_color, cone).scale(cone * incidence * attenuation.factor(distance))
312 }
313 }
314 }
315}
316
317#[derive(Clone, Copy, Debug, PartialEq)]
321pub struct LightingMaterial {
322 pub ambient: f64,
323 pub diffuse: f64,
324 pub specular: f64,
325 pub shine: f64,
326}
327
328impl LightingMaterial {
329 pub const VISUAL: LightingMaterial = LightingMaterial { ambient: 1.0, diffuse: 1.0, specular: 0.0, shine: 16.0 };
332}
333
334fn specular_factor(to_light: Vector3D, shine: f64) -> f64 {
337 let len = length(to_light);
338 if len <= 0.0 || to_light.z <= 0.0 {
339 return 0.0;
340 }
341 let half = Vector3D::new(to_light.x / len, to_light.y / len, to_light.z / len + 1.0);
342 let half_length = length(half);
343 if half_length <= 0.0 {
344 return 0.0;
345 }
346 (half.z / half_length).max(0.0).powf(shine.max(1.0))
347}
348
349impl LightSource {
350 pub fn lit_at(&self, point: Point, material: &LightingMaterial) -> LightColor {
355 if let LightSource::Ambient { color } = *self {
356 return color.scale(material.ambient);
357 }
358 let diffuse = if material.diffuse == 0.0 { LightColor::BLACK } else { self.light_at(point).scale(material.diffuse) };
359 if material.specular == 0.0 {
360 return diffuse;
361 }
362 let specular = match *self {
363 LightSource::Ambient { .. } => LightColor::BLACK,
364 LightSource::Distant { color, direction } => {
365 color.scale(specular_factor(Vector3D::new(-direction.x, -direction.y, -direction.z), material.shine))
366 }
367 LightSource::Point { color, position, attenuation } => {
368 let to_light = Vector3D::new(position.x - point.x, position.y - point.y, position.z);
369 color.scale(specular_factor(to_light, material.shine) * attenuation.factor(length(to_light)))
370 }
371 LightSource::Spot {
372 inner_color,
373 outer_color,
374 position,
375 direction,
376 inner_cone_angle,
377 outer_cone_angle,
378 attenuation,
379 } => {
380 let cone = spot_light_cone(position, direction, inner_cone_angle, outer_cone_angle, point);
381 if cone <= 0.0 {
382 LightColor::BLACK
383 } else {
384 let to_light = Vector3D::new(position.x - point.x, position.y - point.y, position.z);
385 outer_color
386 .lerp(inner_color, cone)
387 .scale(cone * specular_factor(to_light, material.shine) * attenuation.factor(length(to_light)))
388 }
389 }
390 };
391 diffuse.add(specular.scale(material.specular))
392 }
393}
394
395#[derive(Clone, Debug, Default)]
398pub struct LightMap {
399 lights: Vec<(LightSource, Matrix)>,
400}
401
402impl LightMap {
403 pub fn new() -> LightMap {
405 LightMap::default()
406 }
407
408 pub fn add(&mut self, source: LightSource, to_light_space: Matrix) {
411 self.lights.push((source, to_light_space));
412 }
413
414 pub fn is_empty(&self) -> bool {
416 self.lights.is_empty()
417 }
418
419 pub fn lit_at(&self, point: Point, material: &LightingMaterial) -> LightColor {
422 let mut light = LightColor::BLACK;
423 for (source, to_light_space) in &self.lights {
424 light = light.add(source.lit_at(to_light_space.transform(point), material));
425 }
426 light
427 }
428
429 pub fn light_at(&self, point: Point) -> LightColor {
431 self.lit_at(point, &LightingMaterial::VISUAL).clamped()
432 }
433
434 pub fn size_for(rect: Rect) -> PixelSize {
438 let cells = |length: f64| ((length / LIGHT_MAP_CELL).ceil() as i32).clamp(2, LIGHT_MAP_MAX_CELLS);
439 PixelSize::new(cells(rect.width), cells(rect.height))
440 }
441
442 pub fn size_for_scale(rect: Rect, device_scale: f64) -> PixelSize {
447 let scale = if device_scale.is_finite() && device_scale > 0.0 { device_scale } else { 1.0 };
448 let samples = |length: f64| {
449 ((length * scale / LIGHT_MAP_SAMPLE_SPACING).ceil().min(f64::from(LIGHT_MAP_MAX_SAMPLES)) as i32)
450 .clamp(2, LIGHT_MAP_MAX_SAMPLES)
451 };
452 PixelSize::new(samples(rect.width), samples(rect.height))
453 }
454
455 fn samples(&self, rect: Rect, size: PixelSize, material: &LightingMaterial) -> Vec<LightColor> {
458 let (columns, rows) = (size.width.max(1), size.height.max(1));
459 let mut samples = Vec::with_capacity((columns * rows) as usize);
460 for row in 0..rows {
461 let y = rect.y + (row as f64 + 0.5) * rect.height / rows as f64;
462 for column in 0..columns {
463 let x = rect.x + (column as f64 + 0.5) * rect.width / columns as f64;
464 samples.push(self.lit_at(Point::new(x, y), material));
465 }
466 }
467 samples
468 }
469
470 pub fn factors(&self, rect: Rect, size: PixelSize, material: &LightingMaterial) -> Option<MultiplyLayerFactors> {
476 let samples = self.samples(rect, size, material);
477 if samples.iter().all(|light| light.r == 1.0 && light.g == 1.0 && light.b == 1.0) {
478 return None;
479 }
480 let brightest = samples.iter().fold(0.0f64, |max, light| max.max(light.r).max(light.g).max(light.b));
481 let scale = brightest.clamp(1.0, LIGHT_MAP_MAX_SCALE);
482 let byte = |value: f64| (value / scale * 255.0).round().clamp(0.0, 255.0) as u8;
483 let mut rgba = Vec::with_capacity(samples.len() * 4);
484 for light in samples {
485 rgba.extend_from_slice(&[byte(light.r), byte(light.g), byte(light.b), 255]);
486 }
487 Some(MultiplyLayerFactors { size, rgba: Arc::new(rgba), scale: scale as f32 })
488 }
489
490 pub fn pixels(&self, rect: Rect, size: PixelSize) -> Option<Vec<u8>> {
496 let samples = self.samples(rect, size, &LightingMaterial::VISUAL);
497 let mut pixels = Vec::with_capacity(samples.len() * 4);
498 let mut changes = false;
499 for light in samples {
500 let pixel = light.atop_pixel();
501 changes |= pixel[3] != 0;
502 pixels.extend_from_slice(&pixel);
503 }
504 changes.then_some(pixels)
505 }
506}
507
508fn no_composition_property(_name: &str) -> Option<&'static CompositionProperty> {
511 None
512}
513
514pub struct ServerVisualUnorderedCollection {
519 base: ServerRenderResource,
520 props: ServerVisualUnorderedCollectionProps,
521}
522
523impl ServerVisualUnorderedCollection {
524 pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerVisualUnorderedCollection> {
526 let collection = Rc::new_cyclic(|this: &Weak<ServerVisualUnorderedCollection>| {
527 ServerVisualUnorderedCollection {
528 base: ServerRenderResource::new(
529 compositor,
530 this.clone() as Weak<dyn IAnimatedServerObject>,
531 this.clone() as Weak<dyn IServerRenderResource>,
532 ),
533 props: ServerVisualUnorderedCollectionProps::new(),
534 }
535 });
536 collection.base.object().activate();
537 collection
538 }
539
540 pub fn visuals(&self) -> Vec<Rc<ServerCompositionVisual>> {
542 self.props
543 .list()
544 .list()
545 .into_iter()
546 .filter_map(|visual| visual.into_any_rc().downcast::<ServerCompositionVisual>().ok())
547 .collect()
548 }
549
550 pub fn count(&self) -> usize {
552 self.props.list().count()
553 }
554
555 pub fn contains(&self, visual: &ServerCompositionVisual) -> bool {
557 let target = visual as *const ServerCompositionVisual;
558 self.props.list().list().iter().any(|item| std::ptr::addr_eq(Rc::as_ptr(item), target))
559 }
560
561 pub fn is_disposed(&self) -> bool {
562 self.base.is_disposed()
563 }
564
565 pub fn of(object: &Option<Rc<dyn IServerObject>>) -> Option<&ServerVisualUnorderedCollection> {
567 object.as_ref().and_then(|object| object.as_any().downcast_ref::<ServerVisualUnorderedCollection>())
568 }
569}
570
571impl_server_render_resource!(ServerVisualUnorderedCollection, base, composition_property: no_composition_property);
572
573impl ServerVisualUnorderedCollectionHooks for ServerVisualUnorderedCollection {}
574
575impl IServerObject for ServerVisualUnorderedCollection {
576 fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
577 self.props.deserialize_changes_core(self, reader, committed_at);
578 }
579
580 fn values_invalidated(&self) {
581 self.base.core().invalidated(self);
582 }
583
584 fn dispose(&self) {
585 self.props.list().clear();
586 self.base.core().dispose();
587 }
588
589 fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
590 Some(self)
591 }
592
593 fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
594 Some(self)
595 }
596
597 fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
598 self.props.find_props(type_id)
599 }
600
601 fn as_any(&self) -> &dyn Any {
602 self
603 }
604
605 fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
606 self
607 }
608}
609
610pub trait IServerCompositionLight {
615 fn light_props(&self) -> &ServerCompositionLightProps;
617
618 fn light_state(&self) -> &ServerLightState;
620
621 fn is_disposed(&self) -> bool;
622
623 fn coordinate_space(&self) -> Option<Rc<ServerCompositionVisual>>;
626
627 fn source(&self) -> LightSource;
629
630 fn target_of(&self, visual: &ServerCompositionVisual) -> Option<Rc<ServerCompositionVisual>> {
634 let props = self.light_props();
635 let (targets, exclusions) = (props.targets(), props.exclusions_from_targets());
636 let targets = ServerVisualUnorderedCollection::of(&targets)?;
637 if targets.count() == 0 {
638 return None;
639 }
640 let exclusions = ServerVisualUnorderedCollection::of(&exclusions);
641 let mut current = visual.weak().upgrade();
642 let mut depth = 0;
643 while let Some(candidate) = current {
644 if exclusions.is_some_and(|exclusions| exclusions.contains(&candidate)) {
645 return None;
646 }
647 if targets.contains(&candidate) {
648 return Some(candidate);
649 }
650 depth += 1;
651 if depth > MAX_LIGHT_DEPTH {
652 return None;
653 }
654 current = candidate.parent();
655 }
656 None
657 }
658}
659
660fn to_root(visual: &ServerCompositionVisual) -> Matrix {
663 let mut matrix = visual.own_transform().unwrap_or(Matrix::IDENTITY);
664 let mut parent = visual.parent();
665 let mut depth = 0;
666 while let Some(current) = parent {
667 if let Some(transform) = current.own_transform() {
668 matrix = matrix * transform;
669 }
670 depth += 1;
671 if depth > MAX_LIGHT_DEPTH {
672 break;
673 }
674 parent = current.parent();
675 }
676 matrix
677}
678
679pub fn light_map_of(visual: &ServerCompositionVisual) -> Option<LightMap> {
682 let lights: Vec<_> = visual.compositor()?.live_lights().into_iter().filter(|light| light.light_props().is_enabled()).collect();
683 if lights.is_empty() {
684 return None;
685 }
686 let mut map = LightMap::new();
687 let mut visual_to_root: Option<Matrix> = None;
688 for light in lights {
689 let Some(target) = light.target_of(visual) else { continue };
690 let space = light.coordinate_space().unwrap_or(target);
691 let to_root_of_visual = *visual_to_root.get_or_insert_with(|| to_root(visual));
692 let Some(from_root_to_space) = to_root(&space).try_invert() else { continue };
694 map.add(light.source(), to_root_of_visual * from_root_to_space);
695 }
696 (!map.is_empty()).then_some(map)
697}
698
699pub fn invalidate_lights_with_moved_spaces(compositor: &ServerCompositor) -> bool {
705 let mut invalidated = false;
706 for light in compositor.live_lights() {
707 let current = light.coordinate_space().map(|space| to_root(&space));
708 let state = light.light_state();
709 let previous = state.space_transform.replace(current);
710 if let (Some(previous), Some(current)) = (previous, current) {
711 if previous != current && light.light_props().is_enabled() {
712 state.invalidate_lit_visuals(&*light);
713 invalidated = true;
714 }
715 }
716 }
717 invalidated
718}
719
720fn invalidate_subtree(visual: &Rc<ServerCompositionVisual>) {
722 let mut stack = vec![visual.clone()];
723 while let Some(current) = stack.pop() {
724 current.invalidate_content();
725 if let Some(children) = current.children() {
726 stack.extend(children.list().iter().cloned());
727 }
728 }
729}
730
731struct LightObserver {
735 light: Weak<dyn IServerCompositionLight>,
736}
737
738impl IServerRenderResourceObserver for LightObserver {
739 fn dependency_queued_invalidate(&self, _sender: &dyn IServerRenderResource) {
740 if let Some(light) = self.light.upgrade() {
741 light.light_state().invalidate_lit_visuals(&*light);
742 }
743 }
744}
745
746#[derive(Default)]
749pub struct ServerLightState {
750 observer: RefCell<Option<Rc<dyn IServerRenderResourceObserver>>>,
751 lit_targets: RefCell<Vec<Weak<ServerCompositionVisual>>>,
752 xaml_binding: RefCell<Option<(String, Option<Weak<ServerCompositionVisual>>)>>,
756 brush_lit: RefCell<Vec<Weak<ServerCompositionVisual>>>,
759 space_transform: std::cell::Cell<Option<Matrix>>,
763}
764
765pub(crate) struct XamlLightChanges {
768 pub(crate) id: Option<String>,
769 pub(crate) scope: Option<super::ServerObjectId>,
770}
771
772impl ServerLightState {
773 fn attach(base: &ServerRenderResource, state: &ServerLightState, light: Weak<dyn IServerCompositionLight>) {
775 let observer: Rc<dyn IServerRenderResourceObserver> = Rc::new(LightObserver { light: light.clone() });
776 base.core().add_observer(&observer);
777 *state.observer.borrow_mut() = Some(observer);
778 if let Some(compositor) = base.object().compositor() {
779 compositor.register_light(light);
780 }
781 }
782
783 pub fn xaml_binding(&self) -> Option<(String, Option<Rc<ServerCompositionVisual>>)> {
786 self.xaml_binding
787 .borrow()
788 .as_ref()
789 .map(|(id, scope)| (id.clone(), scope.as_ref().and_then(Weak::upgrade)))
790 }
791
792 fn set_xaml_binding(&self, id: Option<String>, scope: Option<Rc<ServerCompositionVisual>>) {
793 *self.xaml_binding.borrow_mut() = id.map(|id| (id, scope.as_ref().map(Rc::downgrade)));
794 }
795
796 fn note_brush_lit(&self, visual: &Rc<ServerCompositionVisual>) {
799 let mut brush_lit = self.brush_lit.borrow_mut();
800 brush_lit.retain(|existing| existing.strong_count() > 0);
801 if !brush_lit.iter().any(|existing| std::ptr::eq(existing.as_ptr(), Rc::as_ptr(visual))) {
802 brush_lit.push(Rc::downgrade(visual));
803 }
804 }
805
806 pub fn lit_targets(&self) -> Vec<Rc<ServerCompositionVisual>> {
808 self.lit_targets.borrow().iter().filter_map(Weak::upgrade).collect()
809 }
810
811 fn invalidate_lit_visuals(&self, light: &dyn IServerCompositionLight) {
814 let current = if light.is_disposed() {
815 Vec::new()
816 } else {
817 let targets = light.light_props().targets();
818 ServerVisualUnorderedCollection::of(&targets).map(|targets| targets.visuals()).unwrap_or_default()
819 };
820 let previous = std::mem::take(&mut *self.lit_targets.borrow_mut());
821 for visual in previous.iter().filter_map(Weak::upgrade).chain(current.iter().cloned()) {
822 invalidate_subtree(&visual);
823 }
824 *self.lit_targets.borrow_mut() = current.iter().map(Rc::downgrade).collect();
825 let brush_lit: Vec<_> = self.brush_lit.borrow().iter().filter_map(Weak::upgrade).collect();
828 for visual in brush_lit {
829 visual.invalidate_content();
830 }
831 }
832
833 fn detach(&self) {
835 let previous = std::mem::take(&mut *self.lit_targets.borrow_mut());
836 for visual in previous.iter().filter_map(Weak::upgrade) {
837 invalidate_subtree(&visual);
838 }
839 for visual in std::mem::take(&mut *self.brush_lit.borrow_mut()).iter().filter_map(Weak::upgrade) {
840 visual.invalidate_content();
841 }
842 self.observer.borrow_mut().take();
843 }
844}
845
846fn visual_of(object: Option<Rc<dyn IServerObject>>) -> Option<Rc<ServerCompositionVisual>> {
848 object.and_then(|visual| visual.into_any_rc().downcast::<ServerCompositionVisual>().ok())
849}
850
851fn attenuation_of(constant: f32, linear: f32, quadratic: f32, min_cutoff: f32, max_cutoff: f32) -> LightAttenuation {
852 LightAttenuation {
853 constant: constant as f64,
854 linear: linear as f64,
855 quadratic: quadratic as f64,
856 min_cutoff: min_cutoff as f64,
857 max_cutoff: max_cutoff as f64,
858 }
859}
860
861macro_rules! server_light {
866 (
867 $(#[$meta:meta])* $name:ident, $props:ident, $hooks:ident,
868 coordinate_space: $coordinate_space:expr,
869 source: $source:expr $(,)?
870 ) => {
871 $(#[$meta])*
872 pub struct $name {
873 base: ServerRenderResource,
874 props: $props,
875 state: ServerLightState,
876 }
877
878 impl $name {
879 pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<$name> {
883 let light = Rc::new_cyclic(|this: &Weak<$name>| $name {
884 base: ServerRenderResource::new(
885 compositor,
886 this.clone() as Weak<dyn IAnimatedServerObject>,
887 this.clone() as Weak<dyn IServerRenderResource>,
888 ),
889 props: $props::new(),
890 state: ServerLightState::default(),
891 });
892 light.base.object().activate();
893 let weak = Rc::downgrade(&light) as Weak<dyn IServerCompositionLight>;
894 ServerLightState::attach(&light.base, &light.state, weak);
895 light
896 }
897
898 pub fn props(&self) -> &$props {
900 &self.props
901 }
902
903 pub fn is_disposed(&self) -> bool {
904 self.base.is_disposed()
905 }
906 }
907
908 impl_server_render_resource!($name, base, composition_property: $props::get_composition_property);
909
910 impl ServerCompositionLightHooks for $name {}
911
912 impl $hooks for $name {}
913
914 impl IServerCompositionLight for $name {
915 fn light_props(&self) -> &ServerCompositionLightProps {
916 self.props.base()
917 }
918
919 fn light_state(&self) -> &ServerLightState {
920 &self.state
921 }
922
923 fn is_disposed(&self) -> bool {
924 self.base.is_disposed()
925 }
926
927 fn coordinate_space(&self) -> Option<Rc<ServerCompositionVisual>> {
928 let coordinate_space: fn(&$props) -> Option<Rc<dyn IServerObject>> = $coordinate_space;
929 visual_of(coordinate_space(&self.props))
930 }
931
932 fn source(&self) -> LightSource {
933 let source: fn(&$props) -> LightSource = $source;
934 source(&self.props)
935 }
936 }
937
938 impl IServerObject for $name {
939 fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
940 self.props.deserialize_changes_core(self, reader, committed_at);
941 if let Some(changes) = reader.read_value::<XamlLightChanges>() {
942 let compositor = self.base.compositor();
943 let scope = changes.scope.and_then(|id| compositor.as_ref().and_then(|compositor| compositor.get_object(id)));
944 self.state.set_xaml_binding(changes.id, visual_of(scope));
945 self.base.core().invalidated(self);
946 }
947 }
948
949 fn values_invalidated(&self) {
950 self.base.core().invalidated(self);
951 }
952
953 fn dispose(&self) {
954 self.state.detach();
955 self.base.core().dispose();
956 }
957
958 fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
959 Some(self)
960 }
961
962 fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
963 Some(self)
964 }
965
966 fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
967 self.props.find_props(type_id)
968 }
969
970 fn as_any(&self) -> &dyn Any {
971 self
972 }
973
974 fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
975 self
976 }
977 }
978 };
979}
980
981server_light!(
982 ServerAmbientLight,
985 ServerAmbientLightProps,
986 ServerAmbientLightHooks,
987 coordinate_space: |_props| None,
988 source: |props| LightSource::Ambient { color: LightColor::from_color(props.color(), props.intensity() as f64) },
989);
990
991server_light!(
992 ServerDistantLight,
995 ServerDistantLightProps,
996 ServerDistantLightHooks,
997 coordinate_space: |props| props.coordinate_space(),
998 source: |props| LightSource::Distant {
999 color: LightColor::from_color(props.color(), props.intensity() as f64),
1000 direction: props.direction(),
1001 },
1002);
1003
1004server_light!(
1005 ServerPointLight,
1008 ServerPointLightProps,
1009 ServerPointLightHooks,
1010 coordinate_space: |props| props.coordinate_space(),
1011 source: |props| LightSource::Point {
1012 color: LightColor::from_color(props.color(), props.intensity() as f64),
1013 position: props.offset(),
1014 attenuation: attenuation_of(
1015 props.constant_attenuation(),
1016 props.linear_attenuation(),
1017 props.quadratic_attenuation(),
1018 props.min_attenuation_cutoff(),
1019 props.max_attenuation_cutoff(),
1020 ),
1021 },
1022);
1023
1024server_light!(
1025 ServerSpotLight,
1028 ServerSpotLightProps,
1029 ServerSpotLightHooks,
1030 coordinate_space: |props| props.coordinate_space(),
1031 source: |props| LightSource::Spot {
1032 inner_color: LightColor::from_color(props.inner_cone_color(), props.inner_cone_intensity() as f64),
1033 outer_color: LightColor::from_color(props.outer_cone_color(), props.outer_cone_intensity() as f64),
1034 position: props.offset(),
1035 direction: props.direction(),
1036 inner_cone_angle: props.inner_cone_angle() as f64,
1037 outer_cone_angle: props.outer_cone_angle() as f64,
1038 attenuation: attenuation_of(
1039 props.constant_attenuation(),
1040 props.linear_attenuation(),
1041 props.quadratic_attenuation(),
1042 props.min_attenuation_cutoff(),
1043 props.max_attenuation_cutoff(),
1044 ),
1045 },
1046);
1047
1048fn device_scale(matrix: &Matrix) -> f64 {
1052 let x = (matrix.m11 * matrix.m11 + matrix.m12 * matrix.m12).sqrt();
1053 let y = (matrix.m21 * matrix.m21 + matrix.m22 * matrix.m22).sqrt();
1054 x.max(y)
1055}
1056
1057pub struct LitContent {
1060 kind: LitContentKind,
1061}
1062
1063enum LitContentKind {
1065 Multiplied,
1067 Atop { bitmap: Arc<SharedBitmapImpl>, rect: Rect, size: PixelSize },
1070}
1071
1072impl LitContent {
1073 pub fn begin(visual: &ServerCompositionVisual, canvas: &mut dyn IDrawingContextImpl) -> Option<LitContent> {
1080 if visual.content_as::<ServerLayerVisual>().is_some() {
1081 return None;
1082 }
1083 let map = light_map_of(visual)?;
1084 let rect = visual.compute_own_content_bounds()?.to_rect();
1085 if rect.width <= 0.0 || rect.height <= 0.0 {
1086 return None;
1087 }
1088 let size = LightMap::size_for_scale(rect, device_scale(&canvas.transform()));
1089 if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
1090 let factors = map.factors(rect, size, &LightingMaterial::VISUAL)?;
1091 layers.push_multiply_layer(rect, &factors);
1092 return Some(LitContent { kind: LitContentKind::Multiplied });
1093 }
1094 let pixels = map.pixels(rect, size)?;
1095 let compositor = visual.compositor()?;
1096 let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
1097 render_interface.load_bitmap_from_pixels(
1098 PixelFormats::RGBA8888,
1099 AlphaFormat::Premul,
1100 &pixels,
1101 size,
1102 Vector::new(96.0, 96.0),
1103 size.width * 4,
1104 )
1105 })?;
1106 canvas.push_layer(rect);
1107 Some(LitContent { kind: LitContentKind::Atop { bitmap, rect, size } })
1108 }
1109
1110 pub fn end(self, canvas: &mut dyn IDrawingContextImpl) {
1113 match self.kind {
1114 LitContentKind::Multiplied => {
1115 if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
1116 layers.pop_multiply_layer();
1117 }
1118 }
1119 LitContentKind::Atop { bitmap, rect, size } => {
1120 canvas.push_render_options(RenderOptions {
1121 bitmap_blending_mode: BitmapBlendingMode::SourceAtop,
1122 ..RenderOptions::default()
1123 });
1124 let source = Rect::new(0.0, 0.0, size.width as f64, size.height as f64);
1125 canvas.draw_bitmap(&*bitmap, 1.0, source, rect);
1126 canvas.pop_render_options();
1127 canvas.pop_layer();
1128 }
1129 }
1130 }
1131}
1132
1133pub const SCENE_LIGHTING_DISTANCE_UNIT: f64 = 100.0;
1144
1145impl LightAttenuation {
1146 fn in_units_of(self, unit: f64) -> LightAttenuation {
1148 LightAttenuation { linear: self.linear / unit, quadratic: self.quadratic / (unit * unit), ..self }
1149 }
1150}
1151
1152impl LightSource {
1153 fn with_distance_unit(self, unit: f64) -> LightSource {
1155 match self {
1156 LightSource::Point { color, position, attenuation } => {
1157 LightSource::Point { color, position, attenuation: attenuation.in_units_of(unit) }
1158 }
1159 LightSource::Spot { inner_color, outer_color, position, direction, inner_cone_angle, outer_cone_angle, attenuation } => {
1160 LightSource::Spot {
1161 inner_color,
1162 outer_color,
1163 position,
1164 direction,
1165 inner_cone_angle,
1166 outer_cone_angle,
1167 attenuation: attenuation.in_units_of(unit),
1168 }
1169 }
1170 other => other,
1171 }
1172 }
1173}
1174
1175impl LightMap {
1176 pub fn scene_pixels(&self, rect: Rect, size: PixelSize, material: &LightingMaterial) -> Vec<u8> {
1180 let channel = |value: f64| (value * 255.0).round().clamp(0.0, 255.0) as u8;
1181 let mut pixels = Vec::with_capacity((size.width.max(1) * size.height.max(1) * 4) as usize);
1182 for light in self.samples(rect, size, material) {
1183 let light = light.clamped();
1184 pixels.extend_from_slice(&[channel(light.r), channel(light.g), channel(light.b), 255]);
1185 }
1186 pixels
1187 }
1188}
1189
1190pub fn scene_light_map(visual: &Rc<ServerCompositionVisual>, brush_light_ids: &[String]) -> LightMap {
1198 let mut map = LightMap::new();
1199 let mut visual_to_root: Option<Matrix> = None;
1200 let Some(compositor) = visual.compositor() else { return map };
1201 for light in compositor.live_lights() {
1202 let space = if let Some(target) = light.target_of(visual) {
1203 light.coordinate_space().unwrap_or(target)
1204 } else if let Some((id, Some(scope))) = light.light_state().xaml_binding() {
1205 if !brush_light_ids.iter().any(|existing| *existing == id) || !super::is_self_or_ancestor(&scope, visual) {
1206 continue;
1207 }
1208 light.light_state().note_brush_lit(visual);
1209 light.coordinate_space().unwrap_or(scope)
1210 } else {
1211 continue;
1212 };
1213 if !light.light_props().is_enabled() {
1214 continue;
1215 }
1216 let to_root_of_visual = *visual_to_root.get_or_insert_with(|| to_root(visual));
1217 let Some(from_root_to_space) = to_root(&space).try_invert() else { continue };
1218 map.add(light.source().with_distance_unit(SCENE_LIGHTING_DISTANCE_UNIT), to_root_of_visual * from_root_to_space);
1219 }
1220 map
1221}
1222
1223pub fn paint_scene_lighting(canvas: &mut dyn IDrawingContextImpl, rect: Rect, material: &LightingMaterial) {
1227 if rect.width <= 0.0 || rect.height <= 0.0 {
1228 return;
1229 }
1230 let Some(visual) = super::painting_visual() else { return };
1231 let ids = super::brush_light_ids();
1232 let map = scene_light_map(&visual, &ids);
1233 let size = LightMap::size_for_scale(rect, device_scale(&canvas.transform()));
1234 let pixels = map.scene_pixels(rect, size, material);
1235 let Some(compositor) = visual.compositor() else { return };
1236 let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
1237 render_interface.load_bitmap_from_pixels(
1238 PixelFormats::RGBA8888,
1239 AlphaFormat::Premul,
1240 &pixels,
1241 size,
1242 Vector::new(96.0, 96.0),
1243 size.width * 4,
1244 )
1245 });
1246 if let Some(bitmap) = bitmap {
1247 let source = Rect::new(0.0, 0.0, size.width as f64, size.height as f64);
1248 canvas.draw_bitmap(&*bitmap, 1.0, source, rect);
1249 }
1250}