use super::super::{DeviceHandle, RenderTargetHandle};
use super::render_commands::{create_render_pass, record};
use super::types::{MetalState, RenderTargetState};
use super::utils::format_to_mtl;
use crate::types::{DepthFormat, TextureFormat};
use ::metal as mtl;
use anyhow::{Context, Result};
use mtl::{MTLStorageMode, MTLTextureUsage, TextureDescriptor};
pub(super) fn create_with_depth(
state: &mut MetalState,
device_handle: DeviceHandle,
width: u32,
height: u32,
color_format: TextureFormat,
depth_format: Option<DepthFormat>,
) -> Result<RenderTargetHandle> {
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
let descriptor = TextureDescriptor::new();
descriptor.set_width(width as u64);
descriptor.set_height(height as u64);
descriptor.set_pixel_format(format_to_mtl(color_format));
descriptor.set_usage(MTLTextureUsage::RenderTarget | MTLTextureUsage::ShaderRead);
descriptor.set_storage_mode(MTLStorageMode::Private);
let texture = logical_device.device.new_texture(&descriptor);
let depth_texture = depth_format.map(|df| {
let depth_desc = TextureDescriptor::new();
depth_desc.set_width(width as u64);
depth_desc.set_height(height as u64);
depth_desc.set_pixel_format(super::utils::depth_format_to_mtl(df));
depth_desc.set_usage(MTLTextureUsage::RenderTarget);
depth_desc.set_storage_mode(MTLStorageMode::Private);
logical_device.device.new_texture(&depth_desc)
});
let handle = state.next_render_target_handle;
state.next_render_target_handle += 1;
state.render_targets.insert(
handle,
RenderTargetState {
device_handle,
width,
height,
texture,
depth_texture,
},
);
tracing::debug!(
"Created render target {} ({}x{}, {:?})",
handle,
width,
height,
color_format
);
Ok(handle)
}
pub(super) fn render_to(
state: &mut MetalState,
device_handle: DeviceHandle,
target: RenderTargetHandle,
color_load: crate::types::TargetLoad,
commands: &[super::super::RenderCommand],
) -> Result<()> {
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
let (staging_data, lowered_commands, has_bindings) =
super::frame_table::prepare_render_commands(&state.buffers, &state.pipelines, commands)?;
let completed = super::context::device_retired(state, device_handle);
let prologue_row = if has_bindings {
Some(super::frame_table::run_prologue_for_device(
state,
device_handle,
logical_device,
&staging_data,
completed,
)?)
} else {
None
};
let render_target = state.render_targets.get(&target).context("Invalid render target")?;
let clear_depth = commands.iter().find_map(|cmd| match cmd {
super::super::RenderCommand::ClearDepth(depth) => Some(*depth),
_ => None,
});
let render_pass = create_render_pass(
&render_target.texture,
render_target.depth_texture.as_deref(),
color_load,
clear_depth,
);
let command_buffer = logical_device.command_queue.new_command_buffer();
let encoder = command_buffer.new_render_command_encoder(render_pass);
let render_stages = mtl::MTLRenderStages::Vertex | mtl::MTLRenderStages::Fragment;
logical_device
.heap_allocator
.lock()
.unwrap()
.use_heaps_for_render(encoder, render_stages);
logical_device
.texture_heap
.lock()
.unwrap()
.use_heaps_for_render(encoder, render_stages);
for buf_state in state.buffers.values() {
if buf_state.device_handle == render_target.device_handle {
encoder.use_resource_at(
&buf_state.buffer,
mtl::MTLResourceUsage::Read | mtl::MTLResourceUsage::Write,
render_stages,
);
}
}
{
let ft = logical_device.frame_table.lock().unwrap();
encoder.use_resource_at(ft.table_buffer(), mtl::MTLResourceUsage::Read, render_stages);
}
encoder.set_vertex_buffer(0, Some(&logical_device.argument_buffer), 0);
encoder.set_fragment_buffer(0, Some(&logical_device.argument_buffer), 0);
encoder.set_viewport(mtl::MTLViewport {
originX: 0.0,
originY: 0.0,
width: render_target.width as f64,
height: render_target.height as f64,
znear: 0.0,
zfar: 1.0,
});
encoder.set_scissor_rect(mtl::MTLScissorRect {
x: 0,
y: 0,
width: render_target.width as u64,
height: render_target.height as u64,
});
record(
encoder,
&lowered_commands,
&state.pipelines,
&state.buffers,
prologue_row,
)?;
encoder.end_encoding();
command_buffer.commit();
command_buffer.wait_until_completed();
if let Some(row) = prologue_row {
if let Some(ld) = state.devices.get(&device_handle) {
super::frame_table::record_submission_for_device(ld, row, completed);
}
}
Ok(())
}