asdf-overlay 2.0.1

Asdf Overlay
//! Manage window states for rendering overlays.
//! You can access states for specific window using [`Backends::with_backend`].
//! This allows you to interact with the overlay state of a window, including its layout and rendering data.

pub mod texture;

use core::sync::atomic::{AtomicI32, AtomicU32, Ordering};

use anyhow::Context;
use asdf_overlay_event::{Event, SurfaceEvent, SurfaceInfo};
use once_cell::sync::Lazy;

use crate::{
    event_sink::OverlayEventSink, interop::DxInterop, surface::texture::OverlayTextureSlot,
    types::IntDashMap,
};

static SURFACES: Lazy<Surfaces> = Lazy::new(|| Surfaces {
    map: IntDashMap::default(),
});

/// Global store for surface states.
pub struct Surfaces {
    map: IntDashMap<u64, SurfaceState>,
}

impl Surfaces {
    /// Iterate over all surfaces.
    pub fn iter() -> impl Iterator<Item = u64> {
        SURFACES.map.iter().map(|r| *r.key())
    }

    /// Run closure with the specified surface, if it exists.
    pub fn state<R>(id: u64, f: impl FnOnce(&SurfaceState) -> R) -> Option<R> {
        SURFACES.map.get(&id).map(|r| f(&r))
    }

    pub fn contains(id: u64) -> bool {
        SURFACES.map.contains_key(&id)
    }

    pub fn reset() {
        for state in SURFACES.map.iter() {
            state.reset();
        }
    }

    #[doc(hidden)]
    pub fn with<R>(
        id: u64,
        setup_fn: impl FnOnce() -> anyhow::Result<SurfaceState>,
        f: impl FnOnce(&SurfaceState) -> R,
    ) -> anyhow::Result<R> {
        if let Some(backend) = SURFACES.map.get(&id) {
            return Ok(f(&backend));
        }

        let backend = SURFACES
            .map
            .entry(id)
            .or_try_insert_with(|| {
                let state = setup_fn().context("failed to setup surface state")?;

                let (width, height) = state.size();
                OverlayEventSink::emit(Event::Surface {
                    id,
                    event: SurfaceEvent::Added {
                        width,
                        height,
                        info: state.info,
                    },
                });

                Ok::<_, anyhow::Error>(state)
            })?
            .downgrade();

        Ok(f(backend.value()))
    }

    #[doc(hidden)]
    pub fn cleanup_state(id: u64) {
        SURFACES.map.remove(&id);

        OverlayEventSink::emit(Event::Surface {
            id,
            event: SurfaceEvent::Destroyed,
        });
    }
}

/// Data associated to a specific window for overlay rendering.
#[non_exhaustive]
pub struct SurfaceState {
    position: (AtomicI32, AtomicI32),
    size: (AtomicU32, AtomicU32),

    pub interop: DxInterop,
    pub info: SurfaceInfo,

    #[doc(hidden)]
    pub texture: OverlayTextureSlot,
}

impl SurfaceState {
    pub fn new(interop: DxInterop, size: (u32, u32), info: SurfaceInfo) -> anyhow::Result<Self> {
        let surface = OverlayTextureSlot::new();

        Ok(Self {
            position: (AtomicI32::new(0), AtomicI32::new(0)),
            size: (AtomicU32::new(size.0), AtomicU32::new(size.1)),
            interop,
            info,
            texture: surface,
        })
    }

    #[doc(hidden)]
    pub fn texture_size(&self) -> Option<(u32, u32)> {
        self.texture.get().as_ref().map(|surface| surface.size())
    }

    pub fn size(&self) -> (u32, u32) {
        (
            self.size.0.load(Ordering::Relaxed),
            self.size.1.load(Ordering::Relaxed),
        )
    }

    #[doc(hidden)]
    pub fn resize(&self, width: u32, height: u32) {
        self.size.0.store(width, Ordering::Relaxed);
        self.size.1.store(height, Ordering::Relaxed);
    }

    pub fn position(&self) -> (i32, i32) {
        (
            self.position.0.load(Ordering::Relaxed),
            self.position.1.load(Ordering::Relaxed),
        )
    }

    pub fn reposition(&self, x: i32, y: i32) {
        self.position.0.store(x, Ordering::Relaxed);
        self.position.1.store(y, Ordering::Relaxed);
    }

    pub fn commit_overlay_texture(
        &self,
        handle: Option<SharedTextureHandle>,
    ) -> anyhow::Result<()> {
        self.texture.update(&self.interop.device, handle)
    }

    /// Reset the surface state to its initial state.
    /// This will reset the position to (0, 0) and remove the overlay texture
    pub fn reset(&self) {
        self.reposition(0, 0);
        _ = self.commit_overlay_texture(None);
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SharedTextureHandle {
    /// KMT handle.
    Kmt(u32),

    /// Owned NT handle.
    Nt(u32),
}

impl SharedTextureHandle {
    pub fn as_raw(&self) -> u32 {
        match self {
            Self::Kmt(handle) | Self::Nt(handle) => *handle,
        }
    }
}