#![deny(unsafe_op_in_unsafe_fn)]
use concinnity_core::gfx::render_types::{ClusterParams, PostProcessParams};
use concinnity_core::render::backend_init::{BackendInit, PostSettings};
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::scene_state::{DrawList, SceneState};
use concinnity_core::render::view_history::ViewHistory;
use self::effects::EffectSettings;
use super::context::*;
use super::frame_pacing::FrameInFlight;
use super::line::LineState;
use super::post::UpscaleState;
use super::resources::skinning::SkinnedState;
mod arg_buffers;
mod bloom;
mod bootstrap;
mod commands;
mod composite;
mod cull;
pub(super) mod effects;
pub(crate) mod pipelines;
pub(super) mod ray_tracing;
mod scene_assets;
mod scene_data;
mod shadow;
pub(super) mod targets;
mod text;
pub(super) mod world_fx;
pub(crate) use bootstrap::set_display_sync;
struct InitGpu<'a> {
hw: &'a MtlHardware,
hot_reload: bool,
frames_in_flight: usize,
}
struct Features {
scene: bool,
hdr_samples: u32,
output: (u32, u32),
render: (u32, u32),
taa_enabled: bool,
ssao_enabled: bool,
gbuffer_enabled: bool,
post_process: PostProcessParams,
}
impl Features {
fn resolve(
hw: &MtlHardware,
post: &PostSettings,
scene: bool,
output: (u32, u32),
upscale: &UpscaleState,
) -> Self {
let hdr_samples = post.hdr_samples.max(1);
tracing::info!("metal HDR target: {hdr_samples}x MSAA");
let render = match &upscale.scaler {
Some(u) => (u.input_width, u.input_height),
None => output,
};
let upscaling_active = upscale.scaler.is_some();
let taa_enabled = post.taa_enabled && !upscaling_active;
let needs_velocity = taa_enabled || upscaling_active;
Self {
scene,
hdr_samples,
output,
render,
taa_enabled,
ssao_enabled: post.ssao.is_some(),
gbuffer_enabled: EffectSettings::from_post(post).gbuffer_needed(needs_velocity),
post_process: hw.hdr_mode.post_process_params(post.post_process),
}
}
}
impl MtlContext {
pub(crate) fn new(init: BackendInit<'_>) -> RenderResult<Self> {
Self::build(init, None)
}
fn build(init: BackendInit<'_>, reuse: Option<MtlHardware>) -> RenderResult<Self> {
let BackendInit {
window,
validation: _,
frames_in_flight,
vsync,
clear_color,
hot_reload,
capture,
embedded_surface,
scene: world,
shaders: world_shaders,
media,
light_uniforms,
local_lights,
spot_shadows,
area_lights,
shadows,
anisotropy,
planar,
post,
fx,
requirements,
} = init;
let (hw, output) = bootstrap::setup(
reuse,
bootstrap::WindowConfig {
title: window.title.as_str(),
width: window.width,
height: window.height,
title_bar: window.title_bar,
geometry_less: !requirements.scene,
capture_enabled: capture,
embedded: embedded_surface,
},
bootstrap::HdrRequest {
display_requested: post.hdr_display,
pq_requested: post.hdr_pq,
},
frames_in_flight,
vsync,
)?;
let gpu = InitGpu {
hw: &hw,
hot_reload,
frames_in_flight,
};
let upscale = effects::build_upscale(&gpu, output, &post);
let features = Features::resolve(&hw, &post, requirements.scene, output, &upscale);
let vert_desc = pipelines::make_vertex_descriptor();
let scene = scene_assets::build_scene_assets(
&gpu,
scene_assets::SceneInputs {
world: &world,
media: &media,
local_lights: &local_lights,
area_lights: &area_lights,
anisotropy,
bindless: features.scene,
},
)?;
let targets = targets::build_targets(&gpu, &features)?;
let light_cull = scene_data::build_light_cull(&gpu)?;
let shadow = shadow::build_shadow(&gpu, &shadows, &light_uniforms)?;
let spot_shadow = shadow::build_spot_shadow(&gpu, &spot_shadows, shadows.map_size)?;
let cull = cull::build_cull(
&gpu,
cull::CullInputs {
world_shaders: &world_shaders,
vert_desc: &vert_desc,
features: &features,
shadow_enabled: shadow.enabled,
occlusion_two_pass: post.occlusion_two_pass,
},
)?;
let probe = cull::build_probe(&gpu, &cull)?;
let arg_buffers = arg_buffers::build_arg_buffers(&gpu, &cull, &scene, &shadow)?;
let text = text::build_text(&gpu, &media.text_atlases)?;
let composite = composite::build_composite(&gpu)?;
let post_device = effects::post_device(&gpu, &composite, &scene);
let bloom = bloom::build_bloom(&post_device, features.scene, features.output)?;
let settings = EffectSettings::from_post(&post);
let taa = effects::build_taa(&post_device, features.taa_enabled, features.render)?;
let ssao = effects::build_ssao(&post_device, &hw.allocator, &settings, features.render)?;
let ssr = effects::build_ssr(&post_device, &settings, features.render)?;
let gbuffer = effects::build_gbuffer(
&hw.device,
features.gbuffer_enabled,
features.render,
hot_reload,
)?;
let ssgi = effects::build_ssgi(&post_device, &settings, features.render)?;
let auto_exposure =
effects::build_auto_exposure(&hw.device, &settings, frames_in_flight, hot_reload)?;
let decal = world_fx::build_decals(&gpu, fx.decals)?;
let fog = world_fx::build_fog(&gpu, fx.fog)?;
let particle = world_fx::build_particles(&gpu, fx.particles)?;
let planar = world_fx::plan_planar(&fx.water_surfaces, &fx.glass_panels, planar);
let n_water = fx.water_surfaces.len();
let slots = &planar.assignment.slots;
let water = world_fx::build_water(&gpu, &fx.water_surfaces, &slots[..n_water])?;
let glass = world_fx::build_glass(
&gpu,
&fx.glass_panels,
&slots[n_water..],
&world.draw_objects,
)?;
let planar_reflection = world_fx::build_planar_reflection(&gpu, planar, &features)?;
let raymarch = world_fx::build_raymarch(&gpu, &fx.sdf_volumes)?;
let sky = super::sky::SkyState::build(
&hw.device,
features.hdr_samples,
media.env_map_background,
hot_reload,
)?;
let diagnostics = commands::build_diagnostics(&gpu);
let rt = ray_tracing::build_ray_tracing(
&gpu,
ray_tracing::RtInputs {
world: &world,
scene: &scene,
glass: &glass,
post: &post,
},
)?;
let instanced = cull::build_instanced(world.instanced_clusters, scene.textures.len());
let rings = commands::build_rings(&gpu, world.material_params);
let n_instances = instanced.clusters.iter().map(|c| c.instances.len()).sum();
let ctx = Self {
last_present_texture: None,
cull,
arg_buffers,
state: SceneState::new(
DrawList::unreserved(world.draw_objects, n_instances),
clear_color,
),
graph_cache: None,
instanced,
scene,
light_uniforms,
shadow,
spot_shadow,
probe,
text,
targets,
composite,
bloom,
post_process: features.post_process,
taa,
view_history: ViewHistory::default(),
upscale,
ssao,
ssr,
gbuffer,
ssgi,
rt,
lines: LineState::new(frames_in_flight),
decal,
fog,
light_cull,
cluster_params: ClusterParams::ZERO,
particle,
auto_exposure,
hot_reload: HotReloadState::new(hot_reload),
world_shader: world_shaders[0].programs.cloned(),
capture,
skinned: SkinnedState::new(),
diagnostics,
frame_pacing: FrameInFlight::new(frames_in_flight),
frames_in_flight: frames_in_flight.max(1),
frame_ring_index: 0,
rings,
water,
planar_reflection,
glass,
raymarch,
sky,
hw,
};
let pooled = ctx.hw.allocator.stats();
tracing::info!(
"device allocator: {} heap(s), {} KiB reserved for {} KiB of resources",
pooled.block_count,
pooled.reserved_bytes / 1024,
pooled.in_use_bytes / 1024,
);
crate::shader::cache::report_init();
crate::shader::runtime_cache::checkpoint();
Ok(ctx)
}
pub(super) fn apply_world_reload(&mut self, init: BackendInit<'_>) -> RenderResult<()> {
debug_assert_main_thread("apply_world_reload");
self.wait_idle();
let reuse = self.hw.hand_over()?;
*self = MtlContext::build(init, Some(reuse))?;
Ok(())
}
}