diff options
Diffstat (limited to 'third_party/rust/gfx-backend-dx12/src/window.rs')
-rw-r--r-- | third_party/rust/gfx-backend-dx12/src/window.rs | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/third_party/rust/gfx-backend-dx12/src/window.rs b/third_party/rust/gfx-backend-dx12/src/window.rs new file mode 100644 index 0000000000..c3de1a24be --- /dev/null +++ b/third_party/rust/gfx-backend-dx12/src/window.rs @@ -0,0 +1,350 @@ +use std::{borrow::Borrow, fmt, mem, os::raw::c_void}; + +use winapi::{ + shared::{ + dxgi, dxgi1_4, dxgi1_5, dxgitype, + minwindef::{BOOL, FALSE, TRUE}, + windef::{HWND, RECT}, + winerror, + }, + um::{d3d12, synchapi, winbase, winnt::HANDLE, winuser::GetClientRect}, +}; + +use crate::{conv, resource as r, Backend, Device, Instance, PhysicalDevice, QueueFamily}; +use hal::{device::Device as _, format as f, image as i, window as w}; + +impl Instance { + pub fn create_surface_from_hwnd(&self, hwnd: *mut c_void) -> Surface { + Surface { + factory: self.factory, + wnd_handle: hwnd as *mut _, + presentation: None, + } + } +} + +#[derive(Debug)] +struct Presentation { + swapchain: Swapchain, + format: f::Format, + size: w::Extent2D, + mode: w::PresentMode, +} + +pub struct Surface { + pub(crate) factory: native::WeakPtr<dxgi1_4::IDXGIFactory4>, + pub(crate) wnd_handle: HWND, + presentation: Option<Presentation>, +} + +impl fmt::Debug for Surface { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("Surface") + } +} + +unsafe impl Send for Surface {} +unsafe impl Sync for Surface {} + +impl Surface { + pub(crate) unsafe fn present(&mut self, image: SwapchainImage) -> Result<(), w::PresentError> { + let present = self.presentation.as_mut().unwrap(); + let sc = &mut present.swapchain; + sc.acquired_count -= 1; + + let expected_index = sc.inner.GetCurrentBackBufferIndex() as w::SwapImageIndex; + if image.index != expected_index { + log::warn!( + "Expected frame {}, got frame {}", + expected_index, + image.index + ); + return Err(w::PresentError::OutOfDate); + } + + let (interval, flags) = match present.mode { + w::PresentMode::IMMEDIATE => (0, dxgi::DXGI_PRESENT_ALLOW_TEARING), + w::PresentMode::FIFO => (1, 0), + _ => (1, 0), // Surface was created with an unsupported present mode, fall back to FIFO + }; + + sc.inner.Present(interval, flags); + Ok(()) + } +} + +impl w::Surface<Backend> for Surface { + fn supports_queue_family(&self, queue_family: &QueueFamily) -> bool { + match queue_family { + &QueueFamily::Present => true, + _ => false, + } + } + + fn capabilities(&self, _physical_device: &PhysicalDevice) -> w::SurfaceCapabilities { + let current_extent = unsafe { + let mut rect: RECT = mem::zeroed(); + if GetClientRect(self.wnd_handle as *mut _, &mut rect as *mut RECT) == 0 { + panic!("GetClientRect failed"); + } + Some(w::Extent2D { + width: (rect.right - rect.left) as u32, + height: (rect.bottom - rect.top) as u32, + }) + }; + + let allow_tearing = unsafe { + let (f5, hr) = self.factory.cast::<dxgi1_5::IDXGIFactory5>(); + if winerror::SUCCEEDED(hr) { + let mut allow_tearing: BOOL = FALSE; + let hr = f5.CheckFeatureSupport( + dxgi1_5::DXGI_FEATURE_PRESENT_ALLOW_TEARING, + &mut allow_tearing as *mut _ as *mut _, + mem::size_of::<BOOL>() as _, + ); + + f5.destroy(); + + winerror::SUCCEEDED(hr) && allow_tearing == TRUE + } else { + false + } + }; + + let mut present_modes = w::PresentMode::FIFO; + if allow_tearing { + present_modes |= w::PresentMode::IMMEDIATE; + } + + w::SurfaceCapabilities { + present_modes, + composite_alpha_modes: w::CompositeAlphaMode::OPAQUE, //TODO + image_count: 2..=16, // we currently use a flip effect which supports 2..=16 buffers + current_extent, + extents: w::Extent2D { + width: 16, + height: 16, + }..=w::Extent2D { + width: 4096, + height: 4096, + }, + max_image_layers: 1, + usage: i::Usage::COLOR_ATTACHMENT | i::Usage::TRANSFER_SRC | i::Usage::TRANSFER_DST, + } + } + + fn supported_formats(&self, _physical_device: &PhysicalDevice) -> Option<Vec<f::Format>> { + Some(vec![ + f::Format::Bgra8Srgb, + f::Format::Bgra8Unorm, + f::Format::Rgba8Srgb, + f::Format::Rgba8Unorm, + f::Format::A2b10g10r10Unorm, + f::Format::Rgba16Sfloat, + ]) + } +} + +#[derive(Debug)] +pub struct SwapchainImage { + index: w::SwapImageIndex, + image: r::Image, + view: r::ImageView, +} + +impl Borrow<r::Image> for SwapchainImage { + fn borrow(&self) -> &r::Image { + &self.image + } +} + +impl Borrow<r::ImageView> for SwapchainImage { + fn borrow(&self) -> &r::ImageView { + &self.view + } +} + +impl w::PresentationSurface<Backend> for Surface { + type SwapchainImage = SwapchainImage; + + unsafe fn configure_swapchain( + &mut self, + device: &Device, + config: w::SwapchainConfig, + ) -> Result<(), w::CreationError> { + assert!(i::Usage::COLOR_ATTACHMENT.contains(config.image_usage)); + + let swapchain = match self.presentation.take() { + Some(present) => { + if present.format == config.format && present.size == config.extent { + self.presentation = Some(present); + return Ok(()); + } + // can't have image resources in flight used by GPU + device.wait_idle().unwrap(); + + let mut flags = dxgi::DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + if config.present_mode.contains(w::PresentMode::IMMEDIATE) { + flags |= dxgi::DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } + + let inner = present.swapchain.release_resources(); + let result = inner.ResizeBuffers( + config.image_count, + config.extent.width, + config.extent.height, + conv::map_format_nosrgb(config.format).unwrap(), + flags, + ); + if result != winerror::S_OK { + error!("ResizeBuffers failed with 0x{:x}", result as u32); + return Err(w::CreationError::WindowInUse(hal::device::WindowInUse)); + } + inner + } + None => { + let (swapchain, _) = + device.create_swapchain_impl(&config, self.wnd_handle, self.factory.clone())?; + swapchain + } + }; + + // Disable automatic Alt+Enter handling by DXGI. + const DXGI_MWA_NO_WINDOW_CHANGES: u32 = 1; + const DXGI_MWA_NO_ALT_ENTER: u32 = 2; + self.factory.MakeWindowAssociation( + self.wnd_handle, + DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER, + ); + + self.presentation = Some(Presentation { + swapchain: device.wrap_swapchain(swapchain, &config), + format: config.format, + size: config.extent, + mode: config.present_mode, + }); + Ok(()) + } + + unsafe fn unconfigure_swapchain(&mut self, device: &Device) { + if let Some(mut present) = self.presentation.take() { + let _ = present.swapchain.wait(winbase::INFINITE); + let _ = device.wait_idle(); //TODO: this shouldn't be needed, + // but it complains that the queue is still used otherwise + let inner = present.swapchain.release_resources(); + inner.destroy(); + } + } + + unsafe fn acquire_image( + &mut self, + timeout_ns: u64, + ) -> Result<(SwapchainImage, Option<w::Suboptimal>), w::AcquireError> { + let present = self.presentation.as_mut().unwrap(); + let sc = &mut present.swapchain; + + sc.wait((timeout_ns / 1_000_000) as u32)?; + + let base_index = sc.inner.GetCurrentBackBufferIndex() as usize; + let index = (base_index + sc.acquired_count) % sc.resources.len(); + sc.acquired_count += 1; + let resource = sc.resources[index]; + + let kind = i::Kind::D2(present.size.width, present.size.height, 1, 1); + let base_format = present.format.base_format(); + let dxgi_format = conv::map_format(present.format).unwrap(); + let rtv = sc.rtv_heap.at(index as _, 0).cpu; + + let descriptor = d3d12::D3D12_RESOURCE_DESC { + Dimension: d3d12::D3D12_RESOURCE_DIMENSION_TEXTURE2D, + Alignment: 0, + Width: present.size.width as _, + Height: present.size.height as _, + DepthOrArraySize: 1, + MipLevels: 1, + Format: dxgi_format, + SampleDesc: dxgitype::DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Layout: d3d12::D3D12_TEXTURE_LAYOUT_UNKNOWN, + Flags: d3d12::D3D12_RESOURCE_FLAG_NONE, //TODO? + }; + + let image = r::ImageBound { + resource, + place: r::Place::Swapchain {}, + surface_type: base_format.0, + kind, + mip_levels: 1, + usage: sc.usage, + default_view_format: None, + view_caps: i::ViewCapabilities::empty(), + descriptor, + clear_cv: Vec::new(), //TODO + clear_dv: Vec::new(), + clear_sv: Vec::new(), + requirements: hal::memory::Requirements { + size: 0, + alignment: 1, + type_mask: 0, + }, + }; + + let swapchain_image = SwapchainImage { + index: index as _, + image: r::Image::Bound(image), + view: r::ImageView { + resource, + handle_srv: None, + handle_rtv: r::RenderTargetHandle::Swapchain(rtv), + handle_uav: None, + handle_dsv: None, + dxgi_format, + num_levels: 1, + mip_levels: (0, 1), + layers: (0, 1), + kind, + }, + }; + + Ok((swapchain_image, None)) + } +} + +#[derive(Debug)] +pub struct Swapchain { + pub(crate) inner: native::WeakPtr<dxgi1_4::IDXGISwapChain3>, + #[allow(dead_code)] + pub(crate) rtv_heap: r::DescriptorHeap, + // need to associate raw image pointers with the swapchain so they can be properly released + // when the swapchain is destroyed + pub(crate) resources: Vec<native::Resource>, + pub(crate) waitable: HANDLE, + pub(crate) usage: i::Usage, + pub(crate) acquired_count: usize, +} + +impl Swapchain { + pub(crate) unsafe fn release_resources(self) -> native::WeakPtr<dxgi1_4::IDXGISwapChain3> { + for resource in &self.resources { + resource.destroy(); + } + self.rtv_heap.destroy(); + self.inner + } + + pub(crate) fn wait(&mut self, timeout_ms: u32) -> Result<(), w::AcquireError> { + match unsafe { synchapi::WaitForSingleObject(self.waitable, timeout_ms) } { + winbase::WAIT_ABANDONED | winbase::WAIT_FAILED => { + Err(w::AcquireError::DeviceLost(hal::device::DeviceLost)) + } + winbase::WAIT_OBJECT_0 => Ok(()), + winerror::WAIT_TIMEOUT => Err(w::AcquireError::Timeout), + hr => panic!("Unexpected wait status 0x{:X}", hr), + } + } +} + +unsafe impl Send for Swapchain {} +unsafe impl Sync for Swapchain {} |