servo-webxr-api 0.1.0

A safe Rust API that provides a way to interact with virtual reality and augmented reality devices and integration with OpenXR. The API is inspired by the WebXR Device API (https://www.w3.org/TR/webxr/) but adapted to Rust design patterns.
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::fmt::Debug;
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicUsize, Ordering};

use euclid::{Rect, Size2D};
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};

use crate::{Error, Viewport, Viewports};

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
pub struct ContextId(pub u64);

pub trait GLTypes {
    type Device;
    type Context;
    type Bindings;
}

pub trait GLContexts<GL: GLTypes> {
    fn device(&self, context_id: ContextId) -> Option<GL::Device>;
    fn bindings(&mut self, context_id: ContextId) -> Option<&GL::Bindings>;
    fn context(&mut self, context_id: ContextId) -> Option<&mut GL::Context>;
}

impl GLTypes for () {
    type Bindings = ();
    type Device = ();
    type Context = ();
}

impl GLContexts<()> for () {
    fn device(&self, _: ContextId) -> Option<()> {
        Some(())
    }

    fn context(&mut self, _: ContextId) -> Option<&mut ()> {
        Some(self)
    }

    fn bindings(&mut self, _: ContextId) -> Option<&()> {
        Some(self)
    }
}

pub trait LayerGrandManagerAPI<GL: GLTypes> {
    fn create_layer_manager(&self, factory: LayerManagerFactory<GL>)
    -> Result<LayerManager, Error>;

    fn clone_layer_grand_manager(&self) -> LayerGrandManager<GL>;
}

pub struct LayerGrandManager<GL>(Box<dyn Send + LayerGrandManagerAPI<GL>>);

impl<GL: GLTypes> Clone for LayerGrandManager<GL> {
    fn clone(&self) -> Self {
        self.0.clone_layer_grand_manager()
    }
}

impl<GL> Debug for LayerGrandManager<GL> {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        "LayerGrandManager(...)".fmt(fmt)
    }
}

impl<GL: GLTypes> LayerGrandManager<GL> {
    pub fn new<GM>(grand_manager: GM) -> LayerGrandManager<GL>
    where
        GM: 'static + Send + LayerGrandManagerAPI<GL>,
    {
        LayerGrandManager(Box::new(grand_manager))
    }

    pub fn create_layer_manager<F, M>(&self, factory: F) -> Result<LayerManager, Error>
    where
        F: 'static + Send + FnOnce(&mut dyn GLContexts<GL>) -> Result<M, Error>,
        M: 'static + LayerManagerAPI<GL>,
    {
        self.0
            .create_layer_manager(LayerManagerFactory::new(factory))
    }
}

pub trait LayerManagerAPI<GL: GLTypes> {
    fn create_layer(
        &mut self,
        contexts: &mut dyn GLContexts<GL>,
        context_id: ContextId,
        init: LayerInit,
    ) -> Result<LayerId, Error>;

    fn destroy_layer(
        &mut self,
        contexts: &mut dyn GLContexts<GL>,
        context_id: ContextId,
        layer_id: LayerId,
    );

    fn layers(&self) -> &[(ContextId, LayerId)];

    fn begin_frame(
        &mut self,
        contexts: &mut dyn GLContexts<GL>,
        layers: &[(ContextId, LayerId)],
    ) -> Result<Vec<SubImages>, Error>;

    fn end_frame(
        &mut self,
        contexts: &mut dyn GLContexts<GL>,
        layers: &[(ContextId, LayerId)],
    ) -> Result<(), Error>;
}

pub struct LayerManager(Box<dyn Send + LayerManagerAPI<()>>);

impl Debug for LayerManager {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        "LayerManager(...)".fmt(fmt)
    }
}

impl LayerManager {
    pub fn create_layer(
        &mut self,
        context_id: ContextId,
        init: LayerInit,
    ) -> Result<LayerId, Error> {
        self.0.create_layer(&mut (), context_id, init)
    }

    pub fn destroy_layer(&mut self, context_id: ContextId, layer_id: LayerId) {
        self.0.destroy_layer(&mut (), context_id, layer_id);
    }

    pub fn begin_frame(
        &mut self,
        layers: &[(ContextId, LayerId)],
    ) -> Result<Vec<SubImages>, Error> {
        self.0.begin_frame(&mut (), layers)
    }

    pub fn end_frame(&mut self, layers: &[(ContextId, LayerId)]) -> Result<(), Error> {
        self.0.end_frame(&mut (), layers)
    }
}

impl LayerManager {
    pub fn new<M>(manager: M) -> LayerManager
    where
        M: 'static + Send + LayerManagerAPI<()>,
    {
        LayerManager(Box::new(manager))
    }
}

impl Drop for LayerManager {
    fn drop(&mut self) {
        log::debug!("Dropping LayerManager");
        let layers: Vec<_> = self.0.layers().to_vec();
        for (context_id, layer_id) in layers.into_iter() {
            self.destroy_layer(context_id, layer_id);
        }
    }
}

#[expect(clippy::type_complexity)]
pub struct LayerManagerFactory<GL: GLTypes>(
    Box<dyn Send + FnOnce(&mut dyn GLContexts<GL>) -> Result<Box<dyn LayerManagerAPI<GL>>, Error>>,
);

impl<GL: GLTypes> Debug for LayerManagerFactory<GL> {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        "LayerManagerFactory(...)".fmt(fmt)
    }
}

impl<GL: GLTypes> LayerManagerFactory<GL> {
    pub fn new<F, M>(factory: F) -> LayerManagerFactory<GL>
    where
        F: 'static + Send + FnOnce(&mut dyn GLContexts<GL>) -> Result<M, Error>,
        M: 'static + LayerManagerAPI<GL>,
    {
        LayerManagerFactory(Box::new(move |contexts| Ok(Box::new(factory(contexts)?))))
    }

    pub fn build(
        self,
        contexts: &mut dyn GLContexts<GL>,
    ) -> Result<Box<dyn LayerManagerAPI<GL>>, Error> {
        (self.0)(contexts)
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Deserialize, Serialize, MallocSizeOf)]
pub struct LayerId(usize);

static NEXT_LAYER_ID: AtomicUsize = AtomicUsize::new(0);

impl Default for LayerId {
    fn default() -> Self {
        LayerId(NEXT_LAYER_ID.fetch_add(1, Ordering::SeqCst))
    }
}

#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum LayerInit {
    /// <https://www.w3.org/TR/webxr/#dictdef-xrwebgllayerinit>
    WebGLLayer {
        antialias: bool,
        depth: bool,
        stencil: bool,
        alpha: bool,
        ignore_depth_values: bool,
        framebuffer_scale_factor: f32,
    },
    /// <https://immersive-web.github.io/layers/#xrprojectionlayerinittype>
    ProjectionLayer {
        depth: bool,
        stencil: bool,
        alpha: bool,
        scale_factor: f32,
    },
    // TODO: other layer types
}

impl LayerInit {
    pub fn texture_size(&self, viewports: &Viewports) -> Size2D<i32, Viewport> {
        match self {
            LayerInit::WebGLLayer {
                framebuffer_scale_factor: scale,
                ..
            } |
            LayerInit::ProjectionLayer {
                scale_factor: scale,
                ..
            } => {
                let native_size = viewports
                    .viewports
                    .iter()
                    .fold(Rect::zero(), |acc, view| acc.union(view))
                    .size;
                (native_size.to_f32() * *scale).to_i32()
            },
        }
    }
}

/// <https://immersive-web.github.io/layers/#enumdef-xrlayerlayout>
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum LayerLayout {
    // TODO: Default
    // Allocates one texture
    Mono,
    // Allocates one texture, which is split in half vertically, giving two subimages
    StereoLeftRight,
    // Allocates one texture, which is split in half horizonally, giving two subimages
    StereoTopBottom,
}

#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
pub struct SubImages {
    pub layer_id: LayerId,
    pub sub_image: Option<SubImage>,
    pub view_sub_images: Vec<SubImage>,
}

/// <https://immersive-web.github.io/layers/#xrsubimagetype>
#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
pub struct SubImage {
    pub color_texture: Option<NonZeroU32>,
    pub depth_stencil_texture: Option<NonZeroU32>,
    pub texture_array_index: Option<u32>,
    pub viewport: Rect<i32, Viewport>,
}