use anyhow::{Result, bail};
pub struct WgpuSurface {
texture: wgpu::Texture,
view: wgpu::TextureView,
format: wgpu::TextureFormat,
width: u32,
height: u32,
}
impl WgpuSurface {
pub const DEFAULT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Result<Self> {
Self::with_format(device, width, height, Self::DEFAULT_FORMAT)
}
pub fn with_format(
device: &wgpu::Device,
width: u32,
height: u32,
format: wgpu::TextureFormat,
) -> Result<Self> {
validate(format, width, height)?;
let (texture, view) = allocate(device, width, height, format);
Ok(Self {
texture,
view,
format,
width,
height,
})
}
pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
pub fn view(&self) -> &wgpu::TextureView {
&self.view
}
pub fn format(&self) -> wgpu::TextureFormat {
self.format
}
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) -> Result<bool> {
if (width, height) == (self.width, self.height) {
return Ok(false);
}
let next = Self::with_format(device, width, height, self.format)?;
*self = next;
Ok(true)
}
}
fn validate(format: wgpu::TextureFormat, width: u32, height: u32) -> Result<()> {
if !matches!(
format,
wgpu::TextureFormat::Rgba8UnormSrgb | wgpu::TextureFormat::Bgra8UnormSrgb
) {
bail!(
"a GPUI surface must sample as sRGB (Rgba8UnormSrgb or Bgra8UnormSrgb), \
but {format:?} was asked for"
);
}
if width == 0 || height == 0 {
bail!("a GPUI surface must have a non-zero extent, but {width}x{height} was asked for");
}
Ok(())
}
fn allocate(
device: &wgpu::Device,
width: u32,
height: u32,
format: wgpu::TextureFormat,
) -> (wgpu::Texture, wgpu::TextureView) {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("gpui_interop_wgpu_surface"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("gpui_interop_wgpu_surface_view"),
..Default::default()
});
(texture, view)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_the_srgb_forms_are_surfaces() {
for format in [
wgpu::TextureFormat::Rgba8UnormSrgb,
wgpu::TextureFormat::Bgra8UnormSrgb,
] {
assert!(
validate(format, 4, 4).is_ok(),
"{format:?} should be accepted"
);
}
for format in [
wgpu::TextureFormat::Rgba8Unorm,
wgpu::TextureFormat::Bgra8Unorm,
wgpu::TextureFormat::Rgba16Float,
] {
let error = validate(format, 4, 4).expect_err("a non-sRGB format is not a surface");
assert!(
error.to_string().contains("sRGB"),
"the refusal should name the colour-space rule, not {error}"
);
}
}
#[test]
fn a_zero_extent_is_refused() {
assert!(validate(WgpuSurface::DEFAULT_FORMAT, 0, 4).is_err());
assert!(validate(WgpuSurface::DEFAULT_FORMAT, 4, 0).is_err());
}
}