use crate::assets::loading::{
loading_pipeline_is_active, loading_pipeline_mark_completed, loading_pipeline_poll_decodes,
loading_pipeline_pop, loading_pipeline_recipe_ready, loading_pipeline_release_recipe_sources,
loading_pipeline_requeue_front, loading_pipeline_reset_source_arena,
};
use crate::render::wgpu::pass_sync;
use crate::render::wgpu::presentation;
use crate::render::wgpu::render_configs::FrameOutputs;
use crate::render::wgpu::texture_uploads;
pub fn render_frame(
renderer: &mut crate::render::wgpu::WgpuRenderer,
world: &mut crate::ecs::world::World,
) -> Result<(), crate::render::wgpu::rendergraph::RenderGraphError> {
poll_depth_pick_readback(renderer, world);
drain_loading_tasks(renderer, world);
super::extract::prepare_text_meshes(renderer, world);
let mut inputs = super::inputs::compose_render_inputs(world);
let mut outputs = FrameOutputs::default();
let result = crate::render::wgpu::frame::render_frame(renderer, &mut inputs, &mut outputs);
super::inputs::restore_render_inputs(world, inputs, outputs);
result
}
pub fn configure_with_state(
renderer: &mut crate::render::wgpu::WgpuRenderer,
state: &mut dyn crate::state::State,
) -> Result<(), Box<dyn std::error::Error>> {
pass_sync::rebind_glyph_atlas(renderer);
state.configure_render_graph(
&mut renderer.graph,
&renderer.device,
renderer.surface_format,
crate::state::RenderResources {
scene_color: renderer.targets.scene_color,
depth: renderer.targets.depth,
compute_output: renderer.targets.compute_output,
swapchain: renderer.targets.swapchain,
view_normals: renderer.targets.view_normals,
velocity: renderer.targets.velocity,
ssao_raw: renderer.targets.ssao_raw,
ssao: renderer.targets.ssao,
ssgi_raw: renderer.targets.ssgi_raw,
ssgi: renderer.targets.ssgi,
ssr_raw: renderer.targets.ssr_raw,
ssr: renderer.targets.ssr,
surface_width: renderer.surface_config.width,
surface_height: renderer.surface_config.height,
},
);
presentation::install_presentation_passes(renderer)
}
pub fn update_with_state(
renderer: &mut crate::render::wgpu::WgpuRenderer,
state: &mut dyn crate::state::State,
world: &mut crate::ecs::world::World,
) -> Result<(), Box<dyn std::error::Error>> {
let pending_font_loads: Vec<_> = world
.res_mut::<crate::assets::loading::LoadingState>()
.pending_font_loads
.drain(..)
.collect();
for pending_load in pending_font_loads {
world
.res_mut::<crate::ecs::text::resources::TextState>()
.font_engine
.load_font(pending_load.font_data);
}
state.update_render_graph(&mut renderer.graph, world);
sync_cloth_write_targets(renderer, world);
Ok(())
}
pub(crate) fn sync_cloth_write_targets(
renderer: &mut crate::render::wgpu::WgpuRenderer,
world: &crate::ecs::world::World,
) {
let mut cloths: Vec<pass_sync::ClothWriteBounds> = Vec::new();
for (_entity, (cloth, render_mesh)) in world
.query_ref::<(
&crate::ecs::cloth::components::Cloth,
&crate::ecs::mesh::components::RenderMesh,
)>()
.iter()
{
let bounds = crate::ecs::cloth::systems::cloth_bounding_volume(cloth);
cloths.push(pass_sync::ClothWriteBounds {
mesh_name: render_mesh.name.clone(),
center: [
bounds.obb.center.x,
bounds.obb.center.y,
bounds.obb.center.z,
],
sphere_radius: bounds.sphere_radius,
});
}
pass_sync::set_cloth_write_targets(renderer, &cloths);
}
pub(crate) fn poll_depth_pick_readback(
renderer: &mut crate::render::wgpu::WgpuRenderer,
world: &mut crate::ecs::world::World,
) {
let Some(readback) = crate::render::wgpu::picking::poll_depth_pick(renderer) else {
return;
};
world
.res_mut::<crate::ecs::gpu_picking::GpuPicking>()
.set_depth_samples(
readback.depth_values,
readback.entity_id_values,
readback.sample_size,
readback.sample_size,
readback.center.0,
readback.center.1,
);
if let Some(camera_entity) = readback.camera
&& let Some(matrices) =
crate::ecs::camera::queries::query_camera_matrices(world, camera_entity)
{
let (texture_width, texture_height) = readback.texture_size;
let inverse_view_proj = (matrices.projection * matrices.view)
.try_inverse()
.unwrap_or_else(nalgebra_glm::Mat4::identity);
world
.res_mut::<crate::ecs::gpu_picking::GpuPicking>()
.compute_result(
&inverse_view_proj,
texture_width as f32,
texture_height as f32,
);
}
}
pub(crate) fn drain_loading_tasks(
renderer: &mut crate::render::wgpu::WgpuRenderer,
world: &mut crate::ecs::world::World,
) {
loading_pipeline_poll_decodes(
&mut world
.res_mut::<crate::assets::loading::LoadingState>()
.pipeline,
crate::assets::loading::DECODE_POLL_BUDGET,
);
let budget = world
.res::<crate::assets::loading::LoadingState>()
.pipeline
.tasks_per_frame
.max(1);
let mut uploaded_textures: Vec<crate::render::asset_id::TextureId> = Vec::new();
let mut deferred: Vec<crate::assets::loading::LoadingTask> = Vec::new();
let mut reserved_any = false;
let mut uploads = 0usize;
while uploads < budget {
let Some(task) = loading_pipeline_pop(
&mut world
.res_mut::<crate::assets::loading::LoadingState>()
.pipeline,
) else {
break;
};
let category = task.category();
let label = match &task {
crate::assets::loading::LoadingTask::ReserveLayer { texture, .. }
| crate::assets::loading::LoadingTask::UploadDecodedTexture { texture, .. }
| crate::assets::loading::LoadingTask::MaterializeTexture { texture, .. } => {
crate::render::wgpu::texture_cache::texture_cache_texture_name(
world.res::<crate::render::wgpu::texture_cache::TextureCache>(),
texture.index,
)
.map(str::to_string)
.unwrap_or_else(|| format!("texture {}", texture.index))
}
};
match task {
crate::assets::loading::LoadingTask::ReserveLayer {
texture,
usage,
sampler,
} => {
if texture_uploads::reserve_material_layer(
renderer,
world.res::<crate::render::wgpu::texture_cache::TextureCache>(),
texture,
usage,
sampler,
) {
reserved_any = true;
}
}
crate::assets::loading::LoadingTask::UploadDecodedTexture {
texture,
rgba_data,
width,
height,
usage,
sampler,
} => {
texture_uploads::upload_material_texture(
renderer,
world.res_mut::<crate::render::wgpu::texture_cache::TextureCache>(),
texture_uploads::MaterialTextureUploadRequest {
texture,
rgba_data: &rgba_data,
width,
height,
usage,
sampler,
},
);
uploaded_textures.push(texture);
uploads += 1;
}
crate::assets::loading::LoadingTask::MaterializeTexture {
texture,
recipe,
usage,
sampler,
} => {
if !loading_pipeline_recipe_ready(
&world.res::<crate::assets::loading::LoadingState>().pipeline,
&recipe,
) {
deferred.push(crate::assets::loading::LoadingTask::MaterializeTexture {
texture,
recipe,
usage,
sampler,
});
continue;
}
let produced = crate::assets::loading::execute_texture_recipe(
&recipe,
&world
.res::<crate::assets::loading::LoadingState>()
.pipeline
.decoded_images,
);
loading_pipeline_release_recipe_sources(
&mut world
.res_mut::<crate::assets::loading::LoadingState>()
.pipeline,
&recipe,
);
if let Some(decoded) = produced {
let texture_name =
crate::render::wgpu::texture_cache::texture_cache_texture_name(
world.res::<crate::render::wgpu::texture_cache::TextureCache>(),
texture.index,
)
.map(str::to_string);
if let Some(name) = texture_name {
world
.res_mut::<crate::assets::texture_source::TextureSources>()
.entry(name)
.or_insert_with(|| crate::assets::texture_source::TextureSourceBytes {
data: crate::assets::texture_source::TextureSourceData::Rgba {
rgba: decoded.rgba.clone(),
width: decoded.width,
height: decoded.height,
},
usage,
sampler,
});
}
texture_uploads::upload_material_texture(
renderer,
world.res_mut::<crate::render::wgpu::texture_cache::TextureCache>(),
texture_uploads::MaterialTextureUploadRequest {
texture,
rgba_data: &decoded.rgba,
width: decoded.width,
height: decoded.height,
usage,
sampler,
},
);
uploaded_textures.push(texture);
} else {
tracing::warn!("texture recipe produced no data for '{}'", label);
}
uploads += 1;
}
}
loading_pipeline_mark_completed(
&mut world
.res_mut::<crate::assets::loading::LoadingState>()
.pipeline,
label,
category,
);
}
for task in deferred.into_iter().rev() {
loading_pipeline_requeue_front(
&mut world
.res_mut::<crate::assets::loading::LoadingState>()
.pipeline,
task,
);
}
let needs_resolve = reserved_any
|| !world
.res::<crate::ecs::material::resources::MaterialRegistry>()
.pending_resolve
.is_empty();
if needs_resolve {
let texture_cache =
std::mem::take(world.res_mut::<crate::render::wgpu::texture_cache::TextureCache>());
crate::ecs::material::resources::material_registry_resolve_uploaded_textures(
world.res_mut::<crate::ecs::material::resources::MaterialRegistry>(),
&texture_cache,
&uploaded_textures,
);
*world.res_mut::<crate::render::wgpu::texture_cache::TextureCache>() = texture_cache;
world
.res_mut::<crate::render::mesh_state::MeshRenderState>()
.request_full_rebuild();
}
if !loading_pipeline_is_active(&world.res::<crate::assets::loading::LoadingState>().pipeline) {
loading_pipeline_reset_source_arena(
&mut world
.res_mut::<crate::assets::loading::LoadingState>()
.pipeline,
);
}
let evicted = texture_uploads::evict_unused_textures(
renderer,
world.res_mut::<crate::render::wgpu::texture_cache::TextureCache>(),
);
if !evicted.is_empty() {
for name in &evicted {
world
.res_mut::<crate::assets::texture_source::TextureSources>()
.remove(name);
}
world
.res_mut::<crate::render::mesh_state::MeshRenderState>()
.request_full_rebuild();
}
}