use std::sync::Arc;
use wgpu::{
Surface, SurfaceConfiguration, TextureFormat, PresentMode, CompositeAlphaMode,
SurfaceTexture,
};
use winit::window::Window;
use log::{info, warn, debug};
use crate::renderer::RenderDevice;
use anvilkit_core::error::{AnvilKitError, Result};
pub struct RenderSurface {
surface: Surface<'static>,
config: SurfaceConfiguration,
format: TextureFormat,
_window: Arc<Window>,
}
impl RenderSurface {
pub fn new(device: &RenderDevice, window: &Arc<Window>) -> Result<Self> {
Self::new_with_vsync(device, window, false)
}
pub fn new_with_vsync(device: &RenderDevice, window: &Arc<Window>, vsync: bool) -> Result<Self> {
info!("创建渲染表面 (vsync={})", vsync);
let surface = device.instance().create_surface(window.clone())
.map_err(|e| AnvilKitError::render(format!("创建表面失败: {}", e)))?;
let capabilities = surface.get_capabilities(device.adapter());
let format = Self::choose_format(&capabilities.formats);
let size = window.inner_size();
let present_mode = if vsync {
PresentMode::Fifo
} else {
Self::choose_present_mode(&capabilities.present_modes)
};
let config = SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width,
height: size.height,
present_mode,
alpha_mode: Self::choose_alpha_mode(&capabilities.alpha_modes),
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(device.device(), &config);
info!("渲染表面创建成功");
info!("表面格式: {:?}", format);
info!("表面大小: {}x{}", config.width, config.height);
info!("呈现模式: {:?}", config.present_mode);
Ok(Self {
surface,
config,
format,
_window: window.clone(),
})
}
fn choose_format(formats: &[TextureFormat]) -> TextureFormat {
for &format in formats {
match format {
TextureFormat::Bgra8UnormSrgb | TextureFormat::Rgba8UnormSrgb => {
debug!("选择纹理格式: {:?}", format);
return format;
}
_ => {}
}
}
let format = formats[0];
debug!("回退到纹理格式: {:?}", format);
format
}
fn choose_present_mode(modes: &[PresentMode]) -> PresentMode {
if modes.contains(&PresentMode::Mailbox) {
debug!("选择呈现模式: Mailbox");
return PresentMode::Mailbox;
}
debug!("选择呈现模式: Fifo");
PresentMode::Fifo
}
fn choose_alpha_mode(modes: &[CompositeAlphaMode]) -> CompositeAlphaMode {
if modes.contains(&CompositeAlphaMode::Auto) {
debug!("选择 Alpha 模式: Auto");
return CompositeAlphaMode::Auto;
}
debug!("选择 Alpha 模式: Opaque");
CompositeAlphaMode::Opaque
}
pub fn resize(&mut self, device: &RenderDevice, width: u32, height: u32) -> Result<()> {
if width == 0 || height == 0 {
warn!("忽略无效的表面大小: {}x{}", width, height);
return Ok(());
}
info!("调整表面大小: {}x{}", width, height);
self.config.width = width;
self.config.height = height;
self.surface.configure(device.device(), &self.config);
Ok(())
}
pub fn get_current_frame(&self) -> Result<SurfaceTexture> {
self.surface.get_current_texture()
.map_err(|e| match e {
wgpu::SurfaceError::Lost => {
AnvilKitError::render("表面丢失,需要重新配置".to_string())
}
wgpu::SurfaceError::OutOfMemory => {
AnvilKitError::render("GPU 内存不足".to_string())
}
wgpu::SurfaceError::Timeout => {
AnvilKitError::render("获取表面纹理超时".to_string())
}
wgpu::SurfaceError::Outdated => {
AnvilKitError::render("表面配置过时,需要重新配置".to_string())
}
})
}
pub fn reconfigure(&self, device: &RenderDevice) {
info!("重新配置渲染表面: {}x{}", self.config.width, self.config.height);
self.surface.configure(device.device(), &self.config);
}
pub fn get_current_frame_with_recovery(&self, device: &RenderDevice) -> Result<SurfaceTexture> {
match self.surface.get_current_texture() {
Ok(frame) => Ok(frame),
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
warn!("表面需要重新配置,正在恢复...");
self.reconfigure(device);
self.surface.get_current_texture()
.map_err(|e| AnvilKitError::render(format!("表面恢复后仍失败: {}", e)))
}
Err(wgpu::SurfaceError::OutOfMemory) => {
Err(AnvilKitError::render("GPU 内存不足".to_string()))
}
Err(wgpu::SurfaceError::Timeout) => {
Err(AnvilKitError::render("获取表面纹理超时".to_string()))
}
}
}
pub fn config(&self) -> &SurfaceConfiguration {
&self.config
}
pub fn format(&self) -> TextureFormat {
self.format
}
pub fn size(&self) -> (u32, u32) {
(self.config.width, self.config.height)
}
pub fn surface(&self) -> &Surface<'static> {
&self.surface
}
}
#[cfg(test)]
mod tests {
use super::*;
use wgpu::{TextureFormat, PresentMode, CompositeAlphaMode};
#[test]
fn test_format_selection() {
let formats = vec![
TextureFormat::Rgba8Unorm,
TextureFormat::Bgra8UnormSrgb,
TextureFormat::Rgba8UnormSrgb,
];
let chosen = RenderSurface::choose_format(&formats);
assert_eq!(chosen, TextureFormat::Bgra8UnormSrgb);
}
#[test]
fn test_present_mode_selection() {
let modes = vec![
PresentMode::Fifo,
PresentMode::Mailbox,
PresentMode::Immediate,
];
let chosen = RenderSurface::choose_present_mode(&modes);
assert_eq!(chosen, PresentMode::Mailbox);
}
#[test]
fn test_alpha_mode_selection() {
let modes = vec![
CompositeAlphaMode::Opaque,
CompositeAlphaMode::Auto,
CompositeAlphaMode::PreMultiplied,
];
let chosen = RenderSurface::choose_alpha_mode(&modes);
assert_eq!(chosen, CompositeAlphaMode::Auto);
}
#[test]
fn test_format_srgb_preference() {
let formats = vec![
TextureFormat::Rgba8Unorm,
TextureFormat::Bgra8UnormSrgb,
TextureFormat::Rgba8UnormSrgb,
];
let srgb = formats.iter().find(|f| {
matches!(f,
TextureFormat::Bgra8UnormSrgb |
TextureFormat::Rgba8UnormSrgb
)
});
assert!(srgb.is_some());
}
#[test]
fn test_present_mode_vsync() {
let mode = PresentMode::Fifo;
assert_eq!(mode, PresentMode::Fifo);
let mode = PresentMode::Immediate;
assert_eq!(mode, PresentMode::Immediate);
}
}