use super::frame_policy::{FrameAction, FramePolicy};
use crate::ecs::StepResult;
use crate::gfx::backend::{FrameParams, RenderBackend};
use crate::gfx::ops::ReplayOutcome;
use crate::gfx::profile::RenderStats;
use crate::gfx::snapshot::{RenderSnapshot, SceneOp};
pub(crate) struct SubmitOutcome {
pub result: StepResult,
pub(crate) render_stats: Option<RenderStats>,
pub(crate) memory_pressure: bool,
pub replay: ReplayOutcome,
pub(crate) device_lost: bool,
}
impl SubmitOutcome {
fn stop() -> Self {
Self {
result: StepResult::Stop,
render_stats: None,
memory_pressure: false,
replay: ReplayOutcome::default(),
device_lost: false,
}
}
}
pub(crate) fn submit(
policy: &mut FramePolicy,
snap: &mut RenderSnapshot,
backend: &mut dyn RenderBackend,
) -> SubmitOutcome {
let replay = snap.ops.replay(backend);
backend.set_ui_cursor_hidden(snap.ui.cursor_hidden);
if let Some(on) = snap.ui.menu_mode {
backend.set_menu_mode(on);
}
if let Some(capture) = snap.ui.camera_capture {
backend.set_camera_capture(capture);
}
if backend.window_closed() {
tracing::info!("GraphicsSystem: window closed");
backend.wait_idle();
return SubmitOutcome {
replay,
..SubmitOutcome::stop()
};
}
if !snap.models.is_empty() {
backend.update_models(&snap.models);
}
for (skinned_index, joints) in snap.poses.iter() {
backend.update_skinned_pose(skinned_index, joints);
}
for (skinned_index, weights) in snap.morphs.iter() {
backend.update_morph_weights(skinned_index, weights);
}
if !snap.skinned_models.is_empty() {
backend.update_skinned_models(&snap.skinned_models);
}
for op in &snap.scene_ops {
match *op {
SceneOp::SetFade(fade) => backend.set_fade(fade),
SceneOp::Visibility { draw_idx, visible } => {
backend.update_visibility(draw_idx, visible)
}
}
}
if let Some(set) = snap.frame.directional {
backend.update_directional_lights(set.as_slice());
}
backend.update_view(snap.frame.view);
let mut memory_pressure = false;
match backend.draw_frame(FrameParams {
elapsed: snap.frame.elapsed,
fov_y_radians: snap.frame.fov_y_radians,
near: snap.frame.near,
far: snap.frame.far,
cam_pos: snap.frame.cam_pos,
text_calls: &snap.text_calls,
lines: &snap.lines,
world_hidden: snap.frame.world_hidden,
view_mode: snap.frame.view_mode,
show: snap.frame.show,
sky_rot: snap.frame.sky_rot,
}) {
Ok(()) => policy.frame_succeeded(),
Err(e) => {
memory_pressure = matches!(e, crate::gfx::error::RenderError::OutOfDeviceMemory(_));
match policy.on_frame_error(&e) {
FrameAction::SkipFrame => {}
FrameAction::Shutdown => {
backend.wait_idle();
return SubmitOutcome {
memory_pressure,
replay,
..SubmitOutcome::stop()
};
}
FrameAction::ShutdownDeviceLost => {
tracing::error!("GraphicsSystem: device lost, stopping: {}", e);
crate::crash::report_device_lost(&e.to_string());
return SubmitOutcome {
memory_pressure,
replay,
device_lost: true,
..SubmitOutcome::stop()
};
}
}
}
}
SubmitOutcome {
result: StepResult::Continue,
render_stats: Some(backend.render_stats()),
memory_pressure,
replay,
device_lost: false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::components::DirectionalLight;
use crate::gfx::mock_backend::{Call, recording_backend};
use concinnity_core::render::lights::DirectionalLightSet;
fn pushed_lights(directional: Option<DirectionalLightSet>) -> Vec<Call> {
let (state, mut backend) = recording_backend();
let mut snap = RenderSnapshot::default();
snap.frame.directional = directional;
submit(&mut FramePolicy::default(), &mut snap, &mut backend);
let calls = state.lock().unwrap().calls.clone();
calls
.into_iter()
.filter(|c| matches!(c, Call::UpdateDirectionalLights(_)))
.collect()
}
#[test]
fn a_frame_carrying_no_lights_leaves_the_backend_set_alone() {
assert!(pushed_lights(None).is_empty());
}
#[test]
fn a_frame_carrying_lights_installs_them_before_the_draw() {
let sun = DirectionalLight {
direction: [0.0, 1.0, 0.0],
color: [1.0, 0.5, 0.25],
intensity: 2.0,
};
let pushed = pushed_lights(Some(DirectionalLightSet::collect(core::iter::once(sun))));
assert_eq!(
pushed,
vec![Call::UpdateDirectionalLights(vec![(
sun.direction,
sun.color,
sun.intensity
)])]
);
}
}