1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// The render backend's construction, and the handoff of it and the init-built
// runtime state to the world's shared resources.
use concinnity_core::ecs::PipelineContext;
use concinnity_core::gfx::render_types::SkinnedIndex;
use concinnity_core::render::backend::RenderBackend;
use concinnity_core::render::backend_init::BackendInit;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::volumetric_fog::FogSettings;
use super::GraphicsSystem;
use super::hot_reload_sources::HotReloadSources;
use super::parked::{PushedFogSettings, TextureNameSlots};
// What init hands to the world beside the backend.
pub(super) struct RuntimeHandoff<'a> {
pub(super) draw_object_count: usize,
pub(super) frames_in_flight: usize,
pub(super) skinned_pool_reservations: &'a [(SkinnedIndex, SkinnedIndex)],
pub(super) fog: Option<FogSettings>,
// Present only under hot-reload capture.
pub(super) texture_name_slots: Option<TextureNameSlots>,
pub(super) hot_reload_sources: Option<HotReloadSources>,
}
impl GraphicsSystem {
// Build the backend for this world. A live editor swap may have parked the
// pre-edit world's backend as a `PendingBackend`: it reloads the new world
// in place, keeping the window and device, when it can hot-swap and the
// swapchain config (pixel format, frames in flight, EDR) is unchanged.
// Otherwise it is idled and dropped, and a new backend is built (a rare
// one-frame flash). DirectX and Vulkan report no hot-swap config.
pub(super) fn build_backend(
&mut self,
ctx: &mut PipelineContext,
backend_init: BackendInit<'_>,
) -> RenderResult<Box<dyn RenderBackend>> {
let reuse_backend = match ctx
.resources
.remove::<crate::live_edit::PendingBackend>()
.map(|p| p.0)
{
Some(backend) if backend.hot_swap_config() == Some(backend_init.swapchain_config()) => {
Some(backend)
}
Some(backend) => {
backend.wait_idle();
None
}
None => None,
};
if let Some(mut backend) = reuse_backend {
backend.reload_world(backend_init)?;
tracing::info!(
"GraphicsSystem: reused live backend (world reloaded in place, window kept)"
);
return Ok(backend);
}
// Tests inject a mock backend factory through `test_hooks`; production
// always routes to the compile-time-selected real backend.
#[cfg(test)]
{
match self.test_hooks.as_mut() {
Some(hooks) => (hooks.backend_factory)(backend_init),
// A test builds no real device: one that forgets its hooks
// fails its own assertions instead of opening a window.
None => Err(concinnity_core::render::error::RenderError::Other(
"no test backend factory".into(),
)),
}
}
#[cfg(not(test))]
{
crate::device::init_backend(backend_init)
}
}
// Park the backend and the init-built runtime state in the world's shared
// resources, where each per-step user takes and returns them.
pub(super) fn park_runtime_state(
&mut self,
ctx: &mut PipelineContext,
handoff: RuntimeHandoff<'_>,
) {
// Seed the frame extraction's viewport from the live backend;
// FrameInput refreshes it once InputSystem starts publishing.
self.viewport = self
.backend
.as_ref()
.map(|b| b.logical_size())
.unwrap_or((0.0, 0.0));
// OverlaySystem shapes the overlay draw list from these each frame.
ctx.insert_resource(crate::gfx::overlay::OverlayAssets {
fonts: std::mem::take(&mut self.loaded_fonts),
sprite_texture_slots: std::mem::take(&mut self.sprite_texture_slots),
debug_hud_chips: std::mem::take(&mut self.debug_hud_chips),
stat_hud_chips: std::mem::take(&mut self.stat_hud_chips),
clip_rects: std::mem::take(&mut self.clip_rects),
initial_viewport: self.viewport,
});
self.publish_streaming_state(ctx, handoff.frames_in_flight);
// The op queue backend effects accumulate into, and the slot-allocation
// authority: the draw-slot free list seeded with the build-time draw
// count, and the skinned instance pool with the pre-reserved copies.
ctx.insert_resource(crate::ecs::ActiveRenderQueues(Some(
crate::ecs::RenderQueues {
ops: Default::default(),
slots: crate::gfx::render_slots::RenderSlots::new(
handoff.draw_object_count,
self.caps.reuses_build_slots,
handoff.skinned_pool_reservations,
),
},
)));
ctx.insert_resource(crate::ecs::ActiveRenderBackend(self.backend.take()));
// Beside it, the state tooling edits the running world through: the fog
// a reload dedupes against, and under hot-reload capture the
// texture-name map and the source catalogs.
ctx.insert_resource(PushedFogSettings(handoff.fog));
if let Some(slots) = handoff.texture_name_slots {
ctx.insert_resource(slots);
}
if let Some(sources) = handoff.hot_reload_sources {
ctx.insert_resource(sources);
}
// SettingsSystem jumps the scene flow and this system ticks it.
ctx.insert_resource(crate::ecs::ActiveSceneFlow::new(self.scene_flow.take()));
}
}