Files
OpenCut/rust/crates/gpu/src/context.rs
T

251 lines
8.5 KiB
Rust
Raw Normal View History

use wgpu::util::DeviceExt;
use crate::{
GPU_TEXTURE_FORMAT, GpuError,
effect_pipeline::{ApplyEffectsOptions, apply_effects},
mask_feather::{ApplyMaskFeatherOptions, MaskFeatherPipeline},
sdf_pipeline::SdfPipeline,
shader_registry::ShaderRegistry,
};
const FULLSCREEN_QUAD_POSITIONS: [[f32; 2]; 6] = [
[-1.0, -1.0],
[1.0, -1.0],
[-1.0, 1.0],
[-1.0, 1.0],
[1.0, -1.0],
[1.0, 1.0],
];
pub struct GpuContext {
instance: wgpu::Instance,
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
fullscreen_quad: wgpu::Buffer,
linear_sampler: wgpu::Sampler,
nearest_sampler: wgpu::Sampler,
shader_registry: ShaderRegistry,
sdf_pipeline: SdfPipeline,
mask_feather_pipeline: MaskFeatherPipeline,
}
impl GpuContext {
pub async fn new() -> Result<Self, GpuError> {
let instance = wgpu::Instance::default();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.map_err(|_| GpuError::AdapterUnavailable)?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("gpu-device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::Performance,
experimental_features: wgpu::ExperimentalFeatures::disabled(),
trace: wgpu::Trace::Off,
})
.await?;
let fullscreen_quad = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("gpu-fullscreen-quad-buffer"),
contents: bytemuck::cast_slice(&FULLSCREEN_QUAD_POSITIONS),
usage: wgpu::BufferUsages::VERTEX,
});
let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("gpu-linear-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::Nearest,
..Default::default()
});
let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("gpu-nearest-sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let shader_registry = ShaderRegistry::new(&device);
let sdf_pipeline = SdfPipeline::new(&device);
let mask_feather_pipeline = MaskFeatherPipeline::new(&device);
Ok(Self {
instance,
adapter,
device,
queue,
fullscreen_quad,
linear_sampler,
nearest_sampler,
shader_registry,
sdf_pipeline,
mask_feather_pipeline,
})
}
pub fn apply_effects(
&self,
options: ApplyEffectsOptions<'_>,
) -> Result<wgpu::Texture, GpuError> {
apply_effects(self, options)
}
pub fn apply_mask_feather(
&self,
options: ApplyMaskFeatherOptions<'_>,
) -> wgpu::Texture {
self.mask_feather_pipeline.apply_mask_feather(self, options)
}
pub fn create_render_texture(
&self,
width: u32,
height: u32,
label: &'static str,
) -> wgpu::Texture {
self.device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: GPU_TEXTURE_FORMAT,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
})
}
pub fn instance(&self) -> &wgpu::Instance {
&self.instance
}
pub fn adapter(&self) -> &wgpu::Adapter {
&self.adapter
}
pub fn device(&self) -> &wgpu::Device {
&self.device
}
pub fn queue(&self) -> &wgpu::Queue {
&self.queue
}
pub fn fullscreen_quad(&self) -> &wgpu::Buffer {
&self.fullscreen_quad
}
pub fn linear_sampler(&self) -> &wgpu::Sampler {
&self.linear_sampler
}
pub fn nearest_sampler(&self) -> &wgpu::Sampler {
&self.nearest_sampler
}
pub fn shader_registry(&self) -> &ShaderRegistry {
&self.shader_registry
}
pub fn sdf_pipeline(&self) -> &SdfPipeline {
&self.sdf_pipeline
}
pub fn render_texture_to_surface(
&self,
texture: &wgpu::Texture,
surface: &wgpu::Surface<'_>,
width: u32,
height: u32,
) -> Result<(), GpuError> {
let Some(config) = surface.get_default_config(&self.adapter, width, height) else {
return Err(GpuError::UnsupportedSurfaceFormat);
};
if config.format != GPU_TEXTURE_FORMAT {
return Err(GpuError::UnsupportedSurfaceFormat);
}
surface.configure(&self.device, &config);
let surface_texture = match surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(surface_texture)
| wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture,
wgpu::CurrentSurfaceTexture::Timeout
| wgpu::CurrentSurfaceTexture::Occluded
| wgpu::CurrentSurfaceTexture::Outdated
| wgpu::CurrentSurfaceTexture::Lost
| wgpu::CurrentSurfaceTexture::Validation => {
return Err(GpuError::UnsupportedSurfaceFormat);
}
};
let source_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let target_view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("gpu-blit-bind-group"),
layout: self.shader_registry.effect_texture_bind_group_layout(),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&source_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.linear_sampler),
},
],
});
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("gpu-surface-blit-encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("gpu-surface-blit-pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &target_view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
multiview_mask: None,
});
render_pass.set_pipeline(self.shader_registry.blit_pipeline());
render_pass.set_vertex_buffer(0, self.fullscreen_quad.slice(..));
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.draw(0..6, 0..1);
}
self.queue.submit([encoder.finish()]);
surface_texture.present();
Ok(())
}
}