use std::sync::Arc;
use wgpu::{util::DeviceExt, Device, Queue, Surface, SurfaceConfiguration};
use winit::window::Window;
pub use crate::gpu_types::{
InstanceRaw, LightData, PostProcessUniforms, SceneUniforms, ShadowVsUniform, Vertex,
};
pub use crate::pipeline::SceneState;
pub use crate::post_process::PostProcessState;
pub struct RenderContext<'a> {
pub(crate) encoder: &'a mut wgpu::CommandEncoder,
pub(crate) view: &'a wgpu::TextureView,
pub(crate) renderer: &'a mut Renderer,
pub(crate) light_time: f32,
}
impl<'a> RenderContext<'a> {
pub fn new(
encoder: &'a mut wgpu::CommandEncoder,
view: &'a wgpu::TextureView,
renderer: &'a mut Renderer,
light_time: f32,
) -> Self {
Self {
encoder,
view,
renderer,
light_time,
}
}
pub fn disable_gpu_compute(&mut self) {
self.renderer.gpu_fluid = None;
self.renderer.gpu_particles = None;
self.renderer.gpu_physics = None;
}
pub fn light_time(&self) -> f32 {
self.light_time
}
pub fn renderer(&self) -> &Renderer {
self.renderer
}
pub fn renderer_mut(&mut self) -> &mut Renderer {
self.renderer
}
pub fn encoder(&mut self) -> &mut wgpu::CommandEncoder {
self.encoder
}
pub fn output_view(&self) -> &wgpu::TextureView {
self.view
}
pub fn parts_mut(&mut self) -> (&mut wgpu::CommandEncoder, &wgpu::TextureView, &mut Renderer) {
(self.encoder, self.view, self.renderer)
}
}
pub struct Renderer {
pub surface: Surface<'static>,
pub device: Device,
pub queue: Queue,
pub config: SurfaceConfiguration,
pub size: winit::dpi::PhysicalSize<u32>,
pub depth_texture_view: wgpu::TextureView,
pub scene: SceneState,
pub post: PostProcessState,
pub gpu_particles: Option<crate::gpu_particles::GpuParticleSystem>,
pub gpu_physics: Option<crate::gpu_physics::GpuPhysicsSystem>,
pub gpu_fluid: Option<crate::gpu_fluid::GpuFluidSystem>,
pub deferred: Option<crate::deferred::DeferredState>,
pub gpu_cull: Option<crate::gpu_cull::GpuCullState>,
pub ssao: Option<crate::ssao::SsaoState>,
pub ssr: Option<crate::ssr::SsrState>,
pub ssgi: Option<crate::ssgi::SsgiState>,
pub volumetric: Option<crate::volumetric::VolumetricState>,
pub decal: Option<crate::decal::DecalState>,
pub taa: Option<crate::taa::TaaState>,
pub fxaa: Option<crate::fxaa::FxaaState>,
pub debug_renderer: Option<crate::debug_renderer::GizmoRendererSystem>,
pub asset_manager: std::sync::RwLock<crate::asset::AssetManager>,
pub web_profile: crate::web_profile::WebProfile,
}
impl Renderer {
pub fn load_shader(
device: &wgpu::Device,
file_path: &str,
fallback_src: &str,
label: &str,
) -> wgpu::ShaderModule {
let source =
std::fs::read_to_string(file_path).unwrap_or_else(|_| fallback_src.to_string());
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(source.into()),
})
}
pub async fn new(window: Arc<Window>) -> Self {
let mut size = window.inner_size();
if size.width == 0 || size.height == 0 {
size = winit::dpi::PhysicalSize::new(1280, 720);
}
#[cfg(target_arch = "wasm32")]
{
if size.width > 640 || size.height > 360 {
let aspect = size.width as f32 / size.height as f32;
if aspect > 1.0 {
size.width = 640;
size.height = (640.0 / aspect) as u32;
} else {
size.height = 360;
size.width = (360.0 * aspect) as u32;
}
}
}
#[cfg(target_arch = "wasm32")]
let backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
#[cfg(not(target_arch = "wasm32"))]
let backends = wgpu::Backends::all();
log::info!("[Renderer] Window size: {}x{}", size.width, size.height);
log::info!("[Renderer] Backends: {:?}", backends);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends,
..Default::default()
});
#[cfg(not(target_arch = "wasm32"))]
{
let adapters = instance.enumerate_adapters(backends);
log::info!("[Renderer] {} adapter bulundu", adapters.len());
for (i, a) in adapters.iter().enumerate() {
let info = a.get_info();
log::info!(
"[Renderer] Adapter {}: {} ({:?}, {:?})",
i,
info.name,
info.backend,
info.device_type
);
}
}
let surface = instance
.create_surface(window.clone())
.expect("Surface oluşturulamadı!");
log::info!("[Renderer] Surface oluşturuldu, adapter aranıyor...");
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await;
let adapter = match adapter {
Some(a) => {
let info = a.get_info();
log::info!(
"[Renderer] Adapter bulundu: {} ({:?})",
info.name,
info.backend
);
a
}
None => {
log::warn!(
"[Renderer] Surface uyumlu adapter bulunamadı, surface'siz deneniyor..."
);
match instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
})
.await
{
Some(a) => {
let info = a.get_info();
log::info!(
"[Renderer] Surface'siz adapter bulundu: {} ({:?})",
info.name,
info.backend
);
a
}
None => {
log::error!(
"[Renderer] Hiçbir adapter bulunamadı! Backends: {:?}",
backends
);
panic!(
"GPU adapter bulunamadı! Backends: {:?}, Window size: {}x{}",
backends, size.width, size.height
);
}
}
}
};
#[cfg(not(target_arch = "wasm32"))]
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::POLYGON_MODE_LINE,
required_limits: wgpu::Limits {
max_bind_groups: 6,
max_storage_buffers_per_shader_stage: 8,
max_storage_buffer_binding_size: 256 << 20, ..wgpu::Limits::default()
},
label: None,
},
None,
)
.await
.unwrap();
#[cfg(target_arch = "wasm32")]
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits {
max_bind_groups: 4,
max_storage_buffers_per_shader_stage: 8,
max_storage_buffer_binding_size: 128 << 20, ..wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits())
},
label: None,
},
None,
)
.await
.unwrap();
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoNoVsync,
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
let depth_texture_view = Self::create_depth_texture(&device, config.width, config.height);
let scene = crate::pipeline::build_scene_pipelines(&device);
let post_res = crate::post_process::build_post_process_resources(
&device,
surface_format,
config.width,
config.height,
&depth_texture_view,
);
#[cfg(not(target_arch = "wasm32"))]
let gpu_particles = {
let max_particles: u32 = 100_000;
Some(crate::gpu_particles::GpuParticleSystem::new(
&device,
max_particles,
&scene.global_bind_group_layout,
wgpu::TextureFormat::Rgba16Float,
))
};
#[cfg(target_arch = "wasm32")]
let gpu_particles: Option<crate::gpu_particles::GpuParticleSystem> = None;
#[cfg(not(target_arch = "wasm32"))]
let gpu_physics = {
let max_physics_spheres: u32 = 50_000;
let mut physics = crate::gpu_physics::GpuPhysicsSystem::new(
&device,
max_physics_spheres,
&scene.global_bind_group_layout,
wgpu::TextureFormat::Rgba16Float,
wgpu::TextureFormat::Depth32Float,
);
physics.enable_debug(&device, 0);
Some(physics)
};
#[cfg(target_arch = "wasm32")]
let gpu_physics: Option<crate::gpu_physics::GpuPhysicsSystem> = None;
#[cfg(not(target_arch = "wasm32"))]
let gpu_fluid = Some(crate::gpu_fluid::GpuFluidSystem::new(
&device,
&queue,
100_000,
&scene.global_bind_group_layout,
post_res.hdr_texture.format(),
config.width,
config.height,
));
#[cfg(target_arch = "wasm32")]
let gpu_fluid: Option<crate::gpu_fluid::GpuFluidSystem> = None;
let debug_renderer = Some(crate::debug_renderer::GizmoRendererSystem::new(
&device,
&scene.global_bind_group_layout,
wgpu::TextureFormat::Rgba16Float,
wgpu::TextureFormat::Depth32Float,
));
let scene_state = SceneState {
render_pipeline: scene.render_pipeline,
render_double_sided_pipeline: scene.render_double_sided_pipeline,
wireframe_pipeline: scene.wireframe_pipeline,
unlit_pipeline: scene.unlit_pipeline,
sky_pipeline: scene.sky_pipeline,
water_pipeline: scene.water_pipeline,
shadow_pipeline: scene.shadow_pipeline,
transparent_pipeline: scene.transparent_pipeline,
grid_pipeline: scene.grid_pipeline,
shadow_texture_view: scene.shadow_texture_view,
shadow_cascade_layer_views: scene.shadow_cascade_layer_views,
shadow_depth_texture: scene.shadow_depth_texture,
point_shadow_depth_texture: scene.point_shadow_depth_texture,
point_shadow_cube_view: scene.point_shadow_cube_view,
point_shadow_face_views: scene.point_shadow_face_views,
shadow_pass_bind_group_layout: scene.shadow_pass_bind_group_layout,
shadow_cascade_uniform_buffers: scene.shadow_cascade_uniform_buffers,
shadow_pass_bind_groups: scene.shadow_pass_bind_groups,
point_shadow_uniform_buffers: scene.point_shadow_uniform_buffers,
point_shadow_pass_bind_groups: scene.point_shadow_pass_bind_groups,
global_uniform_buffer: scene.global_uniform_buffer,
global_bind_group_layout: scene.global_bind_group_layout,
global_bind_group: scene.global_bind_group,
shadow_bind_group_layout: scene.shadow_bind_group_layout,
shadow_bind_group: scene.shadow_bind_group,
texture_bind_group_layout: scene.texture_bind_group_layout,
skeleton_bind_group_layout: scene.skeleton_bind_group_layout,
dummy_skeleton_bind_group: scene.dummy_skeleton_bind_group,
instance_bind_group_layout: scene.instance_bind_group_layout,
instance_buffer: scene.instance_buffer,
instance_bind_group: scene.instance_bind_group,
instance_capacity: scene.instance_capacity,
};
#[cfg(not(target_arch = "wasm32"))]
let deferred = Some(crate::deferred::DeferredState::new(
&device,
&scene_state,
size.width,
size.height,
));
#[cfg(target_arch = "wasm32")]
let deferred: Option<crate::deferred::DeferredState> = None;
#[cfg(not(target_arch = "wasm32"))]
let gpu_cull = Some(crate::gpu_cull::GpuCullState::new(
&device,
&scene_state,
scene_state.instance_capacity as u32,
));
#[cfg(target_arch = "wasm32")]
let gpu_cull: Option<crate::gpu_cull::GpuCullState> = None;
let ssao = deferred.as_ref().map(|def| {
crate::ssao::SsaoState::new(&device, &queue, &scene_state, def, size.width, size.height)
});
let ssr = deferred.as_ref().map(|def| {
crate::ssr::SsrState::new(
&device,
&scene_state,
def,
&post_res.hdr_texture_view,
size.width,
size.height,
)
});
let ssgi = deferred.as_ref().map(|def| {
crate::ssgi::SsgiState::new(
&device,
&scene_state,
def,
&post_res.hdr_texture_view,
size.width,
size.height,
)
});
let volumetric = deferred.as_ref().map(|def| {
crate::volumetric::VolumetricState::new(
&device,
&scene_state,
def,
size.width,
size.height,
)
});
let decal = deferred
.as_ref()
.map(|def| crate::decal::DecalState::new(&device, &scene_state, def));
let taa = if let Some(ref def) = deferred {
Some(crate::taa::TaaState::new(
&device,
&post_res.hdr_texture_view,
&def.world_position_view,
size.width,
size.height,
))
} else {
None
};
let post_state = PostProcessState {
hdr_texture: post_res.hdr_texture,
hdr_texture_view: post_res.hdr_texture_view,
hdr_bind_group: post_res.hdr_bind_group,
bloom_extract_texture_view: post_res.bloom_extract_texture_view,
bloom_extract_bind_group: post_res.bloom_extract_bind_group,
bloom_blur_texture_view: post_res.bloom_blur_texture_view,
bloom_blur_bind_group: post_res.bloom_blur_bind_group,
post_bind_group_layout: post_res.post_bind_group_layout,
bloom_extract_pipeline: post_res.bloom_extract_pipeline,
bloom_blur_pipeline: post_res.bloom_blur_pipeline,
composite_pipeline: post_res.composite_pipeline,
blur_params_buffer: post_res.blur_params_buffer,
blur_params_bind_group_layout: post_res.blur_params_bind_group_layout,
blur_h_bind_group: post_res.blur_h_bind_group,
blur_v_bind_group: post_res.blur_v_bind_group,
composite_bloom_bind_group_layout: post_res.composite_bloom_bind_group_layout,
composite_bloom_bind_group: post_res.composite_bloom_bind_group,
post_params_buffer: post_res.post_params_buffer,
post_params_bind_group_layout: post_res.post_params_bind_group_layout,
post_params_bind_group: post_res.post_params_bind_group,
};
let fxaa = Some(crate::fxaa::FxaaState::new(
&device,
config.format,
size.width,
size.height,
));
Self {
surface,
device,
queue,
config,
size,
depth_texture_view,
scene: scene_state,
post: post_state,
deferred,
gpu_cull,
ssao,
ssr,
ssgi,
volumetric,
decal,
taa,
fxaa,
gpu_particles,
gpu_physics,
gpu_fluid,
debug_renderer,
asset_manager: std::sync::RwLock::new(crate::asset::AssetManager::new()),
web_profile: crate::web_profile::WebProfile::auto(),
}
}
pub fn rebuild_shaders(&mut self) {
tracing::info!("🚀 Rebuilding Shaders Pipeline...");
crate::pipeline::rebuild_pipelines(self);
}
pub fn ensure_instance_capacity(&mut self, needed: usize) -> bool {
self.scene.ensure_instance_capacity(&self.device, needed)
}
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
self.depth_texture_view =
Self::create_depth_texture(&self.device, new_size.width, new_size.height);
if let Some(ref mut def) = self.deferred {
def.resize(&self.device, new_size.width, new_size.height);
if let Some(ref mut decal) = self.decal {
decal.resize(&self.device, def);
}
}
let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let (hdr_t, hdr_tv, hdr_bg, be_tv, be_bg, bb_tv, bb_bg, cb_bg) =
crate::post_process::create_post_textures(
&self.device,
&self.post.post_bind_group_layout,
&self.post.composite_bloom_bind_group_layout,
&sampler,
new_size.width,
new_size.height,
&self.depth_texture_view,
);
self.post.hdr_texture = hdr_t;
self.post.hdr_texture_view = hdr_tv;
self.post.hdr_bind_group = hdr_bg;
self.post.bloom_extract_texture_view = be_tv;
self.post.bloom_extract_bind_group = be_bg;
self.post.bloom_blur_texture_view = bb_tv;
self.post.bloom_blur_bind_group = bb_bg;
self.post.composite_bloom_bind_group = cb_bg;
let (buf, h_bg, v_bg) = crate::post_process::create_blur_buffers(
&self.device,
&self.post.blur_params_bind_group_layout,
new_size.width,
new_size.height,
);
self.post.blur_params_buffer = buf;
self.post.blur_h_bind_group = h_bg;
self.post.blur_v_bind_group = v_bg;
if let (Some(ref mut taa), Some(ref def)) = (&mut self.taa, &self.deferred) {
taa.resize(
&self.device,
&self.post.hdr_texture_view,
&def.world_position_view,
new_size.width,
new_size.height,
);
}
if let (Some(ref mut ssgi), Some(ref def)) = (&mut self.ssgi, &self.deferred) {
ssgi.resize(
&self.device,
def,
&self.post.hdr_texture_view,
new_size.width,
new_size.height,
);
}
if let (Some(ref mut ssao), Some(ref def)) = (&mut self.ssao, &self.deferred) {
ssao.resize(
&self.device,
def,
new_size.width,
new_size.height,
);
}
if let (Some(ref mut vol), Some(ref def)) = (&mut self.volumetric, &self.deferred) {
vol.resize(
&self.device,
def,
new_size.width,
new_size.height,
);
}
if let Some(ref mut fxaa) = self.fxaa {
fxaa.resize(&self.device, &self.queue, self.config.format, new_size.width, new_size.height);
}
}
}
pub fn create_cube(&self) -> crate::components::Mesh {
crate::asset::AssetManager::create_cube(&self.device)
}
pub fn create_sphere(&self, radius: f32, stacks: u32, slices: u32) -> crate::components::Mesh {
crate::asset::AssetManager::create_sphere(&self.device, radius, stacks, slices)
}
pub fn create_plane(&self, size: f32) -> crate::components::Mesh {
crate::asset::AssetManager::create_plane(&self.device, size)
}
pub fn create_checkerboard_texture(&self) -> Arc<wgpu::BindGroup> {
self.asset_manager
.write()
.unwrap()
.create_checkerboard_texture(
&self.device,
&self.queue,
&self.scene.texture_bind_group_layout,
)
}
pub fn create_white_texture(&self) -> Arc<wgpu::BindGroup> {
self.asset_manager.write().unwrap().create_white_texture(
&self.device,
&self.queue,
&self.scene.texture_bind_group_layout,
)
}
pub fn load_texture(&self, path: &str) -> Result<Arc<wgpu::BindGroup>, String> {
self.asset_manager.write().unwrap().load_material_texture(
&self.device,
&self.queue,
&self.scene.texture_bind_group_layout,
path,
)
}
pub fn load_gltf(&self, path: &str) -> Result<crate::asset::loaders::GltfSceneAsset, String> {
let white_tex = self.create_white_texture();
self.asset_manager.write().unwrap().load_gltf_scene(
&self.device,
&self.queue,
&self.scene.texture_bind_group_layout,
white_tex,
path,
)
}
pub fn create_skeleton(
&self,
hierarchy: std::sync::Arc<crate::animation::SkeletonHierarchy>,
) -> crate::components::Skeleton {
use wgpu::util::DeviceExt;
let local_poses: Vec<gizmo_math::Mat4> = hierarchy
.joints
.iter()
.map(|j| j.local_bind_transform)
.collect();
let global_matrices = hierarchy.calculate_global_matrices(&local_poses);
let mut joint_matrices = vec![gizmo_math::Mat4::IDENTITY; 128];
for (i, joint) in hierarchy.joints.iter().enumerate() {
if i < 128 {
joint_matrices[i] = global_matrices[i] * joint.inverse_bind_matrix;
}
}
let buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Skeleton Joint Buffer"),
contents: bytemuck::cast_slice(&joint_matrices),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Skeleton Bind Group"),
layout: &self.scene.skeleton_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
});
crate::components::Skeleton::new(
std::sync::Arc::new(bind_group),
std::sync::Arc::new(buffer),
hierarchy,
local_poses,
)
}
pub fn run_post_processing(
&self,
encoder: &mut wgpu::CommandEncoder,
output_view: &wgpu::TextureView,
) {
if let Some(ref fxaa) = self.fxaa {
if fxaa.enabled {
crate::post_process::run_post_processing(self, encoder, &fxaa.input_texture_view);
crate::fxaa::run_fxaa_pass(fxaa, encoder, output_view);
return;
}
}
crate::post_process::run_post_processing(self, encoder, output_view);
}
pub fn update_post_process(&self, queue: &wgpu::Queue, params: PostProcessUniforms) {
queue.write_buffer(
&self.post.post_params_buffer,
0,
bytemuck::cast_slice(&[params]),
);
}
pub fn create_mesh(&self, vertices: &[Vertex]) -> wgpu::Buffer {
self.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Mesh Vertex Buffer"),
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX,
})
}
pub fn create_texture(&self, rgba_bytes: &[u8], width: u32, height: u32) -> wgpu::BindGroup {
let mip_level_count = width.max(height).ilog2() + 1;
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("Game Texture"),
size,
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
self.queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba_bytes,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
size,
);
Self::generate_mipmaps(
&self.device,
&self.queue,
&texture,
wgpu::TextureFormat::Rgba8UnormSrgb,
mip_level_count,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});
self.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.scene.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: Some("texture_bind_group"),
})
}
fn create_depth_texture(device: &wgpu::Device, width: u32, height: u32) -> wgpu::TextureView {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Depth Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
tex.create_view(&wgpu::TextureViewDescriptor::default())
}
fn generate_mipmaps(
device: &wgpu::Device,
queue: &wgpu::Queue,
texture: &wgpu::Texture,
format: wgpu::TextureFormat,
mip_level_count: u32,
) {
if mip_level_count <= 1 {
return;
}
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Mipmap Blit Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/mipmap.wgsl").into()),
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Mipmap Blit Pipeline"),
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let bind_group_layout = pipeline.get_bind_group_layout(0);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Mipmap Encoder"),
});
let views: Vec<wgpu::TextureView> = (0..mip_level_count)
.map(|mip| {
texture.create_view(&wgpu::TextureViewDescriptor {
label: Some(&format!("Mip {}", mip)),
format: None,
dimension: None,
aspect: wgpu::TextureAspect::All,
base_mip_level: mip,
mip_level_count: Some(1),
base_array_layer: 0,
array_layer_count: None,
})
})
.collect();
for target_mip in 1..mip_level_count as usize {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: None,
});
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &views[target_mip],
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
pass.set_pipeline(&pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.draw(0..3, 0..1);
}
queue.submit(Some(encoder.finish()));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mipmap_level_calculation() {
let width = 4096u32;
let height = 2048u32;
let mip_level_count = width.max(height).ilog2() + 1;
assert_eq!(mip_level_count, 13);
let width2 = 512u32;
let height2 = 512u32;
assert_eq!(width2.max(height2).ilog2() + 1, 10);
}
#[test]
fn test_headless_mipmap_generation() {
pollster::block_on(async {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: None,
force_fallback_adapter: false,
})
.await;
let adapter = match adapter {
Some(a) => a,
None => {
tracing::info!(
"No suitable GPU adapter found for headless test. Skipping wgpu test."
);
return;
}
};
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_defaults(),
label: None,
},
None,
)
.await
.unwrap();
let width = 256u32;
let height = 256u32;
let mip_level_count = width.max(height).ilog2() + 1;
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Test Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
Renderer::generate_mipmaps(
&device,
&queue,
&texture,
wgpu::TextureFormat::Rgba8UnormSrgb,
mip_level_count,
);
device.poll(wgpu::Maintain::Wait);
});
}
}