use oxiui_core::UiError;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
#[derive(Clone, Debug)]
pub struct SurfaceConfig {
pub width: u32,
pub height: u32,
pub present_mode: wgpu::PresentMode,
pub alpha_mode: wgpu::CompositeAlphaMode,
pub desired_format: Option<wgpu::TextureFormat>,
}
impl Default for SurfaceConfig {
fn default() -> Self {
Self {
width: 800,
height: 600,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
desired_format: None,
}
}
}
pub struct SurfaceContext {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface: wgpu::Surface<'static>,
pub surface_format: wgpu::TextureFormat,
config: wgpu::SurfaceConfiguration,
}
impl SurfaceContext {
pub unsafe fn from_raw_handles(
window_handle: RawWindowHandle,
display_handle: RawDisplayHandle,
config: SurfaceConfig,
) -> Result<Self, UiError> {
let instance = wgpu::Instance::default();
let surface_target = wgpu::SurfaceTargetUnsafe::RawHandle {
raw_window_handle: window_handle,
raw_display_handle: Some(display_handle),
};
let surface = unsafe { instance.create_surface_unsafe(surface_target) }
.map_err(|e| UiError::Backend(format!("wgpu surface creation failed: {e}")))?;
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
compatible_surface: Some(&surface),
}))
.map_err(|e| UiError::Unsupported(format!("no GPU adapter for surface: {e}")))?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("oxiui-render-wgpu windowed device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
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 surface_format = if let Some(fmt) = config.desired_format {
fmt
} else {
let caps = surface.get_capabilities(&adapter);
caps.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or_else(|| {
*caps
.formats
.first()
.unwrap_or(&wgpu::TextureFormat::Bgra8Unorm)
})
};
let sc_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: config.width.max(1),
height: config.height.max(1),
present_mode: config.present_mode,
alpha_mode: config.alpha_mode,
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &sc_config);
Ok(Self {
device,
queue,
surface,
surface_format,
config: sc_config,
})
}
pub fn resize(&mut self, width: u32, height: u32) {
self.config.width = width.max(1);
self.config.height = height.max(1);
self.surface.configure(&self.device, &self.config);
}
pub fn acquire_frame(&self) -> Option<wgpu::SurfaceTexture> {
match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame) => Some(frame),
wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
Some(frame)
}
wgpu::CurrentSurfaceTexture::Timeout => None,
wgpu::CurrentSurfaceTexture::Occluded => None,
other => {
eprintln!("[oxiui-render-wgpu] surface error: {other:?} — frame skipped");
None
}
}
}
pub fn present_frame(&self, frame: wgpu::SurfaceTexture) {
frame.present();
}
pub fn width(&self) -> u32 {
self.config.width
}
pub fn height(&self) -> u32 {
self.config.height
}
pub fn format(&self) -> wgpu::TextureFormat {
self.surface_format
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn surface_config_default_is_sane() {
let c = SurfaceConfig::default();
assert!(c.width > 0 && c.height > 0);
assert_eq!(c.width, 800);
assert_eq!(c.height, 600);
}
#[test]
fn surface_config_clone_and_debug() {
let c = SurfaceConfig::default();
let c2 = c.clone();
assert_eq!(c2.width, c.width);
let _ = format!("{c:?}");
}
}