Centralize world and viewport game loops
This commit is contained in:
@@ -45,6 +45,19 @@ enum RenderCommand {
|
||||
limits: RenderLimits,
|
||||
reply: mpsc::SyncSender<Result<RenderedFrame, RenderError>>,
|
||||
},
|
||||
LoadScene {
|
||||
camera: Camera,
|
||||
renderables: std::sync::Arc<[Renderable]>,
|
||||
textures: std::sync::Arc<[Texture]>,
|
||||
limits: RenderLimits,
|
||||
reply: mpsc::SyncSender<Result<RenderTimings, RenderError>>,
|
||||
},
|
||||
View {
|
||||
camera: Camera,
|
||||
background_srgb: [u8; 4],
|
||||
limits: RenderLimits,
|
||||
reply: mpsc::SyncSender<Result<RenderedFrame, RenderError>>,
|
||||
},
|
||||
Stop,
|
||||
}
|
||||
|
||||
@@ -102,6 +115,32 @@ impl Renderer {
|
||||
limits,
|
||||
));
|
||||
}
|
||||
RenderCommand::LoadScene {
|
||||
camera,
|
||||
renderables,
|
||||
textures,
|
||||
limits,
|
||||
reply,
|
||||
} => {
|
||||
let _ = reply.send(backend.load_scene(
|
||||
camera,
|
||||
&renderables,
|
||||
&textures,
|
||||
limits,
|
||||
));
|
||||
}
|
||||
RenderCommand::View {
|
||||
camera,
|
||||
background_srgb,
|
||||
limits,
|
||||
reply,
|
||||
} => {
|
||||
let _ = reply.send(backend.render_view(
|
||||
camera,
|
||||
background_srgb,
|
||||
limits,
|
||||
));
|
||||
}
|
||||
RenderCommand::Stop => break,
|
||||
}
|
||||
}
|
||||
@@ -170,6 +209,83 @@ impl Renderer {
|
||||
frame.timings.command_copy = command_copy;
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
pub fn load_scene(
|
||||
&self,
|
||||
camera: Camera,
|
||||
renderables: &[Renderable],
|
||||
textures: &[Texture],
|
||||
limits: RenderLimits,
|
||||
) -> Result<RenderTimings, RenderError> {
|
||||
self.load_scene_shared(
|
||||
camera,
|
||||
renderables.to_vec().into(),
|
||||
textures.to_vec().into(),
|
||||
limits,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_scene_shared(
|
||||
&self,
|
||||
camera: Camera,
|
||||
renderables: std::sync::Arc<[Renderable]>,
|
||||
textures: std::sync::Arc<[Texture]>,
|
||||
limits: RenderLimits,
|
||||
) -> Result<RenderTimings, 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::LoadScene {
|
||||
camera,
|
||||
renderables,
|
||||
textures,
|
||||
limits,
|
||||
reply: sender,
|
||||
};
|
||||
let command_copy = started.elapsed();
|
||||
self.send(command)?;
|
||||
let mut timings = receiver
|
||||
.recv_timeout(Duration::from_mins(2))
|
||||
.map_err(|_| RenderError::TimedOut)??;
|
||||
timings.validation = validation;
|
||||
timings.command_copy = command_copy;
|
||||
Ok(timings)
|
||||
}
|
||||
|
||||
pub fn render_view(
|
||||
&self,
|
||||
camera: Camera,
|
||||
background_srgb: [u8; 4],
|
||||
limits: RenderLimits,
|
||||
) -> Result<RenderedFrame, RenderError> {
|
||||
camera.basis()?;
|
||||
if limits.width == 0 || limits.height == 0 || limits.far_distance == 0 {
|
||||
return Err(RenderError::InvalidScene);
|
||||
}
|
||||
let (sender, receiver) = mpsc::sync_channel(1);
|
||||
self.send(RenderCommand::View {
|
||||
camera,
|
||||
background_srgb,
|
||||
limits,
|
||||
reply: sender,
|
||||
})?;
|
||||
receiver
|
||||
.recv_timeout(Duration::from_mins(2))
|
||||
.map_err(|_| RenderError::TimedOut)?
|
||||
}
|
||||
|
||||
fn send(&self, command: RenderCommand) -> Result<(), RenderError> {
|
||||
self.commands
|
||||
.try_send(command)
|
||||
.map_err(|error| match error {
|
||||
mpsc::TrySendError::Full(_) => RenderError::TimedOut,
|
||||
mpsc::TrySendError::Disconnected(_) => {
|
||||
RenderError::Device("renderer thread stopped".into())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Renderer {
|
||||
@@ -189,6 +305,9 @@ impl Drop for Renderer {
|
||||
#[derive(Component)]
|
||||
struct FrameEntity;
|
||||
|
||||
#[derive(Component)]
|
||||
struct PersistentSceneEntity;
|
||||
|
||||
struct LegacyMaterialPlugin;
|
||||
struct PbrMaterialPlugin;
|
||||
|
||||
@@ -328,13 +447,14 @@ struct CachedGpuMesh {
|
||||
struct BevyBackend {
|
||||
apps: SubApps,
|
||||
frame_entities: Vec<Entity>,
|
||||
scene_entities: Vec<Entity>,
|
||||
texture_handles: Vec<GpuTexture>,
|
||||
texture_cache: HashMap<u64, Vec<CachedGpuTexture>>,
|
||||
mesh_cache: HashMap<u64, Vec<CachedGpuMesh>>,
|
||||
pbr_handles: Vec<Handle<PbrBevyMaterial>>,
|
||||
legacy_handles: Vec<Handle<LegacyBevyMaterial>>,
|
||||
environment_map: EnvironmentMapLight,
|
||||
target: Option<Handle<Image>>,
|
||||
target: Option<(u32, u32, Handle<Image>)>,
|
||||
}
|
||||
|
||||
impl BevyBackend {
|
||||
@@ -381,6 +501,7 @@ impl BevyBackend {
|
||||
Ok(Self {
|
||||
apps: std::mem::take(app.sub_apps_mut()),
|
||||
frame_entities: Vec::new(),
|
||||
scene_entities: Vec::new(),
|
||||
texture_handles: Vec::new(),
|
||||
texture_cache: HashMap::new(),
|
||||
mesh_cache: HashMap::new(),
|
||||
@@ -400,18 +521,28 @@ impl BevyBackend {
|
||||
background_srgb: [u8; 4],
|
||||
limits: RenderLimits,
|
||||
) -> Result<RenderedFrame, RenderError> {
|
||||
let mut timings = self.load_scene(camera, renderables, textures, limits)?;
|
||||
let frame = self.render_view(camera, background_srgb, limits)?;
|
||||
timings.clear_previous_frame += frame.timings.clear_previous_frame;
|
||||
timings.scene_setup += frame.timings.scene_setup;
|
||||
timings.capture += frame.timings.capture;
|
||||
Ok(RenderedFrame {
|
||||
rgba: frame.rgba,
|
||||
timings,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn load_scene(
|
||||
&mut self,
|
||||
camera: Camera,
|
||||
renderables: &[Renderable],
|
||||
textures: &[Texture],
|
||||
_limits: RenderLimits,
|
||||
) -> Result<RenderTimings, RenderError> {
|
||||
let started = Instant::now();
|
||||
self.clear_frame();
|
||||
self.clear_scene();
|
||||
let clear_previous_frame = started.elapsed();
|
||||
self.apps
|
||||
.main
|
||||
.world_mut()
|
||||
.insert_resource(ClearColor(Color::srgba_u8(
|
||||
background_srgb[0],
|
||||
background_srgb[1],
|
||||
background_srgb[2],
|
||||
background_srgb[3],
|
||||
)));
|
||||
let started = Instant::now();
|
||||
self.texture_handles = add_textures(
|
||||
self.apps.main.world_mut(),
|
||||
@@ -443,7 +574,7 @@ impl BevyBackend {
|
||||
.spawn((
|
||||
Mesh3d(mesh.clone()),
|
||||
MeshMaterial3d(material.clone()),
|
||||
FrameEntity,
|
||||
PersistentSceneEntity,
|
||||
))
|
||||
.id();
|
||||
self.legacy_handles.push(material);
|
||||
@@ -463,15 +594,70 @@ impl BevyBackend {
|
||||
.spawn((
|
||||
Mesh3d(mesh.clone()),
|
||||
MeshMaterial3d(material.clone()),
|
||||
FrameEntity,
|
||||
PersistentSceneEntity,
|
||||
))
|
||||
.id();
|
||||
self.pbr_handles.push(material);
|
||||
entity
|
||||
}
|
||||
};
|
||||
self.frame_entities.push(entity);
|
||||
self.scene_entities.push(entity);
|
||||
}
|
||||
let light = self
|
||||
.apps
|
||||
.main
|
||||
.world_mut()
|
||||
.spawn((
|
||||
DirectionalLight {
|
||||
illuminance: 12_000.0,
|
||||
shadow_maps_enabled: false,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from_xyz(40.0, 80.0, 30.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
PersistentSceneEntity,
|
||||
))
|
||||
.id();
|
||||
self.scene_entities.push(light);
|
||||
Ok(RenderTimings {
|
||||
clear_previous_frame,
|
||||
texture_cache_sync,
|
||||
mesh_cache_sync,
|
||||
scene_setup: started.elapsed(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn render_view(
|
||||
&mut self,
|
||||
camera: Camera,
|
||||
background_srgb: [u8; 4],
|
||||
limits: RenderLimits,
|
||||
) -> Result<RenderedFrame, RenderError> {
|
||||
let started = Instant::now();
|
||||
self.clear_frame();
|
||||
let clear_previous_frame = started.elapsed();
|
||||
self.apps
|
||||
.main
|
||||
.world_mut()
|
||||
.insert_resource(ClearColor(Color::srgba_u8(
|
||||
background_srgb[0],
|
||||
background_srgb[1],
|
||||
background_srgb[2],
|
||||
background_srgb[3],
|
||||
)));
|
||||
let camera_position = world_vector(camera.position).extend(1.0);
|
||||
for handle in &self.legacy_handles {
|
||||
if let Some(mut material) = self
|
||||
.apps
|
||||
.main
|
||||
.world_mut()
|
||||
.resource_mut::<Assets<LegacyBevyMaterial>>()
|
||||
.get_mut(handle)
|
||||
{
|
||||
material.uniform.camera_position = camera_position;
|
||||
}
|
||||
}
|
||||
let started = Instant::now();
|
||||
let target = self.new_target(limits.width, limits.height);
|
||||
let transform = camera_transform(camera)?;
|
||||
let camera_entity = self
|
||||
@@ -494,21 +680,6 @@ impl BevyBackend {
|
||||
))
|
||||
.id();
|
||||
self.frame_entities.push(camera_entity);
|
||||
let light = self
|
||||
.apps
|
||||
.main
|
||||
.world_mut()
|
||||
.spawn((
|
||||
DirectionalLight {
|
||||
illuminance: 12_000.0,
|
||||
shadow_maps_enabled: false,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from_xyz(40.0, 80.0, 30.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
FrameEntity,
|
||||
))
|
||||
.id();
|
||||
self.frame_entities.push(light);
|
||||
let scene_setup = started.elapsed();
|
||||
let started = Instant::now();
|
||||
let rgba = self.capture(&target)?;
|
||||
@@ -516,8 +687,6 @@ impl BevyBackend {
|
||||
rgba,
|
||||
timings: RenderTimings {
|
||||
clear_previous_frame,
|
||||
texture_cache_sync,
|
||||
mesh_cache_sync,
|
||||
scene_setup,
|
||||
capture: started.elapsed(),
|
||||
..Default::default()
|
||||
@@ -526,7 +695,12 @@ impl BevyBackend {
|
||||
}
|
||||
|
||||
fn new_target(&mut self, width: u32, height: u32) -> RenderTarget {
|
||||
if let Some(old) = self.target.take() {
|
||||
if let Some((old_width, old_height, handle)) = &self.target
|
||||
&& (*old_width, *old_height) == (width, height)
|
||||
{
|
||||
return handle.clone().into();
|
||||
}
|
||||
if let Some((_, _, old)) = self.target.take() {
|
||||
self.apps
|
||||
.main
|
||||
.world_mut()
|
||||
@@ -550,7 +724,7 @@ impl BevyBackend {
|
||||
.world_mut()
|
||||
.resource_mut::<Assets<Image>>()
|
||||
.add(image);
|
||||
self.target = Some(handle.clone());
|
||||
self.target = Some((width, height, handle.clone()));
|
||||
handle.into()
|
||||
}
|
||||
|
||||
@@ -600,6 +774,13 @@ impl BevyBackend {
|
||||
for entity in self.frame_entities.drain(..) {
|
||||
let _ = self.apps.main.world_mut().despawn(entity);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_scene(&mut self) {
|
||||
self.clear_frame();
|
||||
for entity in self.scene_entities.drain(..) {
|
||||
let _ = self.apps.main.world_mut().despawn(entity);
|
||||
}
|
||||
let world = self.apps.main.world_mut();
|
||||
self.texture_handles.clear();
|
||||
let mut materials = world.resource_mut::<Assets<PbrBevyMaterial>>();
|
||||
@@ -1231,6 +1412,36 @@ mod tests {
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
let limits = RenderLimits {
|
||||
width: 96,
|
||||
height: 64,
|
||||
max_triangles: 2,
|
||||
max_texture_bytes: 64,
|
||||
max_texture_pixels: 16,
|
||||
far_distance: 64,
|
||||
};
|
||||
renderer
|
||||
.load_scene(
|
||||
camera,
|
||||
&[renderable(Material::BlinnPhong(BlinnPhongMaterial {
|
||||
diffuse_color_srgb: [30, 220, 60, 255],
|
||||
fullbright: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
&textures,
|
||||
limits,
|
||||
)
|
||||
.unwrap();
|
||||
for _ in 0..2 {
|
||||
let frame = renderer
|
||||
.render_view(camera, [0, 0, 0, 255], limits)
|
||||
.unwrap();
|
||||
assert_eq!(frame.rgba.len(), 96 * 64 * 4);
|
||||
assert_eq!(frame.timings.command_copy, Duration::ZERO);
|
||||
assert_eq!(frame.timings.texture_cache_sync, Duration::ZERO);
|
||||
assert_eq!(frame.timings.mesh_cache_sync, Duration::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_slot(texture: usize, rotation_radians: f32) -> TextureSlot {
|
||||
|
||||
Reference in New Issue
Block a user