use super::*;
impl ViewportGpuResources {
pub fn upload_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
width: u32,
height: u32,
rgba_data: &[u8],
) -> crate::error::ViewportResult<u64> {
let expected = (width * height * 4) as usize;
if rgba_data.len() != expected {
return Err(crate::error::ViewportError::InvalidTextureData {
expected,
actual: rgba_data.len(),
});
}
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("user_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::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba_data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width * 4),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("user_texture_sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("user_texture_bg"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&self.fallback_normal_map_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(&self.fallback_ao_map_view),
},
],
});
let id = self.textures.len() as u64;
self.texture_allocated_bytes += (width * height * 4) as u64;
self.textures.push(GpuTexture {
texture,
view,
sampler,
bind_group,
});
tracing::debug!(texture_id = id, width, height, "texture uploaded");
Ok(id)
}
pub fn upload_normal_map(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
width: u32,
height: u32,
rgba_data: &[u8],
) -> crate::error::ViewportResult<u64> {
let expected = (width * height * 4) as usize;
if rgba_data.len() != expected {
return Err(crate::error::ViewportError::InvalidTextureData {
expected,
actual: rgba_data.len(),
});
}
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("normal_map_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::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba_data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width * 4),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("normal_map_sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("normal_map_bg"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.fallback_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(&self.fallback_ao_map_view),
},
],
});
let id = self.textures.len() as u64;
self.texture_allocated_bytes += (width * height * 4) as u64;
self.textures.push(GpuTexture {
texture,
view,
sampler,
bind_group,
});
tracing::debug!(texture_id = id, width, height, "normal map uploaded");
Ok(id)
}
pub fn upload_texture_async(
&mut self,
device: &wgpu::Device,
width: u32,
height: u32,
rgba: &[u8],
) -> crate::error::ViewportResult<PendingTextureId> {
let expected = (width * height * 4) as usize;
if rgba.len() != expected {
return Err(crate::error::ViewportError::InvalidTextureData {
expected,
actual: rgba.len(),
});
}
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let raw_bpr = width * 4;
let aligned_bytes_per_row = (raw_bpr + align - 1) / align * align;
let staging_size = aligned_bytes_per_row as u64 * height as u64;
let (staging_buf, pool_band) = self.staging_pool.acquire(device, staging_size);
{
let mut mapped = staging_buf.slice(..).get_mapped_range_mut();
if aligned_bytes_per_row == raw_bpr {
mapped[..rgba.len()].copy_from_slice(rgba);
} else {
for row in 0..height as usize {
let src = row * raw_bpr as usize..(row + 1) * raw_bpr as usize;
let dst_start = row * aligned_bytes_per_row as usize;
mapped[dst_start..dst_start + raw_bpr as usize]
.copy_from_slice(&rgba[src]);
}
}
}
staging_buf.unmap();
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("async_user_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::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("async_user_texture_sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("async_user_texture_bg"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(
&self.fallback_normal_map_view,
),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(&self.fallback_ao_map_view),
},
],
});
let pending_id = self.next_pending_texture_id;
self.next_pending_texture_id += 1;
self.pending_texture_uploads.push(PendingUploadEntry {
pending_id,
gpu_texture: GpuTexture {
texture,
view,
sampler,
bind_group,
},
staging_buf,
pool_band,
width,
height,
aligned_bytes_per_row,
data_bytes: expected as u64,
copy_submitted: false,
ready: false,
});
tracing::debug!(pending_id, width, height, "async texture upload queued");
Ok(PendingTextureId(pending_id))
}
pub fn is_upload_ready(&self, id: PendingTextureId) -> bool {
self.pending_texture_uploads
.iter()
.find(|e| e.pending_id == id.0)
.map_or(false, |e| e.ready)
}
pub fn promote_texture(&mut self, id: PendingTextureId) -> Option<u64> {
let pos = self
.pending_texture_uploads
.iter()
.position(|e| e.pending_id == id.0 && e.ready)?;
let entry = self.pending_texture_uploads.swap_remove(pos);
self.staging_pool.release(entry.staging_buf, entry.pool_band);
let texture_id = self.textures.len() as u64;
self.texture_allocated_bytes += entry.data_bytes;
self.textures.push(entry.gpu_texture);
tracing::debug!(
texture_id,
width = entry.width,
height = entry.height,
"async texture promoted"
);
Some(texture_id)
}
pub fn texture_memory_stats(&self) -> TextureMemoryStats {
TextureMemoryStats {
used_bytes: self.texture_allocated_bytes,
texture_count: self.textures.len() as u32,
}
}
pub(crate) fn submit_pending_texture_uploads(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
) {
if self.pending_texture_uploads.is_empty() {
return;
}
for entry in &mut self.pending_texture_uploads {
if entry.copy_submitted && !entry.ready {
entry.ready = true;
}
}
let has_new = self
.pending_texture_uploads
.iter()
.any(|e| !e.copy_submitted);
if !has_new {
return;
}
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("async_texture_copy"),
});
for entry in &mut self.pending_texture_uploads {
if entry.copy_submitted {
continue;
}
encoder.copy_buffer_to_texture(
wgpu::TexelCopyBufferInfo {
buffer: &entry.staging_buf,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(entry.aligned_bytes_per_row),
rows_per_image: Some(entry.height),
},
},
wgpu::TexelCopyTextureInfo {
texture: &entry.gpu_texture.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::Extent3d {
width: entry.width,
height: entry.height,
depth_or_array_layers: 1,
},
);
entry.copy_submitted = true;
}
queue.submit(std::iter::once(encoder.finish()));
}
#[allow(dead_code)]
pub(crate) fn get_material_bind_group(
&mut self,
device: &wgpu::Device,
albedo_id: Option<u64>,
normal_map_id: Option<u64>,
ao_map_id: Option<u64>,
) -> &wgpu::BindGroup {
let key = (
albedo_id.unwrap_or(u64::MAX),
normal_map_id.unwrap_or(u64::MAX),
ao_map_id.unwrap_or(u64::MAX),
);
if !self.material_bind_groups.contains_key(&key) {
let albedo_view = match albedo_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_texture.view,
};
let normal_view = match normal_map_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_normal_map_view,
};
let ao_view = match ao_map_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_ao_map_view,
};
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("material_bg"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(albedo_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.material_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(normal_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(ao_view),
},
],
});
self.material_bind_groups.insert(key, bg);
}
self.material_bind_groups.get(&key).unwrap()
}
pub(crate) fn update_mesh_texture_bind_group(
&mut self,
device: &wgpu::Device,
mesh_id: crate::resources::mesh_store::MeshId,
albedo_id: Option<u64>,
normal_map_id: Option<u64>,
ao_map_id: Option<u64>,
lut_id: Option<ColourmapId>,
active_attr: Option<&str>,
matcap_id: Option<crate::resources::MatcapId>,
warp_attr: Option<&str>,
metallic_roughness_id: Option<u64>,
emissive_texture_id: Option<u64>,
) {
let hash_str = |name: &str| -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut h);
h.finish()
};
let attr_hash = active_attr.map(|n| hash_str(n)).unwrap_or(u64::MAX);
let warp_hash = warp_attr.map(|n| hash_str(n)).unwrap_or(u64::MAX);
let key = (
albedo_id.unwrap_or(u64::MAX),
normal_map_id.unwrap_or(u64::MAX),
ao_map_id.unwrap_or(u64::MAX),
lut_id.map(|id| id.0 as u64).unwrap_or(u64::MAX),
attr_hash,
matcap_id.map(|id| id.index as u64).unwrap_or(u64::MAX),
warp_hash,
metallic_roughness_id.unwrap_or(u64::MAX),
emissive_texture_id.unwrap_or(u64::MAX),
);
{
let Some(mesh) = self.mesh_store.get(mesh_id) else {
return;
};
if mesh.last_tex_key == key {
return;
}
}
let albedo_view = match albedo_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_texture.view,
};
let normal_view = match normal_map_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_normal_map_view,
};
let ao_view = match ao_map_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_ao_map_view,
};
let lut_view = match lut_id {
Some(id) if id.0 < self.colourmap_views.len() => &self.colourmap_views[id.0],
_ => &self.fallback_lut_view,
};
let Some(mesh) = self.mesh_store.get_mut(mesh_id) else {
return;
};
let scalar_buf: &wgpu::Buffer = match active_attr {
Some(name) => {
let found_vertex = mesh.attribute_buffers.get(name);
let found_face = mesh.face_attribute_buffers.get(name);
found_vertex
.or(found_face)
.unwrap_or(&self.fallback_scalar_buf)
}
None => &self.fallback_scalar_buf,
};
let face_colour_buf: &wgpu::Buffer = match active_attr {
Some(name) => mesh
.face_colour_buffers
.get(name)
.unwrap_or(&self.fallback_face_colour_buf),
None => &self.fallback_face_colour_buf,
};
let matcap_view: &wgpu::TextureView = match matcap_id {
Some(id) if id.index < self.matcap_views.len() => &self.matcap_views[id.index],
_ => self
.fallback_matcap_view
.as_ref()
.unwrap_or(&self.fallback_texture.view),
};
let warp_buf: &wgpu::Buffer = match warp_attr {
Some(name) => mesh
.vector_attribute_buffers
.get(name)
.unwrap_or(&self.fallback_warp_buf),
None => &self.fallback_warp_buf,
};
let metallic_roughness_view: &wgpu::TextureView = match metallic_roughness_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_metallic_roughness_texture_view,
};
let emissive_view: &wgpu::TextureView = match emissive_texture_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_emissive_texture_view,
};
mesh.object_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("object_bind_group"),
layout: &self.object_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: mesh.object_uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(albedo_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.material_sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(normal_view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(ao_view),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(lut_view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: scalar_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(matcap_view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: face_colour_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 9,
resource: warp_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::Sampler(&self.lut_sampler),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::TextureView(metallic_roughness_view),
},
wgpu::BindGroupEntry {
binding: 12,
resource: wgpu::BindingResource::TextureView(emissive_view),
},
],
});
mesh.last_tex_key = key;
}
pub fn upload_colourmap(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
rgba_data: &[[u8; 4]; 256],
) -> ColourmapId {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("lut_texture"),
size: wgpu::Extent3d {
width: 256,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let flat: Vec<u8> = rgba_data.iter().flat_map(|p| p.iter().copied()).collect();
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&flat,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(256 * 4),
rows_per_image: Some(1),
},
wgpu::Extent3d {
width: 256,
height: 1,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let id = ColourmapId(self.colourmap_textures.len());
self.colourmap_textures.push(texture);
self.colourmap_views.push(view);
self.colourmaps_cpu.push(*rgba_data);
id
}
pub fn get_colourmap_rgba(&self, id: ColourmapId) -> Option<&[[u8; 4]; 256]> {
self.colourmaps_cpu.get(id.0)
}
pub fn builtin_colourmap_id(&self, preset: BuiltinColourmap) -> ColourmapId {
self.builtin_colourmap_ids
.expect("call ensure_colourmaps_initialized before using built-in colourmaps")
[preset as usize]
}
pub fn ensure_colourmaps_initialized(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
if self.colourmaps_initialized {
return;
}
let viridis = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::viridis_rgba(),
);
let plasma = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::plasma_rgba(),
);
let greyscale = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::greyscale_rgba(),
);
let coolwarm = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::coolwarm_rgba(),
);
let rainbow = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::rainbow_rgba(),
);
let magma = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::magma_rgba(),
);
let inferno = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::inferno_rgba(),
);
let turbo = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::turbo_rgba(),
);
let jet = self.upload_colourmap(device, queue, &crate::resources::colourmap_data::jet_rgba());
let rdbu = self.upload_colourmap(
device,
queue,
&crate::resources::colourmap_data::rdbu_r_rgba(),
);
self.builtin_colourmap_ids = Some([
viridis, plasma, greyscale, coolwarm, rainbow, magma, inferno, turbo, jet, rdbu,
]);
self.colourmaps_initialized = true;
}
pub fn upload_matcap(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
rgba_data: &[u8],
blendable: bool,
) -> crate::error::ViewportResult<crate::resources::MatcapId> {
let (width, height) = (256u32, 256u32);
let expected = (width * height * 4) as usize;
if rgba_data.len() != expected {
return Err(crate::error::ViewportError::InvalidTextureData {
expected,
actual: rgba_data.len(),
});
}
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("matcap_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::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba_data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width * 4),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
if self.matcap_sampler.is_none() {
self.matcap_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("matcap_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
}));
}
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let index = self.matcap_textures.len();
self.matcap_textures.push(texture);
self.matcap_views.push(view);
if self.fallback_matcap_view.is_none() {
self.fallback_matcap_view = Some(
self.fallback_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default()),
);
}
tracing::debug!(matcap_index = index, blendable, "matcap uploaded");
Ok(crate::resources::MatcapId { index, blendable })
}
pub fn builtin_matcap_id(
&self,
preset: crate::resources::BuiltinMatcap,
) -> crate::resources::MatcapId {
self.builtin_matcap_ids
.expect("call ensure_matcaps_initialized (or run one prepare frame) before using built-in matcaps")
[preset as usize]
}
pub fn ensure_matcaps_initialized(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
if self.matcaps_initialized {
return;
}
use crate::resources::matcap_data;
let clay = self
.upload_matcap(device, queue, &matcap_data::clay(), true)
.unwrap();
let wax = self
.upload_matcap(device, queue, &matcap_data::wax(), true)
.unwrap();
let candy = self
.upload_matcap(device, queue, &matcap_data::candy(), true)
.unwrap();
let flat = self
.upload_matcap(device, queue, &matcap_data::flat(), true)
.unwrap();
let ceramic = self
.upload_matcap(device, queue, &matcap_data::ceramic(), false)
.unwrap();
let jade = self
.upload_matcap(device, queue, &matcap_data::jade(), false)
.unwrap();
let mud = self
.upload_matcap(device, queue, &matcap_data::mud(), false)
.unwrap();
let normal = self
.upload_matcap(device, queue, &matcap_data::normal(), false)
.unwrap();
self.builtin_matcap_ids = Some([clay, wax, candy, flat, ceramic, jade, mud, normal]);
self.matcaps_initialized = true;
}
}