pub struct Renderer {
context: ghi::implementation::Context,
device: Arc<ghi::implementation::Device>,
instance: ghi::implementation::Instance,
started_frame_count: usize,
frame_queue_depth: usize,
windows: SmallVec<[(ghi::Window, ghi::SwapchainHandle); 16]>,
sink_cameras: SmallVec<[(SinkId, Handle); 16]>,
cameras: SmallVec<[(Handle, Camera); 16]>,
render_targets: RenderTargets,
resource_manager: Option<crate::core::entity::handle::WeakHandle<ResourceManager>>,
render_passes: SmallVec<[RenderPassHarness; 64]>,
render_passes_by_sink: SmallVec<[(RenderPassId, SinkId); 32]>,
post_scene_render_pass_factories: SmallVec<[Box<RenderPassFactory>; 16]>,
pending_swapchain_captures: SmallVec<[SwapchainCapture; 16]>,
pending_sink_initializations: SmallVec<[SinkId; 16]>,
configuration: ConfigurationPort,
pending_configuration: VecDeque<PendingRenderPassConfiguration>,
render_pass_states: HashMap<String, RenderPassState>,
pipeline_managers: SmallVec<[Box<dyn PipelineManager>; 16]>,
pipeline_manager_resources_by_sink: SmallVec<[(PipelineManagerId, SinkId, Vec<(String, ghi::AccessPolicies)>); 64]>,
graphics_queue_handle: ghi::QueueHandle,
pub transfer_queue_handle: ghi::QueueHandle,
render_command_buffer: ghi::CommandBufferHandle,
render_finished_synchronizer: ghi::SynchronizerHandle,
defer_first_frame_sink_setup: bool,
}
impl Renderer {
pub fn new(parameters: &dyn Parameters, configuration: &Configuration) -> Self {
let settings = Settings::new();
let settings = if let Some(param) = parameters.get_parameter("render.debug") {
settings.validation(param.as_bool_simple())
} else {
settings
};
let settings = if let Some(param) = parameters.get_parameter("render.debug.dump") {
settings.api_dump(param.as_bool_simple())
} else {
settings
};
let settings = if let Some(param) = parameters.get_parameter("render.debug.extended") {
settings.extended_validation(param.as_bool_simple())
} else {
settings
};
let settings = if let Some(param) = parameters.get_parameter("render.debug.labels") {
settings.debug_labels(param.as_bool_simple())
} else {
let validation = settings.validation;
settings.debug_labels(validation)
};
let settings = if let Some(param) = parameters.get_parameter("render.ghi.features.mesh-shading") {
settings.mesh_shading(param.as_bool_simple())
} else {
settings
};
let defer_first_frame_sink_setup = parameters
.get_parameter("render.startup.defer-sink-setup")
.map(|parameter| parameter.as_bool_simple())
.unwrap_or(false);
let mut features = ghi::device::Features::new()
.validation(settings.validation)
.api_dump(settings.api_dump)
.gpu_validation(settings.extended_validation)
.debug_labels(settings.debug_labels)
.debug_log_function(|message| {
let backtrace = std::backtrace::Backtrace::force_capture().to_string();
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let workspace_root = manifest_dir
.rsplit_once("/crates/")
.map(|(root, _)| root)
.unwrap_or(manifest_dir);
let mut filtered = String::new();
for line in backtrace.lines() {
if line.contains(workspace_root) {
filtered.push_str(line);
filtered.push('\n');
}
}
if filtered.trim().is_empty() {
log::error!("{}\n{}", message, backtrace);
} else {
log::error!("{}\n{}", message, filtered.trim_end());
}
})
.geometry_shader(false)
.mesh_shading(settings.mesh_shading);
let mut instance = match ghi::implementation::Instance::new(features) {
Ok(instance) => instance,
Err(error) if settings.validation => {
log::warn!(
"Renderer validation was requested but could not be enabled: {error} Falling back to renderer validation disabled. The most likely cause is missing or unsupported platform graphics tooling. See {}.",
crate::online_docs_url("use/setup/environment")
);
features = features
.validation(false)
.gpu_validation(false)
.api_dump(false)
.debug_labels(false);
ghi::implementation::Instance::new(features).unwrap()
}
Err(error) => panic!("Failed to create GHI instance: {error}"),
};
let mut graphics_queue_handle = None;
let mut transfer_queue_handle = None;
let device = instance
.create_device(
features,
&mut [
(
ghi::QueueSelection::new(ghi::types::WorkloadTypes::RASTER),
&mut graphics_queue_handle,
),
(
ghi::QueueSelection::new(ghi::types::WorkloadTypes::TRANSFER),
&mut transfer_queue_handle,
),
],
)
.unwrap();
let mut context = device.create_context().unwrap();
let frame_queue_depth = 2;
context.set_frames_in_flight(frame_queue_depth);
let graphics_queue_handle = graphics_queue_handle.unwrap();
let transfer_queue_handle = transfer_queue_handle.unwrap();
let render_command_buffer = context
.queue_reference(graphics_queue_handle)
.create_command_buffer(Some("Render"));
let render_finished_synchronizer = context.create_synchronizer(Some("Render Finisished"), true);
Renderer {
context,
device: Arc::new(device),
instance,
started_frame_count: 0,
frame_queue_depth: frame_queue_depth as usize,
windows: SmallVec::with_capacity(16),
sink_cameras: SmallVec::with_capacity(16),
cameras: SmallVec::with_capacity(16),
render_targets: RenderTargets::new(),
resource_manager: None,
render_passes: SmallVec::with_capacity(64),
render_passes_by_sink: SmallVec::with_capacity(32),
post_scene_render_pass_factories: SmallVec::with_capacity(16),
pending_swapchain_captures: SmallVec::with_capacity(16),
pending_sink_initializations: SmallVec::with_capacity(16),
configuration: configuration.register(RENDER_PASS_PARAMETER_PREFIX),
pending_configuration: VecDeque::new(),
render_pass_states: HashMap::new(),
pipeline_managers: SmallVec::with_capacity(8),
pipeline_manager_resources_by_sink: SmallVec::with_capacity(64),
graphics_queue_handle,
transfer_queue_handle,
render_command_buffer,
render_finished_synchronizer,
defer_first_frame_sink_setup,
}
}
pub fn set_resource_manager(&mut self, resource_manager: &EntityHandle<ResourceManager>) {
self.resource_manager = Some(resource_manager.weak());
}
pub fn add_pipeline_manager(&mut self, mut pipeline_manager: impl PipelineManager + 'static) {
let pipeline_manager_id = self.pipeline_managers.len();
{
let sink_swapchains: SmallVec<[(SinkId, ghi::SwapchainHandle); 16]> = self
.sink_cameras
.iter()
.map(|(sink_id, _)| (*sink_id, self.windows[*sink_id].1))
.collect();
for (sink_id, swapchain) in sink_swapchains {
if self.pending_sink_initializations.contains(&sink_id) {
continue;
}
let resource_manager = self
.resource_manager
.as_ref()
.and_then(|resource_manager| resource_manager.upgrade());
let mut rpb = RenderPassBuilder::new(&mut self.context, &mut self.render_targets, sink_id, swapchain);
if let Some(resource_manager) = resource_manager.as_deref() {
rpb = rpb.with_shader_resources(resource_manager);
}
pipeline_manager.create_sink(sink_id, &mut rpb);
let consumed_resources = rpb
.consumed_resources
.iter()
.map(|(name, access)| ((*name).to_string(), *access))
.collect();
self.pipeline_manager_resources_by_sink
.push((pipeline_manager_id, sink_id, consumed_resources));
if rpb.consumed_resources.is_empty() {
log::debug!("No resources consumed by scene manager");
}
}
}
self.pipeline_managers.push(Box::new(pipeline_manager));
}
fn initialize_scene_sink(&mut self, sink_id: SinkId) {
let swapchain = self.windows[sink_id].1;
{
let Renderer {
context,
render_targets,
resource_manager,
pipeline_managers,
pipeline_manager_resources_by_sink,
..
} = self;
for (pipeline_manager_id, sm) in pipeline_managers.iter_mut().enumerate() {
let resource_manager = resource_manager
.as_ref()
.and_then(|resource_manager| resource_manager.upgrade());
let mut rpb = RenderPassBuilder::new(context, render_targets, sink_id, swapchain);
if let Some(resource_manager) = resource_manager.as_deref() {
rpb = rpb.with_shader_resources(resource_manager);
}
sm.create_sink(sink_id, &mut rpb);
let consumed_resources = rpb
.consumed_resources
.iter()
.map(|(name, access)| ((*name).to_string(), *access))
.collect();
pipeline_manager_resources_by_sink.push((pipeline_manager_id, sink_id, consumed_resources));
if rpb.consumed_resources.is_empty() {
log::debug!("No resources consumed by scene manager");
}
}
}
self.add_post_scene_render_passes_for_sink(sink_id);
}
fn initialize_pending_sink_resources(&mut self) {
let pending_sink_initializations = std::mem::take(&mut self.pending_sink_initializations);
for sink_id in pending_sink_initializations {
self.initialize_scene_sink(sink_id);
}
}
fn add_render_pass(&mut self, render_pass: Box<dyn RenderPass>, sink_id: SinkId) {
let render_pass_id = self.render_passes.len();
self.render_passes
.push(render_pass_harness_with_state(render_pass, &self.render_pass_states));
self.render_passes_by_sink.push((render_pass_id, sink_id));
}
pub fn set_render_pass_state(&mut self, name: &str, state: RenderPassState) -> usize {
self.render_pass_states.insert(name.to_string(), state);
set_render_pass_state_by_name(&mut self.render_passes, name, state)
}
fn apply_configuration(&mut self) {
apply_render_pass_configuration(
&self.configuration,
&mut self.pending_configuration,
&mut self.render_pass_states,
&mut self.render_passes,
);
}
pub fn add_post_scene_render_pass_for_all_sinks<F>(&mut self, render_pass_factory: F)
where
F: for<'a> Fn(&'a mut RenderPassBuilder<'a>) -> Box<dyn RenderPass> + 'static,
{
let render_pass_factory: Box<RenderPassFactory> = Box::new(render_pass_factory);
let sink_ids: SmallVec<[usize; 16]> = self.sink_cameras.iter().map(|(sink_id, _)| *sink_id).collect();
for sink_id in sink_ids {
let render_pass = {
let swapchain = self.windows[sink_id].1;
let resource_manager = self
.resource_manager
.as_ref()
.and_then(|resource_manager| resource_manager.upgrade());
let mut render_pass_builder =
RenderPassBuilder::new(&mut self.context, &mut self.render_targets, sink_id, swapchain);
if let Some(resource_manager) = resource_manager.as_deref() {
render_pass_builder = render_pass_builder.with_shader_resources(resource_manager);
}
render_pass_factory(&mut render_pass_builder)
};
self.add_render_pass(render_pass, sink_id);
}
self.post_scene_render_pass_factories.push(render_pass_factory);
}
fn add_post_scene_render_passes_for_sink(&mut self, sink_id: SinkId) {
let mut render_passes_for_sink: SmallVec<[Box<dyn RenderPass>; 16]> = SmallVec::new();
let swapchain = self.windows[sink_id].1;
for render_pass_factory in &self.post_scene_render_pass_factories {
let render_pass = {
let resource_manager = self
.resource_manager
.as_ref()
.and_then(|resource_manager| resource_manager.upgrade());
let mut render_pass_builder =
RenderPassBuilder::new(&mut self.context, &mut self.render_targets, sink_id, swapchain);
if let Some(resource_manager) = resource_manager.as_deref() {
render_pass_builder = render_pass_builder.with_shader_resources(resource_manager);
}
render_pass_factory(&mut render_pass_builder)
};
render_passes_for_sink.push(render_pass);
}
for render_pass in render_passes_for_sink {
self.add_render_pass(render_pass, sink_id);
}
}
pub fn update_windows<'a>(&'a mut self) -> impl Iterator<Item = impl Iterator<Item = ghi::window::Events> + 'a> + 'a {
self.windows.iter_mut().map(|(window, _)| window.poll())
}
pub fn capture_swapchain_to_buffer(
&mut self,
sink_id: SinkId,
destination_buffer: impl Into<ghi::BaseBufferHandle>,
destination_offset: usize,
destination_bytes_per_row: usize,
destination_bytes_per_image: usize,
) {
self.pending_swapchain_captures.push(SwapchainCapture {
sink_id,
destination_buffer: destination_buffer.into(),
destination_offset,
destination_bytes_per_row,
destination_bytes_per_image,
});
}
pub fn prepare(
&'_ mut self,
transforms_listener: &mut impl Listener<TransformationUpdate>,
frame_allocator: &bumpalo::Bump,
) {
let span = debug_span!(
"Renderer::prepare",
frame = self.started_frame_count,
windows = self.windows.len()
);
let _enter = span.enter();
let Some(_) = self.windows.first() else {
log::debug!("No swapchains available to present to. Skipping rendering!");
return;
};
if self.started_frame_count > 0 && !self.pending_sink_initializations.is_empty() {
self.initialize_pending_sink_resources();
}
self.apply_configuration();
self.context.start_frame_capture();
{
let span = debug_span!("Renderer::update_camera_transforms");
let _enter = span.enter();
while let Some(message) = transforms_listener.read() {
let handle = *message.handle();
if let Some(camera) = self
.cameras
.iter_mut()
.find_map(|(h, camera)| if handle == *h { Some(camera) } else { None })
{
camera.set_position(message.transform().get_position());
camera.set_orientation(message.transform().get_orientation());
}
}
}
let mut queue = self.context.queue(self.graphics_queue_handle);
let frame = ghi::queue::FrameRequest {
index: self.started_frame_count as u32,
synchronizer: self.render_finished_synchronizer,
};
self.started_frame_count += 1;
let command_buffer = self.render_command_buffer;
let synchronizer = self.render_finished_synchronizer;
let wait_for = &[];
let windows = &self.windows;
let sink_cameras = &self.sink_cameras;
let cameras = &self.cameras;
let render_targets = &self.render_targets;
let pipeline_managers = &mut self.pipeline_managers;
let pipeline_manager_resources_by_sink = &self.pipeline_manager_resources_by_sink;
let render_passes = &mut self.render_passes;
let render_passes_by_sink = &self.render_passes_by_sink;
let pending_swapchain_captures = self.pending_swapchain_captures.drain(..).collect::<SmallVec<[_; 16]>>();
let frame_allocator = frame_allocator;
{
let span = debug_span!("Renderer::queue_execute");
let _enter = span.enter();
queue.execute(Some(frame), wait_for, synchronizer, |execution| {
let completed_graphics_frame = execution.completed_frame();
let (sinks, pipeline_manager_commands, render_pass_commands, present_keys, swapchain_capture_copies) = {
let span = debug_span!("Renderer::prepare_frame_work");
let _enter = span.enter();
let frame = execution.frame().expect(
"Frame is required to prepare renderer frame work. The most likely cause is that Renderer::render called Queue::execute without a frame request.",
);
let swapchains: SmallVec<[Option<(ghi::PresentKey, Extent, ghi::SwapchainHandle)>; 16]> = {
let span = debug_span!("Renderer::acquire_swapchains", count = windows.len());
let _enter = span.enter();
windows
.iter()
.map(|(_window, swapchain)| {
let (present_key, extent) = frame.acquire_swapchain_image(*swapchain);
if extent.width() == 0 || extent.height() == 0 {
log::warn!("The extent is too small: {:?}. Rendering will be skipped.", extent);
return None;
}
if extent.width() >= 65535 || extent.height() >= 65535 {
log::warn!(
"The extent is too large: {:?}. The renderer only supports dimensions as big as 16 bits. Rendering will be skipped.",
extent
);
return None;
}
Some((present_key, extent, *swapchain))
})
.collect()
};
let mut sinks: SmallVec<[Sink; 16]> = SmallVec::new();
{
let span = debug_span!("Renderer::build_sinks", cameras = cameras.len());
let _enter = span.enter();
for (sink_id, camera_handle) in sink_cameras.iter() {
let Some((_present_key, extent, _swapchain)) = swapchains[*sink_id] else {
continue;
};
let Some(camera) = cameras
.iter()
.find_map(|(handle, camera)| if handle == camera_handle { Some(camera) } else { None })
else {
continue;
};
let view = make_perspective_view_from_camera(camera, extent);
sinks.push(Sink::new(view, extent, *sink_id));
}
}
{
let span = debug_span!("Renderer::resize_render_targets", sinks = sinks.len());
let _enter = span.enter();
for sink in &sinks {
let images = render_targets.get_images_for_sink(sink.index());
for &image in images {
frame.resize_image(image, sink.extent());
}
}
}
let pipeline_managers = pipeline_managers.iter_mut().enumerate();
let pipeline_manager_commands: SmallVec<[(PipelineManagerId, SmallVec<[RenderPassReturn<'_>; 16]>); 16]> = {
let span = debug_span!("Renderer::prepare_pipeline_managers");
let _enter = span.enter();
pipeline_managers
.filter_map(|(pipeline_manager_id, sm)| {
sm.prepare(frame, &sinks, frame_allocator).map(|commands| (pipeline_manager_id, commands))
})
.collect()
};
let render_pass_commands: SmallVec<[(RenderPassReturn, RenderPassId, SinkId); 64]> = {
let span = debug_span!("Renderer::prepare_render_passes");
let _enter = span.enter();
render_passes_by_sink
.iter()
.filter_map(|(render_pass_id, sink_id)| {
if let Some(render_pass) = render_passes.get_mut(*render_pass_id) {
if let Some(sink) = sinks.iter().find(|sink| sink.index() == *sink_id) {
if let Some(command) = render_pass.prepare(frame, sink, frame_allocator) {
return Some((command, *render_pass_id, sink.index()));
}
}
}
None
})
.collect()
};
let present_keys = swapchains
.iter()
.filter_map(|sc| sc.as_ref().map(|(pk, ..)| *pk))
.collect::<SmallVec<[ghi::PresentKey; 16]>>();
let swapchain_capture_copies = {
let span = debug_span!("Renderer::prepare_swapchain_captures", captures = pending_swapchain_captures.len());
let _enter = span.enter();
pending_swapchain_captures
.iter()
.filter_map(|capture| {
let Some(Some((_present_key, _extent, swapchain))) = swapchains.get(capture.sink_id) else {
return None;
};
Some(ghi::ImageBufferCopyDescriptor::swapchain(
*swapchain,
capture.destination_buffer,
capture.destination_offset,
capture.destination_bytes_per_row,
capture.destination_bytes_per_image,
))
})
.collect::<SmallVec<[ghi::ImageBufferCopyDescriptor; 16]>>()
};
(sinks, pipeline_manager_commands, render_pass_commands, present_keys, swapchain_capture_copies)
};
execution.record_with_present_keys(command_buffer, &present_keys, |command_buffer_recording| {
let span = debug_span!("Renderer::record_commands", sinks = sinks.len());
let _enter = span.enter();
{
let span = debug_span!("Renderer::record_pipeline_manager_commands");
let _enter = span.enter();
for (pipeline_manager_id, commands) in pipeline_manager_commands {
for (command, sink) in commands.into_iter().zip(sinks.iter()) {
let attachment_infos = render_targets.get_attachment_infos_for_resources(
sink.index(),
pipeline_manager_resources_by_sink
.iter()
.find_map(|(id, sink_id, resources)| {
(*id == pipeline_manager_id && *sink_id == sink.index()).then_some(resources.as_slice())
})
.unwrap_or(&[]),
);
command(&mut *command_buffer_recording, &attachment_infos);
}
}
}
{
let span = debug_span!("Renderer::record_render_pass_commands");
let _enter = span.enter();
for (command, _render_pass_id, sink) in render_pass_commands {
let attachment_infos = render_targets.get_attachment_infos(sink);
command(&mut *command_buffer_recording, &attachment_infos);
}
}
if !swapchain_capture_copies.is_empty() {
let span = debug_span!("Renderer::record_swapchain_captures", captures = swapchain_capture_copies.len());
let _enter = span.enter();
command_buffer_recording.copy_images_to_buffer(&swapchain_capture_copies);
}
});
present_keys
});
}
}
pub fn context_mut(&mut self) -> &mut ghi::implementation::Context {
&mut self.context
}
pub fn create_window(&mut self, window: Window) {
let name = window.name();
let extent = window.extent();
let camera = window.camera();
let features = if window.features().contains(window::Features::DECORATIONS) {
ghi::window::Features::DECORATIONS
} else {
ghi::window::Features::empty()
};
let window = ghi::Window::new_with_params(name, extent, "main_window", features);
match window {
Ok(window) => {
let os_handles = window.os_handles();
let swapchain_handle = self.context.bind_to_window(
&os_handles,
ghi::PresentationModes::FIFO,
extent,
ghi::Uses::RenderTarget | ghi::Uses::Storage,
);
let sink_id = self.windows.len();
let sink_has_camera = if let Some(camera) = camera {
self.sink_cameras.push((sink_id, *camera));
true
} else {
false
};
self.windows.push((window, swapchain_handle));
if sink_has_camera {
if self.defer_first_frame_sink_setup && self.started_frame_count == 0 {
self.pending_sink_initializations.push(sink_id);
} else {
self.initialize_scene_sink(sink_id);
}
}
}
Err(msg) => {
log::error!(
"Failed to create GHI window: {msg}. The most likely cause is missing platform graphics support or an incomplete environment setup. See {}.",
crate::online_docs_url("use/setup/environment")
);
}
}
}
pub fn create_camera(&mut self, handle: Handle, camera: Camera) {
if let Some((_, existing_camera)) = self
.cameras
.iter_mut()
.find(|(existing_handle, _)| *existing_handle == handle)
{
*existing_camera = camera;
return;
}
self.cameras.push((handle, camera));
}
}
struct Attachment {
name: String,
image: ghi::BaseImageHandle,
}
#[derive(Clone, Copy)]
struct SwapchainCapture {
sink_id: SinkId,
destination_buffer: ghi::BaseBufferHandle,
destination_offset: usize,
destination_bytes_per_row: usize,
destination_bytes_per_image: usize,
}
pub struct Settings {
validation: bool,
api_dump: bool,
extended_validation: bool,
debug_labels: bool,
mesh_shading: bool,
}
impl Settings {
pub fn new() -> Self {
Self {
validation: cfg!(debug_assertions),
api_dump: false,
extended_validation: false,
debug_labels: cfg!(debug_assertions),
mesh_shading: true,
}
}
pub fn validation(mut self, value: bool) -> Self {
self.validation = value;
self
}
pub fn api_dump(mut self, value: bool) -> Self {
self.api_dump = value;
self
}
pub fn extended_validation(mut self, value: bool) -> Self {
self.extended_validation = value;
self
}
pub fn debug_labels(mut self, value: bool) -> Self {
self.debug_labels = value;
self
}
pub fn mesh_shading(mut self, value: bool) -> Self {
self.mesh_shading = value;
self
}
}
pub struct RenderTargets {
images: Vec<(ghi::BaseImageHandle, ghi::Formats)>,
by_name: Vec<(usize, String, usize)>,
by_sink_index: Vec<(usize, (usize, ghi::AccessPolicies))>,
}
impl Default for RenderTargets {
fn default() -> Self {
Self::new()
}
}
impl RenderTargets {
pub fn new() -> Self {
Self {
images: Vec::with_capacity(32),
by_name: Vec::with_capacity(32),
by_sink_index: Vec::with_capacity(32),
}
}
pub fn alias(&mut self, sink_id: usize, orig: &str, alias: &str) {
if let Some(index) = self.get_image_index(orig, sink_id) {
self.by_name.push((sink_id, alias.to_string(), index));
}
}
pub fn insert(&mut self, name: String, sink_id: usize, image: ghi::BaseImageHandle, format: ghi::Formats) -> usize {
if self.get_image_index(&name, sink_id).is_some() {
panic!(
"Render target image '{name}' already exists for sink {sink_id}. The most likely cause is that two render pipeline setup paths create the same named target."
);
};
if self.get_attachment_index(&name, sink_id).is_some() {
panic!(
"Render target image '{name}' is already registered as an attachment for sink {sink_id}. The most likely cause is that a target was manually added to the attachment list before insertion."
);
}
let index = self.images.len();
self.images.push((image, format));
self.by_name.push((sink_id, name, index));
self.by_sink_index.push((sink_id, (index, ghi::AccessPolicies::WRITE)));
index
}
pub fn read_from(&mut self, name: &str, sink_id: usize) {
if self.get_attachment_index(name, sink_id).is_some() {
return;
}
let Some(index) = self.get_image_index(name, sink_id) else {
log::warn!(
"Render target image '{name}' does not exist for sink {sink_id}; read attachment was not registered. The most likely cause is that a render pass was added before the pipeline that creates this target."
);
return;
};
self.by_sink_index.push((sink_id, (index, ghi::AccessPolicies::READ)));
}
pub fn write_to(&mut self, name: &str, sink_id: usize) {
if self.get_attachment_index(name, sink_id).is_some() {
return;
}
let Some(index) = self.get_image_index(name, sink_id) else {
log::warn!(
"Render target image '{name}' does not exist for sink {sink_id}; write attachment was not registered. The most likely cause is that a render pass was added before the pipeline that creates this target."
);
return;
};
self.by_sink_index.push((sink_id, (index, ghi::AccessPolicies::WRITE)));
}
pub fn get(&self, name: &str, sink_id: usize) -> Option<&(ghi::BaseImageHandle, ghi::Formats)> {
self.get_image_index(name, sink_id).and_then(|index| self.images.get(index))
}
pub fn get_attachment_infos(&self, sink_id: usize) -> SmallVec<[ghi::AttachmentInformation; 8]> {
let attachments = self
.by_sink_index
.iter()
.filter_map(|(v, (i, ap))| {
if *v == sink_id {
let (image, format) = self.images.get(*i)?;
Some((image, format, ap))
} else {
None
}
})
.filter(|(_, _, access)| access.intersects(ghi::AccessPolicies::WRITE))
.map(|(image, format, access)| {
let load = access.intersects(ghi::AccessPolicies::READ);
let store = access.intersects(ghi::AccessPolicies::WRITE);
let clear_value = if load {
ghi::ClearValue::None
} else {
ghi::ClearValue::Color(RGBA::black())
};
ghi::AttachmentInformation::new(*image, ghi::Layouts::RenderTarget, clear_value, load, store)
});
attachments.collect()
}
pub fn get_attachment_infos_for_resources(
&self,
sink_id: usize,
resources: &[(String, ghi::AccessPolicies)],
) -> SmallVec<[ghi::AttachmentInformation; 8]> {
let mut accesses_by_name = SmallVec::<[(&str, ghi::AccessPolicies); 8]>::new();
for (name, access) in resources {
if let Some((_, existing)) = accesses_by_name
.iter_mut()
.find(|(existing_name, _)| *existing_name == name.as_str())
{
*existing |= *access;
} else {
accesses_by_name.push((name.as_str(), *access));
}
}
accesses_by_name
.into_iter()
.filter_map(|(name, access)| {
if !access.intersects(ghi::AccessPolicies::WRITE) {
return None;
}
let (image, _format) = self.get(name, sink_id)?;
let load = access.intersects(ghi::AccessPolicies::READ);
let clear_value = if load {
ghi::ClearValue::None
} else {
ghi::ClearValue::Color(RGBA::black())
};
Some(ghi::AttachmentInformation::new(
*image,
ghi::Layouts::RenderTarget,
clear_value,
load,
true,
))
})
.collect()
}
fn get_image(&self, name: &str, sink_id: usize) -> &ghi::BaseImageHandle {
let index = self.get_attachment_index(name, sink_id).unwrap();
&self.images.get(index).unwrap().0
}
fn get_image_index(&self, name: &str, sink_id: usize) -> Option<usize> {
self.by_name
.iter()
.rev()
.find(|(sink, n, _)| *sink == sink_id && n == name)
.map(|(_, _, i)| *i)
}
fn get_attachment_index(&self, name: &str, sink_id: usize) -> Option<usize> {
let image_index = self.get_image_index(name, sink_id)?;
self.by_sink_index
.iter()
.find_map(|(v, (i, _))| if *v == sink_id && *i == image_index { Some(*i) } else { None })
}
fn get_images_for_sink(&self, index: usize) -> impl Iterator<Item = &ghi::BaseImageHandle> {
self.by_sink_index.iter().filter_map(move |(v, (i, _))| {
if *v != index {
return None;
}
self.images.get(*i).map(|(image, _)| image)
})
}
}
fn set_render_pass_state_by_name(render_passes: &mut [RenderPassHarness], name: &str, state: RenderPassState) -> usize {
let mut updated = 0;
for render_pass in render_passes {
if render_pass.name() == name {
render_pass.set_state(state);
updated += 1;
}
}
updated
}
const RENDER_PASS_PARAMETER_PREFIX: &str = "render.pass.";
fn render_pass_harness_with_state(
render_pass: Box<dyn RenderPass>,
render_pass_states: &HashMap<String, RenderPassState>,
) -> RenderPassHarness {
let mut harness = RenderPassHarness::new(render_pass);
if let Some(state) = render_pass_states.get(harness.name()) {
harness.set_state(*state);
}
harness
}
fn apply_render_pass_configuration(
configuration: &ConfigurationPort,
pending: &mut VecDeque<PendingRenderPassConfiguration>,
render_pass_states: &mut HashMap<String, RenderPassState>,
render_passes: &mut [RenderPassHarness],
) {
while let Some(update) = configuration.read() {
match PendingRenderPassConfiguration::from_update(update) {
Ok(update) => pending.push_back(update),
Err((id, reason)) => configuration.not_set(id, reason),
}
}
let pending_count = pending.len();
for _ in 0..pending_count {
let update = pending.pop_front().expect("pending configuration count changed");
let updated = set_render_pass_state_by_name(render_passes, &update.render_pass_name, update.state);
if updated == 0 {
pending.push_back(update);
continue;
}
render_pass_states.insert(update.render_pass_name.clone(), update.state);
configuration.set(update.event, ConfigurationValue::from(update.state.as_parameter_value()));
}
}
struct PendingRenderPassConfiguration {
event: ConfigurationEventId,
render_pass_name: String,
state: RenderPassState,
}
impl PendingRenderPassConfiguration {
fn from_update(update: ConfigurationUpdate) -> Result<Self, (ConfigurationEventId, String)> {
let event = update.id();
let Some(render_pass_name) = update.parameter().strip_prefix(RENDER_PASS_PARAMETER_PREFIX) else {
return Err((
event,
"Render pass state was not set. The most likely cause is that the parameter is outside the `render.pass.` namespace."
.to_string(),
));
};
if render_pass_name.is_empty() {
return Err((
event,
"Render pass state was not set. The most likely cause is that the parameter does not name a render pass."
.to_string(),
));
}
let Some(value) = update.value().as_text() else {
return Err((
event,
"Render pass state was not set. The most likely cause is that the requested value is not text.".to_string(),
));
};
let state = match value {
"enabled" => RenderPassState::Enabled,
"bypassed" => RenderPassState::Bypassed,
_ => {
return Err((
event,
"Render pass state was not set. The most likely cause is that the value is neither `enabled` nor `bypassed`."
.to_string(),
));
}
};
Ok(Self {
event,
render_pass_name: render_pass_name.to_string(),
state,
})
}
}
#[cfg(test)]
#[allow(unsafe_code)]
mod tests {
use utils::Box;
use super::*;
use crate::configuration::ConfigurationUpdateState;
struct NamedRenderPass(&'static str);
impl RenderPass for NamedRenderPass {
fn name(&self) -> &'static str {
self.0
}
fn prepare<'a>(
&mut self,
_frame: &mut ghi::implementation::Frame,
_sink: &Sink,
_frame_allocator: &'a bumpalo::Bump,
) -> Option<RenderPassReturn<'a>> {
None
}
fn bypass<'a>(
&mut self,
_frame: &mut ghi::implementation::Frame,
_sink: &Sink,
_frame_allocator: &'a bumpalo::Bump,
) -> Option<RenderPassReturn<'a>> {
None
}
}
#[test]
fn render_pass_state_updates_every_sink_instance_with_the_requested_name() {
let mut render_passes = [
RenderPassHarness::new(Box::new(NamedRenderPass("bloom"))),
RenderPassHarness::new(Box::new(NamedRenderPass("ui"))),
RenderPassHarness::new(Box::new(NamedRenderPass("bloom"))),
];
let updated = set_render_pass_state_by_name(&mut render_passes, "bloom", RenderPassState::Bypassed);
assert_eq!(updated, 2);
assert_eq!(render_passes[0].state(), RenderPassState::Bypassed);
assert_eq!(render_passes[1].state(), RenderPassState::Enabled);
assert_eq!(render_passes[2].state(), RenderPassState::Bypassed);
assert_eq!(
set_render_pass_state_by_name(&mut render_passes, "missing", RenderPassState::Enabled),
0
);
}
#[test]
fn render_configuration_sets_existing_and_future_pass_instances() {
let configuration = Configuration::new();
let port = configuration.register(RENDER_PASS_PARAMETER_PREFIX);
let event = configuration.update("render.pass.bloom", "bypassed");
let mut pending = VecDeque::new();
let mut states = HashMap::new();
let mut passes = [
RenderPassHarness::new(Box::new(NamedRenderPass("bloom"))),
RenderPassHarness::new(Box::new(NamedRenderPass("bloom"))),
];
apply_render_pass_configuration(&port, &mut pending, &mut states, &mut passes);
assert_eq!(passes[0].state(), RenderPassState::Bypassed);
assert_eq!(passes[1].state(), RenderPassState::Bypassed);
assert!(matches!(
configuration.event(event).unwrap().state(),
ConfigurationUpdateState::Set { value }
if value == &ConfigurationValue::from("bypassed")
));
let future = render_pass_harness_with_state(Box::new(NamedRenderPass("bloom")), &states);
assert_eq!(future.state(), RenderPassState::Bypassed);
}
#[test]
fn render_configuration_stays_pending_until_the_pass_exists() {
let configuration = Configuration::new();
let port = configuration.register(RENDER_PASS_PARAMETER_PREFIX);
let event = configuration.update("render.pass.bloom", "bypassed");
let mut pending = VecDeque::new();
let mut states = HashMap::new();
let mut passes = [];
apply_render_pass_configuration(&port, &mut pending, &mut states, &mut passes);
assert_eq!(pending.len(), 1);
assert_eq!(
configuration.event(event).unwrap().state(),
&ConfigurationUpdateState::Pending
);
let mut passes = [RenderPassHarness::new(Box::new(NamedRenderPass("bloom")))];
apply_render_pass_configuration(&port, &mut pending, &mut states, &mut passes);
assert_eq!(pending.len(), 0);
assert_eq!(passes[0].state(), RenderPassState::Bypassed);
}
#[test]
fn render_configuration_reports_an_unsupported_state() {
let configuration = Configuration::new();
let port = configuration.register(RENDER_PASS_PARAMETER_PREFIX);
let event = configuration.update("render.pass.bloom", "disabled");
let mut pending = VecDeque::new();
let mut states = HashMap::new();
let mut passes = [RenderPassHarness::new(Box::new(NamedRenderPass("bloom")))];
apply_render_pass_configuration(&port, &mut pending, &mut states, &mut passes);
assert!(matches!(
configuration.event(event).unwrap().state(),
ConfigurationUpdateState::NotSet { reason } if reason.contains("neither `enabled` nor `bypassed`")
));
assert_eq!(passes[0].state(), RenderPassState::Enabled);
}
#[test]
fn test_render_targets_new() {
let rt = RenderTargets::new();
assert!(rt.images.is_empty());
assert!(rt.by_name.is_empty());
assert!(rt.by_sink_index.is_empty());
}
#[test]
fn test_insert_and_get() {
let mut rt = RenderTargets::new();
let image = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(1) };
let format = ghi::Formats::RGBA8UNORM;
let index = rt.insert("test".to_string(), 0, image, format);
assert_eq!(index, 0);
let retrieved = rt.get("test", 0);
assert!(retrieved.is_some());
assert_eq!(rt.get("nonexistent", 0), None);
}
#[test]
fn test_insert_multiple() {
let mut rt = RenderTargets::new();
let image1 = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(1) };
let format1 = ghi::Formats::RGBA8UNORM;
let image2 = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(2) };
let format2 = ghi::Formats::Depth32;
rt.insert("color".to_string(), 0, image1, format1);
rt.insert("depth".to_string(), 0, image2, format2);
assert!(rt.get("color", 0).is_some());
assert!(rt.get("depth", 0).is_some());
}
#[test]
fn test_get_attachment_infos() {
let mut rt = RenderTargets::new();
let image1 = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(1) };
let format1 = ghi::Formats::RGBA8UNORM;
let image2 = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(2) };
let format2 = ghi::Formats::Depth32;
rt.insert("color".to_string(), 0, image1, format1);
rt.insert("depth".to_string(), 0, image2, format2);
rt.insert(
"other".to_string(),
1,
unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(3) },
ghi::Formats::RGBA16UNORM,
);
let attachments = rt.get_attachment_infos(0);
assert_eq!(attachments.len(), 2);
let attachments_view1 = rt.get_attachment_infos(1);
assert_eq!(attachments_view1.len(), 1);
}
#[test]
fn test_get_attachment_infos_empty_view() {
let rt = RenderTargets::new();
let attachments = rt.get_attachment_infos(0);
assert!(attachments.is_empty());
}
#[test]
fn test_alias_overrides_previous_mapping() {
let mut rt = RenderTargets::new();
let first_image = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(1) };
let second_image = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(2) };
rt.insert("first".to_string(), 0, first_image, ghi::Formats::RGBA16UNORM);
rt.insert("second".to_string(), 0, second_image, ghi::Formats::RGBA16UNORM);
rt.alias(0, "first", "main");
rt.alias(0, "second", "main");
let (image, _) = rt.get("main", 0).expect("main alias should resolve");
assert_eq!(*image, second_image);
}
#[test]
fn test_insert_same_name_for_different_sinks() {
let mut rt = RenderTargets::new();
let image1 = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(1) };
let image2 = unsafe { std::mem::transmute::<u64, ghi::BaseImageHandle>(2) };
rt.insert("main".to_string(), 0, image1, ghi::Formats::RGBA16UNORM);
rt.insert("main".to_string(), 1, image2, ghi::Formats::RGBA16UNORM);
let (sink0_image, _) = rt.get("main", 0).expect("sink 0 main should resolve");
let (sink1_image, _) = rt.get("main", 1).expect("sink 1 main should resolve");
assert_eq!(*sink0_image, image1);
assert_eq!(*sink1_image, image2);
}
}
type RenderPassFactory = dyn for<'a> Fn(&'a mut RenderPassBuilder<'a>) -> Box<dyn RenderPass>;
type SinkId = usize;
type RenderPassId = usize;
type PipelineManagerId = usize;
use std::{
collections::VecDeque,
io::Write,
ops::{Deref, DerefMut},
rc::Rc,
sync::Arc,
};
use ghi::{
command_buffer::{
BoundComputePipelineMode as _, BoundRasterizationPipelineMode as _, CommandBufferRecording,
RasterizationRenderPassMode as _,
},
context::{Context as _, ContextCreate as _},
device::Device as _,
frame::Frame as _,
queue::{Queue as _, QueueExecution as _},
};
use math::direction_from_orientation;
use resource_management::resource::resource_manager::ResourceManager;
use smallvec::SmallVec;
use tracing::debug_span;
use utils::Box;
use utils::{
hash::{HashMap, HashMapExt},
sync::RwLock,
Extent, RGBA,
};
use super::render_pass::{RenderPass, RenderPassBuilder, RenderPassHarness, RenderPassState};
use crate::{
application::parameters::Parameters,
configuration::{Configuration, ConfigurationEventId, ConfigurationPort, ConfigurationUpdate, ConfigurationValue},
core::{
channel::{Channel, DefaultChannel},
factory::Handle,
listener::Listener,
Entity, EntityHandle,
},
gameplay::transform::TransformationUpdate,
rendering::{
make_perspective_view_from_camera,
pipeline_manager::PipelineManager,
render_pass::{FramePrepare, RenderPassReturn},
window::{self, Window},
Camera, Sink, View,
},
space::{Orientable as _, Positionable as _},
};