use super::super::prepare::{PreparedEnvironmentCubemap, PreparedEnvironmentLighting};
use super::output::create_output_bind_group;
use super::shadow::{
self, ShadowCasterResources, create_shadow_caster_resources, create_shadow_sampler,
};
pub(super) struct OutputResources {
pub(super) shadow_caster: ShadowCasterResources,
pub(super) shadow_sampler: wgpu::Sampler,
pub(super) environment_cubemap: wgpu::Texture,
pub(super) environment_sampler: wgpu::Sampler,
pub(super) brdf_lut_texture: wgpu::Texture,
pub(super) output_bind_group: wgpu::BindGroup,
}
#[allow(clippy::too_many_arguments)]
pub(super) fn build_output_resources(
device: &wgpu::Device,
queue: &wgpu::Queue,
output_bind_group_layout: &wgpu::BindGroupLayout,
draw_bind_group_layout: &wgpu::BindGroupLayout,
output_uniform: &wgpu::Buffer,
directional_shadow_map_resolution: Option<u32>,
environment_lighting: &PreparedEnvironmentLighting,
) -> OutputResources {
let _ = shadow::SHADOW_CASTER_SHADER;
let shadow_caster = create_shadow_caster_resources(
device,
directional_shadow_map_resolution,
output_uniform,
draw_bind_group_layout,
);
let shadow_sampler = create_shadow_sampler(device);
let environment_cubemap =
create_environment_cubemap_texture(device, queue, environment_lighting.cubemap());
let environment_cubemap_view = environment_cubemap.create_view(&wgpu::TextureViewDescriptor {
label: Some("scena.output.environment_cubemap_view"),
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let environment_sampler = create_environment_sampler(device);
let brdf_lut_texture = create_brdf_lut_texture(device, queue, environment_lighting.cubemap());
let brdf_lut_view = brdf_lut_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("scena.output.brdf_lut_view"),
..Default::default()
});
let output_bind_group = create_output_bind_group(
device,
output_bind_group_layout,
output_uniform,
&shadow_caster.view,
&shadow_sampler,
&environment_cubemap_view,
&environment_sampler,
&brdf_lut_view,
);
OutputResources {
shadow_caster,
shadow_sampler,
environment_cubemap,
environment_sampler,
brdf_lut_texture,
output_bind_group,
}
}
pub(super) fn create_environment_cubemap_texture(
device: &wgpu::Device,
queue: &wgpu::Queue,
prepared: Option<&PreparedEnvironmentCubemap>,
) -> wgpu::Texture {
let resolution = prepared.map(|c| c.resolution).unwrap_or(1).max(1);
let mip_count = prepared.map(|c| c.mip_count).unwrap_or(1).max(1);
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("scena.m3.environment_cubemap"),
size: wgpu::Extent3d {
width: resolution,
height: resolution,
depth_or_array_layers: 6,
},
mip_level_count: mip_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
if let Some(prepared) = prepared {
for (mip_index, faces) in prepared.mips.iter().enumerate() {
let mip_resolution = (prepared.resolution >> mip_index).max(1);
for (face_index, face_pixels) in faces.iter().enumerate() {
let bytes = f32_slice_to_rgba16f_bytes(face_pixels);
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: mip_index as u32,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: face_index as u32,
},
aspect: wgpu::TextureAspect::All,
},
&bytes,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(mip_resolution * 8),
rows_per_image: Some(mip_resolution),
},
wgpu::Extent3d {
width: mip_resolution,
height: mip_resolution,
depth_or_array_layers: 1,
},
);
}
}
}
texture
}
fn f32_slice_to_rgba16f_bytes(values: &[f32]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(values.len() * 2);
for &value in values {
let half = f32_to_f16_bits(value);
bytes.extend_from_slice(&half.to_le_bytes());
}
bytes
}
fn f32_to_f16_bits(value: f32) -> u16 {
let bits = value.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16;
let exp32 = ((bits >> 23) & 0xff) as i32;
let mant32 = bits & 0x007f_ffff;
if exp32 == 0xff {
let mant16 = if mant32 != 0 { 0x200 } else { 0 };
return sign | 0x7c00 | mant16;
}
let exp = exp32 - 127 + 15;
if exp >= 0x1f {
return sign | 0x7c00; }
if exp <= 0 {
if exp < -10 {
return sign; }
let mant = mant32 | 0x0080_0000;
let shift = 14 - exp;
let rounded = (mant + (1 << (shift - 1))) >> shift;
return sign | rounded as u16;
}
let mant = mant32 >> 13;
let round = (mant32 & 0x1000) >> 12;
sign | ((exp as u16) << 10) | (mant as u16 + round as u16)
}
pub(super) fn create_brdf_lut_texture(
device: &wgpu::Device,
queue: &wgpu::Queue,
prepared: Option<&PreparedEnvironmentCubemap>,
) -> wgpu::Texture {
let size = prepared.map(|c| c.brdf_lut_size).unwrap_or(1).max(1);
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("scena.m3.brdf_lut"),
size: wgpu::Extent3d {
width: size,
height: size,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
if let Some(prepared) = prepared {
let bytes = f32_slice_to_bytes_le(&prepared.brdf_lut);
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&bytes,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(prepared.brdf_lut_size * 8),
rows_per_image: Some(prepared.brdf_lut_size),
},
wgpu::Extent3d {
width: prepared.brdf_lut_size,
height: prepared.brdf_lut_size,
depth_or_array_layers: 1,
},
);
}
texture
}
pub(super) fn create_environment_sampler(device: &wgpu::Device) -> wgpu::Sampler {
device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("scena.output.environment_sampler"),
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::MipmapFilterMode::Linear,
..Default::default()
})
}
fn f32_slice_to_bytes_le(values: &[f32]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(values.len() * 4);
for value in values {
bytes.extend_from_slice(&value.to_le_bytes());
}
bytes
}