use std::sync::Arc;
use egui::{ColorImage, Context, FontDefinitions, TextureId};
use egui_wgpu::RenderState;
use crate::live::LiveResources;
use crate::renderer::GlassResources;
use crate::LiveBackdrop;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GlassError {
AlreadyInitialized,
UnsupportedSampleCount(u32),
InvalidImage {
size: [usize; 2],
pixels: usize,
},
ImageTooLarge {
size: [usize; 2],
limit: u32,
},
}
impl std::fmt::Display for GlassError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AlreadyInitialized => write!(f, "glass is already initialized on this renderer; reuse the existing GlassContext or legacy setup"),
Self::UnsupportedSampleCount(samples) => write!(f, "MSAA sample count {samples} is unsupported for this renderer's target format"),
Self::InvalidImage { size, pixels } => write!(f, "invalid backdrop: {}x{} dimensions with {pixels} pixels; use nonzero dimensions and matching pixel storage", size[0], size[1]),
Self::ImageTooLarge { size, limit } => write!(f, "backdrop {}x{} exceeds the device's {limit}-pixel texture limit; resize it before uploading", size[0], size[1]),
}
}
}
impl std::error::Error for GlassError {}
#[derive(Clone)]
pub struct GlassContext {
ctx: Context,
render_state: RenderState,
live: LiveBackdrop,
}
impl GlassContext {
pub fn new(ctx: &Context, render_state: &RenderState, msaa_samples: u32) -> Result<Self, GlassError> {
let mut renderer = render_state.renderer.write();
if renderer.callback_resources.get::<GlassResources>().is_some() || renderer.callback_resources.get::<LiveResources>().is_some() {
return Err(GlassError::AlreadyInitialized);
}
let samples = msaa_samples.max(1);
let features = render_state.device.features();
let format_features = if features.contains(wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES) {
render_state.adapter.get_texture_format_features(render_state.target_format)
} else {
render_state.target_format.guaranteed_format_features(features)
};
if !format_features.flags.sample_count_supported(samples) {
return Err(GlassError::UnsupportedSampleCount(msaa_samples));
}
renderer.callback_resources.insert(GlassResources::new(render_state, samples));
let live = LiveBackdrop::new_in(render_state, &mut renderer, None);
Ok(Self { ctx: ctx.clone(), render_state: render_state.clone(), live })
}
pub fn set_fonts(&self, fonts: FontDefinitions) {
self.ctx.set_fonts(fonts.clone());
self.live.set_fonts(fonts);
}
pub fn set_backdrop(&self, image: &ColorImage) -> Result<TextureId, GlassError> {
validate_image(image, self.render_state.device.limits().max_texture_dimension_2d)?;
Ok(crate::set_backdrop(&self.ctx, &self.render_state, image))
}
pub fn live_backdrop(&self) -> &LiveBackdrop {
&self.live
}
pub fn register_native_texture(&self, view: &wgpu::TextureView) -> GlassTexture {
let id = crate::register_native_texture(&self.render_state, view);
GlassTexture(Arc::new(TextureRegistration { id, render_state: self.render_state.clone() }))
}
}
#[derive(Clone)]
#[must_use = "keep the texture handle alive while its ID is being rendered"]
pub struct GlassTexture(Arc<TextureRegistration>);
impl GlassTexture {
pub fn id(&self) -> TextureId {
self.0.id
}
}
struct TextureRegistration {
id: TextureId,
render_state: RenderState,
}
impl Drop for TextureRegistration {
fn drop(&mut self) {
crate::free_native_texture_id(&self.render_state, &self.id);
}
}
fn validate_image(image: &ColorImage, limit: u32) -> Result<(), GlassError> {
if image.size.contains(&0) || image.size[0].checked_mul(image.size[1]) != Some(image.pixels.len()) {
return Err(GlassError::InvalidImage { size: image.size, pixels: image.pixels.len() });
}
if image.size.iter().any(|&side| side > limit as usize) {
return Err(GlassError::ImageTooLarge { size: image.size, limit });
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn image_validation_rejects_empty_mismatched_overflowing_and_oversized_images() {
for size in [[0, 1], [2, 2], [usize::MAX, 2]] {
let mut image = egui::ColorImage::filled([1, 1], egui::Color32::WHITE);
image.size = size;
assert!(matches!(validate_image(&image, 8192), Err(GlassError::InvalidImage { .. })));
}
let image = egui::ColorImage::filled([16, 8], egui::Color32::WHITE);
assert!(matches!(validate_image(&image, 8), Err(GlassError::ImageTooLarge { .. })));
assert!(validate_image(&image, 16).is_ok());
}
}