use astrelis_core::{
geometry::{LogicalSize, PhysicalPosition, PhysicalSize, ScaleFactor},
profiling::profile_function,
};
use astrelis_winit::{
WindowId,
window::{Window, WindowBackend},
};
use std::cell::{Cell, RefCell};
use std::sync::Arc;
use crate::{
context::{GraphicsContext, GraphicsError},
depth::{DEFAULT_DEPTH_FORMAT, DepthTexture},
frame::{AtomicFrameStats, Frame, Surface},
gpu_profiling::GpuFrameProfiler,
};
#[derive(Debug, Clone, Copy)]
pub struct Viewport {
pub position: PhysicalPosition<f32>,
pub size: PhysicalSize<f32>,
pub scale_factor: ScaleFactor,
}
impl Default for Viewport {
fn default() -> Self {
Self {
position: PhysicalPosition::new(0.0, 0.0),
size: PhysicalSize::new(800.0, 600.0),
scale_factor: ScaleFactor(1.0),
}
}
}
impl Viewport {
pub fn new(width: f32, height: f32, scale_factor: ScaleFactor) -> Self {
Self {
position: PhysicalPosition::new(0.0, 0.0),
size: PhysicalSize::new(width, height),
scale_factor,
}
}
pub fn from_physical_size(size: PhysicalSize<u32>, scale_factor: ScaleFactor) -> Self {
Self {
position: PhysicalPosition::new(0.0, 0.0),
size: PhysicalSize::new(size.width as f32, size.height as f32),
scale_factor,
}
}
pub fn is_valid(&self) -> bool {
self.size.width > 0.0 && self.size.height > 0.0 && self.scale_factor.0 > 0.0
}
pub fn to_logical(&self) -> LogicalSize<f32> {
self.size.to_logical(self.scale_factor)
}
pub fn width(&self) -> f32 {
self.size.width
}
pub fn height(&self) -> f32 {
self.size.height
}
pub fn x(&self) -> f32 {
self.position.x
}
pub fn y(&self) -> f32 {
self.position.y
}
}
#[derive(Default)]
pub struct WindowContextDescriptor {
pub format: Option<wgpu::TextureFormat>,
pub present_mode: Option<wgpu::PresentMode>,
pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
pub with_depth: bool,
pub depth_format: Option<wgpu::TextureFormat>,
}
pub(crate) struct PendingReconfigure {
pub(crate) resize: Option<PhysicalSize<u32>>,
}
impl PendingReconfigure {
const fn new() -> Self {
Self { resize: None }
}
}
pub struct WindowContext {
pub(crate) window: Window,
pub(crate) context: Arc<GraphicsContext>,
pub(crate) surface: wgpu::Surface<'static>,
pub(crate) config: wgpu::SurfaceConfiguration,
pub(crate) reconfigure: PendingReconfigure,
pub(crate) depth_texture: Option<DepthTexture>,
}
impl WindowContext {
pub fn new(
window: Window,
context: Arc<GraphicsContext>,
descriptor: WindowContextDescriptor,
) -> Result<Self, GraphicsError> {
profile_function!();
let scale_factor = window.scale_factor();
let logical_size = window.logical_size();
let physical_size = logical_size.to_physical(scale_factor);
let surface = context
.instance()
.create_surface(window.window.clone())
.map_err(|e| GraphicsError::SurfaceCreationFailed(e.to_string()))?;
let mut config = surface
.get_default_config(context.adapter(), physical_size.width, physical_size.height)
.ok_or_else(|| {
GraphicsError::SurfaceConfigurationFailed(
"No suitable surface configuration found".to_string(),
)
})?;
if let Some(format) = descriptor.format {
config.format = format;
}
if let Some(present_mode) = descriptor.present_mode {
config.present_mode = present_mode;
}
if let Some(alpha_mode) = descriptor.alpha_mode {
config.alpha_mode = alpha_mode;
}
surface.configure(context.device(), &config);
let depth_texture = if descriptor.with_depth {
let depth_format = descriptor.depth_format.unwrap_or(DEFAULT_DEPTH_FORMAT);
Some(DepthTexture::with_label(
context.device(),
physical_size.width,
physical_size.height,
depth_format,
"Window Depth Texture",
))
} else {
None
};
Ok(Self {
window,
surface,
config,
reconfigure: PendingReconfigure::new(),
context,
depth_texture,
})
}
pub fn resized(&mut self, new_size: LogicalSize<u32>) {
let scale_factor = self.window.scale_factor();
let physical_size = new_size.to_physical(scale_factor);
self.reconfigure.resize = Some(physical_size);
}
pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
self.reconfigure.resize = Some(new_size);
if let Some(ref mut depth) = self.depth_texture
&& depth.needs_resize(new_size.width, new_size.height)
{
depth.resize(self.context.device(), new_size.width, new_size.height);
}
}
pub fn window(&self) -> &Window {
&self.window
}
pub fn graphics_context(&self) -> &GraphicsContext {
&self.context
}
pub fn surface(&self) -> &wgpu::Surface<'static> {
&self.surface
}
pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
&self.config
}
pub fn surface_format(&self) -> wgpu::TextureFormat {
self.config.format
}
pub fn logical_size(&self) -> LogicalSize<u32> {
self.window.logical_size()
}
pub fn physical_size(&self) -> PhysicalSize<u32> {
self.window.physical_size()
}
pub fn logical_size_f32(&self) -> LogicalSize<f32> {
let size = self.logical_size();
LogicalSize::new(size.width as f32, size.height as f32)
}
pub fn physical_size_f32(&self) -> PhysicalSize<f32> {
let size = self.physical_size();
PhysicalSize::new(size.width as f32, size.height as f32)
}
pub fn reconfigure_surface(&mut self, config: wgpu::SurfaceConfiguration) {
self.config = config;
self.surface.configure(self.context.device(), &self.config);
}
pub fn has_depth(&self) -> bool {
self.depth_texture.is_some()
}
pub fn depth_view(&self) -> Option<std::sync::Arc<wgpu::TextureView>> {
self.depth_texture.as_ref().map(|d| d.view())
}
pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
self.depth_texture.as_ref().map(|d| d.format())
}
pub fn ensure_depth(&mut self, format: wgpu::TextureFormat) {
if self.depth_texture.is_none() {
self.depth_texture = Some(DepthTexture::with_label(
self.context.device(),
self.config.width,
self.config.height,
format,
"Window Depth Texture",
));
}
}
}
impl WindowContext {
fn try_acquire_surface_texture(&mut self) -> Result<wgpu::SurfaceTexture, GraphicsError> {
match self.surface.get_current_texture() {
Ok(frame) => return Ok(frame),
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
tracing::debug!("Surface lost/outdated, reconfiguring...");
self.surface.configure(self.context.device(), &self.config);
}
Err(wgpu::SurfaceError::OutOfMemory) => {
return Err(GraphicsError::SurfaceOutOfMemory);
}
Err(wgpu::SurfaceError::Timeout) => {
return Err(GraphicsError::SurfaceTimeout);
}
Err(e) => {
return Err(GraphicsError::SurfaceTextureAcquisitionFailed(
e.to_string(),
));
}
}
match self.surface.get_current_texture() {
Ok(frame) => Ok(frame),
Err(wgpu::SurfaceError::Lost) => Err(GraphicsError::SurfaceLost),
Err(wgpu::SurfaceError::Outdated) => Err(GraphicsError::SurfaceOutdated),
Err(wgpu::SurfaceError::OutOfMemory) => Err(GraphicsError::SurfaceOutOfMemory),
Err(wgpu::SurfaceError::Timeout) => Err(GraphicsError::SurfaceTimeout),
Err(e) => Err(GraphicsError::SurfaceTextureAcquisitionFailed(
e.to_string(),
)),
}
}
}
#[derive(Default)]
pub struct RenderWindowBuilder {
present_mode: Option<wgpu::PresentMode>,
color_format: Option<wgpu::TextureFormat>,
alpha_mode: Option<wgpu::CompositeAlphaMode>,
depth_format: Option<wgpu::TextureFormat>,
enable_profiling: bool,
}
impl RenderWindowBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn present_mode(mut self, mode: wgpu::PresentMode) -> Self {
self.present_mode = Some(mode);
self
}
pub fn color_format(mut self, format: wgpu::TextureFormat) -> Self {
self.color_format = Some(format);
self
}
pub fn alpha_mode(mut self, mode: wgpu::CompositeAlphaMode) -> Self {
self.alpha_mode = Some(mode);
self
}
pub fn with_depth(mut self, format: wgpu::TextureFormat) -> Self {
self.depth_format = Some(format);
self
}
pub fn with_depth_default(mut self) -> Self {
self.depth_format = Some(DEFAULT_DEPTH_FORMAT);
self
}
pub fn with_profiling(mut self, enabled: bool) -> Self {
self.enable_profiling = enabled;
self
}
pub fn build(
self,
window: Window,
graphics: Arc<GraphicsContext>,
) -> Result<RenderWindow, GraphicsError> {
let descriptor = WindowContextDescriptor {
format: self.color_format,
present_mode: self.present_mode,
alpha_mode: self.alpha_mode,
with_depth: self.depth_format.is_some(),
depth_format: self.depth_format,
};
let context = WindowContext::new(window, graphics.clone(), descriptor)?;
let gpu_profiler = if self.enable_profiling {
Some(Arc::new(GpuFrameProfiler::new(&graphics)?))
} else {
None
};
Ok(RenderWindow {
context,
gpu_profiler,
})
}
}
pub struct RenderWindow {
pub(crate) context: WindowContext,
pub(crate) gpu_profiler: Option<Arc<GpuFrameProfiler>>,
}
impl RenderWindow {
pub fn builder() -> RenderWindowBuilder {
RenderWindowBuilder::new()
}
pub fn new(window: Window, context: Arc<GraphicsContext>) -> Result<Self, GraphicsError> {
Self::new_with_descriptor(window, context, WindowContextDescriptor::default())
}
pub fn new_with_depth(
window: Window,
context: Arc<GraphicsContext>,
) -> Result<Self, GraphicsError> {
Self::builder().with_depth_default().build(window, context)
}
pub fn new_with_descriptor(
window: Window,
context: Arc<GraphicsContext>,
descriptor: WindowContextDescriptor,
) -> Result<Self, GraphicsError> {
profile_function!();
let context = WindowContext::new(window, context, descriptor)?;
Ok(Self {
context,
gpu_profiler: None,
})
}
pub fn begin_frame(&mut self) -> Option<Frame<'_>> {
self.try_begin_frame().ok()
}
pub fn try_begin_frame(&mut self) -> Result<Frame<'_>, GraphicsError> {
profile_function!();
let mut configure_needed = false;
if let Some(new_size) = self.context.reconfigure.resize.take() {
self.context.config.width = new_size.width;
self.context.config.height = new_size.height;
configure_needed = true;
if let Some(ref mut depth) = self.context.depth_texture
&& depth.needs_resize(new_size.width, new_size.height)
{
depth.resize(
self.context.context.device(),
new_size.width,
new_size.height,
);
}
}
if configure_needed {
self.context
.surface
.configure(self.context.context.device(), &self.context.config);
}
let surface_texture = self.context.try_acquire_surface_texture()?;
let view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
Ok(Frame {
window: self,
surface: Some(Surface {
texture: surface_texture,
view,
}),
command_buffers: RefCell::new(Vec::new()),
stats: Arc::new(AtomicFrameStats::new()),
submitted: Cell::new(false),
surface_format: self.context.config.format,
gpu_profiler: self.gpu_profiler.clone(),
winit_window: self.context.window.window.clone(),
})
}
pub fn id(&self) -> WindowId {
self.context.window.id()
}
pub fn window(&self) -> &Window {
&self.context.window
}
pub fn context(&self) -> &WindowContext {
&self.context
}
pub fn context_mut(&mut self) -> &mut WindowContext {
&mut self.context
}
pub fn graphics(&self) -> &GraphicsContext {
&self.context.context
}
pub fn graphics_arc(&self) -> &Arc<GraphicsContext> {
&self.context.context
}
pub fn surface_format(&self) -> wgpu::TextureFormat {
self.context.surface_format()
}
pub fn resized(&mut self, new_size: LogicalSize<u32>) {
self.context.resized(new_size);
}
pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
self.context.resized_physical(new_size);
}
pub fn physical_size(&self) -> PhysicalSize<u32> {
self.context.physical_size()
}
pub fn size(&self) -> (u32, u32) {
(self.context.config.width, self.context.config.height)
}
pub fn scale_factor(&self) -> ScaleFactor {
self.window().scale_factor()
}
pub fn viewport(&self) -> Viewport {
let physical_size = self.physical_size();
let scale_factor = self.scale_factor();
Viewport {
position: PhysicalPosition::new(0.0, 0.0),
size: PhysicalSize::new(physical_size.width as f32, physical_size.height as f32),
scale_factor,
}
}
pub fn set_gpu_profiler(&mut self, profiler: Arc<GpuFrameProfiler>) {
self.gpu_profiler = Some(profiler);
}
pub fn remove_gpu_profiler(&mut self) -> Option<Arc<GpuFrameProfiler>> {
self.gpu_profiler.take()
}
pub fn gpu_profiler(&self) -> Option<&Arc<GpuFrameProfiler>> {
self.gpu_profiler.as_ref()
}
pub fn has_depth(&self) -> bool {
self.context.has_depth()
}
pub fn depth_view(&self) -> Option<Arc<wgpu::TextureView>> {
self.context.depth_view()
}
pub fn depth_view_ref(&self) -> Option<&wgpu::TextureView> {
self.context.depth_texture.as_ref().map(|d| d.view_ref())
}
pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
self.context.depth_format()
}
pub fn ensure_depth(&mut self, format: wgpu::TextureFormat) {
self.context.ensure_depth(format);
}
}
impl std::ops::Deref for RenderWindow {
type Target = WindowContext;
fn deref(&self) -> &Self::Target {
&self.context
}
}
impl std::ops::DerefMut for RenderWindow {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.context
}
}
impl WindowBackend for RenderWindow {
type FrameContext = Frame<'static>;
type Error = GraphicsError;
fn try_begin_drawing(&mut self) -> Result<Self::FrameContext, Self::Error> {
unimplemented!(
"Use RenderWindow::begin_frame() instead of WindowBackend::try_begin_drawing()"
)
}
}