use crate::ecs::world::World;
#[cfg(feature = "egui")]
pub type UiOutput = egui::FullOutput;
#[cfg(not(feature = "egui"))]
pub type UiOutput = ();
#[cfg(feature = "egui")]
pub type UiPrimitives = Vec<egui::ClippedPrimitive>;
#[cfg(not(feature = "egui"))]
pub type UiPrimitives = ();
pub trait Render {
fn render_frame(
&mut self,
_world: &mut World,
ui_output: Option<UiOutput>,
ui_primitives: Option<UiPrimitives>,
) -> Result<(), Box<dyn std::error::Error>>;
fn resize_surface(&mut self, width: u32, height: u32)
-> Result<(), Box<dyn std::error::Error>>;
fn surface_size(&self) -> (u32, u32);
fn configure_with_state(
&mut self,
_state: &mut dyn crate::run::State,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn update_with_state(
&mut self,
_state: &mut dyn crate::run::State,
_world: &mut World,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn initialize_fonts(&mut self, _world: &mut World) {}
fn copy_fonts_to_world(&self, _world: &mut World) {}
fn render_world_to_texture(
&mut self,
_world: &mut World,
_target_texture: Option<&wgpu::Texture>,
_target_view: &wgpu::TextureView,
_width: u32,
_height: u32,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn create_secondary_surface(
&mut self,
_id: usize,
_window: std::sync::Arc<winit::window::Window>,
_width: u32,
_height: u32,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn remove_secondary_surface(&mut self, _id: usize) {}
fn resize_secondary_surface(
&mut self,
_id: usize,
_width: u32,
_height: u32,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn render_world_to_secondary_surface(
&mut self,
_id: usize,
_world: &mut World,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn render_retained_ui_to_secondary_surface(
&mut self,
_id: usize,
_world: &mut World,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn secondary_surface_size(&self, _id: usize) -> Option<(u32, u32)> {
None
}
fn initialize_secondary_egui(&mut self, _id: usize) {}
#[cfg(feature = "egui")]
fn register_secondary_egui_texture(
&mut self,
_id: usize,
_view: &wgpu::TextureView,
) -> Option<egui::TextureId> {
None
}
#[cfg(feature = "egui")]
fn register_camera_viewports_for_secondary(
&mut self,
_window_id: usize,
_cameras: &[freecs::Entity],
) -> Vec<egui::TextureId> {
vec![]
}
fn render_egui_to_secondary_surface(
&mut self,
_id: usize,
_ui_output: Option<UiOutput>,
_ui_primitives: Option<UiPrimitives>,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn device(&self) -> &wgpu::Device;
fn queue(&self) -> &wgpu::Queue;
fn surface_format(&self) -> wgpu::TextureFormat;
#[cfg(feature = "egui")]
fn register_egui_texture(&mut self, _view: &wgpu::TextureView) -> Option<egui::TextureId> {
None
}
fn register_render_texture(&mut self, _name: &str, _view: wgpu::TextureView) {}
#[cfg(not(target_arch = "wasm32"))]
fn read_texture_to_rgba(
&self,
texture: &wgpu::Texture,
width: u32,
height: u32,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let device = self.device();
let queue = self.queue();
let bytes_per_pixel = 4u32;
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
let buffer_size = (padded_bytes_per_row * height) as u64;
let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Readback Staging Buffer"),
size: buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Readback Encoder"),
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &staging_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: Some(height),
},
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
queue.submit(std::iter::once(encoder.finish()));
let buffer_slice = staging_buffer.slice(..);
let map_complete = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let map_complete_clone = map_complete.clone();
buffer_slice.map_async(wgpu::MapMode::Read, move |_| {
map_complete_clone.store(true, std::sync::atomic::Ordering::Release);
});
while !map_complete.load(std::sync::atomic::Ordering::Acquire) {
let _ = device.poll(wgpu::PollType::Poll);
}
let data = buffer_slice.get_mapped_range();
let mut pixels = Vec::with_capacity((width * height * bytes_per_pixel) as usize);
for row in 0..height {
let start = (row * padded_bytes_per_row) as usize;
let end = start + (unpadded_bytes_per_row) as usize;
pixels.extend_from_slice(&data[start..end]);
}
drop(data);
staging_buffer.unmap();
Ok(pixels)
}
#[cfg(all(not(target_arch = "wasm32"), feature = "screenshot"))]
fn save_texture_to_file(
&self,
texture: &wgpu::Texture,
width: u32,
height: u32,
path: &std::path::Path,
) {
let pixels = match self.read_texture_to_rgba(texture, width, height) {
Ok(pixels) => pixels,
Err(error) => {
tracing::error!("Failed to read texture: {}", error);
return;
}
};
let path = path.to_owned();
std::thread::spawn(
move || match image::RgbaImage::from_raw(width, height, pixels) {
Some(image) => {
if let Err(error) = image.save(&path) {
tracing::error!("Failed to save texture: {}", error);
} else {
tracing::info!("Saved texture to: {}", path.display());
}
}
None => {
tracing::error!("Failed to create image from texture data");
}
},
);
}
}