use std::hash::{Hash, Hasher};
use cranpose_render_common::geometry::blur_reach;
use cranpose_ui_graphics::{FxHasher, Point, Rect, RenderHash};
use crate::{
draw_pass::{ResolvedComposite, ResolvedCompositeKind, SourceContent},
effect_renderer::{CompositeSampleMode, RoundedCompositeMask},
render::{
hash_f32_for_cache, hash_run_item, hash_shadow_device_offset, hash_shadow_device_rect,
shadow_content_hash, shadow_draw_bounds,
},
scene::{CompositorScene, DrawOp, DrawOpKind, ImageDraw, ShadowDraw, SnapAnchor, TextDraw},
};
#[derive(Clone, Copy)]
pub(crate) struct CaptureWindow {
pub(crate) x: f32,
pub(crate) y: f32,
pub(crate) width: f32,
pub(crate) height: f32,
}
type DeviceTuple = (f32, f32, f32, f32);
impl CaptureWindow {
fn origin(self, scale: f32) -> Point {
Point::new(self.x / scale, self.y / scale)
}
fn touches_logical(self, rect: Rect, margin: f32, scale: f32) -> bool {
self.touches_device((
rect.x * scale - margin,
rect.y * scale - margin,
rect.width * scale + 2.0 * margin,
rect.height * scale + 2.0 * margin,
))
}
fn touches_device(self, (x, y, width, height): DeviceTuple) -> bool {
x < self.x + self.width
&& x + width > self.x
&& y < self.y + self.height
&& y + height > self.y
}
}
const OP_MARGIN: f32 = 1.0;
pub(crate) fn capture_hasher() -> FxHasher {
FxHasher::default()
}
pub(crate) fn hash_capture_ops<H: Hasher>(
scene: &CompositorScene,
ops: &[DrawOp],
window: CaptureWindow,
scale: f32,
state: &mut H,
) {
let origin = window.origin(scale);
for op in ops {
match op.kind {
DrawOpKind::Run(index) => {
let run = &scene.runs[index];
if window.touches_logical(run.bounds, OP_MARGIN, scale) {
0u8.hash(state);
hash_run_item(run, origin.x, origin.y, scale, state);
}
}
DrawOpKind::Image(index) => {
let image = &scene.images[index];
if window.touches_logical(image.rect, OP_MARGIN, scale) {
1u8.hash(state);
hash_image(image, origin, scale, state);
}
}
DrawOpKind::Text(index) => {
let text = &scene.texts[index];
if window.touches_logical(text.rect, OP_MARGIN, scale) {
2u8.hash(state);
hash_text(text, origin, scale, state);
}
}
DrawOpKind::Shadow(index) => {
let shadow = &scene.shadow_draws[index];
let margin = blur_reach(shadow.blur_radius, scale) * scale + OP_MARGIN;
if shadow_draw_bounds(shadow)
.is_some_and(|bounds| window.touches_logical(bounds, margin, scale))
{
3u8.hash(state);
hash_shadow(shadow, origin, scale, state);
}
}
}
}
}
fn hash_anchor<H: Hasher>(anchor: Option<SnapAnchor>, origin: Point, scale: f32, state: &mut H) {
match anchor {
Some(anchor) => {
1u8.hash(state);
hash_shadow_device_offset(anchor.origin.x, origin.x, scale, state);
hash_shadow_device_offset(anchor.origin.y, origin.y, scale, state);
hash_f32_for_cache(anchor.device_pixel_step, state);
}
None => 0u8.hash(state),
}
}
fn hash_optional_rect<H: Hasher>(rect: Option<Rect>, origin: Point, scale: f32, state: &mut H) {
match rect {
Some(rect) => {
1u8.hash(state);
hash_shadow_device_rect(rect, origin.x, origin.y, scale, state);
}
None => 0u8.hash(state),
}
}
fn hash_optional_render_hash<H: Hasher, T: RenderHash>(value: Option<&T>, state: &mut H) {
match value {
Some(value) => {
1u8.hash(state);
value.render_hash().hash(state);
}
None => 0u8.hash(state),
}
}
fn hash_text<H: Hasher>(text: &TextDraw, origin: Point, scale: f32, state: &mut H) {
hash_shadow_device_rect(text.rect, origin.x, origin.y, scale, state);
hash_anchor(text.snap_anchor, origin, scale, state);
text.text.render_hash().hash(state);
text.color.render_hash().hash(state);
text.text_style.render_hash().hash(state);
hash_f32_for_cache(text.font_size, state);
hash_f32_for_cache(text.scale, state);
text.layout_options.hash(state);
hash_optional_rect(text.clip, origin, scale, state);
}
fn hash_image<H: Hasher>(image: &ImageDraw, origin: Point, scale: f32, state: &mut H) {
hash_shadow_device_rect(image.rect, origin.x, origin.y, scale, state);
hash_shadow_device_rect(image.local_rect, origin.x, origin.y, scale, state);
for point in image.quad {
hash_shadow_device_offset(point[0], origin.x, scale, state);
hash_shadow_device_offset(point[1], origin.y, scale, state);
}
hash_anchor(image.snap_anchor, origin, scale, state);
image.image.render_hash().hash(state);
hash_f32_for_cache(image.alpha, state);
hash_optional_render_hash(image.color_filter.as_ref(), state);
image.sampling.hash(state);
hash_optional_rect(image.clip, origin, scale, state);
hash_optional_render_hash(image.src_rect.as_ref(), state);
image.blend_mode.hash(state);
image.motion_context_animated.hash(state);
}
fn hash_shadow<H: Hasher>(shadow: &ShadowDraw, origin: Point, scale: f32, state: &mut H) {
shadow_content_hash(shadow, scale).hash(state);
hash_optional_rect(shadow_draw_bounds(shadow), origin, scale, state);
if let Some(run) = &shadow.shapes {
hash_shadow_device_offset(run.placement.offset.x, origin.x, scale, state);
hash_shadow_device_offset(run.placement.offset.y, origin.y, scale, state);
hash_anchor(run.placement.snap_anchor, origin, scale, state);
}
for text in &shadow.texts {
hash_text(text, origin, scale, state);
}
hash_f32_for_cache(shadow.blur_radius, state);
hash_optional_rect(shadow.clip, origin, scale, state);
hash_optional_rect(shadow.occluder, origin, scale, state);
match shadow.rounded_clip {
Some(clip) => {
1u8.hash(state);
hash_shadow_device_rect(clip.rect, origin.x, origin.y, scale, state);
hash_radii(clip.radii, state);
}
None => 0u8.hash(state),
}
}
fn hash_radii<H: Hasher>(radii: [f32; 4], state: &mut H) {
for radius in radii {
hash_f32_for_cache(radius, state);
}
}
fn hash_device_tuple<H: Hasher>(
(x, y, width, height): DeviceTuple,
window: CaptureWindow,
state: &mut H,
) {
hash_f32_for_cache(x - window.x, state);
hash_f32_for_cache(y - window.y, state);
hash_f32_for_cache(width, state);
hash_f32_for_cache(height, state);
}
fn hash_optional_tuple<H: Hasher>(
tuple: Option<DeviceTuple>,
window: CaptureWindow,
state: &mut H,
) {
match tuple {
Some(tuple) => {
1u8.hash(state);
hash_device_tuple(tuple, window, state);
}
None => 0u8.hash(state),
}
}
fn hash_mask<H: Hasher>(mask: Option<RoundedCompositeMask>, window: CaptureWindow, state: &mut H) {
match mask {
Some(mask) => {
1u8.hash(state);
hash_device_tuple(
(mask.rect[0], mask.rect[1], mask.rect[2], mask.rect[3]),
window,
state,
);
hash_radii(mask.radii, state);
}
None => 0u8.hash(state),
}
}
const SOURCE_SPACE: CaptureWindow = CaptureWindow {
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
};
pub(crate) fn hash_capture_composites<H: Hasher>(
composites: &[ResolvedComposite],
window: CaptureWindow,
state: &mut H,
) -> bool {
for composite in composites {
if !window.touches_device(composite.dest) {
continue;
}
let SourceContent::Retained(content) = composite.content else {
return false;
};
content.hash(state);
hash_device_tuple(composite.dest, window, state);
hash_optional_tuple(composite.scissor, window, state);
hash_composite_kind(&composite.kind, window, state);
}
true
}
fn hash_composite_kind<H: Hasher>(
kind: &ResolvedCompositeKind,
window: CaptureWindow,
state: &mut H,
) {
match kind {
ResolvedCompositeKind::Blit {
alpha,
blend_mode,
rounded_mask,
sample_mode,
source_viewport,
} => {
0u8.hash(state);
hash_f32_for_cache(*alpha, state);
blend_mode.hash(state);
hash_mask(*rounded_mask, window, state);
(*sample_mode == CompositeSampleMode::Nearest).hash(state);
hash_optional_tuple(*source_viewport, SOURCE_SPACE, state);
}
ResolvedCompositeKind::Shader {
shader,
layer_pixel_rect,
source_region,
source_logical_size,
substrate_regions,
rounded_mask,
alpha,
} => {
1u8.hash(state);
shader.render_hash().hash(state);
hash_radii(*layer_pixel_rect, state);
hash_optional_tuple(*source_region, SOURCE_SPACE, state);
for region in substrate_regions {
hash_optional_tuple(*region, SOURCE_SPACE, state);
}
source_logical_size.is_some().hash(state);
if let Some((width, height)) = source_logical_size {
hash_f32_for_cache(*width, state);
hash_f32_for_cache(*height, state);
}
hash_mask(*rounded_mask, window, state);
hash_f32_for_cache(*alpha, state);
}
ResolvedCompositeKind::Projective {
dest_quad,
alpha,
blend_mode,
..
} => {
2u8.hash(state);
for point in dest_quad {
hash_f32_for_cache(point[0] - window.x, state);
hash_f32_for_cache(point[1] - window.y, state);
}
hash_f32_for_cache(*alpha, state);
blend_mode.hash(state);
}
}
}