use crate::ecs::world::World;
use crate::render::wgpu::brdf_lut;
use crate::render::wgpu::font_atlas;
use crate::render::wgpu::passes;
use crate::render::wgpu::rendergraph::RenderGraph;
pub struct XrRenderer {
graph: RenderGraph<World>,
_depth_id: crate::render::wgpu::rendergraph::ResourceId,
_scene_color_id: crate::render::wgpu::rendergraph::ResourceId,
swapchain_id: crate::render::wgpu::rendergraph::ResourceId,
_brdf_lut_texture: wgpu::Texture,
brdf_lut_view: wgpu::TextureView,
_msdf_font_textures: Vec<(wgpu::Texture, wgpu::TextureView)>,
font_data: Vec<crate::ecs::text::resources::FontAtlasData>,
font_initialized: bool,
}
impl XrRenderer {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
resolution: (u32, u32),
surface_format: wgpu::TextureFormat,
) -> Result<Self, Box<dyn std::error::Error>> {
let depth_format = wgpu::TextureFormat::Depth32Float;
let hdr_format = wgpu::TextureFormat::Rgba16Float;
let clear_pass = passes::ClearPass::new();
let sky_pass = passes::SkyPass::new(device, hdr_format, depth_format);
let grid_pass = passes::GridPass::new(device, hdr_format, depth_format);
let scene_pass = passes::ScenePass::new(device, hdr_format, depth_format);
let shadow_depth_pass = passes::ShadowDepthPass::new(device, queue);
let mesh_pass = passes::MeshPass::new(device, queue, hdr_format, depth_format, resolution);
let skinned_mesh_pass =
passes::SkinnedMeshPass::new(device, queue, hdr_format, depth_format);
let water_pass = passes::WaterPass::new(device, hdr_format, depth_format);
let water_mesh_pass = passes::WaterMeshPass::new(device, hdr_format, depth_format);
let lines_pass = passes::LinesPass::new(device, hdr_format);
let particle_pass = passes::ParticlePass::new(device, hdr_format);
let mut graph = RenderGraph::new();
let depth_id = graph
.add_depth_texture("depth")
.size(resolution.0.max(1), resolution.1.max(1))
.clear_depth(0.0)
.transient();
let scene_color_id = graph
.add_color_texture("scene_color")
.format(hdr_format)
.size(resolution.0.max(1), resolution.1.max(1))
.clear_color(wgpu::Color::BLACK)
.transient();
let swapchain_id = graph
.add_color_texture("swapchain")
.format(surface_format)
.external();
let shadow_depth_id = graph
.add_depth_texture("shadow_depth")
.size(2048, 2048)
.format(wgpu::TextureFormat::Depth32Float)
.clear_depth(0.0)
.transient();
let spotlight_shadow_atlas_id = graph
.add_depth_texture("spotlight_shadow_atlas")
.size(2048, 2048)
.format(wgpu::TextureFormat::Depth32Float)
.clear_depth(0.0)
.transient();
let entity_id_id = graph
.add_color_texture("entity_id")
.format(wgpu::TextureFormat::R32Float)
.size(resolution.0.max(1), resolution.1.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
let view_normals_id = graph
.add_color_texture("view_normals")
.format(wgpu::TextureFormat::Rgba16Float)
.size(resolution.0.max(1), resolution.1.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
graph.add_pass(
Box::new(clear_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
graph.add_pass(Box::new(sky_pass), &[("color", scene_color_id)])?;
graph.add_pass(
Box::new(scene_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
graph.add_pass(
Box::new(shadow_depth_pass),
&[
("shadow_depth", shadow_depth_id),
("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
],
)?;
graph.add_pass(
Box::new(mesh_pass),
&[
("color", scene_color_id),
("depth", depth_id),
("shadow_depth", shadow_depth_id),
("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
("entity_id", entity_id_id),
("view_normals", view_normals_id),
],
)?;
graph.add_pass(
Box::new(skinned_mesh_pass),
&[
("color", scene_color_id),
("depth", depth_id),
("shadow_depth", shadow_depth_id),
("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
],
)?;
graph.add_pass(
Box::new(water_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
graph.add_pass(
Box::new(water_mesh_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
graph.add_pass(
Box::new(grid_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
graph.add_pass(
Box::new(lines_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
graph.add_pass(
Box::new(particle_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
let bloom_width = (resolution.0 / 4).max(1);
let bloom_height = (resolution.1 / 4).max(1);
let bloom_texture_id = graph
.add_color_texture("bloom")
.format(hdr_format)
.size(bloom_width, bloom_height)
.clear_color(wgpu::Color::BLACK)
.transient();
let bloom_pass = passes::BloomPass::new(device, bloom_width * 2, bloom_height * 2);
graph.add_pass(
Box::new(bloom_pass),
&[("hdr", scene_color_id), ("bloom", bloom_texture_id)],
)?;
let ssao_width = (resolution.0 / 2).max(1);
let ssao_height = (resolution.1 / 2).max(1);
let ssao_raw_id = graph
.add_color_texture("ssao_raw")
.format(wgpu::TextureFormat::R8Unorm)
.size(ssao_width, ssao_height)
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::WHITE)
.transient();
let ssao_id = graph
.add_color_texture("ssao")
.format(wgpu::TextureFormat::R8Unorm)
.size(ssao_width, ssao_height)
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::WHITE)
.transient();
let ssao_pass = passes::SsaoPass::new(device);
graph.add_pass(
Box::new(ssao_pass),
&[
("depth", depth_id),
("view_normals", view_normals_id),
("ssao_raw", ssao_raw_id),
],
)?;
let ssao_blur_pass = passes::SsaoBlurPass::new(device);
graph.add_pass(
Box::new(ssao_blur_pass),
&[
("ssao_raw", ssao_raw_id),
("depth", depth_id),
("view_normals", view_normals_id),
("ssao", ssao_id),
],
)?;
let msdf_atlas = font_atlas::generate_font_atlas_msdf(
device,
queue,
font_atlas::DEFAULT_FONT_DATA,
32.0,
)?;
let mut _msdf_font_textures = Vec::new();
_msdf_font_textures.push((msdf_atlas.texture, msdf_atlas.texture_view));
let font_data = vec![crate::ecs::text::resources::FontAtlasData {
texture_index: 0,
glyphs: msdf_atlas.glyphs,
kerning: msdf_atlas.kerning,
width: msdf_atlas.width,
height: msdf_atlas.height,
font_size: msdf_atlas.font_size,
sdf_range: msdf_atlas.sdf_range,
}];
let mut text_pass = passes::TextPass::new(device, hdr_format, depth_format);
text_pass.update_texture_bind_groups(device, &_msdf_font_textures);
graph.add_pass(
Box::new(text_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
let postprocess_output_id = graph
.add_color_texture("postprocess_output")
.format(surface_format)
.size(resolution.0.max(1), resolution.1.max(1))
.usage(wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT)
.transient();
let postprocess_pass = passes::PostProcessPass::new(device, surface_format, 0.1);
graph.add_pass(
Box::new(postprocess_pass),
&[
("hdr", scene_color_id),
("bloom", bloom_texture_id),
("ssao", ssao_id),
("output", postprocess_output_id),
],
)?;
let fxaa_pass = passes::FxaaPass::new(device, surface_format);
graph.add_pass(
Box::new(fxaa_pass),
&[("input", postprocess_output_id), ("output", swapchain_id)],
)?;
graph.compile()?;
let (brdf_lut_texture, brdf_lut_view) = brdf_lut::generate_brdf_lut(device, queue);
Ok(Self {
graph,
_depth_id: depth_id,
_scene_color_id: scene_color_id,
swapchain_id,
_brdf_lut_texture: brdf_lut_texture,
brdf_lut_view,
_msdf_font_textures,
font_data,
font_initialized: false,
})
}
pub fn render_eye(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
world: &mut World,
eye_texture_view: wgpu::TextureView,
resolution: (u32, u32),
) -> Result<(), Box<dyn std::error::Error>> {
self.graph.set_external_texture(
self.swapchain_id,
None,
eye_texture_view,
resolution.0,
resolution.1,
);
let command_buffers = self.graph.execute(device, queue, world)?;
queue.submit(command_buffers);
Ok(())
}
pub fn process_commands(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
world: &mut World,
) {
let mut texture_load_commands = Vec::new();
let mut hdr_skybox_commands = Vec::new();
let mut procedural_ibl_commands = Vec::new();
let mut ibl_snapshot_commands = Vec::new();
for cmd in world.resources.command_queue.iter() {
match cmd {
crate::ecs::world::WorldCommand::LoadTexture {
name,
rgba_data,
width,
height,
} => {
texture_load_commands.push((name.clone(), rgba_data.clone(), *width, *height));
}
crate::ecs::world::WorldCommand::LoadHdrSkybox { hdr_data } => {
hdr_skybox_commands.push(hdr_data.clone());
}
crate::ecs::world::WorldCommand::LoadHdrSkyboxFromPath { path } => {
match std::fs::read(path) {
Ok(bytes) => {
hdr_skybox_commands.push(bytes);
}
Err(error) => {
tracing::error!(
"Failed to read HDR file {}: {}",
path.display(),
error
);
}
}
}
crate::ecs::world::WorldCommand::CaptureProceduralAtmosphereIBL {
atmosphere,
time,
} => {
procedural_ibl_commands.push((*atmosphere, *time));
}
crate::ecs::world::WorldCommand::CaptureIblSnapshots { atmosphere, hours } => {
ibl_snapshot_commands.push((*atmosphere, hours.clone()));
}
_ => {}
}
}
if !texture_load_commands.is_empty() {
for (name, rgba_data, width, height) in texture_load_commands {
if let Err(e) = world.resources.texture_cache.load_texture_from_raw_rgba(
device,
queue,
name.clone(),
&rgba_data,
(width, height),
) {
tracing::error!("Failed to load texture: {}", e);
continue;
}
if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
&& let Some(mesh_pass) =
(mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
&& let Some(texture_entry) = world.resources.texture_cache.get(&name)
{
mesh_pass.register_texture(
name.clone(),
texture_entry.view.clone(),
texture_entry.sampler.clone(),
);
}
if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
&& let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
.downcast_mut::<passes::SkinnedMeshPass>()
&& let Some(texture_entry) = world.resources.texture_cache.get(&name)
{
skinned_mesh_pass.register_texture(
name,
texture_entry.view.clone(),
texture_entry.sampler.clone(),
);
}
}
world
.resources
.command_queue
.retain(|cmd| !matches!(cmd, crate::ecs::world::WorldCommand::LoadTexture { .. }));
}
if !hdr_skybox_commands.is_empty() {
let mut ibl_views = None;
if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
&& let Some(sky_pass) =
(sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
{
for hdr_data in hdr_skybox_commands {
ibl_views = Some(sky_pass.load_hdr_skybox(device, queue, &hdr_data));
}
}
if let Some((irradiance_view, prefiltered_view)) = ibl_views {
if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
&& let Some(mesh_pass) =
(mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
{
mesh_pass.update_ibl_textures(
self.brdf_lut_view.clone(),
irradiance_view.clone(),
prefiltered_view.clone(),
);
}
if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
&& let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
.downcast_mut::<passes::geometry::SkinnedMeshPass>(
)
{
skinned_mesh_pass.set_ibl_textures(
self.brdf_lut_view.clone(),
irradiance_view.clone(),
prefiltered_view.clone(),
);
}
world.resources.ibl_views.brdf_lut_view = Some(self.brdf_lut_view.clone());
world.resources.ibl_views.irradiance_view = Some(irradiance_view);
world.resources.ibl_views.prefiltered_view = Some(prefiltered_view);
}
world.resources.command_queue.retain(|cmd| {
!matches!(
cmd,
crate::ecs::world::WorldCommand::LoadHdrSkybox { .. }
| crate::ecs::world::WorldCommand::LoadHdrSkyboxFromPath { .. }
)
});
}
#[cfg(feature = "assets")]
if !procedural_ibl_commands.is_empty() {
let mut ibl_views = None;
if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
&& let Some(sky_pass) =
(sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
{
for (atmosphere, time) in procedural_ibl_commands {
ibl_views =
sky_pass.capture_procedural_atmosphere(device, queue, atmosphere, time);
}
}
if let Some((irradiance_view, prefiltered_view)) = ibl_views {
if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
&& let Some(mesh_pass) =
(mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
{
mesh_pass.update_ibl_textures(
self.brdf_lut_view.clone(),
irradiance_view.clone(),
prefiltered_view.clone(),
);
}
if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
&& let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
.downcast_mut::<passes::geometry::SkinnedMeshPass>(
)
{
skinned_mesh_pass.set_ibl_textures(
self.brdf_lut_view.clone(),
irradiance_view.clone(),
prefiltered_view.clone(),
);
}
world.resources.ibl_views.brdf_lut_view = Some(self.brdf_lut_view.clone());
world.resources.ibl_views.irradiance_view = Some(irradiance_view);
world.resources.ibl_views.prefiltered_view = Some(prefiltered_view);
}
world.resources.command_queue.retain(|cmd| {
!matches!(
cmd,
crate::ecs::world::WorldCommand::CaptureProceduralAtmosphereIBL { .. }
)
});
}
#[cfg(feature = "assets")]
if !ibl_snapshot_commands.is_empty() {
if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
&& let Some(sky_pass) =
(sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
{
for (atmosphere, hours) in ibl_snapshot_commands {
sky_pass.capture_ibl_snapshots(device, queue, atmosphere, &hours);
}
}
world.resources.command_queue.retain(|cmd| {
!matches!(
cmd,
crate::ecs::world::WorldCommand::CaptureIblSnapshots { .. }
)
});
}
if let Some(lines_pass) = self.graph.get_pass_mut("lines_pass")
&& let Some(lines_pass) =
(lines_pass as &mut dyn std::any::Any).downcast_mut::<passes::LinesPass>()
{
passes::sync_lines_data(lines_pass, device, queue, world);
}
}
pub fn update_per_frame_passes(&mut self, world: &mut World) {
let point_shadow_view = self
.graph
.get_pass_mut("shadow_depth_pass")
.and_then(|pass| {
(pass as &dyn std::any::Any)
.downcast_ref::<passes::ShadowDepthPass>()
.map(|shadow_pass| shadow_pass.point_light_cubemap_view().clone())
});
if let Some(view) = point_shadow_view {
if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
&& let Some(mesh_pass) =
(mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
{
mesh_pass.update_point_shadow_cubemap(view.clone());
}
if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
&& let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
.downcast_mut::<passes::SkinnedMeshPass>()
{
skinned_mesh_pass.update_point_shadow_cubemap(view);
}
}
{
let raw_hour = world.resources.graphics.day_night.hour;
let hour = raw_hour - (raw_hour / 24.0).floor() * 24.0;
let world_id = world.resources.world_id;
let mut bracket_views: Option<(
wgpu::TextureView,
wgpu::TextureView,
wgpu::TextureView,
wgpu::TextureView,
f32,
)> = None;
if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
&& let Some(sky_pass) =
(sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
&& let Some((index_a, index_b, blend)) =
sky_pass.select_ibl_bracket(world.resources.graphics.atmosphere, hour)
{
let irr_a =
sky_pass.ibl_snapshots[index_a]
.1
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let pref_a =
sky_pass.ibl_snapshots[index_a]
.2
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let irr_b =
sky_pass.ibl_snapshots[index_b]
.1
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let pref_b =
sky_pass.ibl_snapshots[index_b]
.2
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
bracket_views = Some((irr_a, pref_a, irr_b, pref_b, blend));
}
if let Some((irr_a, pref_a, irr_b, pref_b, blend)) = bracket_views {
world.resources.graphics.ibl_blend_factor = blend;
if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
&& let Some(mesh_pass) =
(mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
{
mesh_pass.update_ibl_textures_blended(
self.brdf_lut_view.clone(),
irr_a.clone(),
pref_a.clone(),
irr_b.clone(),
pref_b.clone(),
blend,
);
}
if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
&& let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
.downcast_mut::<passes::geometry::SkinnedMeshPass>(
)
{
skinned_mesh_pass.update_ibl_textures_blended_for_world(
world_id,
passes::geometry::BlendedIblViews {
brdf_lut: self.brdf_lut_view.clone(),
irradiance_a: irr_a.clone(),
prefiltered_a: pref_a.clone(),
irradiance_b: irr_b,
prefiltered_b: pref_b,
blend_factor: blend,
},
);
}
world.resources.ibl_views.brdf_lut_view = Some(self.brdf_lut_view.clone());
world.resources.ibl_views.irradiance_view = Some(irr_a);
world.resources.ibl_views.prefiltered_view = Some(pref_a);
}
}
}
pub fn initialize_fonts(&mut self, world: &mut World) {
if !self.font_initialized {
for font_data in &self.font_data {
let font_index = world
.resources
.text_cache
.font_manager
.add_font(font_data.clone());
if world.resources.text_cache.font_manager.default_font == 0 {
world.resources.text_cache.font_manager.default_font = font_index;
}
}
self.font_initialized = true;
}
}
pub fn graph_mut(&mut self) -> &mut RenderGraph<World> {
&mut self.graph
}
}