Profile and preinitialize viewport rendering
Some checks failed
CI / rust-skia (Rust only) (push) Has been cancelled
CI / required (push) Has been cancelled

This commit is contained in:
2026-08-23 12:16:52 +02:00
parent b59bc4c3cd
commit cb9233c59c
8 changed files with 444 additions and 84 deletions

View File

@@ -30,7 +30,7 @@ use std::{
marker::PhantomData,
sync::{Mutex, mpsc},
thread,
time::Duration,
time::{Duration, Instant},
};
const WORLD_AMBIENT_BRIGHTNESS: f32 = 400.0;
@@ -43,11 +43,30 @@ enum RenderCommand {
textures: Vec<Texture>,
background_srgb: [u8; 4],
limits: RenderLimits,
reply: mpsc::SyncSender<Result<Vec<u8>, RenderError>>,
reply: mpsc::SyncSender<Result<RenderedFrame, RenderError>>,
},
Stop,
}
/// Wall-clock costs of one renderer call. GPU work and readback are included in
/// `capture`; the fields before it are CPU-side preparation.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct RenderTimings {
pub validation: Duration,
pub command_copy: Duration,
pub clear_previous_frame: Duration,
pub texture_cache_sync: Duration,
pub mesh_cache_sync: Duration,
pub scene_setup: Duration,
pub capture: Duration,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RenderedFrame {
pub rgba: Vec<u8>,
pub timings: RenderTimings,
}
/// Reusable renderer facade. Bevy and its ECS stay on a dedicated OS thread.
pub struct Renderer {
commands: mpsc::SyncSender<RenderCommand>,
@@ -110,26 +129,46 @@ impl Renderer {
background_srgb: [u8; 4],
limits: RenderLimits,
) -> Result<Vec<u8>, RenderError> {
self.render_profiled(camera, renderables, textures, background_srgb, limits)
.map(|frame| frame.rgba)
}
pub fn render_profiled(
&self,
camera: Camera,
renderables: &[Renderable],
textures: &[Texture],
background_srgb: [u8; 4],
limits: RenderLimits,
) -> Result<RenderedFrame, RenderError> {
let started = Instant::now();
validate_scene(camera, renderables, textures, limits)?;
let validation = started.elapsed();
let started = Instant::now();
let (sender, receiver) = mpsc::sync_channel(1);
let command = RenderCommand::Frame {
camera,
renderables: renderables.to_vec(),
textures: textures.to_vec(),
background_srgb,
limits,
reply: sender,
};
let command_copy = started.elapsed();
self.commands
.try_send(RenderCommand::Frame {
camera,
renderables: renderables.to_vec(),
textures: textures.to_vec(),
background_srgb,
limits,
reply: sender,
})
.try_send(command)
.map_err(|error| match error {
mpsc::TrySendError::Full(_) => RenderError::TimedOut,
mpsc::TrySendError::Disconnected(_) => {
RenderError::Device("renderer thread stopped".into())
}
})?;
receiver
let mut frame = receiver
.recv_timeout(Duration::from_mins(2))
.map_err(|_| RenderError::TimedOut)?
.map_err(|_| RenderError::TimedOut)??;
frame.timings.validation = validation;
frame.timings.command_copy = command_copy;
Ok(frame)
}
}
@@ -360,8 +399,10 @@ impl BevyBackend {
textures: &[Texture],
background_srgb: [u8; 4],
limits: RenderLimits,
) -> Result<Vec<u8>, RenderError> {
) -> Result<RenderedFrame, RenderError> {
let started = Instant::now();
self.clear_frame();
let clear_previous_frame = started.elapsed();
self.apps
.main
.world_mut()
@@ -371,16 +412,21 @@ impl BevyBackend {
background_srgb[2],
background_srgb[3],
)));
let started = Instant::now();
self.texture_handles = add_textures(
self.apps.main.world_mut(),
textures,
&mut self.texture_cache,
);
let texture_cache_sync = started.elapsed();
let started = Instant::now();
let mesh_handles = add_meshes(
self.apps.main.world_mut(),
renderables,
&mut self.mesh_cache,
)?;
let mesh_cache_sync = started.elapsed();
let started = Instant::now();
for (renderable, mesh) in renderables.iter().zip(mesh_handles) {
let entity = match &renderable.material {
Material::BlinnPhong(material) => {
@@ -463,7 +509,20 @@ impl BevyBackend {
))
.id();
self.frame_entities.push(light);
self.capture(&target)
let scene_setup = started.elapsed();
let started = Instant::now();
let rgba = self.capture(&target)?;
Ok(RenderedFrame {
rgba,
timings: RenderTimings {
clear_previous_frame,
texture_cache_sync,
mesh_cache_sync,
scene_setup,
capture: started.elapsed(),
..Default::default()
},
})
}
fn new_target(&mut self, width: u32, height: u32) -> RenderTarget {

View File

@@ -260,7 +260,7 @@ fn cross(left: [f32; 3], right: [f32; 3]) -> [f32; 3] {
#[cfg(feature = "wgpu")]
mod gpu;
#[cfg(feature = "wgpu")]
pub use gpu::Renderer;
pub use gpu::{RenderTimings, RenderedFrame, Renderer};
#[cfg(test)]
mod tests {