use std::{borrow::Cow, sync::Mutex};
use awsm_renderer_core::{
bind_groups::{
BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry,
BindGroupLayoutResource, BindGroupResource, StorageTextureAccess,
StorageTextureBindingLayout, TextureBindingLayout,
},
command::{
compute_pass::{ComputePassDescriptor, ComputePassEncoder},
CommandEncoder,
},
error::AwsmCoreError,
pipeline::{
layout::{PipelineLayoutDescriptor, PipelineLayoutKind},
ComputePipelineDescriptor, ProgrammableStage,
},
renderer::AwsmRendererWebGpu,
shaders::{ShaderModuleDescriptor, ShaderModuleExt},
texture::{TextureFormat, TextureSampleType, TextureViewDescriptor, TextureViewDimension},
};
use thiserror::Error;
const SHADER_SOURCE: &str = r#"
@group(0) @binding(0) var src: texture_2d<f32>;
@group(0) @binding(1) var dst: texture_storage_2d<rgba16float, write>;
@compute @workgroup_size(8, 8, 1)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let dst_size = textureDimensions(dst);
if (gid.x >= dst_size.x || gid.y >= dst_size.y) { return; }
let coord = vec2<i32>(gid.xy);
let src_size = vec2<i32>(textureDimensions(src));
let src_max = src_size - vec2<i32>(1);
let base = coord * 2;
let s00 = textureLoad(src, clamp(base + vec2<i32>(0, 0), vec2<i32>(0), src_max), 0);
let s10 = textureLoad(src, clamp(base + vec2<i32>(1, 0), vec2<i32>(0), src_max), 0);
let s01 = textureLoad(src, clamp(base + vec2<i32>(0, 1), vec2<i32>(0), src_max), 0);
let s11 = textureLoad(src, clamp(base + vec2<i32>(1, 1), vec2<i32>(0), src_max), 0);
textureStore(dst, coord, (s00 + s10 + s01 + s11) * 0.25);
}
"#;
pub struct OpaqueMipgen {
pipeline: web_sys::GpuComputePipeline,
bind_group_layout: web_sys::GpuBindGroupLayout,
cache: Mutex<Option<MipgenCache>>,
}
struct MipgenCache {
width: u32,
height: u32,
mip_count: u32,
entries: Vec<MipgenCacheEntry>,
}
struct MipgenCacheEntry {
dst_width: u32,
dst_height: u32,
bind_group: web_sys::GpuBindGroup,
}
impl OpaqueMipgen {
pub async fn new(gpu: &AwsmRendererWebGpu) -> Result<Self> {
let shader_module = gpu.compile_shader(
&ShaderModuleDescriptor::new(SHADER_SOURCE, Some("Opaque Mipgen Shader")).into(),
);
shader_module.validate_shader().await?;
let bind_group_layout = gpu.create_bind_group_layout(
&BindGroupLayoutDescriptor::new(Some("Opaque Mipgen Layout"))
.with_entries(vec![
BindGroupLayoutEntry::new(
0,
BindGroupLayoutResource::Texture(
TextureBindingLayout::new()
.with_sample_type(TextureSampleType::UnfilterableFloat)
.with_view_dimension(TextureViewDimension::N2d)
.with_multisampled(false),
),
)
.with_visibility_compute(),
BindGroupLayoutEntry::new(
1,
BindGroupLayoutResource::StorageTexture(
StorageTextureBindingLayout::new(TextureFormat::Rgba16float)
.with_view_dimension(TextureViewDimension::N2d)
.with_access(StorageTextureAccess::WriteOnly),
),
)
.with_visibility_compute(),
])
.into(),
)?;
let pipeline_layout = gpu.create_pipeline_layout(
&PipelineLayoutDescriptor::new(
Some("Opaque Mipgen Pipeline Layout"),
vec![bind_group_layout.clone()],
)
.into(),
);
let compute = ProgrammableStage::new(&shader_module, None);
let pipeline = gpu
.create_compute_pipeline(
&ComputePipelineDescriptor::new(
compute,
PipelineLayoutKind::Custom(&pipeline_layout),
Some("Opaque Mipgen Pipeline"),
)
.into(),
)
.await?;
Ok(Self {
pipeline,
bind_group_layout,
cache: Mutex::new(None),
})
}
pub fn invalidate(&self) {
*self.cache.lock().unwrap() = None;
}
pub fn record(
&self,
gpu: &AwsmRendererWebGpu,
encoder: &CommandEncoder,
opaque_texture: &web_sys::GpuTexture,
mip_count: u32,
) -> Result<()> {
if mip_count < 2 {
return Ok(());
}
let width = opaque_texture.width();
let height = opaque_texture.height();
let needs_rebuild = match self.cache.lock().unwrap().as_ref() {
Some(cache) => {
cache.width != width || cache.height != height || cache.mip_count != mip_count
}
None => true,
};
if needs_rebuild {
let built = self.build_cache(gpu, opaque_texture, mip_count)?;
*self.cache.lock().unwrap() = Some(built);
}
let cache_ref = self.cache.lock().unwrap();
let cache = cache_ref.as_ref().expect("mipgen cache");
for entry in &cache.entries {
let descriptor: web_sys::GpuComputePassDescriptor =
ComputePassDescriptor::new(Some("Opaque Mipgen Pass")).into();
let pass: ComputePassEncoder = encoder.begin_compute_pass(Some(&descriptor));
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &entry.bind_group, None)?;
pass.dispatch_workgroups(
entry.dst_width.div_ceil(8),
Some(entry.dst_height.div_ceil(8)),
None,
);
pass.end();
}
Ok(())
}
fn build_cache(
&self,
gpu: &AwsmRendererWebGpu,
opaque_texture: &web_sys::GpuTexture,
mip_count: u32,
) -> Result<MipgenCache> {
let width = opaque_texture.width();
let height = opaque_texture.height();
let mut entries = Vec::with_capacity((mip_count - 1) as usize);
let mut src_width = width;
let mut src_height = height;
for mip in 1..mip_count {
let src_view = opaque_texture
.create_view_with_descriptor(
&TextureViewDescriptor::new(Some("Opaque Mipgen Src"))
.with_dimension(TextureViewDimension::N2d)
.with_base_mip_level(mip - 1)
.with_mip_level_count(1)
.into(),
)
.map_err(AwsmCoreError::create_texture_view)?;
let dst_view = opaque_texture
.create_view_with_descriptor(
&TextureViewDescriptor::new(Some("Opaque Mipgen Dst"))
.with_dimension(TextureViewDimension::N2d)
.with_base_mip_level(mip)
.with_mip_level_count(1)
.into(),
)
.map_err(AwsmCoreError::create_texture_view)?;
let bind_group = gpu.create_bind_group(
&BindGroupDescriptor::new(
&self.bind_group_layout,
Some("Opaque Mipgen Bind Group"),
vec![
BindGroupEntry::new(
0,
BindGroupResource::TextureView(Cow::Borrowed(&src_view)),
),
BindGroupEntry::new(
1,
BindGroupResource::TextureView(Cow::Borrowed(&dst_view)),
),
],
)
.into(),
);
let dst_width = (src_width / 2).max(1);
let dst_height = (src_height / 2).max(1);
entries.push(MipgenCacheEntry {
dst_width,
dst_height,
bind_group,
});
src_width = dst_width;
src_height = dst_height;
}
Ok(MipgenCache {
width,
height,
mip_count,
entries,
})
}
}
#[derive(Debug, Error)]
pub enum AwsmOpaqueMipgenError {
#[error("[opaque mipgen] core: {0:?}")]
Core(#[from] AwsmCoreError),
}
type Result<T> = std::result::Result<T, AwsmOpaqueMipgenError>;