use super::brdf_lut;
use super::glyph_atlas;
use super::passes;
use super::rendergraph;
use crate::render::wgpu::rendergraph::{
render_graph_add_color_texture, render_graph_add_depth_texture, render_graph_add_pass,
};
pub(super) fn pick_present_mode(modes: &[wgpu::PresentMode]) -> wgpu::PresentMode {
let preferred: &[wgpu::PresentMode] = if cfg!(target_os = "macos") {
&[wgpu::PresentMode::Immediate, wgpu::PresentMode::FifoRelaxed]
} else {
&[wgpu::PresentMode::Mailbox, wgpu::PresentMode::FifoRelaxed]
};
for candidate in preferred.iter().copied() {
if modes.contains(&candidate) {
return candidate;
}
}
wgpu::PresentMode::Fifo
}
impl super::WgpuRenderer {
pub async fn new_async<W>(
window_handle: W,
initial_width: u32,
initial_height: u32,
) -> Result<Self, Box<dyn std::error::Error>>
where
W: Into<wgpu::SurfaceTarget<'static>>,
{
let instance =
wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let surface = instance.create_surface(window_handle)?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.expect("Failed to request adapter!");
let mut required_features = wgpu::Features::INDIRECT_FIRST_INSTANCE;
if !cfg!(target_os = "macos") && !cfg!(target_arch = "wasm32") {
required_features |= wgpu::Features::MULTI_DRAW_INDIRECT_COUNT;
}
let adapter_limits = adapter.limits();
let mut required_limits = wgpu::Limits::default().using_resolution(adapter_limits.clone());
required_limits.max_bind_groups = if cfg!(target_arch = "wasm32") { 4 } else { 8 };
required_limits.max_sampled_textures_per_shader_stage =
adapter_limits.max_sampled_textures_per_shader_stage.min(64);
required_limits.max_samplers_per_shader_stage =
adapter_limits.max_samplers_per_shader_stage.min(64);
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("WGPU Device"),
memory_hints: wgpu::MemoryHints::default(),
required_features,
required_limits,
experimental_features: wgpu::ExperimentalFeatures::disabled(),
trace: wgpu::Trace::Off,
})
.await?;
tracing::info!("wgpu device features: {:?}", device.features());
let surface_capabilities = surface.get_capabilities(&adapter);
let surface_format = surface_capabilities
.formats
.iter()
.copied()
.find(|f| !f.is_srgb())
.unwrap_or(surface_capabilities.formats[0]);
let present_mode = pick_present_mode(&surface_capabilities.present_modes);
tracing::info!(
"Surface present modes available: {:?}, selected: {:?}",
surface_capabilities.present_modes,
present_mode
);
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: initial_width,
height: initial_height,
present_mode,
alpha_mode: surface_capabilities.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
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 shadow_depth_pass = passes::ShadowDepthPass::new(&device, &queue);
let material_texture_arrays =
crate::render::wgpu::material_texture_arrays::MaterialTextureArrays::new(&device);
let mip_generator = crate::render::wgpu::mip_generator::MipGenerator::new(&device);
let mut mesh_pass = passes::MeshPass::new(
&device,
&queue,
hdr_format,
depth_format,
(initial_width, initial_height),
);
mesh_pass.set_material_array_views(
&device,
material_texture_arrays.srgb_view().clone(),
material_texture_arrays.linear_view().clone(),
material_texture_arrays.sampler().clone(),
);
let mut skinned_mesh_pass =
passes::SkinnedMeshPass::new(&device, &queue, hdr_format, depth_format);
skinned_mesh_pass.set_material_array_views(
&device,
material_texture_arrays.srgb_view().clone(),
material_texture_arrays.linear_view().clone(),
material_texture_arrays.sampler().clone(),
);
#[cfg(feature = "grass")]
let grass_pass = passes::GrassPass::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 ui_texture_array = crate::render::wgpu::ui_texture_array::UiTextureArray::new(&device);
let mut ui_image_pass = passes::UiImagePass::new(&device, surface_format);
ui_image_pass.set_texture_bind_group(
&device,
&ui_texture_array.view,
&ui_texture_array.sampler,
);
#[cfg(feature = "debug_render")]
let selection_mask_pass = passes::SelectionMaskPass::new(&device, depth_format);
let mut graph = rendergraph::render_graph_new();
let depth_id = render_graph_add_depth_texture(&mut graph, "depth")
.size(initial_width.max(1), initial_height.max(1))
.usage(
wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_SRC,
)
.clear_depth(0.0)
.force_store()
.transient();
let scene_color_id = render_graph_add_color_texture(&mut graph, "scene_color")
.format(wgpu::TextureFormat::Rgba16Float)
.size(initial_width.max(1), initial_height.max(1))
.usage(
wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_SRC,
)
.clear_color(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
})
.transient();
let compute_output_id = render_graph_add_color_texture(&mut graph, "compute_output")
.format(surface_format)
.size(initial_width.max(1), initial_height.max(1))
.usage(
wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
)
.transient();
let swapchain_id = render_graph_add_color_texture(&mut graph, "swapchain")
.format(surface_format)
.external();
let viewport_resource_id = render_graph_add_color_texture(&mut graph, "viewport_output")
.format(surface_format)
.external();
let shadow_map_size = if cfg!(target_arch = "wasm32") {
4096
} else {
8192
};
let shadow_depth_id = render_graph_add_depth_texture(&mut graph, "shadow_depth")
.size(shadow_map_size, shadow_map_size)
.format(wgpu::TextureFormat::Depth32Float)
.clear_depth(0.0)
.fixed_size()
.transient();
let spotlight_shadow_atlas_size = if cfg!(target_arch = "wasm32") {
1024
} else {
4096
};
let spotlight_shadow_atlas_id =
render_graph_add_depth_texture(&mut graph, "spotlight_shadow_atlas")
.size(spotlight_shadow_atlas_size, spotlight_shadow_atlas_size)
.format(wgpu::TextureFormat::Depth32Float)
.clear_depth(0.0)
.fixed_size()
.transient();
#[cfg(feature = "debug_render")]
let selection_mask_id = render_graph_add_color_texture(&mut graph, "selection_mask")
.format(wgpu::TextureFormat::R8Unorm)
.size(initial_width.max(1), initial_height.max(1))
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
let entity_id_id = render_graph_add_color_texture(&mut graph, "entity_id")
.format(wgpu::TextureFormat::R32Float)
.size(initial_width.max(1), initial_height.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.force_store()
.transient();
let view_normals_id = render_graph_add_color_texture(&mut graph, "view_normals")
.format(wgpu::TextureFormat::Rgba16Float)
.size(initial_width.max(1), initial_height.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
let ssao_raw_id = render_graph_add_color_texture(&mut graph, "ssao_raw")
.format(wgpu::TextureFormat::R8Unorm)
.size(initial_width.max(1), initial_height.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::WHITE)
.transient();
let ssao_id = render_graph_add_color_texture(&mut graph, "ssao")
.format(wgpu::TextureFormat::R8Unorm)
.size(initial_width.max(1), initial_height.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::WHITE)
.transient();
let ssgi_half_width = (initial_width / 2).max(1);
let ssgi_half_height = (initial_height / 2).max(1);
let ssgi_raw_id = render_graph_add_color_texture(&mut graph, "ssgi_raw")
.format(wgpu::TextureFormat::Rgba16Float)
.size(ssgi_half_width, ssgi_half_height)
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
let ssgi_id = render_graph_add_color_texture(&mut graph, "ssgi")
.format(wgpu::TextureFormat::Rgba16Float)
.size(ssgi_half_width, ssgi_half_height)
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
let ssr_raw_id = render_graph_add_color_texture(&mut graph, "ssr_raw")
.format(wgpu::TextureFormat::Rgba16Float)
.size(initial_width.max(1), initial_height.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
let ssr_id = render_graph_add_color_texture(&mut graph, "ssr")
.format(wgpu::TextureFormat::Rgba16Float)
.size(initial_width.max(1), initial_height.max(1))
.usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
.clear_color(wgpu::Color::TRANSPARENT)
.transient();
let ui_depth_id = render_graph_add_depth_texture(&mut graph, "ui_depth")
.size(initial_width.max(1), initial_height.max(1))
.format(wgpu::TextureFormat::Depth32Float)
.clear_depth(0.0)
.transient();
render_graph_add_pass(
&mut graph,
Box::new(clear_pass),
&[
("color", scene_color_id),
("depth", depth_id),
("entity_id", entity_id_id),
("view_normals", view_normals_id),
],
)?;
render_graph_add_pass(&mut graph, Box::new(sky_pass), &[("color", scene_color_id)])?;
render_graph_add_pass(
&mut graph,
Box::new(shadow_depth_pass),
&[
("shadow_depth", shadow_depth_id),
("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
],
)?;
render_graph_add_pass(
&mut graph,
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),
],
)?;
render_graph_add_pass(
&mut graph,
Box::new(skinned_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),
],
)?;
#[cfg(feature = "grass")]
render_graph_add_pass(
&mut graph,
Box::new(grass_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
render_graph_add_pass(
&mut graph,
Box::new(grid_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
render_graph_add_pass(
&mut graph,
Box::new(lines_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
render_graph_add_pass(
&mut graph,
Box::new(particle_pass),
&[("color", scene_color_id), ("depth", depth_id)],
)?;
#[cfg(feature = "debug_render")]
{
render_graph_add_pass(
&mut graph,
Box::new(selection_mask_pass),
&[
("selection_mask", selection_mask_id),
("entity_id", entity_id_id),
],
)?;
let outline_pass = passes::OutlinePass::new(&device, hdr_format);
render_graph_add_pass(
&mut graph,
Box::new(outline_pass),
&[
("selection_mask", selection_mask_id),
("color", scene_color_id),
],
)?;
}
let bloom_texture_id = render_graph_add_color_texture(&mut graph, "bloom")
.format(hdr_format)
.size((initial_width / 2).max(1), (initial_height / 2).max(1))
.clear_color(wgpu::Color::BLACK)
.transient();
let dof_output_id = render_graph_add_color_texture(&mut graph, "dof_output")
.format(hdr_format)
.size(initial_width.max(1), initial_height.max(1))
.clear_color(wgpu::Color::BLACK)
.transient();
let bloom_pass =
passes::BloomPass::new(&device, initial_width.max(1), initial_height.max(1));
render_graph_add_pass(
&mut graph,
Box::new(bloom_pass),
&[("hdr", scene_color_id), ("bloom", bloom_texture_id)],
)?;
let dof_pass = passes::DepthOfFieldPass::new(
&device,
hdr_format,
initial_width.max(1),
initial_height.max(1),
);
render_graph_add_pass(
&mut graph,
Box::new(dof_pass),
&[
("hdr", scene_color_id),
("depth", depth_id),
("dof_output", dof_output_id),
],
)?;
let ssao_pass = passes::SsaoPass::new(&device);
render_graph_add_pass(
&mut graph,
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);
render_graph_add_pass(
&mut graph,
Box::new(ssao_blur_pass),
&[
("ssao_raw", ssao_raw_id),
("depth", depth_id),
("view_normals", view_normals_id),
("ssao", ssao_id),
],
)?;
let postprocess_pass = passes::PostProcessPass::new(&device, surface_format, 1.0);
render_graph_add_pass(
&mut graph,
Box::new(postprocess_pass),
&[
("hdr", dof_output_id),
("bloom", bloom_texture_id),
("ssao", ssao_id),
("output", compute_output_id),
],
)?;
let fxaa_output_id = render_graph_add_color_texture(&mut graph, "fxaa_output")
.format(surface_format)
.size(initial_width.max(1), initial_height.max(1))
.transient();
let fxaa_pass = passes::FxaaPass::new(&device, surface_format);
render_graph_add_pass(
&mut graph,
Box::new(fxaa_pass),
&[("input", compute_output_id), ("output", fxaa_output_id)],
)?;
let (brdf_lut_texture, brdf_lut_view) = brdf_lut::generate_brdf_lut(&device, &queue);
let depth_pick_shader = crate::render::wgpu::shader_compose::compile_wgsl(
&device,
"Depth Pick Shader",
include_str!("shaders/depth_pick.wgsl"),
);
let depth_pick_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Depth Pick Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let depth_pick_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Depth Pick Pipeline Layout"),
bind_group_layouts: &[Some(&depth_pick_bind_group_layout)],
immediate_size: 0,
});
let depth_pick_compute_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Depth Pick Compute Pipeline"),
layout: Some(&depth_pick_pipeline_layout),
module: &depth_pick_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let depth_pick_buffer_size =
(super::DEPTH_PICK_SAMPLE_SIZE * super::DEPTH_PICK_SAMPLE_SIZE * 8) as u64;
let depth_pick_storage_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Depth Pick Storage Buffer"),
size: depth_pick_buffer_size,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let depth_pick_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Depth Pick Uniform Buffer"),
size: 16,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let depth_pick_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Depth Pick Staging Buffer"),
size: depth_pick_buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
#[cfg(not(target_arch = "wasm32"))]
let screenshot_staging_buffer = {
let bytes_per_pixel = 4u32;
let unpadded_bytes_per_row = initial_width.max(1) * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
let buffer_size = (padded_bytes_per_row * initial_height.max(1)) as u64;
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Screenshot Staging Buffer"),
size: buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
})
};
let glyph_atlas = glyph_atlas::GlyphAtlas::new(&device);
Ok(Self {
surface,
device,
queue,
surface_config,
surface_format,
graph,
depth_id,
scene_color_id,
compute_output_id,
fxaa_output_id,
swapchain_id,
viewport_resource_id,
ui_depth_id,
entity_id_id,
view_normals_id,
ssao_raw_id,
ssao_id,
ssgi_raw_id,
ssgi_id,
ssr_raw_id,
ssr_id,
ui_image_pass: Some(Box::new(ui_image_pass)),
glyph_atlas,
glyph_atlas_initialized: false,
text_mesh_signatures: std::collections::HashMap::new(),
camera_viewports: std::collections::HashMap::new(),
_brdf_lut_texture: brdf_lut_texture,
brdf_lut_view,
material_texture_arrays,
ui_texture_array,
mip_generator,
depth_pick_compute_pipeline,
depth_pick_bind_group_layout,
depth_pick_storage_buffer,
depth_pick_uniform_buffer,
depth_pick_staging_buffer,
depth_pick_bind_group: None,
depth_pick_pending: false,
depth_pick_map_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
depth_pick_center: (0, 0),
depth_pick_texture_size: (1, 1),
depth_pick_camera: None,
#[cfg(not(target_arch = "wasm32"))]
screenshot_staging_buffer,
#[cfg(not(target_arch = "wasm32"))]
screenshot_pending: false,
#[cfg(not(target_arch = "wasm32"))]
screenshot_map_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
#[cfg(not(target_arch = "wasm32"))]
screenshot_path: None,
#[cfg(not(target_arch = "wasm32"))]
screenshot_width: initial_width.max(1),
#[cfg(not(target_arch = "wasm32"))]
screenshot_height: initial_height.max(1),
#[cfg(not(target_arch = "wasm32"))]
screenshot_max_dimension: None,
render_buffer_size: (initial_width.max(1), initial_height.max(1)),
window_render_state: super::WindowRenderState::default(),
last_settings_signature: None,
frame_index: 0,
text_meshes_prepared_for_frame: u64::MAX,
extract_dirty_done_for_frame: u64::MAX,
})
}
}