use super::brdf_lut;
use super::passes;
use super::rendergraph;
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_os = "android")
&& !cfg!(target_arch = "wasm32")
{
required_features |= wgpu::Features::MULTI_DRAW_INDIRECT_COUNT;
}
let mut required_limits = wgpu::Limits::default().using_resolution(adapter.limits());
required_limits.max_bind_groups = if cfg!(target_arch = "wasm32") { 4 } else { 8 };
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?;
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 surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: initial_width,
height: initial_height,
present_mode: surface_capabilities.present_modes[0],
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 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,
(initial_width, initial_height),
);
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 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 sprite_pass = passes::SpritePass::new(&device, hdr_format, &queue);
let mut sprite_particle_pass = passes::SpriteParticlePass::new(&device, hdr_format);
sprite_particle_pass.set_atlas_bind_group(
&device,
sprite_pass.atlas_view(),
sprite_pass.atlas_sampler(),
);
let mut ui_image_pass = passes::UiImagePass::new(&device, surface_format);
ui_image_pass.set_atlas_bind_group(
&device,
sprite_pass.atlas_view(),
sprite_pass.atlas_sampler(),
);
ui_image_pass.set_atlas_slots_per_row(sprite_pass.atlas_slots_per_row());
let selection_mask_pass = passes::SelectionMaskPass::new(&device, depth_format);
#[cfg(feature = "egui")]
let egui_pass = passes::EguiPass::new(&device, surface_format);
let mut graph = rendergraph::RenderGraph::new();
let depth_id = graph
.add_depth_texture("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)
.transient();
let scene_color_id = graph
.add_color_texture("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 = graph
.add_color_texture("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 = graph
.add_color_texture("swapchain")
.format(surface_format)
.external();
let viewport_resource_id = graph
.add_color_texture("viewport_output")
.format(surface_format)
.external();
let shadow_map_size = if cfg!(target_arch = "wasm32") {
4096
} else {
8192
};
let shadow_depth_id = graph
.add_depth_texture("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 = graph
.add_depth_texture("spotlight_shadow_atlas")
.size(spotlight_shadow_atlas_size, spotlight_shadow_atlas_size)
.format(wgpu::TextureFormat::Depth32Float)
.clear_depth(0.0)
.fixed_size()
.transient();
let selection_mask_id = graph
.add_color_texture("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 = graph
.add_color_texture("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)
.transient();
let view_normals_id = graph
.add_color_texture("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 = graph
.add_color_texture("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 = graph
.add_color_texture("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 = graph
.add_color_texture("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 = graph
.add_color_texture("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 = graph
.add_color_texture("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 = graph
.add_color_texture("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 = graph
.add_depth_texture("ui_depth")
.size(initial_width.max(1), initial_height.max(1))
.format(wgpu::TextureFormat::Depth32Float)
.clear_depth(0.0)
.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(grass_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)],
)?;
graph.add_pass(Box::new(sprite_pass), &[("color", scene_color_id)])?;
graph.add_pass(Box::new(sprite_particle_pass), &[("color", scene_color_id)])?;
graph.add_pass(
Box::new(selection_mask_pass),
&[("selection_mask", selection_mask_id), ("depth", depth_id)],
)?;
let outline_pass = passes::OutlinePass::new(&device, hdr_format);
graph.add_pass(
Box::new(outline_pass),
&[
("selection_mask", selection_mask_id),
("color", scene_color_id),
],
)?;
let (brdf_lut_texture, brdf_lut_view) = brdf_lut::generate_brdf_lut(&device, &queue);
let depth_pick_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Depth Pick Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/depth_pick.wgsl").into()),
});
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,
})
};
Ok(Self {
instance,
adapter,
secondary_surfaces: std::collections::HashMap::new(),
surface,
device,
queue,
surface_config,
surface_format,
graph,
depth_id,
scene_color_id,
compute_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)),
#[cfg(feature = "egui")]
egui_pass: Some(Box::new(egui_pass)),
msdf_font_textures: Vec::new(),
bitmap_font_textures: Vec::new(),
font_data: Vec::new(),
bitmap_font_data: Vec::new(),
font_initialized: false,
bitmap_atlas_scale: 1.0,
bitmap_atlas_subpixel: !cfg!(target_arch = "wasm32"),
bitmap_font_sources: Vec::new(),
camera_viewports: std::collections::HashMap::new(),
_brdf_lut_texture: brdf_lut_texture,
brdf_lut_view,
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),
#[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),
render_buffer_size: (initial_width.max(1), initial_height.max(1)),
#[cfg(feature = "egui")]
secondary_camera_viewport_cache: std::collections::HashMap::new(),
})
}
}