bevy_live_wallpaper 0.5.0

A Bevy plugin to create live wallpapers.
Documentation
use bevy::{
    asset::RenderAssetUsages,
    log::{debug, error, warn},
    prelude::{Assets, Handle, Image, Res, ResMut, Resource},
    render::{
        extract_resource::ExtractResource,
        render_asset::RenderAssets,
        render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
        renderer::{RenderAdapter, RenderDevice, RenderInstance, RenderQueue},
        texture::GpuImage,
    },
};
use wgpu::{
    CommandEncoderDescriptor, CompositeAlphaMode, CurrentSurfaceTexture, Origin3d, PresentMode,
    SurfaceConfiguration, SurfaceTargetUnsafe, TextureAspect,
};

use crate::x11::surface::X11SurfaceHandles;

pub const X11_SURFACE_FORMAT: TextureFormat = TextureFormat::Bgra8UnormSrgb;

pub(crate) fn create_x11_image(images: &mut Assets<Image>) -> Handle<Image> {
    let size = Extent3d {
        width: 1,
        height: 1,
        depth_or_array_layers: 1,
    };
    let mut image = Image::new_fill(
        size,
        TextureDimension::D2,
        &[0, 0, 0, 255],
        X11_SURFACE_FORMAT,
        RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
    );
    image.texture_descriptor.usage =
        TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_SRC;
    images.add(image)
}

#[derive(Resource, ExtractResource, Clone, Debug, Default)]
pub(crate) struct X11SurfaceDescriptor {
    pub handles: Option<X11SurfaceHandles>,
    pub width: u32,
    pub height: u32,
    pub generation: u64,
}

impl X11SurfaceDescriptor {
    pub(crate) fn new() -> Self {
        Self {
            handles: None,
            width: 0,
            height: 0,
            generation: 0,
        }
    }

    pub(crate) fn bump_generation(&mut self) {
        self.generation = self.generation.wrapping_add(1);
    }
}

#[derive(Resource, ExtractResource, Clone, Debug)]
pub(crate) struct X11RenderTarget {
    pub image: Handle<Image>,
    pub last_applied_generation: u64,
}

impl X11RenderTarget {
    pub(crate) fn new(image: Handle<Image>) -> Self {
        Self {
            image,
            last_applied_generation: 0,
        }
    }
}

#[derive(Resource, Default)]
pub(crate) struct X11GpuSurfaceState {
    pub surface: Option<wgpu::Surface<'static>>,
    pub config: Option<SurfaceConfiguration>,
    pub last_applied_generation: u64,
}

impl X11GpuSurfaceState {
    pub(crate) fn mark_stale(&mut self) {
        self.surface = None;
        self.config = None;
        self.last_applied_generation = 0;
    }
}

pub(crate) fn prepare_x11_surface(
    descriptor: Res<X11SurfaceDescriptor>,
    mut state: ResMut<X11GpuSurfaceState>,
    render_instance: Res<RenderInstance>,
    render_adapter: Res<RenderAdapter>,
    render_device: Res<RenderDevice>,
) {
    if descriptor.handles.is_none() {
        if state.surface.is_some() {
            debug!("X11 surface handles dropped; tearing down wgpu surface");
        }
        state.mark_stale();
        return;
    }

    if descriptor.width == 0 || descriptor.height == 0 {
        return;
    }

    let needs_recreate =
        state.surface.is_none() || state.last_applied_generation != descriptor.generation;

    if needs_recreate {
        let handles = descriptor.handles.expect("handles exist");
        let raw_display_handle = handles.raw_display_handle();
        let raw_window_handle = handles.raw_window_handle();
        let instance = render_instance.0.as_ref();
        let surface = unsafe {
            instance
                .create_surface_unsafe(SurfaceTargetUnsafe::RawHandle {
                    raw_display_handle: Some(raw_display_handle),
                    raw_window_handle,
                })
                .expect("failed to create X11 wgpu surface")
        };
        state.surface = Some(surface);
    }

    let Some(surface) = state.surface.as_ref() else {
        return;
    };

    let width = descriptor.width.max(1);
    let height = descriptor.height.max(1);

    let needs_reconfigure = state
        .config
        .as_ref()
        .map(|config| config.width != width || config.height != height)
        .unwrap_or(true);

    if needs_reconfigure || needs_recreate {
        let capabilities = surface.get_capabilities(render_adapter.0.as_ref());
        if capabilities.formats.is_empty() {
            warn!("X11 surface reported no supported formats; retrying later");
            state.mark_stale();
            return;
        }

        let format = capabilities
            .formats
            .iter()
            .copied()
            .find(|fmt| *fmt == X11_SURFACE_FORMAT)
            .or_else(|| capabilities.formats.first().copied())
            .expect("X11 surface has no supported formats");

        let present_mode = capabilities
            .present_modes
            .iter()
            .copied()
            .find(|mode| matches!(mode, PresentMode::Mailbox | PresentMode::Immediate))
            .unwrap_or(PresentMode::Fifo);

        let alpha_mode = capabilities
            .alpha_modes
            .iter()
            .copied()
            .find(|mode| matches!(mode, CompositeAlphaMode::Opaque))
            .unwrap_or(capabilities.alpha_modes[0]);

        let mut usage = TextureUsages::RENDER_ATTACHMENT;
        if capabilities.usages.contains(TextureUsages::COPY_DST) {
            usage |= TextureUsages::COPY_DST;
        }

        let config = SurfaceConfiguration {
            usage,
            format,
            width,
            height,
            present_mode,
            alpha_mode,
            view_formats: vec![],
            desired_maximum_frame_latency: 1,
        };

        render_device.configure_surface(surface, &config);

        state.config = Some(config);
    }

    state.last_applied_generation = descriptor.generation;
}

pub(crate) fn present_x11_surface(
    mut state: ResMut<X11GpuSurfaceState>,
    target: Option<Res<X11RenderTarget>>,
    images: Res<RenderAssets<GpuImage>>,
    render_device: Res<RenderDevice>,
    render_queue: Res<RenderQueue>,
) {
    let Some(target) = target else {
        return;
    };

    let Some(surface) = state.surface.as_ref() else {
        return;
    };

    let Some(config) = state.config.as_ref() else {
        return;
    };

    let Some(gpu_image) = images.get(&target.image) else {
        return;
    };

    let extent = Extent3d {
        width: config.width.min(gpu_image.texture_descriptor.size.width),
        height: config.height.min(gpu_image.texture_descriptor.size.height),
        depth_or_array_layers: 1,
    };

    let surface_texture = match surface.get_current_texture() {
        CurrentSurfaceTexture::Success(texture) | CurrentSurfaceTexture::Suboptimal(texture) => {
            texture
        }
        CurrentSurfaceTexture::Outdated | CurrentSurfaceTexture::Lost => {
            warn!("X11 surface outdated/lost; scheduling recreate");
            state.mark_stale();
            return;
        }
        CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => {
            debug!("X11 surface acquire timeout");
            return;
        }
        CurrentSurfaceTexture::Validation => {
            error!("X11 surface validation failed");
            return;
        }
    };

    let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor {
        label: Some("x11-surface-present"),
    });

    encoder.copy_texture_to_texture(
        gpu_image.texture.as_image_copy(),
        wgpu::TexelCopyTextureInfo {
            texture: &surface_texture.texture,
            mip_level: 0,
            origin: Origin3d::ZERO,
            aspect: TextureAspect::All,
        },
        extent,
    );

    render_queue.submit(Some(encoder.finish()));
    surface_texture.present();
}