use oxiui_core::UiError;
pub const TARGET_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
pub struct GpuContext {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub color_texture: wgpu::Texture,
pub color_view: wgpu::TextureView,
pub width: u32,
pub height: u32,
pub sample_count: u32,
pub msaa_view: Option<wgpu::TextureView>,
}
impl GpuContext {
pub fn headless_with_sample_count(
width: u32,
height: u32,
requested: u32,
) -> Result<Self, UiError> {
if width == 0 || height == 0 {
return Err(UiError::Unsupported(
"headless target dimensions must be non-zero".to_string(),
));
}
let instance = wgpu::Instance::default();
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
compatible_surface: None,
}))
.map_err(|e| UiError::Unsupported(format!("no GPU adapter available: {e}")))?;
let effective_count = match requested {
4 => {
let flags = adapter.get_texture_format_features(TARGET_FORMAT).flags;
if flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X4) {
4
} else {
1
}
}
8 => {
let flags = adapter.get_texture_format_features(TARGET_FORMAT).flags;
if flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X8) {
8
} else {
1
}
}
_ => 1,
};
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("oxiui-render-wgpu headless device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_defaults(),
memory_hints: wgpu::MemoryHints::Performance,
experimental_features: wgpu::ExperimentalFeatures::disabled(),
trace: wgpu::Trace::Off,
}))
.map_err(|e| UiError::Backend(format!("GPU device request failed: {e}")))?;
let color_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("oxiui-render-wgpu offscreen target"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: TARGET_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let color_view = color_texture.create_view(&wgpu::TextureViewDescriptor::default());
let msaa_view = if effective_count > 1 {
let msaa_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("oxiui-render-wgpu msaa color"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: effective_count,
dimension: wgpu::TextureDimension::D2,
format: TARGET_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
Some(msaa_texture.create_view(&wgpu::TextureViewDescriptor::default()))
} else {
None
};
Ok(Self {
device,
queue,
color_texture,
color_view,
width,
height,
sample_count: effective_count,
msaa_view,
})
}
pub fn headless(width: u32, height: u32) -> Result<Self, UiError> {
Self::headless_with_sample_count(width, height, 1)
}
pub fn color_attachment(&self) -> (&wgpu::TextureView, Option<&wgpu::TextureView>) {
match &self.msaa_view {
Some(msaa) => (msaa, Some(&self.color_view)),
None => (&self.color_view, None),
}
}
pub fn sample_count(&self) -> u32 {
self.sample_count
}
pub fn resize(&mut self, new_width: u32, new_height: u32) -> Result<(), UiError> {
if new_width == 0 || new_height == 0 {
return Err(UiError::Unsupported(
"GpuContext resize dimensions must be non-zero".to_string(),
));
}
let color_texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("oxiui-render-wgpu offscreen target (resized)"),
size: wgpu::Extent3d {
width: new_width,
height: new_height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: TARGET_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let color_view = color_texture.create_view(&wgpu::TextureViewDescriptor::default());
let msaa_view = if self.sample_count > 1 {
let msaa_texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("oxiui-render-wgpu msaa color (resized)"),
size: wgpu::Extent3d {
width: new_width,
height: new_height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: self.sample_count,
dimension: wgpu::TextureDimension::D2,
format: TARGET_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
Some(msaa_texture.create_view(&wgpu::TextureViewDescriptor::default()))
} else {
None
};
self.color_texture = color_texture;
self.color_view = color_view;
self.msaa_view = msaa_view;
self.width = new_width;
self.height = new_height;
Ok(())
}
}