renderdog 0.3.0

RenderDoc in-application API wrapper + automation helpers + an MCP server.
Documentation
use std::{
    ffi::{CString, c_void},
    ptr,
};

use renderdog_sys as sys;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GlResourceReference {
    pub identifier: u32,
    pub name: u32,
}

impl GlResourceReference {
    pub const fn new(identifier: u32, name: u32) -> Self {
        Self { identifier, name }
    }

    fn into_sys(self) -> sys::RENDERDOC_GLResourceReference {
        sys::RENDERDOC_GLResourceReference {
            identifier: self.identifier,
            name: self.name,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AnnotationTarget {
    Raw(*mut c_void),
    Gl(GlResourceReference),
}

impl From<*mut c_void> for AnnotationTarget {
    fn from(value: *mut c_void) -> Self {
        Self::Raw(value)
    }
}

impl From<GlResourceReference> for AnnotationTarget {
    fn from(value: GlResourceReference) -> Self {
        Self::Gl(value)
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AnnotationVector<T>
where
    T: Copy,
{
    D1([T; 1]),
    D2([T; 2]),
    D3([T; 3]),
    D4([T; 4]),
}

impl<T> AnnotationVector<T>
where
    T: Copy + Default,
{
    fn width(self) -> u32 {
        match self {
            Self::D1(_) => 1,
            Self::D2(_) => 2,
            Self::D3(_) => 3,
            Self::D4(_) => 4,
        }
    }

    fn into_array4(self) -> [T; 4] {
        match self {
            Self::D1(values) => copy_into_4(values),
            Self::D2(values) => copy_into_4(values),
            Self::D3(values) => copy_into_4(values),
            Self::D4(values) => values,
        }
    }
}

fn copy_into_4<T, const N: usize>(values: [T; N]) -> [T; 4]
where
    T: Copy + Default,
{
    let mut out = [T::default(); 4];
    out[..N].copy_from_slice(&values);
    out
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AnnotationValue<'a> {
    Empty,
    Bool(bool),
    BoolVector(AnnotationVector<bool>),
    Int32(i32),
    Int32Vector(AnnotationVector<i32>),
    UInt32(u32),
    UInt32Vector(AnnotationVector<u32>),
    Int64(i64),
    Int64Vector(AnnotationVector<i64>),
    UInt64(u64),
    UInt64Vector(AnnotationVector<u64>),
    Float32(f32),
    Float32Vector(AnnotationVector<f32>),
    Float64(f64),
    Float64Vector(AnnotationVector<f64>),
    String(&'a str),
    ApiObject(AnnotationTarget),
}

impl From<bool> for AnnotationValue<'_> {
    fn from(value: bool) -> Self {
        Self::Bool(value)
    }
}

impl From<i32> for AnnotationValue<'_> {
    fn from(value: i32) -> Self {
        Self::Int32(value)
    }
}

impl From<u32> for AnnotationValue<'_> {
    fn from(value: u32) -> Self {
        Self::UInt32(value)
    }
}

impl From<i64> for AnnotationValue<'_> {
    fn from(value: i64) -> Self {
        Self::Int64(value)
    }
}

impl From<u64> for AnnotationValue<'_> {
    fn from(value: u64) -> Self {
        Self::UInt64(value)
    }
}

impl From<f32> for AnnotationValue<'_> {
    fn from(value: f32) -> Self {
        Self::Float32(value)
    }
}

impl From<f64> for AnnotationValue<'_> {
    fn from(value: f64) -> Self {
        Self::Float64(value)
    }
}

impl<'a> From<&'a str> for AnnotationValue<'a> {
    fn from(value: &'a str) -> Self {
        Self::String(value)
    }
}

impl From<AnnotationTarget> for AnnotationValue<'_> {
    fn from(value: AnnotationTarget) -> Self {
        Self::ApiObject(value)
    }
}

pub(crate) struct PreparedAnnotationTarget {
    raw: *mut c_void,
    gl: Option<Box<sys::RENDERDOC_GLResourceReference>>,
}

impl PreparedAnnotationTarget {
    pub(crate) fn as_ptr(&mut self) -> *mut c_void {
        if let Some(gl) = self.gl.as_deref_mut() {
            (gl as *mut sys::RENDERDOC_GLResourceReference).cast()
        } else {
            self.raw
        }
    }
}

impl AnnotationTarget {
    pub(crate) fn prepare(self) -> PreparedAnnotationTarget {
        match self {
            Self::Raw(raw) => PreparedAnnotationTarget { raw, gl: None },
            Self::Gl(gl) => PreparedAnnotationTarget {
                raw: ptr::null_mut(),
                gl: Some(Box::new(gl.into_sys())),
            },
        }
    }
}

pub(crate) struct PreparedAnnotationValue {
    pub(crate) value_type: sys::RENDERDOC_AnnotationType,
    pub(crate) vector_width: u32,
    value: Option<sys::RENDERDOC_AnnotationValue>,
    _string: Option<CString>,
    _gl: Option<Box<sys::RENDERDOC_GLResourceReference>>,
}

impl PreparedAnnotationValue {
    pub(crate) fn as_ptr(&self) -> *const sys::RENDERDOC_AnnotationValue {
        self.value
            .as_ref()
            .map_or(ptr::null(), |value| value as *const _)
    }
}

impl<'a> AnnotationValue<'a> {
    pub(crate) fn prepare(self) -> Result<PreparedAnnotationValue, std::ffi::NulError> {
        match self {
            Self::Empty => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Empty,
                vector_width: 0,
                value: None,
                _string: None,
                _gl: None,
            }),
            Self::Bool(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Bool,
                vector_width: 1,
                value: Some(sys::RENDERDOC_AnnotationValue { boolean: value }),
                _string: None,
                _gl: None,
            }),
            Self::BoolVector(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Bool,
                vector_width: value.width(),
                value: Some(sys::RENDERDOC_AnnotationValue {
                    vector: sys::RENDERDOC_AnnotationVectorValue {
                        boolean: value.into_array4(),
                    },
                }),
                _string: None,
                _gl: None,
            }),
            Self::Int32(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Int32,
                vector_width: 1,
                value: Some(sys::RENDERDOC_AnnotationValue { int32: value }),
                _string: None,
                _gl: None,
            }),
            Self::Int32Vector(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Int32,
                vector_width: value.width(),
                value: Some(sys::RENDERDOC_AnnotationValue {
                    vector: sys::RENDERDOC_AnnotationVectorValue {
                        int32: value.into_array4(),
                    },
                }),
                _string: None,
                _gl: None,
            }),
            Self::UInt32(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_UInt32,
                vector_width: 1,
                value: Some(sys::RENDERDOC_AnnotationValue { uint32: value }),
                _string: None,
                _gl: None,
            }),
            Self::UInt32Vector(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_UInt32,
                vector_width: value.width(),
                value: Some(sys::RENDERDOC_AnnotationValue {
                    vector: sys::RENDERDOC_AnnotationVectorValue {
                        uint32: value.into_array4(),
                    },
                }),
                _string: None,
                _gl: None,
            }),
            Self::Int64(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Int64,
                vector_width: 1,
                value: Some(sys::RENDERDOC_AnnotationValue { int64: value }),
                _string: None,
                _gl: None,
            }),
            Self::Int64Vector(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Int64,
                vector_width: value.width(),
                value: Some(sys::RENDERDOC_AnnotationValue {
                    vector: sys::RENDERDOC_AnnotationVectorValue {
                        int64: value.into_array4(),
                    },
                }),
                _string: None,
                _gl: None,
            }),
            Self::UInt64(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_UInt64,
                vector_width: 1,
                value: Some(sys::RENDERDOC_AnnotationValue { uint64: value }),
                _string: None,
                _gl: None,
            }),
            Self::UInt64Vector(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_UInt64,
                vector_width: value.width(),
                value: Some(sys::RENDERDOC_AnnotationValue {
                    vector: sys::RENDERDOC_AnnotationVectorValue {
                        uint64: value.into_array4(),
                    },
                }),
                _string: None,
                _gl: None,
            }),
            Self::Float32(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Float,
                vector_width: 1,
                value: Some(sys::RENDERDOC_AnnotationValue { float32: value }),
                _string: None,
                _gl: None,
            }),
            Self::Float32Vector(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Float,
                vector_width: value.width(),
                value: Some(sys::RENDERDOC_AnnotationValue {
                    vector: sys::RENDERDOC_AnnotationVectorValue {
                        float32: value.into_array4(),
                    },
                }),
                _string: None,
                _gl: None,
            }),
            Self::Float64(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Double,
                vector_width: 1,
                value: Some(sys::RENDERDOC_AnnotationValue { float64: value }),
                _string: None,
                _gl: None,
            }),
            Self::Float64Vector(value) => Ok(PreparedAnnotationValue {
                value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_Double,
                vector_width: value.width(),
                value: Some(sys::RENDERDOC_AnnotationValue {
                    vector: sys::RENDERDOC_AnnotationVectorValue {
                        float64: value.into_array4(),
                    },
                }),
                _string: None,
                _gl: None,
            }),
            Self::String(value) => {
                let string = CString::new(value)?;
                let ptr = string.as_ptr();
                Ok(PreparedAnnotationValue {
                    value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_String,
                    vector_width: 1,
                    value: Some(sys::RENDERDOC_AnnotationValue { string: ptr }),
                    _string: Some(string),
                    _gl: None,
                })
            }
            Self::ApiObject(target) => match target {
                AnnotationTarget::Raw(raw) => Ok(PreparedAnnotationValue {
                    value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_APIObject,
                    vector_width: 1,
                    value: Some(sys::RENDERDOC_AnnotationValue { apiObject: raw }),
                    _string: None,
                    _gl: None,
                }),
                AnnotationTarget::Gl(gl) => {
                    let gl = Box::new(gl.into_sys());
                    let ptr = (gl.as_ref() as *const sys::RENDERDOC_GLResourceReference).cast_mut();
                    Ok(PreparedAnnotationValue {
                        value_type: sys::RENDERDOC_AnnotationType::eRENDERDOC_APIObject,
                        vector_width: 1,
                        value: Some(sys::RENDERDOC_AnnotationValue {
                            apiObject: ptr.cast(),
                        }),
                        _string: None,
                        _gl: Some(gl),
                    })
                }
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn annotation_vector_is_padded_to_four_elements() {
        let prepared = AnnotationValue::UInt32Vector(AnnotationVector::D3([1, 2, 3]))
            .prepare()
            .expect("prepare value");
        let value = prepared.value.expect("annotation value");
        let vector = unsafe { value.vector };
        assert_eq!(prepared.vector_width, 3);
        assert_eq!(unsafe { vector.uint32 }, [1, 2, 3, 0]);
    }

    #[test]
    fn gl_annotation_target_produces_pointer() {
        let mut prepared = AnnotationTarget::Gl(GlResourceReference::new(0x82E1, 7)).prepare();
        assert!(!prepared.as_ptr().is_null());
    }
}