use std::collections::HashMap;
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::wayland::surface::WaylandSurfaceHandles;
pub(crate) const WAYLAND_SURFACE_FORMAT: TextureFormat = TextureFormat::Bgra8UnormSrgb;
pub(crate) fn create_wayland_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],
WAYLAND_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 WaylandSurfaceDescriptor {
pub surfaces: Vec<SurfaceDescriptorEntry>,
pub generation: u64,
}
impl WaylandSurfaceDescriptor {
pub(crate) fn new() -> Self {
Self {
surfaces: Vec::new(),
generation: 0,
}
}
pub(crate) fn upsert_surface(&mut self, config: crate::wayland::WaylandSurfaceConfig) {
if let Some(entry) = self
.surfaces
.iter_mut()
.find(|entry| entry.output == config.output)
{
entry.handles = Some(config.handles);
entry.width = config.width;
entry.height = config.height;
entry.offset_x = config.offset_x;
entry.offset_y = config.offset_y;
} else {
self.surfaces.push(SurfaceDescriptorEntry {
output: config.output,
handles: Some(config.handles),
width: config.width,
height: config.height,
offset_x: config.offset_x,
offset_y: config.offset_y,
});
}
}
pub(crate) fn overall_bounds(&self) -> Option<(i32, i32, u32, u32)> {
let mut iter_all = self.surfaces.iter().filter(|s| s.handles.is_some());
let first = iter_all.next()?;
let mut min_x = first.offset_x;
let mut min_y = first.offset_y;
let mut max_x = first.offset_x + first.width as i32;
let mut max_y = first.offset_y + first.height as i32;
for s in iter_all {
min_x = min_x.min(s.offset_x);
min_y = min_y.min(s.offset_y);
max_x = max_x.max(s.offset_x + s.width as i32);
max_y = max_y.max(s.offset_y + s.height as i32);
}
let width = (max_x - min_x).max(1) as u32;
let height = (max_y - min_y).max(1) as u32;
Some((min_x, min_y, width, height))
}
pub(crate) fn bump_generation(&mut self) {
self.generation = self.generation.wrapping_add(1);
}
}
#[derive(Clone, Debug)]
pub(crate) struct SurfaceDescriptorEntry {
pub output: u32,
pub handles: Option<WaylandSurfaceHandles>,
pub width: u32,
pub height: u32,
pub offset_x: i32,
pub offset_y: i32,
}
#[derive(Resource, ExtractResource, Clone, Debug)]
pub(crate) struct WaylandRenderTarget {
pub image: Handle<Image>,
pub last_applied_generation: u64,
}
impl WaylandRenderTarget {
pub(crate) fn new(image: Handle<Image>) -> Self {
Self {
image,
last_applied_generation: 0,
}
}
}
#[derive(Resource, Default)]
pub(crate) struct WaylandGpuSurfaceState {
pub surfaces: HashMap<u32, WaylandGpuPerSurface>,
}
#[derive(Default)]
pub(crate) struct WaylandGpuPerSurface {
pub surface: Option<wgpu::Surface<'static>>,
pub config: Option<SurfaceConfiguration>,
pub last_applied_generation: u64,
}
pub(crate) fn prepare_wayland_surface(
descriptor: Res<WaylandSurfaceDescriptor>,
mut state: ResMut<WaylandGpuSurfaceState>,
render_instance: Res<RenderInstance>,
render_adapter: Res<RenderAdapter>,
render_device: Res<RenderDevice>,
) {
let valid_outputs: Vec<u32> = descriptor.surfaces.iter().map(|s| s.output).collect();
state
.surfaces
.retain(|output, _| valid_outputs.contains(output));
for surf_desc in descriptor.surfaces.iter().filter(|s| s.handles.is_some()) {
let entry = state.surfaces.entry(surf_desc.output).or_default();
let needs_recreate =
entry.surface.is_none() || entry.last_applied_generation != descriptor.generation;
if needs_recreate {
let handles = surf_desc.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 Wayland wgpu surface")
};
entry.surface = Some(surface);
}
let Some(surface) = entry.surface.as_ref() else {
continue;
};
let width = surf_desc.width.max(1);
let height = surf_desc.height.max(1);
let needs_reconfigure = entry
.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!("Wayland surface reported no supported formats; retrying later");
entry.surface = None;
entry.config = None;
entry.last_applied_generation = 0;
continue;
}
let format = capabilities
.formats
.iter()
.copied()
.find(|fmt| *fmt == WAYLAND_SURFACE_FORMAT)
.or_else(|| capabilities.formats.first().copied())
.expect("Wayland surface has no supported formats");
let present_mode = capabilities
.present_modes
.iter()
.copied()
.find(|mode| matches!(mode, PresentMode::Fifo))
.or_else(|| capabilities.present_modes.first().copied())
.expect("Wayland surface has no supported present mode");
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);
entry.config = Some(config);
}
entry.last_applied_generation = descriptor.generation;
}
}
pub(crate) fn present_wayland_surface(
mut state: ResMut<WaylandGpuSurfaceState>,
target: Option<Res<WaylandRenderTarget>>,
images: Res<RenderAssets<GpuImage>>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
descriptor: Res<WaylandSurfaceDescriptor>,
) {
let Some(target) = target else { return };
let Some(gpu_image) = images.get(&target.image) else {
return;
};
let Some((min_x, min_y, _, _)) = descriptor.overall_bounds() else {
return;
};
for (output, entry) in state.surfaces.iter_mut() {
let Some(surface) = entry.surface.as_ref() else {
continue;
};
let Some(config) = entry.config.as_ref() else {
continue;
};
let Some(desc_entry) = descriptor
.surfaces
.iter()
.find(|s| s.output == *output && s.handles.is_some())
else {
continue;
};
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 => {
debug!(
"Wayland surface for output {} outdated; scheduling reconfigure",
output
);
entry.config = None;
entry.last_applied_generation = 0;
continue;
}
CurrentSurfaceTexture::Lost => {
warn!(
"Wayland surface for output {} lost; scheduling recreate",
output
);
entry.surface = None;
entry.config = None;
entry.last_applied_generation = 0;
continue;
}
CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => {
debug!("Wayland surface acquire timeout (output {})", output);
continue;
}
CurrentSurfaceTexture::Validation => {
error!("Wayland surface validation failed (output {})", output);
continue;
}
};
let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("wayland-surface-present"),
});
let src_origin = Origin3d {
x: (desc_entry.offset_x - min_x).max(0) as u32,
y: (desc_entry.offset_y - min_y).max(0) as u32,
z: 0,
};
let mut src = gpu_image.texture.as_image_copy();
src.origin = src_origin;
let dst = wgpu::TexelCopyTextureInfo {
texture: &surface_texture.texture,
mip_level: 0,
origin: Origin3d::ZERO,
aspect: TextureAspect::All,
};
encoder.copy_texture_to_texture(src, dst, extent);
render_queue.submit(Some(encoder.finish()));
surface_texture.present();
}
}