use crate::config::render_settings_signature;
use crate::mesh_cache::{MeshCache, mesh_cache_take_dirty, mesh_cache_take_dirty_skinned};
use crate::mesh_state::MeshRenderStateInner;
use crate::wgpu::WgpuRenderer;
use crate::wgpu::passes;
use crate::wgpu::passes::geometry::lines::LinesPass;
use crate::wgpu::render_configs::RenderInputs;
use crate::wgpu::rendergraph::{RenderGraph, render_graph_set_pass_enabled};
pub fn apply_frame_dirty_state(
renderer: &mut WgpuRenderer,
frame_state: MeshRenderStateInner,
mesh_cache: &mut MeshCache,
) {
let dirty_meshes = mesh_cache_take_dirty(mesh_cache);
let dirty_skinned = mesh_cache_take_dirty_skinned(mesh_cache);
let shadow_scene_dirty = !frame_state.transform_dirty.is_empty()
|| !frame_state.entities_added.is_empty()
|| !frame_state.entities_removed.is_empty()
|| frame_state.instanced_meshes_changed
|| !dirty_meshes.is_empty()
|| !dirty_skinned.is_empty();
if let Some(pass) = crate::wgpu::pass_access::shadow_depth_pass_mut(&mut renderer.graph) {
pass.shadow_scene_dirty |= shadow_scene_dirty;
}
if let Some(pass) = crate::wgpu::pass_access::mesh_pass_mut(&mut renderer.graph) {
let dirty_ids: std::collections::HashSet<u32> = dirty_meshes
.iter()
.filter_map(|name| pass.mesh_id_for_name(name))
.collect();
match pass.frame_dirty.take() {
Some(mut unconsumed) if !pass.frame_dirty_consumed => {
unconsumed.merge_newer(frame_state);
pass.frame_dirty = Some(unconsumed);
pass.dirty_mesh_ids.extend(dirty_ids);
}
_ => {
pass.frame_dirty = Some(frame_state);
pass.dirty_mesh_ids = dirty_ids;
}
}
pass.frame_dirty_consumed = false;
}
if let Some(pass) = crate::wgpu::pass_access::skinned_mesh_pass_mut(&mut renderer.graph) {
pass.dirty_skinned_mesh_names.extend(dirty_skinned);
}
}
pub fn rebind_glyph_atlas(renderer: &mut WgpuRenderer) {
if let Some(overlay_pass) =
crate::wgpu::pass_access::scene_overlay_pass_mut(&mut renderer.graph)
{
overlay_pass
.text
.update_glyph_atlas(&renderer.device, &renderer.glyph_atlas.texture_view);
}
if let Some(ui_pass) = crate::wgpu::pass_access::ui_pass_mut(&mut renderer.graph) {
ui_pass.update_glyph_atlas(&renderer.device, &renderer.glyph_atlas.texture_view);
}
}
pub fn lines_pass_mut(graph: &mut RenderGraph<RenderInputs>) -> Option<&mut LinesPass> {
let overlay_pass = crate::wgpu::pass_access::scene_overlay_pass_mut(graph)?;
Some(&mut overlay_pass.lines)
}
pub fn set_color_lut(renderer: &mut WgpuRenderer, data: Vec<u8>) {
if let Some(pass) = crate::wgpu::pass_access::postprocess_pass_mut(&mut renderer.graph) {
pass.set_color_lut(data);
}
}
pub fn sync_point_shadow_cubemap(renderer: &mut WgpuRenderer) {
let point_shadow_view = crate::wgpu::pass_access::shadow_depth_pass_mut(&mut renderer.graph)
.map(|shadow_pass| shadow_pass.point_light_cubemap_view().clone());
if let Some(view) = point_shadow_view {
if let Some(mesh_pass) = crate::wgpu::pass_access::mesh_pass_mut(&mut renderer.graph) {
mesh_pass.update_point_shadow_cubemap(view.clone());
}
if let Some(skinned_mesh_pass) =
crate::wgpu::pass_access::skinned_mesh_pass_mut(&mut renderer.graph)
{
skinned_mesh_pass.update_point_shadow_cubemap(view);
}
}
}
pub struct ClothWriteBounds {
pub mesh_name: String,
pub center: [f32; 3],
pub sphere_radius: f32,
}
pub fn set_cloth_write_targets(renderer: &mut WgpuRenderer, cloths: &[ClothWriteBounds]) {
if cloths.is_empty() {
return;
}
let mut targets: std::collections::HashMap<String, Vec<passes::geometry::ClothWriteTarget>> =
std::collections::HashMap::new();
if let Some(mesh_pass) = crate::wgpu::pass_access::mesh_pass_mut(&mut renderer.graph) {
for cloth in cloths {
mesh_pass.override_mesh_bounds(
&renderer.queue,
&cloth.mesh_name,
cloth.center,
cloth.sphere_radius,
);
if let Some((buffer, buffer_generation, vertex_offset)) =
mesh_pass.vertex_write_target(&cloth.mesh_name)
{
targets.entry(cloth.mesh_name.clone()).or_default().push(
passes::geometry::ClothWriteTarget {
buffer,
buffer_generation,
vertex_offset,
},
);
}
}
}
if let Some(shadow_pass) = crate::wgpu::pass_access::shadow_depth_pass_mut(&mut renderer.graph)
{
for cloth in cloths {
if let Some((buffer, buffer_generation, vertex_offset)) =
shadow_pass.vertex_write_target(&cloth.mesh_name)
{
targets.entry(cloth.mesh_name.clone()).or_default().push(
passes::geometry::ClothWriteTarget {
buffer,
buffer_generation,
vertex_offset,
},
);
}
}
}
if let Some(cloth_pass) = crate::wgpu::pass_access::cloth_pass_mut(&mut renderer.graph) {
cloth_pass.set_write_targets(targets);
}
}
pub fn sync_conditional_pass_toggles(
renderer: &mut WgpuRenderer,
inputs: &RenderInputs,
picking_active: bool,
has_skinned_meshes: bool,
) {
let view = &inputs.scene.active_view;
let _ = render_graph_set_pass_enabled(&mut renderer.graph, "ssao_pass", view.ssao_enabled);
let _ = render_graph_set_pass_enabled(&mut renderer.graph, "ssao_blur_pass", view.ssao_enabled);
let _ = render_graph_set_pass_enabled(&mut renderer.graph, "bloom_pass", view.bloom_enabled);
let _ = render_graph_set_pass_enabled(
&mut renderer.graph,
"depth_of_field_pass",
view.depth_of_field.enabled,
);
let _ =
render_graph_set_pass_enabled(&mut renderer.graph, "pick_keepalive_pass", picking_active);
let _ =
render_graph_set_pass_enabled(&mut renderer.graph, "skinned_mesh_pass", has_skinned_meshes);
let taa_on = inputs.settings.taa_enabled;
let _ = render_graph_set_pass_enabled(&mut renderer.graph, "taa_pass", taa_on);
let _ = render_graph_set_pass_enabled(&mut renderer.graph, "taa_passthrough", !taa_on);
}
fn taa_halton(index: u32, base: u32) -> f32 {
let mut result = 0.0;
let mut fraction = 1.0;
let mut current = index;
while current > 0 {
fraction /= base as f32;
result += fraction * (current % base) as f32;
current /= base;
}
result
}
pub fn sync_taa_jitter(renderer: &WgpuRenderer, inputs: &mut RenderInputs) {
if inputs.settings.taa_enabled {
let jitter_index = (renderer.frame_state.index % 8) as u32 + 1;
let (render_width, render_height) = renderer.render_buffer_size;
let render_scale = inputs.settings.render_scale.clamp(0.25, 4.0);
inputs.scene.taa_jitter = [
(taa_halton(jitter_index, 2) - 0.5) * 2.0 * render_scale / render_width.max(1) as f32,
(taa_halton(jitter_index, 3) - 0.5) * 2.0 * render_scale / render_height.max(1) as f32,
];
} else {
inputs.scene.taa_jitter = [0.0, 0.0];
}
}
pub fn sync_settings_version(renderer: &mut WgpuRenderer, inputs: &mut RenderInputs) {
let current_settings_signature = render_settings_signature(
&inputs.settings,
&inputs.debug_draw,
&inputs.scene.render_selection_outline_ids,
inputs.scene.render_selection_outline_color,
);
if renderer.frame_state.last_settings_signature != Some(current_settings_signature) {
inputs.scene.settings_version = inputs.scene.settings_version.wrapping_add(1);
renderer.frame_state.last_settings_signature = Some(current_settings_signature);
}
}
pub fn sync_gpu_profile(renderer: &WgpuRenderer, inputs: &mut RenderInputs) {
if inputs.scene.gpu_profile.is_none() {
inputs.scene.gpu_profile = Some(renderer.gpu_profile.clone());
if renderer.gpu_profile.backend == crate::config::GpuBackend::Metal {
inputs.scene.occlusion_culling_enabled = false;
}
}
}
#[cfg(feature = "egui")]
pub fn prepare_egui_pass(
renderer: &mut WgpuRenderer,
textures_delta: &egui::TexturesDelta,
pixels_per_point: f32,
paint_jobs: Vec<egui::ClippedPrimitive>,
) {
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [
renderer.surface_config.width,
renderer.surface_config.height,
],
pixels_per_point,
};
if let Some(egui_pass) = crate::wgpu::pass_access::egui_pass_mut(&mut renderer.graph) {
egui_pass.update_textures(&renderer.device, &renderer.queue, textures_delta);
egui_pass.set_screen_descriptor(screen_descriptor);
egui_pass.set_paint_jobs(paint_jobs);
}
}