diff --git a/Cargo.toml b/Cargo.toml index 6a2155e..fc45ec4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ bitflags = "2" bytemuck = { version = "1", features = ["derive"] } choir = "0.7" egui = "0.29" -glam = { version = "0.27", features = ["mint"] } +glam = { version = "0.28", features = ["mint"] } gltf = { version = "1.1", default-features = false } log = "0.4" mint = "0.5" diff --git a/blade-graphics/src/gles/command.rs b/blade-graphics/src/gles/command.rs index 95c58d3..03001e6 100644 --- a/blade-graphics/src/gles/command.rs +++ b/blade-graphics/src/gles/command.rs @@ -258,13 +258,13 @@ impl crate::traits::CommandEncoder for super::CommandEncoder { self.commands.clear(); self.plain_data.clear(); self.string_data.clear(); - self.has_present = false; + self.present_frames.clear(); } fn init_texture(&mut self, _texture: super::Texture) {} - fn present(&mut self, _frame: super::Frame) { - self.has_present = true; + fn present(&mut self, frame: super::Frame) { + self.present_frames.push(frame.platform); } fn timings(&self) -> &crate::Timings { @@ -327,7 +327,7 @@ impl Drop for super::PassEncoder<'_, T> { .push(super::Command::InvalidateAttachment(attachment)); } match self.kind { - super::PassKind::Transfer | super::PassKind::AccelerationStructure => {} + super::PassKind::Transfer => {} super::PassKind::Compute => { self.commands.push(super::Command::ResetAllSamplers); } @@ -713,6 +713,7 @@ impl super::Command { let row_texels = bytes_per_row / block_info.size as u32 * block_info.dimensions.0 as u32; gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); + gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, row_texels as i32); gl.bind_buffer(glow::PIXEL_UNPACK_BUFFER, Some(src.raw)); gl.bind_texture(dst.target, Some(dst.raw)); let unpack_data = glow::PixelUnpackData::BufferOffset(src.offset as u32); diff --git a/blade-graphics/src/gles/egl.rs b/blade-graphics/src/gles/egl.rs index f7247a3..84997d1 100644 --- a/blade-graphics/src/gles/egl.rs +++ b/blade-graphics/src/gles/egl.rs @@ -3,16 +3,17 @@ use std::{ ffi, os::raw, ptr, - sync::{Arc, Mutex, MutexGuard}, + sync::{Mutex, MutexGuard}, }; const EGL_CONTEXT_FLAGS_KHR: i32 = 0x30FC; const EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR: i32 = 0x0001; -const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8; -const EGL_PLATFORM_X11_KHR: u32 = 0x31D5; -const EGL_PLATFORM_XCB_EXT: u32 = 0x31DC; +const _EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8; +const _EGL_PLATFORM_X11_KHR: u32 = 0x31D5; +const _EGL_PLATFORM_XCB_EXT: u32 = 0x31DC; const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202; const EGL_PLATFORM_ANGLE_TYPE_ANGLE: u32 = 0x3203; +const EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE: u32 = 0x3206; const EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE: u32 = 0x3489; const EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE: u32 = 0x348F; const EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED: u32 = 0x3451; @@ -23,13 +24,11 @@ const EGL_DEBUG_MSG_ERROR_KHR: u32 = 0x33BA; const EGL_DEBUG_MSG_WARN_KHR: u32 = 0x33BB; const EGL_DEBUG_MSG_INFO_KHR: u32 = 0x33BC; -type XOpenDisplayFun = +type _XOpenDisplayFun = unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void; - -type WlDisplayConnectFun = +type _WlDisplayConnectFun = unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void; - -type WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void); +type _WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void); type EglInstance = egl::DynamicInstance; @@ -39,7 +38,7 @@ type WlEglWindowCreateFun = unsafe extern "system" fn( height: raw::c_int, ) -> *mut raw::c_void; -type WlEglWindowResizeFun = unsafe extern "system" fn( +type _WlEglWindowResizeFun = unsafe extern "system" fn( window: *const raw::c_void, width: raw::c_int, height: raw::c_int, @@ -65,6 +64,12 @@ type DebugProcKHR = Option< type EglDebugMessageControlFun = unsafe extern "system" fn(proc: DebugProcKHR, attrib_list: *const egl::Attrib) -> raw::c_int; +#[derive(Debug)] +pub enum PlatformError { + Loading(egl::LoadError), + Init(egl::Error), +} + #[derive(Clone, Copy, Debug)] enum SrgbFrameBufferKind { /// No support for SRGB surface @@ -99,28 +104,34 @@ impl EglContext { } #[derive(Clone, Debug)] -struct WindowSystemInterface { - library: Option>, - window_handle: raw_window_handle::RawWindowHandle, - renderbuf: glow::Renderbuffer, - framebuf: glow::Framebuffer, -} - struct Swapchain { surface: egl::Surface, extent: crate::Extent, - format: crate::TextureFormat, + info: crate::SurfaceInfo, swap_interval: i32, } -struct ContextInner { - egl: EglContext, - swapchain: Option, +unsafe impl Send for Swapchain {} +unsafe impl Sync for Swapchain {} + +#[derive(Debug)] +pub struct PlatformFrame { + swapchain: Swapchain, + framebuf: glow::Framebuffer, +} + +pub struct PlatformSurface { + library: Option, + window_handle: raw_window_handle::RawWindowHandle, + swapchain: Mutex>, +} + +pub(super) struct ContextInner { glow: glow::Context, + egl: EglContext, } pub struct PlatformContext { - wsi: Option, inner: Mutex, } @@ -139,210 +150,107 @@ impl<'a> Drop for ContextLock<'a> { } } -fn init_egl(desc: &crate::ContextDesc) -> Result<(EglInstance, String), crate::NotSupportedError> { - let egl = unsafe { - let egl_result = if cfg!(windows) { - egl::DynamicInstance::::load_required_from_filename("libEGL.dll") - } else if cfg!(any(target_os = "macos", target_os = "ios")) { - egl::DynamicInstance::::load_required_from_filename("libEGL.dylib") - } else { - egl::DynamicInstance::::load_required() - }; - egl_result.map_err(|e| crate::NotSupportedError::GLESLoadingError(e))? - }; - - let client_ext_str = match egl.query_string(None, egl::EXTENSIONS) { - Ok(ext) => ext.to_string_lossy().into_owned(), - Err(_) => String::new(), - }; - log::debug!( - "Client extensions: {:#?}", - client_ext_str.split_whitespace().collect::>() - ); - - if desc.validation && client_ext_str.contains("EGL_KHR_debug") { - log::info!("Enabling EGL debug output"); - let function: EglDebugMessageControlFun = { - let addr = egl.get_proc_address("eglDebugMessageControlKHR").unwrap(); - unsafe { std::mem::transmute(addr) } - }; - let attributes = [ - EGL_DEBUG_MSG_CRITICAL_KHR as egl::Attrib, - 1, - EGL_DEBUG_MSG_ERROR_KHR as egl::Attrib, - 1, - EGL_DEBUG_MSG_WARN_KHR as egl::Attrib, - 1, - EGL_DEBUG_MSG_INFO_KHR as egl::Attrib, - 1, - egl::ATTRIB_NONE, - ]; - unsafe { (function)(Some(egl_debug_proc), attributes.as_ptr()) }; - } - - Ok((egl, client_ext_str)) -} - impl super::Context { pub unsafe fn init(desc: crate::ContextDesc) -> Result { - let (egl, client_extensions) = init_egl(&desc)?; - - let display = if client_extensions.contains("EGL_MESA_platform_surfaceless") { - log::info!("Using surfaceless platform"); - let egl1_5 = egl - .upcast::() - .expect("Failed to get EGL 1.5 for surfaceless"); - egl1_5 - .get_platform_display( - EGL_PLATFORM_SURFACELESS_MESA, - ptr::null_mut(), - &[egl::ATTRIB_NONE], - ) - .unwrap() - } else { - log::info!("EGL_MESA_platform_surfaceless not available. Using default platform"); - egl.get_display(egl::DEFAULT_DISPLAY).unwrap() + let egl = unsafe { + let egl_result = if cfg!(windows) { + egl::DynamicInstance::::load_required_from_filename("libEGL.dll") + } else if cfg!(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos" + )) { + egl::DynamicInstance::::load_required_from_filename("libEGL.dylib") + } else { + egl::DynamicInstance::::load_required() + }; + egl_result.map_err(PlatformError::Loading)? }; - let egl_context = EglContext::init(&desc, egl, display)?; - egl_context.make_current(); - let (glow, capabilities, toggles, device_information, limits) = - egl_context.load_functions(&desc); - egl_context.unmake_current(); - - Ok(Self { - platform: PlatformContext { - wsi: None, - inner: Mutex::new(ContextInner { - egl: egl_context, - swapchain: None, - glow, - }), - }, - capabilities, - toggles, - limits, - device_information, - }) - } - - pub unsafe fn init_windowed< - I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle, - >( - window: I, - desc: crate::ContextDesc, - ) -> Result { - use raw_window_handle::RawDisplayHandle as Rdh; + let client_extensions = match egl.query_string(None, egl::EXTENSIONS) { + Ok(ext) => ext.to_string_lossy().into_owned(), + Err(_) => String::new(), + }; + log::debug!( + "Client extensions: {:#?}", + client_extensions.split_whitespace().collect::>() + ); - let (egl, _client_extensions) = init_egl(&desc)?; - let egl1_5 = egl - .upcast::() - .ok_or(crate::NotSupportedError::NoSupportedDeviceFound)?; + if desc.validation && client_extensions.contains("EGL_KHR_debug") { + log::info!("Enabling EGL debug output"); + let function: EglDebugMessageControlFun = { + let addr = egl.get_proc_address("eglDebugMessageControlKHR").unwrap(); + unsafe { std::mem::transmute(addr) } + }; + let attributes = [ + EGL_DEBUG_MSG_CRITICAL_KHR as egl::Attrib, + 1, + EGL_DEBUG_MSG_ERROR_KHR as egl::Attrib, + 1, + EGL_DEBUG_MSG_WARN_KHR as egl::Attrib, + 1, + EGL_DEBUG_MSG_INFO_KHR as egl::Attrib, + 1, + egl::ATTRIB_NONE, + ]; + unsafe { (function)(Some(egl_debug_proc), attributes.as_ptr()) }; + } - let (display, wsi_library) = match window.display_handle().unwrap().as_raw() { - Rdh::Windows(display_handle) => { + let display = if let Some(egl1_5) = egl.upcast::() { + if client_extensions.contains("EGL_ANGLE_platform_angle") { + log::info!("Using Angle"); let display_attributes = [ + EGL_PLATFORM_ANGLE_TYPE_ANGLE as egl::Attrib, + if cfg!(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + )) { + EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE + } else { + EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE + } as egl::Attrib, EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE as egl::Attrib, - EGL_PLATFORM_X11_KHR as egl::Attrib, + EGL_PLATFORM_SURFACELESS_MESA as egl::Attrib, EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as egl::Attrib, if desc.validation { 1 } else { 0 }, egl::ATTRIB_NONE, ]; - let display = egl1_5 + egl1_5 .get_platform_display( EGL_PLATFORM_ANGLE_ANGLE, ptr::null_mut(), &display_attributes, ) - .unwrap(); - (display, None) - } - Rdh::Xlib(display_handle) => { - log::info!("Using X11 (xlib) platform"); - let display_attributes = [egl::ATTRIB_NONE]; - let display = egl1_5 - .get_platform_display( - EGL_PLATFORM_X11_KHR, - display_handle - .display - .map_or(ptr::null_mut(), ptr::NonNull::as_ptr), - &display_attributes, - ) - .unwrap(); - let library = find_x_library().unwrap(); - (display, Some(library)) - } - Rdh::Xcb(display_handle) => { - log::info!("Using X11 (xcb) platform"); - let display_attributes = [egl::ATTRIB_NONE]; - let display = egl1_5 - .get_platform_display( - EGL_PLATFORM_XCB_EXT, - display_handle - .connection - .map_or(ptr::null_mut(), ptr::NonNull::as_ptr), - &display_attributes, - ) - .unwrap(); - let library = find_x_library().unwrap(); - (display, Some(library)) - } - Rdh::Wayland(display_handle) => { - log::info!("Using Wayland platform"); - let display_attributes = [egl::ATTRIB_NONE]; - let display = egl1_5 - .get_platform_display( - EGL_PLATFORM_WAYLAND_KHR, - display_handle.display.as_ptr(), - &display_attributes, - ) - .unwrap(); - let library = find_wayland_library().unwrap(); - (display, Some(library)) - } - Rdh::AppKit(_display_handle) => { - let display_attributes = [ - EGL_PLATFORM_ANGLE_TYPE_ANGLE as egl::Attrib, - EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE as egl::Attrib, - EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as egl::Attrib, - if desc.validation { 1 } else { 0 }, - egl::ATTRIB_NONE, - ]; - let display = egl1_5 + .unwrap() + } else if client_extensions.contains("EGL_MESA_platform_surfaceless") { + log::info!("Using surfaceless platform"); + egl1_5 .get_platform_display( - EGL_PLATFORM_ANGLE_ANGLE, + EGL_PLATFORM_SURFACELESS_MESA, ptr::null_mut(), - &display_attributes, + &[egl::ATTRIB_NONE], ) - .unwrap(); - (display, None) - } - other => { - log::error!("Unsupported RDH {:?}", other); - return Err(crate::NotSupportedError::PlatformNotSupported); + .unwrap() + } else { + log::info!("EGL_MESA_platform_surfaceless not available. Using default platform"); + egl.get_display(egl::DEFAULT_DISPLAY).unwrap() } + } else { + egl.get_display(egl::DEFAULT_DISPLAY).unwrap() }; let egl_context = EglContext::init(&desc, egl, display)?; egl_context.make_current(); let (glow, capabilities, toggles, device_information, limits) = egl_context.load_functions(&desc); - let renderbuf = glow.create_renderbuffer().unwrap(); - let framebuf = glow.create_framebuffer().unwrap(); egl_context.unmake_current(); Ok(Self { platform: PlatformContext { - wsi: Some(WindowSystemInterface { - library: wsi_library.map(Arc::new), - window_handle: window.window_handle().unwrap().as_raw(), - renderbuf, - framebuf, - }), inner: Mutex::new(ContextInner { - egl: egl_context, - swapchain: None, glow, + egl: egl_context, }), }, capabilities, @@ -352,13 +260,72 @@ impl super::Context { }) } - pub fn resize(&self, config: crate::SurfaceConfig) -> crate::SurfaceInfo { + pub fn create_surface( + &self, + window: I, + ) -> Result { + use raw_window_handle::RawWindowHandle as Rwh; + + let window_handle = window.window_handle().unwrap().as_raw(); + let library = match window_handle { + Rwh::Xlib(_) => Some(find_x_library().unwrap()), + Rwh::Xcb(_) => Some(find_x_library().unwrap()), + Rwh::Wayland(_) => Some(find_wayland_library().unwrap()), + _ => None, + }; + + Ok(unsafe { + let guard = self.lock(); + super::Surface { + platform: PlatformSurface { + library, + window_handle, + swapchain: Mutex::new(None), + }, + renderbuf: guard.create_renderbuffer().unwrap(), + framebuf: guard.create_framebuffer().unwrap(), + } + }) + } + + pub fn destroy_surface(&self, surface: &mut super::Surface) { + use raw_window_handle::RawWindowHandle as Rwh; + + let inner = self.platform.inner.lock().unwrap(); + let mut swapchain = surface.platform.swapchain.lock().unwrap(); + if let Some(s) = swapchain.take() { + inner + .egl + .instance + .destroy_surface(inner.egl.display, s.surface) + .unwrap(); + } + if let Rwh::Wayland(handle) = surface.platform.window_handle { + unsafe { + let wl_egl_window_destroy: libloading::Symbol = surface + .platform + .library + .as_ref() + .unwrap() + .get(b"wl_egl_window_destroy") + .unwrap(); + wl_egl_window_destroy(handle.surface.as_ptr()); + } + } + inner.egl.make_current(); + unsafe { + inner.glow.delete_renderbuffer(surface.renderbuf); + inner.glow.delete_framebuffer(surface.framebuf); + } + inner.egl.unmake_current(); + } + + pub fn reconfigure_surface(&self, surface: &mut super::Surface, config: crate::SurfaceConfig) { use raw_window_handle::RawWindowHandle as Rwh; - let wsi = self.platform.wsi.as_ref().unwrap(); let (mut temp_xlib_handle, mut temp_xcb_handle); #[allow(trivial_casts)] - let native_window_ptr = match wsi.window_handle { + let native_window_ptr = match surface.platform.window_handle { Rwh::Xlib(handle) if cfg!(windows) => handle.window as *mut ffi::c_void, Rwh::Xlib(handle) => { temp_xlib_handle = handle.window; @@ -370,7 +337,8 @@ impl super::Context { } Rwh::AndroidNdk(handle) => handle.a_native_window.as_ptr(), Rwh::Wayland(handle) => unsafe { - let wl_egl_window_create: libloading::Symbol = wsi + let wl_egl_window_create: libloading::Symbol = surface + .platform .library .as_ref() .unwrap() @@ -404,8 +372,9 @@ impl super::Context { log::warn!("Unable to forbid exclusive full screen"); } - let mut inner = self.platform.inner.lock().unwrap(); - if let Some(s) = inner.swapchain.take() { + let inner = self.platform.inner.lock().unwrap(); + let mut swapchain = surface.platform.swapchain.lock().unwrap(); + if let Some(s) = swapchain.take() { inner .egl .instance @@ -445,40 +414,39 @@ impl super::Context { crate::ColorSpace::Srgb => crate::TextureFormat::Rgba8Unorm, }; - // Careful, we can still be in 1.4 version even if `upcast` succeeds - let surface = match inner.egl.instance.upcast::() { - Some(egl) => { - let attributes_usize = attributes - .into_iter() - .map(|v| v as usize) - .collect::>(); - unsafe { - egl.create_platform_window_surface( - inner.egl.display, - inner.egl.config, - native_window_ptr, - &attributes_usize, - ) - .unwrap() + let _ = swapchain.insert(Swapchain { + // Careful, we can still be in 1.4 version even if `upcast` succeeds + surface: match inner.egl.instance.upcast::() { + Some(egl) => { + let attributes_usize = attributes + .into_iter() + .map(|v| v as usize) + .collect::>(); + unsafe { + egl.create_platform_window_surface( + inner.egl.display, + inner.egl.config, + native_window_ptr, + &attributes_usize, + ) + .unwrap() + } } - } - _ => unsafe { - inner - .egl - .instance - .create_window_surface( - inner.egl.display, - inner.egl.config, - native_window_ptr, - Some(&attributes), - ) - .unwrap() + _ => unsafe { + inner + .egl + .instance + .create_window_surface( + inner.egl.display, + inner.egl.config, + native_window_ptr, + Some(&attributes), + ) + .unwrap() + }, }, - }; - inner.swapchain = Some(Swapchain { - surface, extent: config.size, - format, + info: crate::SurfaceInfo { format, alpha }, swap_interval: match config.display_sync { crate::DisplaySync::Block => 1, crate::DisplaySync::Recent | crate::DisplaySync::Tear => 0, @@ -489,39 +457,24 @@ impl super::Context { inner.egl.make_current(); unsafe { let gl = &inner.glow; - gl.bind_renderbuffer(glow::RENDERBUFFER, Some(wsi.renderbuf)); + gl.bind_renderbuffer(glow::RENDERBUFFER, Some(surface.renderbuf)); gl.renderbuffer_storage( glow::RENDERBUFFER, format_desc.internal, config.size.width as _, config.size.height as _, ); - gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(wsi.framebuf)); + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(surface.framebuf)); gl.framebuffer_renderbuffer( glow::READ_FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::RENDERBUFFER, - Some(wsi.renderbuf), + Some(surface.renderbuf), ); gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None); gl.bind_renderbuffer(glow::RENDERBUFFER, None); }; inner.egl.unmake_current(); - - crate::SurfaceInfo { format, alpha } - } - - pub fn acquire_frame(&self) -> super::Frame { - let wsi = self.platform.wsi.as_ref().unwrap(); - let inner = self.platform.inner.lock().unwrap(); - let sc = inner.swapchain.as_ref().unwrap(); - super::Frame { - texture: super::Texture { - inner: super::TextureInner::Renderbuffer { raw: wsi.renderbuf }, - target_size: [sc.extent.width as u16, sc.extent.height as u16], - format: sc.format, - }, - } } pub(super) fn lock(&self) -> ContextLock { @@ -529,46 +482,70 @@ impl super::Context { inner.egl.make_current(); ContextLock { guard: inner } } - - pub(super) fn present(&self) { - let inner = self.platform.inner.lock().unwrap(); - let wsi = self.platform.wsi.as_ref().unwrap(); - inner.present(wsi); - } } -impl ContextInner { - fn present(&self, wsi: &WindowSystemInterface) { - let sc = self.swapchain.as_ref().unwrap(); - self.egl +impl PlatformContext { + pub(super) fn present(&self, frame: PlatformFrame) { + let sc = frame.swapchain; + let inner = self.inner.lock().unwrap(); + inner + .egl .instance .make_current( - self.egl.display, + inner.egl.display, Some(sc.surface), Some(sc.surface), - Some(self.egl.raw), + Some(inner.egl.raw), ) .unwrap(); - self.egl + inner + .egl .instance - .swap_interval(self.egl.display, sc.swap_interval) + .swap_interval(inner.egl.display, sc.swap_interval) .unwrap(); unsafe { - super::present_blit(&self.glow, wsi.framebuf, sc.extent); + super::present_blit(&inner.glow, frame.framebuf, sc.extent); } - self.egl + inner + .egl .instance - .swap_buffers(self.egl.display, sc.surface) + .swap_buffers(inner.egl.display, sc.surface) .unwrap(); - self.egl + inner + .egl .instance - .make_current(self.egl.display, None, None, None) + .make_current(inner.egl.display, None, None, None) .unwrap(); } } +impl super::Surface { + pub fn info(&self) -> crate::SurfaceInfo { + let sc_maybe = self.platform.swapchain.lock().unwrap(); + sc_maybe.as_ref().unwrap().info + } + + pub fn acquire_frame(&mut self) -> super::Frame { + let sc_maybe = self.platform.swapchain.lock().unwrap(); + let sc = sc_maybe.as_ref().unwrap(); + super::Frame { + platform: PlatformFrame { + swapchain: sc.clone(), + framebuf: self.framebuf, + }, + texture: super::Texture { + inner: super::TextureInner::Renderbuffer { + raw: self.renderbuf, + }, + target_size: [sc.extent.width as u16, sc.extent.height as u16], + format: sc.info.format, + }, + } + } +} + unsafe fn find_library(paths: &[&str]) -> Option { paths .iter() @@ -631,7 +608,7 @@ fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, m let &(log_severity, _) = LOG_LEVEL_SEVERITY .iter() - .find(|&&(level, sev)| sev == severity) + .find(|&&(_level, sev)| sev == severity) .unwrap(); let type_str = match gltype { @@ -663,9 +640,7 @@ impl EglContext { egl: EglInstance, display: egl::Display, ) -> Result { - let version = egl - .initialize(display) - .map_err(|e| crate::NotSupportedError::GLESError(e))?; + let version = egl.initialize(display).map_err(PlatformError::Init)?; let vendor = egl.query_string(Some(display), egl::VENDOR).unwrap(); let display_extensions = egl .query_string(Some(display), egl::EXTENSIONS) @@ -736,7 +711,7 @@ impl EglContext { Ok(context) => context, Err(e) => { log::warn!("unable to create GLES 3.x context: {:?}", e); - return Err(crate::NotSupportedError::GLESError(e)); + return Err(PlatformError::Init(e).into()); } }; @@ -752,7 +727,7 @@ impl EglContext { .map(Some) .map_err(|e| { log::warn!("Error in create_pbuffer_surface: {:?}", e); - crate::NotSupportedError::GLESError(e) + PlatformError::Init(e) })? }; diff --git a/blade-graphics/src/gles/mod.rs b/blade-graphics/src/gles/mod.rs index de7efdd..a56c9b2 100644 --- a/blade-graphics/src/gles/mod.rs +++ b/blade-graphics/src/gles/mod.rs @@ -12,6 +12,8 @@ const DEBUG_ID: u32 = 0; const MAX_TIMEOUT: u64 = 1_000_000_000; // MAX_CLIENT_WAIT_TIMEOUT_WEBGL; const MAX_QUERIES: usize = crate::limits::PASS_COUNT + 1; +pub use platform::PlatformError; + bitflags::bitflags! { struct Capabilities: u32 { const BUFFER_STORAGE = 1 << 0; @@ -39,6 +41,12 @@ pub struct Context { device_information: crate::DeviceInformation, } +pub struct Surface { + platform: platform::PlatformSurface, + renderbuf: glow::Renderbuffer, + framebuf: glow::Framebuffer, +} + #[derive(Clone, Copy, Debug, Hash, PartialEq)] pub struct Buffer { raw: glow::Buffer, @@ -137,6 +145,7 @@ pub struct RenderPipeline { #[derive(Debug)] pub struct Frame { + platform: platform::PlatformFrame, texture: Texture, } @@ -368,7 +377,7 @@ pub struct CommandEncoder { plain_data: Vec, string_data: Vec, needs_scopes: bool, - has_present: bool, + present_frames: Vec, limits: Limits, timing_datas: Option>, timings: crate::Timings, @@ -376,7 +385,6 @@ pub struct CommandEncoder { enum PassKind { Transfer, - AccelerationStructure, Compute, Render, } @@ -475,7 +483,7 @@ impl crate::traits::CommandDevice for Context { plain_data: Vec::new(), string_data: Vec::new(), needs_scopes: self.toggles.scoping, - has_present: false, + present_frames: Vec::new(), limits: self.limits.clone(), timing_datas, timings: Default::default(), @@ -538,8 +546,8 @@ impl crate::traits::CommandDevice for Context { gl.fence_sync(glow::SYNC_GPU_COMMANDS_COMPLETE, 0).unwrap() } }; - if encoder.has_present { - self.present(); + for frame in encoder.present_frames.drain(..) { + self.platform.present(frame); } SyncPoint { fence } } diff --git a/blade-graphics/src/gles/pipeline.rs b/blade-graphics/src/gles/pipeline.rs index 291f658..7d9e819 100644 --- a/blade-graphics/src/gles/pipeline.rs +++ b/blade-graphics/src/gles/pipeline.rs @@ -131,7 +131,7 @@ impl super::Context { let attribute_mappings = crate::Shader::fill_vertex_locations(&mut module, ep_index, vertex_fetch_states); - for (index, mapping) in attribute_mappings.into_iter().enumerate() { + for mapping in attribute_mappings { let vf = &vertex_fetch_states[mapping.buffer_index]; let (_, attrib) = vf.layout.attributes[mapping.attribute_index]; attributes.push(super::VertexAttributeInfo { diff --git a/blade-graphics/src/gles/web.rs b/blade-graphics/src/gles/web.rs index 3d91349..19134db 100644 --- a/blade-graphics/src/gles/web.rs +++ b/blade-graphics/src/gles/web.rs @@ -1,76 +1,87 @@ use glow::HasContext as _; -use std::cell::Cell; use wasm_bindgen::JsCast; -//TODO: consider sharing this struct with EGL -struct Swapchain { - renderbuf: glow::Renderbuffer, - framebuf: glow::Framebuffer, - format: crate::TextureFormat, - extent: Cell, -} - pub struct PlatformContext { #[allow(unused)] webgl2: web_sys::WebGl2RenderingContext, glow: glow::Context, - swapchain: Swapchain, } -impl super::Context { - pub unsafe fn init(_desc: crate::ContextDesc) -> Result { - Err(crate::NotSupportedError::PlatformNotSupported) +pub struct PlatformSurface { + info: crate::SurfaceInfo, + extent: crate::Extent, +} +#[derive(Debug)] +pub struct PlatformFrame { + framebuf: glow::Framebuffer, + extent: crate::Extent, +} + +pub type PlatformError = (); + +impl super::Surface { + pub fn info(&self) -> crate::SurfaceInfo { + self.platform.info } + pub fn acquire_frame(&self) -> super::Frame { + let size = self.platform.extent; + super::Frame { + platform: PlatformFrame { + framebuf: self.framebuf, + extent: self.platform.extent, + }, + texture: super::Texture { + inner: super::TextureInner::Renderbuffer { + raw: self.renderbuf, + }, + target_size: [size.width as u16, size.height as u16], + format: self.platform.info.format, + }, + } + } +} - pub unsafe fn init_windowed< - I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle, - >( - window: I, - desc: crate::ContextDesc, - ) -> Result { - let webgl2 = match window.window_handle().unwrap().as_raw() { - raw_window_handle::RawWindowHandle::Web(handle) => { - let canvas: web_sys::HtmlCanvasElement = web_sys::window() - .and_then(|win| win.document()) - .expect("Cannot get document") - .query_selector(&format!("canvas[data-raw-handle=\"{}\"]", handle.id)) - .expect("Cannot query for canvas") - .expect("Canvas is not found") - .dyn_into() - .expect("Failed to downcast to canvas type"); +impl PlatformContext { + pub(super) fn present(&self, frame: PlatformFrame) { + unsafe { + super::present_blit(&self.glow, frame.framebuf, frame.extent); + } + } +} - let context_options = js_sys::Object::new(); - js_sys::Reflect::set( - &context_options, - &"antialias".into(), - &wasm_bindgen::JsValue::FALSE, - ) - .expect("Cannot create context options"); - //Note: could also set: "alpha", "premultipliedAlpha" +impl super::Context { + pub unsafe fn init(_desc: crate::ContextDesc) -> Result { + let canvas = web_sys::window() + .and_then(|win| win.document()) + .expect("Cannot get document") + .get_element_by_id("blade") + .expect("Canvas is not found") + .dyn_into::() + .expect("Failed to downcast to canvas type"); - canvas - .get_context_with_context_options("webgl2", &context_options) - .expect("Cannot create WebGL2 context") - .and_then(|context| context.dyn_into::().ok()) - .expect("Cannot convert into WebGL2 context") - } - _ => return Err(crate::NotSupportedError::PlatformNotSupported), - }; + let context_options = js_sys::Object::new(); + js_sys::Reflect::set( + &context_options, + &"antialias".into(), + &wasm_bindgen::JsValue::FALSE, + ) + .expect("Cannot create context options"); + //Note: could also set: "alpha", "premultipliedAlpha" + + let webgl2 = canvas + .get_context_with_context_options("webgl2", &context_options) + .expect("Cannot create WebGL2 context") + .and_then(|context| context.dyn_into::().ok()) + .expect("Cannot convert into WebGL2 context"); let glow = glow::Context::from_webgl2_context(webgl2.clone()); + let capabilities = super::Capabilities::empty(); let limits = super::Limits { uniform_buffer_alignment: unsafe { glow.get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT) as u32 }, }; - let swapchain = Swapchain { - renderbuf: unsafe { glow.create_renderbuffer().unwrap() }, - framebuf: unsafe { glow.create_framebuffer().unwrap() }, - format: crate::TextureFormat::Rgba8Unorm, - extent: Cell::default(), - }; - let device_information = crate::DeviceInformation { is_software_emulated: false, device_name: glow.get_parameter_string(glow::VENDOR), @@ -78,12 +89,8 @@ impl super::Context { driver_info: glow.get_parameter_string(glow::VERSION), }; - Ok(Self { - platform: PlatformContext { - webgl2, - glow, - swapchain, - }, + Ok(super::Context { + platform: PlatformContext { webgl2, glow }, capabilities, toggles: super::Toggles::default(), limits, @@ -91,47 +98,51 @@ impl super::Context { }) } - pub fn resize(&self, config: crate::SurfaceConfig) -> crate::SurfaceInfo { + pub fn create_surface( + &self, + _window: &I, + ) -> Result { + let platform = PlatformSurface { + info: crate::SurfaceInfo { + format: crate::TextureFormat::Rgba8Unorm, + alpha: crate::AlphaMode::PreMultiplied, + }, + extent: crate::Extent::default(), + }; + Ok(unsafe { + super::Surface { + platform, + renderbuf: self.platform.glow.create_renderbuffer().unwrap(), + framebuf: self.platform.glow.create_framebuffer().unwrap(), + } + }) + } + + pub fn destroy_surface(&self, _surface: &mut super::Surface) {} + + pub fn reconfigure_surface(&self, surface: &mut super::Surface, config: crate::SurfaceConfig) { //TODO: create WebGL context here - let sc = &self.platform.swapchain; - let format_desc = super::describe_texture_format(sc.format); + let format_desc = super::describe_texture_format(surface.platform.info.format); let gl = &self.platform.glow; //Note: this code can be shared with EGL unsafe { - gl.bind_renderbuffer(glow::RENDERBUFFER, Some(sc.renderbuf)); + gl.bind_renderbuffer(glow::RENDERBUFFER, Some(surface.renderbuf)); gl.renderbuffer_storage( glow::RENDERBUFFER, format_desc.internal, config.size.width as _, config.size.height as _, ); - gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuf)); + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(surface.framebuf)); gl.framebuffer_renderbuffer( glow::READ_FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::RENDERBUFFER, - Some(sc.renderbuf), + Some(surface.renderbuf), ); gl.bind_renderbuffer(glow::RENDERBUFFER, None); } - sc.extent.set(config.size); - - crate::SurfaceInfo { - format: sc.format, - alpha: crate::AlphaMode::PreMultiplied, - } - } - - pub fn acquire_frame(&self) -> super::Frame { - let sc = &self.platform.swapchain; - let size = sc.extent.get(); - super::Frame { - texture: super::Texture { - inner: super::TextureInner::Renderbuffer { raw: sc.renderbuf }, - target_size: [size.width as u16, size.height as u16], - format: sc.format, - }, - } + surface.platform.extent = config.size; } /// Obtain a lock to the EGL context and get handle to the [`glow::Context`] that can be used to @@ -139,11 +150,4 @@ impl super::Context { pub(super) fn lock(&self) -> &glow::Context { &self.platform.glow } - - pub(super) fn present(&self) { - let sc = &self.platform.swapchain; - unsafe { - super::present_blit(&self.platform.glow, sc.framebuf, sc.extent.get()); - } - } } diff --git a/blade-graphics/src/lib.rs b/blade-graphics/src/lib.rs index fe3dea4..d8470a1 100644 --- a/blade-graphics/src/lib.rs +++ b/blade-graphics/src/lib.rs @@ -86,10 +86,15 @@ pub mod limits { pub use hal::*; +#[cfg(target_arch = "wasm32")] +pub const CANVAS_ID: &str = "blade"; + use std::{fmt, num::NonZeroU32}; #[derive(Clone, Debug, Default)] pub struct ContextDesc { + /// Ability to present contents to a window. + pub presentation: bool, /// Enable validation of the GAPI, shaders, /// and insert crash markers into command buffers. pub validation: bool, @@ -103,38 +108,17 @@ pub struct ContextDesc { #[derive(Debug)] pub enum NotSupportedError { - #[cfg(all( - not(gles), - any( - vulkan, - windows, - target_os = "linux", - target_os = "android", - target_os = "freebsd" - ) - ))] - VulkanLoadingError(ash::LoadingError), - #[cfg(all( - not(gles), - any( - vulkan, - windows, - target_os = "linux", - target_os = "android", - target_os = "freebsd" - ) - ))] - VulkanError(ash::vk::Result), - - #[cfg(gles)] - GLESLoadingError(egl::LoadError), - #[cfg(gles)] - GLESError(egl::Error), - + Platform(PlatformError), NoSupportedDeviceFound, PlatformNotSupported, } +impl From for NotSupportedError { + fn from(error: PlatformError) -> Self { + Self::Platform(error) + } +} + #[derive(Clone, Debug, Default, PartialEq)] pub struct Capabilities { /// Which shader stages support ray queries @@ -153,6 +137,20 @@ pub struct DeviceInformation { pub driver_info: String, } +impl Context { + pub fn create_surface_configured< + I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle, + >( + &self, + window: &I, + config: SurfaceConfig, + ) -> Result { + let mut surface = self.create_surface(window)?; + self.reconfigure_surface(&mut surface, config); + Ok(surface) + } +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum Memory { /// Device-local memory. Fast for GPU operations. diff --git a/blade-graphics/src/metal/mod.rs b/blade-graphics/src/metal/mod.rs index 28ea951..f14df22 100644 --- a/blade-graphics/src/metal/mod.rs +++ b/blade-graphics/src/metal/mod.rs @@ -14,9 +14,12 @@ mod surface; const MAX_TIMESTAMPS: u64 = crate::limits::PASS_COUNT as u64 * 2; -struct Surface { +pub type PlatformError = (); + +pub struct Surface { view: *mut objc::runtime::Object, render_layer: metal::MetalLayer, + info: crate::SurfaceInfo, } unsafe impl Send for Surface {} @@ -53,7 +56,6 @@ struct PrivateInfo { pub struct Context { device: Mutex, queue: Arc>, - surface: Option>, capture: Option, info: PrivateInfo, device_information: crate::DeviceInformation, @@ -461,7 +463,6 @@ impl Context { Ok(Context { device: Mutex::new(device), queue: Arc::new(Mutex::new(queue)), - surface: None, capture, info: PrivateInfo { //TODO: determine based on OS version @@ -474,30 +475,6 @@ impl Context { }) } - pub unsafe fn init_windowed< - I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle, - >( - window: &I, - desc: super::ContextDesc, - ) -> Result { - let mut context = Self::init(desc)?; - - let surface = match window.window_handle().unwrap().as_raw() { - #[cfg(target_os = "ios")] - raw_window_handle::RawWindowHandle::UiKit(handle) => { - Surface::from_view(handle.ui_view.as_ptr() as *mut _) - } - #[cfg(target_os = "macos")] - raw_window_handle::RawWindowHandle::AppKit(handle) => { - Surface::from_view(handle.ns_view.as_ptr() as *mut _) - } - _ => return Err(crate::NotSupportedError::PlatformNotSupported), - }; - - context.surface = Some(Mutex::new(surface)); - Ok(context) - } - pub fn capabilities(&self) -> crate::Capabilities { let device = self.device.lock().unwrap(); crate::Capabilities { @@ -517,14 +494,6 @@ impl Context { &self.device_information } - /// Get the CALayerMetal for this surface, if any. - /// This is platform specific API. - pub fn metal_layer(&self) -> Option { - self.surface - .as_ref() - .map(|suf| suf.lock().unwrap().render_layer.clone()) - } - /// Get an MTLDevice of this context. /// This is platform specific API. pub fn metal_device(&self) -> metal::Device { diff --git a/blade-graphics/src/metal/surface.rs b/blade-graphics/src/metal/surface.rs index 79e3010..90e75e3 100644 --- a/blade-graphics/src/metal/surface.rs +++ b/blade-graphics/src/metal/surface.rs @@ -8,7 +8,7 @@ use objc::{ sel, sel_impl, }; -use std::mem; +use std::{mem, ptr}; #[cfg(target_os = "macos")] #[link(name = "QuartzCore", kind = "framework")] @@ -17,14 +17,6 @@ extern "C" { static kCAGravityTopLeft: *mut Object; } -impl Drop for super::Surface { - fn drop(&mut self) { - unsafe { - let () = msg_send![self.view, release]; - } - } -} - impl super::Surface { pub unsafe fn from_view(view: *mut Object) -> Self { let main_layer: *mut Object = msg_send![view, layer]; @@ -63,40 +55,64 @@ impl super::Surface { Self { view: msg_send![view, retain], render_layer: mem::transmute::<_, &metal::MetalLayerRef>(raw_layer).to_owned(), + info: crate::SurfaceInfo { + format: crate::TextureFormat::Rgba8Unorm, + alpha: crate::AlphaMode::Ignored, + }, } } - fn reconfigure( - &mut self, - device: &metal::DeviceRef, - config: crate::SurfaceConfig, - ) -> crate::SurfaceInfo { - let format = match config.color_space { - crate::ColorSpace::Linear => crate::TextureFormat::Bgra8UnormSrgb, - crate::ColorSpace::Srgb => crate::TextureFormat::Bgra8Unorm, - }; - let vsync = match config.display_sync { - crate::DisplaySync::Block => true, - crate::DisplaySync::Recent | crate::DisplaySync::Tear => false, - }; + /// Get the CALayerMetal for this surface, if any. + /// This is platform specific API. + pub fn metal_layer(&self) -> metal::MetalLayer { + self.render_layer.clone() + } - self.render_layer.set_opaque(!config.transparent); - self.render_layer.set_device(device); - self.render_layer - .set_pixel_format(super::map_texture_format(format)); - self.render_layer - .set_framebuffer_only(config.usage == crate::TextureUsage::TARGET); - self.render_layer.set_maximum_drawable_count(3); - self.render_layer.set_drawable_size(CGSize::new( - config.size.width as f64, - config.size.height as f64, - )); + pub fn info(&self) -> crate::SurfaceInfo { + self.info + } + + pub fn acquire_frame(&self) -> super::Frame { + let (drawable, texture) = objc::rc::autoreleasepool(|| { + let drawable = self.render_layer.next_drawable().unwrap(); + (drawable.to_owned(), drawable.texture().to_owned()) + }); + super::Frame { drawable, texture } + } +} + +impl super::Context { + pub fn create_surface( + &self, + window: &I, + ) -> Result { + Ok(match window.window_handle().unwrap().as_raw() { + #[cfg(target_os = "ios")] + raw_window_handle::RawWindowHandle::UiKit(handle) => unsafe { + super::Surface::from_view(handle.ui_view.as_ptr() as *mut _) + }, + #[cfg(target_os = "macos")] + raw_window_handle::RawWindowHandle::AppKit(handle) => unsafe { + super::Surface::from_view(handle.ns_view.as_ptr() as *mut _) + }, + _ => return Err(crate::NotSupportedError::PlatformNotSupported), + }) + } + + pub fn destroy_surface(&self, surface: &mut super::Surface) { unsafe { - let () = msg_send![self.render_layer, setDisplaySyncEnabled: vsync]; + let () = msg_send![surface.view, release]; } + surface.view = ptr::null_mut(); + } - crate::SurfaceInfo { - format, + pub fn reconfigure_surface(&self, surface: &mut super::Surface, config: crate::SurfaceConfig) { + let device = self.device.lock().unwrap(); + surface.info = crate::SurfaceInfo { + format: match config.color_space { + crate::ColorSpace::Linear => crate::TextureFormat::Bgra8UnormSrgb, + crate::ColorSpace::Srgb => crate::TextureFormat::Bgra8Unorm, + }, alpha: if config.transparent { crate::AlphaMode::PostMultiplied } else { @@ -104,22 +120,27 @@ impl super::Surface { // https://developer.apple.com/documentation/quartzcore/calayer/1410763-isopaque crate::AlphaMode::Ignored }, - } - } -} - -impl super::Context { - pub fn resize(&self, config: crate::SurfaceConfig) -> crate::SurfaceInfo { - let mut surface = self.surface.as_ref().unwrap().lock().unwrap(); - surface.reconfigure(&*self.device.lock().unwrap(), config) - } + }; + let vsync = match config.display_sync { + crate::DisplaySync::Block => true, + crate::DisplaySync::Recent | crate::DisplaySync::Tear => false, + }; - pub fn acquire_frame(&self) -> super::Frame { - let surface = self.surface.as_ref().unwrap().lock().unwrap(); - let (drawable, texture) = objc::rc::autoreleasepool(|| { - let drawable = surface.render_layer.next_drawable().unwrap(); - (drawable.to_owned(), drawable.texture().to_owned()) - }); - super::Frame { drawable, texture } + surface.render_layer.set_opaque(!config.transparent); + surface.render_layer.set_device(&*device); + surface + .render_layer + .set_pixel_format(super::map_texture_format(surface.info.format)); + surface + .render_layer + .set_framebuffer_only(config.usage == crate::TextureUsage::TARGET); + surface.render_layer.set_maximum_drawable_count(3); + surface.render_layer.set_drawable_size(CGSize::new( + config.size.width as f64, + config.size.height as f64, + )); + unsafe { + let () = msg_send![surface.render_layer, setDisplaySyncEnabled: vsync]; + } } } diff --git a/blade-graphics/src/vulkan/command.rs b/blade-graphics/src/vulkan/command.rs index 711891e..c8d4425 100644 --- a/blade-graphics/src/vulkan/command.rs +++ b/blade-graphics/src/vulkan/command.rs @@ -515,21 +515,22 @@ impl crate::traits::CommandEncoder for super::CommandEncoder { } fn present(&mut self, frame: super::Frame) { - if frame.acquire_semaphore == vk::Semaphore::null() { + if frame.internal.acquire_semaphore == vk::Semaphore::null() { return; } assert_eq!(self.present, None); let wa = &self.device.workarounds; self.present = Some(super::Presentation { + acquire_semaphore: frame.internal.acquire_semaphore, + swapchain: frame.swapchain.raw, image_index: frame.image_index, - acquire_semaphore: frame.acquire_semaphore, }); let barrier = vk::ImageMemoryBarrier { old_layout: vk::ImageLayout::GENERAL, new_layout: vk::ImageLayout::PRESENT_SRC_KHR, - image: frame.image, + image: frame.internal.image, subresource_range: vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, diff --git a/blade-graphics/src/vulkan/init.rs b/blade-graphics/src/vulkan/init.rs index 9037ea2..95d564e 100644 --- a/blade-graphics/src/vulkan/init.rs +++ b/blade-graphics/src/vulkan/init.rs @@ -1,6 +1,6 @@ use ash::{amd, ext, khr, vk}; use naga::back::spv; -use std::{ffi, fs, mem, sync::Mutex}; +use std::{ffi, fs, sync::Mutex}; use crate::NotSupportedError; @@ -60,7 +60,7 @@ unsafe fn inspect_adapter( phd: vk::PhysicalDevice, instance: &super::Instance, driver_api_version: u32, - surface: Option, + desc: &crate::ContextDesc, ) -> Option { let mut inline_uniform_block_properties = vk::PhysicalDeviceInlineUniformBlockPropertiesEXT::default(); @@ -122,32 +122,10 @@ unsafe fn inspect_adapter( intel_fix_descriptor_pool_leak: cfg!(windows) && properties.vendor_id == db::intel::VENDOR, }; - let mut full_screen_exclusive = false; let queue_family_index = 0; //TODO - if let Some(surface) = surface { - let khr = instance.surface.as_ref()?; - if khr.get_physical_device_surface_support(phd, queue_family_index, surface) != Ok(true) { - log::warn!("Rejected for not presenting to the window surface"); - return None; - } - - let surface_info = vk::PhysicalDeviceSurfaceInfo2KHR { - surface, - ..Default::default() - }; - let mut fullscreen_exclusive_ext = vk::SurfaceCapabilitiesFullScreenExclusiveEXT::default(); - let mut capabilities2_khr = - vk::SurfaceCapabilities2KHR::default().push_next(&mut fullscreen_exclusive_ext); - let _ = instance - .get_surface_capabilities2 - .get_physical_device_surface_capabilities2(phd, &surface_info, &mut capabilities2_khr); - log::debug!("{:?}", capabilities2_khr.surface_capabilities); - full_screen_exclusive = fullscreen_exclusive_ext.full_screen_exclusive_supported != 0; - - if bugs.intel_unable_to_present { - log::warn!("Rejecting Intel for not presenting when Nvidia is present (on Linux)"); - return None; - } + if desc.presentation && bugs.intel_unable_to_present { + log::warn!("Rejecting Intel for not presenting when Nvidia is present (on Linux)"); + return None; } let mut inline_uniform_block_features = @@ -253,6 +231,7 @@ unsafe fn inspect_adapter( let buffer_marker = supported_extensions.contains(&vk::AMD_BUFFER_MARKER_NAME); let shader_info = supported_extensions.contains(&vk::AMD_SHADER_INFO_NAME); + let full_screen_exclusive = supported_extensions.contains(&vk::EXT_FULL_SCREEN_EXCLUSIVE_NAME); let device_information = crate::DeviceInformation { is_software_emulated: properties.device_type == vk::PhysicalDeviceType::CPU, @@ -283,18 +262,12 @@ unsafe fn inspect_adapter( } impl super::Context { - unsafe fn init_impl( - desc: crate::ContextDesc, - surface_handles: Option<( - raw_window_handle::WindowHandle, - raw_window_handle::DisplayHandle, - )>, - ) -> Result { + pub unsafe fn init(desc: crate::ContextDesc) -> Result { let entry = match ash::Entry::load() { Ok(entry) => entry, Err(err) => { log::error!("Missing Vulkan entry points: {:?}", err); - return Err(NotSupportedError::VulkanLoadingError(err)); + return Err(super::PlatformError::Loading(err).into()); } }; let driver_api_version = match entry.try_enumerate_instance_version() { @@ -303,7 +276,7 @@ impl super::Context { Ok(None) => return Err(NotSupportedError::NoSupportedDeviceFound), Err(err) => { log::error!("try_enumerate_instance_version: {:?}", err); - return Err(NotSupportedError::VulkanError(err)); + return Err(super::PlatformError::Init(err).into()); } }; @@ -311,7 +284,7 @@ impl super::Context { Ok(layers) => layers, Err(err) => { log::error!("enumerate_instance_layer_properties: {:?}", err); - return Err(NotSupportedError::VulkanError(err)); + return Err(super::PlatformError::Init(err).into()); } }; let supported_layer_names = supported_layers @@ -340,7 +313,7 @@ impl super::Context { Ok(extensions) => extensions, Err(err) => { log::error!("enumerate_instance_extension_properties: {:?}", err); - return Err(NotSupportedError::VulkanError(err)); + return Err(super::PlatformError::Init(err).into()); } }; let supported_instance_extensions = supported_instance_extension_properties @@ -356,11 +329,20 @@ impl super::Context { vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_NAME, vk::KHR_GET_SURFACE_CAPABILITIES2_NAME, ]; - if let Some((_, dh)) = surface_handles { - match ash_window::enumerate_required_extensions(dh.as_raw()) { - Ok(extensions) => instance_extensions - .extend(extensions.iter().map(|&ptr| ffi::CStr::from_ptr(ptr))), - Err(e) => return Err(NotSupportedError::VulkanError(e)), + if desc.presentation { + instance_extensions.push(vk::KHR_SURFACE_NAME); + let candidates = [ + vk::KHR_WAYLAND_SURFACE_NAME, + vk::KHR_XCB_SURFACE_NAME, + vk::KHR_XLIB_SURFACE_NAME, + vk::KHR_WIN32_SURFACE_NAME, + vk::KHR_ANDROID_SURFACE_NAME, + ]; + for candidate in candidates.iter() { + if supported_instance_extensions.contains(candidate) { + log::info!("Presentation support: {:?}", candidate); + instance_extensions.push(candidate); + } } } @@ -395,19 +377,8 @@ impl super::Context { .flags(create_flags) .enabled_layer_names(layer_strings) .enabled_extension_names(extension_strings); - match entry.create_instance(&create_info, None) { - Ok(instance) => instance, - Err(e) => return Err(NotSupportedError::VulkanError(e)), - } - }; - - let vk_surface = if let Some((wh, dh)) = surface_handles { - Some( - ash_window::create_surface(&entry, &core_instance, dh.as_raw(), wh.as_raw(), None) - .map_err(|e| NotSupportedError::VulkanError(e))?, - ) - } else { - None + unsafe { entry.create_instance(&create_info, None) } + .map_err(super::PlatformError::Init)? }; let instance = @@ -419,7 +390,7 @@ impl super::Context { &entry, &core_instance, ), - surface: if surface_handles.is_some() { + surface: if desc.presentation { Some(khr::surface::Instance::new(&entry, &core_instance)) } else { None @@ -430,12 +401,11 @@ impl super::Context { let physical_devices = instance .core .enumerate_physical_devices() - .map_err(|e| NotSupportedError::VulkanError(e))?; + .map_err(super::PlatformError::Init)?; let (physical_device, capabilities) = physical_devices .into_iter() .find_map(|phd| { - inspect_adapter(phd, &instance, driver_api_version, vk_surface) - .map(|caps| (phd, caps)) + inspect_adapter(phd, &instance, driver_api_version, &desc).map(|caps| (phd, caps)) }) .ok_or_else(|| NotSupportedError::NoSupportedDeviceFound)?; @@ -448,7 +418,7 @@ impl super::Context { let family_infos = [family_info]; let mut device_extensions = REQUIRED_DEVICE_EXTENSIONS.to_vec(); - if surface_handles.is_some() { + if desc.presentation { device_extensions.push(vk::KHR_SWAPCHAIN_NAME); } if capabilities.layered { @@ -533,10 +503,15 @@ impl super::Context { instance .core .create_device(physical_device, &device_create_info, None) - .map_err(|e| NotSupportedError::VulkanError(e))? + .map_err(super::PlatformError::Init)? }; let device = super::Device { + swapchain: if desc.presentation { + Some(khr::swapchain::Device::new(&instance.core, &device_core)) + } else { + None + }, debug_utils: ext::debug_utils::Device::new(&instance.core, &device_core), timeline_semaphore: khr::timeline_semaphore::Device::new(&instance.core, &device_core), dynamic_rendering: khr::dynamic_rendering::Device::new(&instance.core, &device_core), @@ -683,37 +658,15 @@ impl super::Context { }; let timeline_semaphore_create_info = vk::SemaphoreCreateInfo::default().push_next(&mut timeline_info); - let timeline_semaphore = unsafe { - device - .core - .create_semaphore(&timeline_semaphore_create_info, None) - .unwrap() - }; + let timeline_semaphore = device + .core + .create_semaphore(&timeline_semaphore_create_info, None) + .unwrap(); let present_semaphore_create_info = vk::SemaphoreCreateInfo::default(); - let present_semaphore = unsafe { - device - .core - .create_semaphore(&present_semaphore_create_info, None) - .unwrap() - }; - - let surface = vk_surface.map(|raw| { - let extension = khr::swapchain::Device::new(&instance.core, &device.core); - let semaphore_create_info = vk::SemaphoreCreateInfo::default(); - let next_semaphore = unsafe { - device - .core - .create_semaphore(&semaphore_create_info, None) - .unwrap() - }; - Mutex::new(super::Surface { - raw, - frames: Vec::new(), - next_semaphore, - swapchain: vk::SwapchainKHR::null(), - extension, - }) - }); + let present_semaphore = device + .core + .create_semaphore(&present_semaphore_create_info, None) + .unwrap(); let mut naga_flags = spv::WriterFlags::FORCE_POINT_SIZE; let shader_debug_path = if desc.validation || desc.capture { @@ -736,32 +689,14 @@ impl super::Context { present_semaphore, last_progress, }), - surface, physical_device, naga_flags, shader_debug_path, instance, - _entry: entry, + entry, }) } - pub unsafe fn init(desc: crate::ContextDesc) -> Result { - Self::init_impl(desc, None) - } - - pub unsafe fn init_windowed< - I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle, - >( - window: &I, - desc: crate::ContextDesc, - ) -> Result { - let handles = ( - window.window_handle().unwrap(), - window.display_handle().unwrap(), - ); - Self::init_impl(desc, Some(handles)) - } - pub(super) fn set_object_name(&self, object: T, name: &str) { let name_cstr = ffi::CString::new(name).unwrap(); let name_info = vk::DebugUtilsObjectNameInfoEXT::default() @@ -788,311 +723,12 @@ impl super::Context { } } -impl super::Context { - pub fn resize(&self, config: crate::SurfaceConfig) -> crate::SurfaceInfo { - let surface_khr = self.instance.surface.as_ref().unwrap(); - let mut surface = self.surface.as_ref().unwrap().lock().unwrap(); - - let capabilities = unsafe { - surface_khr - .get_physical_device_surface_capabilities(self.physical_device, surface.raw) - .unwrap() - }; - if config.size.width < capabilities.min_image_extent.width - || config.size.width > capabilities.max_image_extent.width - || config.size.height < capabilities.min_image_extent.height - || config.size.height > capabilities.max_image_extent.height - { - log::warn!( - "Requested size {}x{} is outside of surface capabilities", - config.size.width, - config.size.height - ); - } - - let (alpha, composite_alpha) = if config.transparent { - if capabilities - .supported_composite_alpha - .contains(vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED) - { - ( - crate::AlphaMode::PostMultiplied, - vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED, - ) - } else if capabilities - .supported_composite_alpha - .contains(vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED) - { - ( - crate::AlphaMode::PreMultiplied, - vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED, - ) - } else { - log::error!( - "No composite alpha flag for transparency: {:?}", - capabilities.supported_composite_alpha - ); - ( - crate::AlphaMode::Ignored, - vk::CompositeAlphaFlagsKHR::OPAQUE, - ) - } - } else { - ( - crate::AlphaMode::Ignored, - vk::CompositeAlphaFlagsKHR::OPAQUE, - ) - }; - - let (requested_frame_count, mode_preferences) = match config.display_sync { - crate::DisplaySync::Block => (3, [vk::PresentModeKHR::FIFO].as_slice()), - crate::DisplaySync::Recent => ( - 3, - [ - vk::PresentModeKHR::MAILBOX, - vk::PresentModeKHR::FIFO_RELAXED, - vk::PresentModeKHR::IMMEDIATE, - ] - .as_slice(), - ), - crate::DisplaySync::Tear => (2, [vk::PresentModeKHR::IMMEDIATE].as_slice()), - }; - let effective_frame_count = requested_frame_count.max(capabilities.min_image_count); - - let present_modes = unsafe { - surface_khr - .get_physical_device_surface_present_modes(self.physical_device, surface.raw) - .unwrap() - }; - let present_mode = *mode_preferences - .iter() - .find(|mode| present_modes.contains(mode)) - .unwrap(); - log::info!("Using surface present mode {:?}", present_mode); - - let queue_families = [self.queue_family_index]; - - let mut supported_formats = Vec::new(); - let (format, surface_format) = - if let Some(&super::Frame { format, .. }) = surface.frames.first() { - log::info!("Retaining current format: {:?}", format); - let vk_color_space = match (format, config.color_space) { - (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Srgb) => { - vk::ColorSpaceKHR::SRGB_NONLINEAR - } - (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Linear) => { - vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT - } - (crate::TextureFormat::Bgra8UnormSrgb, crate::ColorSpace::Linear) => { - vk::ColorSpaceKHR::default() - } - _ => panic!( - "Unexpected format {:?} under color space {:?}", - format, config.color_space - ), - }; - ( - format, - vk::SurfaceFormatKHR { - format: super::map_texture_format(format), - color_space: vk_color_space, - }, - ) - } else { - supported_formats = unsafe { - surface_khr - .get_physical_device_surface_formats(self.physical_device, surface.raw) - .unwrap() - }; - match config.color_space { - crate::ColorSpace::Linear => { - let surface_format = vk::SurfaceFormatKHR { - format: vk::Format::B8G8R8A8_UNORM, - color_space: vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT, - }; - if supported_formats.contains(&surface_format) { - log::info!("Using linear SRGB color space"); - (crate::TextureFormat::Bgra8Unorm, surface_format) - } else { - ( - crate::TextureFormat::Bgra8UnormSrgb, - vk::SurfaceFormatKHR { - format: vk::Format::B8G8R8A8_SRGB, - color_space: vk::ColorSpaceKHR::default(), - }, - ) - } - } - crate::ColorSpace::Srgb => ( - crate::TextureFormat::Bgra8Unorm, - vk::SurfaceFormatKHR { - format: vk::Format::B8G8R8A8_UNORM, - color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR, - }, - ), - } - }; - if !supported_formats.is_empty() && !supported_formats.contains(&surface_format) { - log::error!("Surface formats are incompatible: {:?}", supported_formats); - } - - let vk_usage = super::resource::map_texture_usage(config.usage, crate::TexelAspects::COLOR); - if !capabilities.supported_usage_flags.contains(vk_usage) { - log::error!( - "Surface usages are incompatible: {:?}", - capabilities.supported_usage_flags - ); - } - - let mut full_screen_exclusive_info = vk::SurfaceFullScreenExclusiveInfoEXT { - full_screen_exclusive: if config.allow_exclusive_full_screen { - vk::FullScreenExclusiveEXT::ALLOWED - } else { - vk::FullScreenExclusiveEXT::DISALLOWED - }, - ..Default::default() - }; - - let mut create_info = vk::SwapchainCreateInfoKHR { - surface: surface.raw, - min_image_count: effective_frame_count, - image_format: surface_format.format, - image_color_space: surface_format.color_space, - image_extent: vk::Extent2D { - width: config.size.width, - height: config.size.height, - }, - image_array_layers: 1, - image_usage: vk_usage, - pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, - composite_alpha, - present_mode, - old_swapchain: surface.swapchain, - ..Default::default() - } - .queue_family_indices(&queue_families); - - if self.device.full_screen_exclusive.is_some() { - create_info = create_info.push_next(&mut full_screen_exclusive_info); - } else if !config.allow_exclusive_full_screen { - log::info!("Unable to forbid exclusive full screen"); - } - let new_swapchain = unsafe { - surface - .extension - .create_swapchain(&create_info, None) - .unwrap() - }; - - unsafe { - surface.deinit_swapchain(&self.device.core); - } - - let images = unsafe { - surface - .extension - .get_swapchain_images(new_swapchain) - .unwrap() - }; - let target_size = [config.size.width as u16, config.size.height as u16]; - let subresource_range = vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }; - for (index, image) in images.into_iter().enumerate() { - let view_create_info = vk::ImageViewCreateInfo { - image, - view_type: vk::ImageViewType::TYPE_2D, - format: surface_format.format, - subresource_range, - ..Default::default() - }; - let view = unsafe { - self.device - .core - .create_image_view(&view_create_info, None) - .unwrap() - }; - let semaphore_create_info = vk::SemaphoreCreateInfo::default(); - let acquire_semaphore = unsafe { - self.device - .core - .create_semaphore(&semaphore_create_info, None) - .unwrap() - }; - surface.frames.push(super::Frame { - image_index: index as u32, - image, - view, - format, - acquire_semaphore, - target_size, - }); - } - surface.swapchain = new_swapchain; - - crate::SurfaceInfo { format, alpha } - } - - pub fn acquire_frame(&self) -> super::Frame { - let mut surface = self.surface.as_ref().unwrap().lock().unwrap(); - let acquire_semaphore = surface.next_semaphore; - match unsafe { - surface.extension.acquire_next_image( - surface.swapchain, - !0, - acquire_semaphore, - vk::Fence::null(), - ) - } { - Ok((index, _suboptimal)) => { - surface.next_semaphore = mem::replace( - &mut surface.frames[index as usize].acquire_semaphore, - acquire_semaphore, - ); - surface.frames[index as usize] - } - Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { - log::warn!("Acquire failed because the surface is out of date"); - super::Frame { - acquire_semaphore: vk::Semaphore::null(), - ..surface.frames[0] - } - } - Err(other) => panic!("Aquire image error {}", other), - } - } -} - -impl super::Surface { - unsafe fn deinit_swapchain(&mut self, ash_device: &ash::Device) { - self.extension.destroy_swapchain(self.swapchain, None); - for frame in self.frames.drain(..) { - ash_device.destroy_image_view(frame.view, None); - ash_device.destroy_semaphore(frame.acquire_semaphore, None); - } - } -} - impl Drop for super::Context { fn drop(&mut self) { if std::thread::panicking() { return; } unsafe { - if let Some(surface_mutex) = self.surface.take() { - let mut surface = surface_mutex.into_inner().unwrap(); - surface.deinit_swapchain(&self.device.core); - self.device - .core - .destroy_semaphore(surface.next_semaphore, None); - if let Some(surface_instance) = self.instance.surface.take() { - surface_instance.destroy_surface(surface.raw, None); - } - } if let Ok(queue) = self.queue.lock() { self.device .core diff --git a/blade-graphics/src/vulkan/mod.rs b/blade-graphics/src/vulkan/mod.rs index bd6456d..400b95a 100644 --- a/blade-graphics/src/vulkan/mod.rs +++ b/blade-graphics/src/vulkan/mod.rs @@ -6,9 +6,16 @@ mod descriptor; mod init; mod pipeline; mod resource; +mod surface; const QUERY_POOL_SIZE: usize = crate::limits::PASS_COUNT + 1; +#[derive(Debug)] +pub enum PlatformError { + Loading(ash::LoadingError), + Init(vk::Result), +} + struct Instance { core: ash::Instance, _debug_utils: ash::ext::debug_utils::Instance, @@ -40,6 +47,7 @@ struct Workarounds { struct Device { core: ash::Device, device_information: crate::DeviceInformation, + swapchain: Option, debug_utils: ash::ext::debug_utils::Device, timeline_semaphore: khr::timeline_semaphore::Device, dynamic_rendering: khr::dynamic_rendering::Device, @@ -65,43 +73,63 @@ struct Queue { last_progress: u64, } -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Frame { - image_index: u32, +#[derive(Clone, Copy, Debug, Default, PartialEq)] +struct InternalFrame { + acquire_semaphore: vk::Semaphore, image: vk::Image, view: vk::ImageView, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +struct Swapchain { + raw: vk::SwapchainKHR, format: crate::TextureFormat, - acquire_semaphore: vk::Semaphore, + alpha: crate::AlphaMode, target_size: [u16; 2], } +pub struct Surface { + device: khr::swapchain::Device, + raw: vk::SurfaceKHR, + frames: Vec, + next_semaphore: vk::Semaphore, + swapchain: Swapchain, + full_screen_exclusive: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +struct Presentation { + swapchain: vk::SwapchainKHR, + image_index: u32, + acquire_semaphore: vk::Semaphore, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Frame { + swapchain: Swapchain, + image_index: u32, + internal: InternalFrame, +} + impl Frame { pub fn texture(&self) -> Texture { Texture { - raw: self.image, + raw: self.internal.image, memory_handle: !0, - target_size: self.target_size, - format: self.format, + target_size: self.swapchain.target_size, + format: self.swapchain.format, } } pub fn texture_view(&self) -> TextureView { TextureView { - raw: self.view, - target_size: self.target_size, + raw: self.internal.view, + target_size: self.swapchain.target_size, aspects: crate::TexelAspects::COLOR, } } } -struct Surface { - raw: vk::SurfaceKHR, - frames: Vec, - next_semaphore: vk::Semaphore, - swapchain: vk::SwapchainKHR, - extension: khr::swapchain::Device, -} - fn map_timeout(millis: u32) -> u64 { if millis == !0 { !0 @@ -115,12 +143,11 @@ pub struct Context { device: Device, queue_family_index: u32, queue: Mutex, - surface: Option>, physical_device: vk::PhysicalDevice, naga_flags: naga::back::spv::WriterFlags, shader_debug_path: Option, instance: Instance, - _entry: ash::Entry, + entry: ash::Entry, } #[derive(Clone, Copy, Debug, Hash, PartialEq)] @@ -232,12 +259,6 @@ struct CommandBuffer { timed_pass_names: Vec, } -#[derive(Debug, PartialEq)] -struct Presentation { - image_index: u32, - acquire_semaphore: vk::Semaphore, -} - struct CrashHandler { name: String, marker_buf: Buffer, @@ -465,15 +486,15 @@ impl crate::traits::CommandDevice for Context { encoder.check_gpu_crash(ret); if let Some(presentation) = encoder.present.take() { - let surface = self.surface.as_ref().unwrap().lock().unwrap(); - let swapchains = [surface.swapchain]; + let khr_swapchain = self.device.swapchain.as_ref().unwrap(); + let swapchains = [presentation.swapchain]; let image_indices = [presentation.image_index]; let wait_semaphores = [queue.present_semaphore]; let present_info = vk::PresentInfoKHR::default() .swapchains(&swapchains) .image_indices(&image_indices) .wait_semaphores(&wait_semaphores); - let ret = unsafe { surface.extension.queue_present(queue.raw, &present_info) }; + let ret = unsafe { khr_swapchain.queue_present(queue.raw, &present_info) }; let _ = encoder.check_gpu_crash(ret); } diff --git a/blade-graphics/src/vulkan/surface.rs b/blade-graphics/src/vulkan/surface.rs new file mode 100644 index 0000000..ae2d7cc --- /dev/null +++ b/blade-graphics/src/vulkan/surface.rs @@ -0,0 +1,387 @@ +use ash::vk; +use std::mem; + +impl super::Surface { + pub fn info(&self) -> crate::SurfaceInfo { + crate::SurfaceInfo { + format: self.swapchain.format, + alpha: self.swapchain.alpha, + } + } + + unsafe fn deinit_swapchain(&mut self, raw_device: &ash::Device) { + self.device + .destroy_swapchain(mem::take(&mut self.swapchain.raw), None); + for frame in self.frames.drain(..) { + raw_device.destroy_image_view(frame.view, None); + raw_device.destroy_semaphore(frame.acquire_semaphore, None); + } + } + + pub fn acquire_frame(&mut self) -> super::Frame { + let acquire_semaphore = self.next_semaphore; + match unsafe { + self.device.acquire_next_image( + self.swapchain.raw, + !0, + acquire_semaphore, + vk::Fence::null(), + ) + } { + Ok((index, _suboptimal)) => { + self.next_semaphore = mem::replace( + &mut self.frames[index as usize].acquire_semaphore, + acquire_semaphore, + ); + super::Frame { + internal: self.frames[index as usize], + swapchain: self.swapchain, + image_index: index, + } + } + Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { + log::warn!("Acquire failed because the surface is out of date"); + super::Frame { + internal: super::InternalFrame::default(), + swapchain: self.swapchain, + image_index: 0, + } + } + Err(other) => panic!("Aquire image error {}", other), + } + } +} + +impl super::Context { + pub fn create_surface< + I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle, + >( + &self, + window: &I, + ) -> Result { + let khr_swapchain = self + .device + .swapchain + .clone() + .ok_or(crate::NotSupportedError::NoSupportedDeviceFound)?; + + let raw = unsafe { + ash_window::create_surface( + &self.entry, + &self.instance.core, + window.display_handle().unwrap().as_raw(), + window.window_handle().unwrap().as_raw(), + None, + ) + .map_err(super::PlatformError::Init)? + }; + + let khr_surface = self + .instance + .surface + .as_ref() + .ok_or(crate::NotSupportedError::PlatformNotSupported)?; + if unsafe { + khr_surface.get_physical_device_surface_support( + self.physical_device, + self.queue_family_index, + raw, + ) != Ok(true) + } { + log::warn!("Rejected for not presenting to the window surface"); + return Err(crate::NotSupportedError::PlatformNotSupported); + } + + let surface_info = vk::PhysicalDeviceSurfaceInfo2KHR { + surface: raw, + ..Default::default() + }; + let mut fullscreen_exclusive_ext = vk::SurfaceCapabilitiesFullScreenExclusiveEXT::default(); + let mut capabilities2_khr = + vk::SurfaceCapabilities2KHR::default().push_next(&mut fullscreen_exclusive_ext); + let _ = unsafe { + self.instance + .get_surface_capabilities2 + .get_physical_device_surface_capabilities2( + self.physical_device, + &surface_info, + &mut capabilities2_khr, + ) + }; + log::debug!("{:?}", capabilities2_khr.surface_capabilities); + + let semaphore_create_info = vk::SemaphoreCreateInfo::default(); + let next_semaphore = unsafe { + self.device + .core + .create_semaphore(&semaphore_create_info, None) + .unwrap() + }; + + Ok(super::Surface { + device: khr_swapchain, + raw, + frames: Vec::new(), + next_semaphore, + swapchain: super::Swapchain { + raw: vk::SwapchainKHR::null(), + format: crate::TextureFormat::Rgba8Unorm, + alpha: crate::AlphaMode::Ignored, + target_size: [0; 2], + }, + full_screen_exclusive: fullscreen_exclusive_ext.full_screen_exclusive_supported != 0, + }) + } + + pub fn destroy_surface(&self, surface: &mut super::Surface) { + unsafe { + surface.deinit_swapchain(&self.device.core); + self.device + .core + .destroy_semaphore(surface.next_semaphore, None) + }; + if let Some(ref surface_instance) = self.instance.surface { + unsafe { surface_instance.destroy_surface(surface.raw, None) }; + } + } + + pub fn reconfigure_surface(&self, surface: &mut super::Surface, config: crate::SurfaceConfig) { + let khr_surface = self.instance.surface.as_ref().unwrap(); + + let capabilities = unsafe { + khr_surface + .get_physical_device_surface_capabilities(self.physical_device, surface.raw) + .unwrap() + }; + if config.size.width < capabilities.min_image_extent.width + || config.size.width > capabilities.max_image_extent.width + || config.size.height < capabilities.min_image_extent.height + || config.size.height > capabilities.max_image_extent.height + { + log::warn!( + "Requested size {}x{} is outside of surface capabilities", + config.size.width, + config.size.height + ); + } + + let (alpha, composite_alpha) = if config.transparent { + if capabilities + .supported_composite_alpha + .contains(vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED) + { + ( + crate::AlphaMode::PostMultiplied, + vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED, + ) + } else if capabilities + .supported_composite_alpha + .contains(vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED) + { + ( + crate::AlphaMode::PreMultiplied, + vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED, + ) + } else { + log::error!( + "No composite alpha flag for transparency: {:?}", + capabilities.supported_composite_alpha + ); + ( + crate::AlphaMode::Ignored, + vk::CompositeAlphaFlagsKHR::OPAQUE, + ) + } + } else { + ( + crate::AlphaMode::Ignored, + vk::CompositeAlphaFlagsKHR::OPAQUE, + ) + }; + + let (requested_frame_count, mode_preferences) = match config.display_sync { + crate::DisplaySync::Block => (3, [vk::PresentModeKHR::FIFO].as_slice()), + crate::DisplaySync::Recent => ( + 3, + [ + vk::PresentModeKHR::MAILBOX, + vk::PresentModeKHR::FIFO_RELAXED, + vk::PresentModeKHR::IMMEDIATE, + ] + .as_slice(), + ), + crate::DisplaySync::Tear => (2, [vk::PresentModeKHR::IMMEDIATE].as_slice()), + }; + let effective_frame_count = requested_frame_count.max(capabilities.min_image_count); + + let present_modes = unsafe { + khr_surface + .get_physical_device_surface_present_modes(self.physical_device, surface.raw) + .unwrap() + }; + let present_mode = *mode_preferences + .iter() + .find(|mode| present_modes.contains(mode)) + .unwrap(); + log::info!("Using surface present mode {:?}", present_mode); + + let queue_families = [self.queue_family_index]; + + let mut supported_formats = Vec::new(); + let (format, surface_format) = if surface.swapchain.target_size[0] > 0 { + let format = surface.swapchain.format; + log::info!("Retaining current format: {:?}", format); + let vk_color_space = match (format, config.color_space) { + (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Srgb) => { + vk::ColorSpaceKHR::SRGB_NONLINEAR + } + (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Linear) => { + vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT + } + (crate::TextureFormat::Bgra8UnormSrgb, crate::ColorSpace::Linear) => { + vk::ColorSpaceKHR::default() + } + _ => panic!( + "Unexpected format {:?} under color space {:?}", + format, config.color_space + ), + }; + ( + format, + vk::SurfaceFormatKHR { + format: super::map_texture_format(format), + color_space: vk_color_space, + }, + ) + } else { + supported_formats = unsafe { + khr_surface + .get_physical_device_surface_formats(self.physical_device, surface.raw) + .unwrap() + }; + match config.color_space { + crate::ColorSpace::Linear => { + let surface_format = vk::SurfaceFormatKHR { + format: vk::Format::B8G8R8A8_UNORM, + color_space: vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT, + }; + if supported_formats.contains(&surface_format) { + log::info!("Using linear SRGB color space"); + (crate::TextureFormat::Bgra8Unorm, surface_format) + } else { + ( + crate::TextureFormat::Bgra8UnormSrgb, + vk::SurfaceFormatKHR { + format: vk::Format::B8G8R8A8_SRGB, + color_space: vk::ColorSpaceKHR::default(), + }, + ) + } + } + crate::ColorSpace::Srgb => ( + crate::TextureFormat::Bgra8Unorm, + vk::SurfaceFormatKHR { + format: vk::Format::B8G8R8A8_UNORM, + color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR, + }, + ), + } + }; + if !supported_formats.is_empty() && !supported_formats.contains(&surface_format) { + log::error!("Surface formats are incompatible: {:?}", supported_formats); + } + + let vk_usage = super::resource::map_texture_usage(config.usage, crate::TexelAspects::COLOR); + if !capabilities.supported_usage_flags.contains(vk_usage) { + log::error!( + "Surface usages are incompatible: {:?}", + capabilities.supported_usage_flags + ); + } + + let mut full_screen_exclusive_info = vk::SurfaceFullScreenExclusiveInfoEXT { + full_screen_exclusive: if config.allow_exclusive_full_screen { + vk::FullScreenExclusiveEXT::ALLOWED + } else { + vk::FullScreenExclusiveEXT::DISALLOWED + }, + ..Default::default() + }; + + let mut create_info = vk::SwapchainCreateInfoKHR { + surface: surface.raw, + min_image_count: effective_frame_count, + image_format: surface_format.format, + image_color_space: surface_format.color_space, + image_extent: vk::Extent2D { + width: config.size.width, + height: config.size.height, + }, + image_array_layers: 1, + image_usage: vk_usage, + pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, + composite_alpha, + present_mode, + old_swapchain: surface.swapchain.raw, + ..Default::default() + } + .queue_family_indices(&queue_families); + + if surface.full_screen_exclusive { + assert!(self.device.full_screen_exclusive.is_some()); + create_info = create_info.push_next(&mut full_screen_exclusive_info); + log::info!( + "Configuring exclusive full screen: {}", + config.allow_exclusive_full_screen + ); + } + let raw_swapchain = unsafe { surface.device.create_swapchain(&create_info, None).unwrap() }; + + unsafe { + surface.deinit_swapchain(&self.device.core); + } + + let images = unsafe { surface.device.get_swapchain_images(raw_swapchain).unwrap() }; + let target_size = [config.size.width as u16, config.size.height as u16]; + let subresource_range = vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }; + for image in images { + let view_create_info = vk::ImageViewCreateInfo { + image, + view_type: vk::ImageViewType::TYPE_2D, + format: surface_format.format, + subresource_range, + ..Default::default() + }; + let view = unsafe { + self.device + .core + .create_image_view(&view_create_info, None) + .unwrap() + }; + let semaphore_create_info = vk::SemaphoreCreateInfo::default(); + let acquire_semaphore = unsafe { + self.device + .core + .create_semaphore(&semaphore_create_info, None) + .unwrap() + }; + surface.frames.push(super::InternalFrame { + acquire_semaphore, + image, + view, + }); + } + surface.swapchain = super::Swapchain { + raw: raw_swapchain, + format, + alpha, + target_size, + }; + } +} diff --git a/blade-render/Cargo.toml b/blade-render/Cargo.toml index 85a67a8..0bf1c39 100644 --- a/blade-render/Cargo.toml +++ b/blade-render/Cargo.toml @@ -37,7 +37,7 @@ exr = { version = "1.6", optional = true } gltf = { workspace = true, features = ["names", "utils"], optional = true } glam = { workspace = true } log = { workspace = true } -mikktspace = { package = "bevy_mikktspace", version = "0.12", optional = true } +mikktspace = { package = "bevy_mikktspace", version = "0.15.0-rc.3", optional = true } mint = { workspace = true } profiling = { workspace = true } slab = { workspace = true, optional = true } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3d2c133..f295d16 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,9 @@ Changelog for Blade ## blade-graphics-0.6 (TBD) - graphics: + - return detailed initialization errors + - API for surface creation + - allows multiple windows used by the same context - API for destruction of pipelines - every pass now takes a label - automatic GPU pass markers diff --git a/examples/bunnymark/main.rs b/examples/bunnymark/main.rs index 9fc40e9..b14c2fe 100644 --- a/examples/bunnymark/main.rs +++ b/examples/bunnymark/main.rs @@ -52,41 +52,46 @@ struct Example { texture: gpu::Texture, view: gpu::TextureView, sampler: gpu::Sampler, + vertex_buf: gpu::Buffer, window_size: winit::dpi::PhysicalSize, bunnies: Vec, rng: nanorand::WyRand, + surface: gpu::Surface, context: gpu::Context, - vertex_buf: gpu::Buffer, } impl Example { + fn make_surface_config(size: winit::dpi::PhysicalSize) -> gpu::SurfaceConfig { + log::info!("Window size: {:?}", size); + gpu::SurfaceConfig { + size: gpu::Extent { + width: size.width, + height: size.height, + depth: 1, + }, + usage: gpu::TextureUsage::TARGET, + display_sync: gpu::DisplaySync::Recent, + ..Default::default() + } + } + fn new(window: &winit::window::Window) -> Self { let context = unsafe { - gpu::Context::init_windowed( - window, - gpu::ContextDesc { - validation: cfg!(debug_assertions), - timing: false, - capture: false, - overlay: true, - }, - ) + gpu::Context::init(gpu::ContextDesc { + presentation: true, + validation: cfg!(debug_assertions), + timing: false, + capture: false, + overlay: true, + }) .unwrap() }; println!("{:?}", context.device_information()); let window_size = window.inner_size(); - log::info!("Initial size: {:?}", window_size); - let surface_info = context.resize(gpu::SurfaceConfig { - size: gpu::Extent { - width: window_size.width, - height: window_size.height, - depth: 1, - }, - usage: gpu::TextureUsage::TARGET, - display_sync: gpu::DisplaySync::Recent, - ..Default::default() - }); + let surface = context + .create_surface_configured(window, Self::make_surface_config(window_size)) + .unwrap(); let global_layout = ::layout(); let local_layout = ::layout(); @@ -113,7 +118,7 @@ impl Example { depth_stencil: None, fragment: shader.at("fs_main"), color_targets: &[gpu::ColorTargetState { - format: surface_info.format, + format: surface.info().format, blend: Some(gpu::BlendState::ALPHA_BLENDING), write_mask: gpu::ColorWrites::default(), }], @@ -217,14 +222,21 @@ impl Example { texture, view, sampler, + vertex_buf, window_size, bunnies, rng: nanorand::WyRand::new_seed(73), + surface, context, - vertex_buf, } } + fn resize(&mut self, size: winit::dpi::PhysicalSize) { + self.window_size = size; + let config = Self::make_surface_config(size); + self.context.reconfigure_surface(&mut self.surface, config); + } + fn increase(&mut self) { use nanorand::Rng as _; let spawn_count = 64 + self.bunnies.len() / 2; @@ -278,7 +290,7 @@ impl Example { if self.window_size == Default::default() { return; } - let frame = self.context.acquire_frame(); + let frame = self.surface.acquire_frame(); self.command_encoder.start(); self.command_encoder.init_texture(frame.texture()); @@ -334,13 +346,14 @@ impl Example { if let Some(sp) = self.prev_sync_point.take() { self.context.wait_for(&sp, !0); } - self.context.destroy_buffer(self.vertex_buf); self.context.destroy_texture_view(self.view); self.context.destroy_texture(self.texture); self.context.destroy_sampler(self.sampler); + self.context.destroy_buffer(self.vertex_buf); self.context .destroy_command_encoder(&mut self.command_encoder); self.context.destroy_render_pipeline(&mut self.pipeline); + self.context.destroy_surface(&mut self.surface); } } @@ -362,6 +375,7 @@ fn main() { console_log::init().expect("could not initialize logger"); // On wasm, append the canvas to the document body let canvas = window.canvas().unwrap(); + canvas.set_id(gpu::CANVAS_ID); web_sys::window() .and_then(|win| win.document()) .and_then(|doc| doc.body()) @@ -387,6 +401,9 @@ fn main() { window.request_redraw(); } winit::event::Event::WindowEvent { event, .. } => match event { + winit::event::WindowEvent::Resized(size) => { + example.resize(size); + } #[cfg(not(target_arch = "wasm32"))] winit::event::WindowEvent::KeyboardInput { event: diff --git a/examples/particle/main.rs b/examples/particle/main.rs index eabfa02..e836f82 100644 --- a/examples/particle/main.rs +++ b/examples/particle/main.rs @@ -8,6 +8,7 @@ struct Example { command_encoder: gpu::CommandEncoder, prev_sync_point: Option, context: gpu::Context, + surface: gpu::Surface, gui_painter: blade_egui::GuiPainter, particle_system: particle::System, } @@ -16,19 +17,16 @@ impl Example { fn new(window: &winit::window::Window) -> Self { let window_size = window.inner_size(); let context = unsafe { - gpu::Context::init_windowed( - window, - gpu::ContextDesc { - validation: cfg!(debug_assertions), - timing: true, - capture: true, - overlay: false, - }, - ) + gpu::Context::init(gpu::ContextDesc { + presentation: true, + validation: cfg!(debug_assertions), + timing: true, + capture: true, + overlay: false, + }) .unwrap() }; - - let surface_info = context.resize(gpu::SurfaceConfig { + let surface_config = gpu::SurfaceConfig { size: gpu::Extent { width: window_size.width, height: window_size.height, @@ -37,7 +35,12 @@ impl Example { usage: gpu::TextureUsage::TARGET, display_sync: gpu::DisplaySync::Block, ..Default::default() - }); + }; + let surface = context + .create_surface_configured(window, surface_config) + .unwrap(); + let surface_info = surface.info(); + let gui_painter = blade_egui::GuiPainter::new(surface_info, &context); let particle_system = particle::System::new( &context, @@ -60,6 +63,7 @@ impl Example { command_encoder, prev_sync_point: Some(sync_point), context, + surface, gui_painter, particle_system, } @@ -73,6 +77,7 @@ impl Example { .destroy_command_encoder(&mut self.command_encoder); self.gui_painter.destroy(&self.context); self.particle_system.destroy(&self.context); + self.context.destroy_surface(&mut self.surface); } fn render( @@ -81,7 +86,7 @@ impl Example { gui_textures: &egui::TexturesDelta, screen_desc: &blade_egui::ScreenDescriptor, ) { - let frame = self.context.acquire_frame(); + let frame = self.surface.acquire_frame(); self.command_encoder.start(); self.command_encoder.init_texture(frame.texture()); diff --git a/examples/ray-query/main.rs b/examples/ray-query/main.rs index add7f4a..3fab8f2 100644 --- a/examples/ray-query/main.rs +++ b/examples/ray-query/main.rs @@ -41,19 +41,18 @@ struct Example { prev_sync_point: Option, screen_size: gpu::Extent, context: gpu::Context, + surface: gpu::Surface, } impl Example { fn new(window: &winit::window::Window) -> Self { let window_size = window.inner_size(); let context = unsafe { - gpu::Context::init_windowed( - window, - gpu::ContextDesc { - validation: cfg!(debug_assertions), - ..Default::default() - }, - ) + gpu::Context::init(gpu::ContextDesc { + presentation: true, + validation: cfg!(debug_assertions), + ..Default::default() + }) .unwrap() }; let capabilities = context.capabilities(); @@ -66,6 +65,15 @@ impl Example { height: window_size.height, depth: 1, }; + let surface_config = gpu::SurfaceConfig { + size: screen_size, + usage: gpu::TextureUsage::TARGET, + transparent: true, + ..Default::default() + }; + let surface = context + .create_surface_configured(window, surface_config) + .unwrap(); let target = context.create_texture(gpu::TextureDesc { name: "main", @@ -86,13 +94,6 @@ impl Example { }, ); - let surface_info = context.resize(gpu::SurfaceConfig { - size: screen_size, - usage: gpu::TextureUsage::TARGET, - transparent: true, - ..Default::default() - }); - let source = std::fs::read_to_string("examples/ray-query/shader.wgsl").unwrap(); let shader = context.create_shader(gpu::ShaderDesc { source: &source }); let rt_layout = ::layout(); @@ -112,7 +113,7 @@ impl Example { vertex: shader.at("draw_vs"), vertex_fetches: &[], fragment: shader.at("draw_fs"), - color_targets: &[surface_info.format.into()], + color_targets: &[surface.info().format.into()], depth_stencil: None, }); @@ -241,6 +242,7 @@ impl Example { command_encoder, prev_sync_point: None, screen_size, + surface, context, } } @@ -258,6 +260,7 @@ impl Example { self.context.destroy_compute_pipeline(&mut self.rt_pipeline); self.context .destroy_render_pipeline(&mut self.draw_pipeline); + self.context.destroy_surface(&mut self.surface); } fn render(&mut self) { @@ -289,7 +292,7 @@ impl Example { } } - let frame = self.context.acquire_frame(); + let frame = self.surface.acquire_frame(); self.command_encoder.init_texture(frame.texture()); if let mut pass = self.command_encoder.render( diff --git a/examples/scene/main.rs b/examples/scene/main.rs index d37e866..ddae495 100644 --- a/examples/scene/main.rs +++ b/examples/scene/main.rs @@ -140,6 +140,7 @@ struct Example { gui_painter: blade_egui::GuiPainter, asset_hub: blade_render::AssetHub, context: Arc, + surface: gpu::Surface, environment_map: Option>, objects: Vec, object_extras: Vec, @@ -183,20 +184,21 @@ impl Example { log::info!("Initializing"); let context = Arc::new(unsafe { - gpu::Context::init_windowed( - window, - gpu::ContextDesc { - validation: cfg!(debug_assertions), - capture: true, - ..Default::default() - }, - ) + gpu::Context::init(gpu::ContextDesc { + presentation: true, + validation: cfg!(debug_assertions), + capture: true, + ..Default::default() + }) .unwrap() }); let surface_config = Self::make_surface_config(window.inner_size()); let surface_size = surface_config.size; - let surface_info = context.resize(surface_config); + let surface = context + .create_surface_configured(window, surface_config) + .unwrap(); + let surface_info = surface.info(); let num_workers = num_cpus::get_physical().max((num_cpus::get() * 3 + 2) / 4); log::info!("Initializing Choir with {} workers", num_workers); @@ -237,6 +239,7 @@ impl Example { gui_painter, asset_hub, context, + surface, environment_map: None, objects: Vec::new(), object_extras: Vec::new(), @@ -275,6 +278,7 @@ impl Example { self.gui_painter.destroy(&self.context); self.renderer.destroy(&self.context); self.asset_hub.destroy(); + self.context.destroy_surface(&mut self.surface); } pub fn load_scene(&mut self, scene_path: &Path) { @@ -402,7 +406,8 @@ impl Example { if new_render_size != self.renderer.get_surface_size() { log::info!("Resizing to {}", new_render_size); self.pacer.wait_for_previous_frame(&self.context); - self.context.resize(surface_config); + self.context + .reconfigure_surface(&mut self.surface, surface_config); } let (command_encoder, temp) = self.pacer.begin_frame(); @@ -467,7 +472,7 @@ impl Example { } } - let frame = self.context.acquire_frame(); + let frame = self.surface.acquire_frame(); command_encoder.init_texture(frame.texture()); if let mut pass = command_encoder.render( diff --git a/src/lib.rs b/src/lib.rs index 15eb66f..3ae92c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -369,6 +369,7 @@ pub struct Engine { load_tasks: Vec, gui_painter: blade_egui::GuiPainter, asset_hub: blade_render::AssetHub, + gpu_surface: gpu::Surface, gpu_context: Arc, environment_map: Option>, objects: slab::Slab, @@ -411,21 +412,22 @@ impl Engine { log::info!("Initializing the engine"); let gpu_context = Arc::new(unsafe { - gpu::Context::init_windowed( - window, - gpu::ContextDesc { - validation: cfg!(debug_assertions), - timing: true, - capture: false, - overlay: false, - }, - ) + gpu::Context::init(gpu::ContextDesc { + presentation: true, + validation: cfg!(debug_assertions), + timing: true, + capture: false, + overlay: false, + }) .unwrap() }); let surface_config = Self::make_surface_config(window.inner_size()); let surface_size = surface_config.size; - let surface_info = gpu_context.resize(surface_config); + let gpu_surface = gpu_context + .create_surface_configured(window, surface_config) + .unwrap(); + let surface_info = gpu_surface.info(); let num_workers = num_cpus::get_physical().max((num_cpus::get() * 3 + 2) / 4); log::info!("Initializing Choir with {} workers", num_workers); @@ -470,6 +472,7 @@ impl Engine { load_tasks: Vec::new(), gui_painter, asset_hub, + gpu_surface, gpu_context, environment_map: None, objects: slab::Slab::new(), @@ -506,6 +509,7 @@ impl Engine { self.workers.clear(); self.pacer.destroy(&self.gpu_context); self.gui_painter.destroy(&self.gpu_context); + self.gpu_context.destroy_surface(&mut self.gpu_surface); self.renderer.destroy(&self.gpu_context); self.asset_hub.destroy(); } @@ -544,7 +548,8 @@ impl Engine { if new_render_size != self.renderer.get_surface_size() { log::info!("Resizing to {}", new_render_size); self.pacer.wait_for_previous_frame(&self.gpu_context); - self.gpu_context.resize(surface_config); + self.gpu_context + .reconfigure_surface(&mut self.gpu_surface, surface_config); } let (command_encoder, temp) = self.pacer.begin_frame(); @@ -677,7 +682,7 @@ impl Engine { } } - let frame = self.gpu_context.acquire_frame(); + let frame = self.gpu_surface.acquire_frame(); command_encoder.init_texture(frame.texture()); if let mut pass = command_encoder.render(