// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_core::api::PhysicalSize as PhysicalWindowSize; use i_slint_core::platform::PlatformError; use std::cell::RefCell; use std::rc::Rc; use windows::core::Interface; use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0; use windows::Win32::Graphics::Dxgi::Common::DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN; use windows::Win32::Foundation::{DXGI_STATUS_OCCLUDED, HANDLE, HWND, S_OK}; use windows::Win32::Graphics::Direct3D12::{ D3D12CreateDevice, ID3D12CommandQueue, ID3D12Device, ID3D12Fence, ID3D12Resource, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_QUEUE_DESC, D3D12_FENCE_FLAG_NONE, D3D12_RESOURCE_STATE_PRESENT, }; use windows::Win32::Graphics::Dxgi::{ Common::{DXGI_FORMAT, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SAMPLE_DESC}, CreateDXGIFactory2, IDXGIFactory4, IDXGISwapChain3, DXGI_ADAPTER_FLAG, DXGI_ADAPTER_FLAG_NONE, DXGI_ADAPTER_FLAG_SOFTWARE, DXGI_CREATE_FACTORY_FLAGS, DXGI_PRESENT, DXGI_SWAP_CHAIN_DESC1, DXGI_SWAP_CHAIN_FLAG, DXGI_SWAP_EFFECT_FLIP_DISCARD, DXGI_USAGE_RENDER_TARGET_OUTPUT, }; use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObjectEx, INFINITE}; trait MapToPlatformError { fn map_platform_error(self, msg: &str) -> std::result::Result; } impl MapToPlatformError for windows::core::Result { fn map_platform_error(self, msg: &str) -> std::result::Result { match self { Ok(r) => Ok(r), Err(hr) => Err(format!("{} failed. {:x}", msg, hr.code().0).into()), } } } const DEFAULT_SURFACE_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM; struct SwapChain { command_queue: ID3D12CommandQueue, swap_chain: IDXGISwapChain3, surfaces: Option<[skia_safe::Surface; 2]>, current_buffer_index: usize, fence: ID3D12Fence, fence_values: [u64; 2], fence_event: HANDLE, gr_context: skia_safe::gpu::DirectContext, } impl SwapChain { fn new( command_queue: ID3D12CommandQueue, device: &ID3D12Device, mut gr_context: skia_safe::gpu::DirectContext, window_handle: raw_window_handle::WindowHandle<'_>, size: PhysicalWindowSize, dxgi_factory: &IDXGIFactory4, ) -> Result { let swap_chain_desc = DXGI_SWAP_CHAIN_DESC1 { Width: size.width, Height: size.height, Format: DEFAULT_SURFACE_FORMAT, BufferCount: 2, BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD, SampleDesc: DXGI_SAMPLE_DESC { Count: 1, ..Default::default() }, ..Default::default() }; let hwnd = match window_handle.as_raw() { raw_window_handle::RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle { hwnd, .. }) => HWND(hwnd.get() as _), _ => { return Err(format!("Metal surface is only supported with Win32WindowHandle").into()) } }; let swap_chain1 = unsafe { dxgi_factory.CreateSwapChainForHwnd(&command_queue, hwnd, &swap_chain_desc, None, None) } .map_platform_error("unable to create D3D swap chain")?; let swap_chain: IDXGISwapChain3 = swap_chain1.cast().map_platform_error("unable to cast swap chain 1 to v3")?; let fence = unsafe { device.CreateFence(0, D3D12_FENCE_FLAG_NONE) } .map_platform_error("unable to create D3D12 fence")?; let fence_values = [0, 0]; let fence_event = unsafe { CreateEventW(None, false, false, None) } .map_platform_error("error creating fence event")?; let current_buffer_index = unsafe { swap_chain.GetCurrentBackBufferIndex() } as usize; let surfaces = Some(Self::create_surfaces( &swap_chain, &mut gr_context, size.width as _, size.height as _, )?); Ok(Self { command_queue, swap_chain, surfaces, current_buffer_index, fence, fence_event, fence_values, gr_context, }) } fn render_and_present( &mut self, callback: impl FnOnce(&mut skia_safe::Surface, &mut skia_safe::gpu::DirectContext) -> T, pre_present_callback: &RefCell>>, ) -> Result { let current_fence_value = self.fence_values[self.current_buffer_index]; self.current_buffer_index = unsafe { self.swap_chain.GetCurrentBackBufferIndex() } as usize; self.wait_for_buffer(self.current_buffer_index)?; self.fence_values[self.current_buffer_index] = current_fence_value + 1; let surface = &mut (*self.surfaces.as_mut().unwrap())[self.current_buffer_index]; let result = callback(surface, &mut self.gr_context); let info = Default::default(); self.gr_context.flush_surface_with_access( surface, skia_safe::surface::BackendSurfaceAccess::Present, &info, ); self.gr_context.submit(None); if let Some(pre_present_callback) = pre_present_callback.borrow_mut().as_mut() { pre_present_callback(); } let present_result = unsafe { self.swap_chain.Present(1, DXGI_PRESENT(0)) }; if present_result != S_OK && present_result != DXGI_STATUS_OCCLUDED { return Err(format!("Error presenting d3d swap chain: {:x}", present_result.0).into()); } unsafe { self.command_queue.Signal(&self.fence, self.fence_values[self.current_buffer_index]) } .map_platform_error("error setting up completion signal for d3d12 command queue")?; Ok(result) } fn create_surfaces( swap_chain: &IDXGISwapChain3, gr_context: &mut skia_safe::gpu::DirectContext, width: i32, height: i32, ) -> Result<[skia_safe::Surface; 2], PlatformError> { let mut make_surface = |buffer_index| { let buffer: ID3D12Resource = unsafe { swap_chain.GetBuffer(buffer_index) } .map_err(|hr| format!("unable to retrieve swap chain back buffer: {hr}"))?; debug_assert_eq!(unsafe { buffer.GetDesc().Width }, width as u64); debug_assert_eq!(unsafe { buffer.GetDesc().Height }, height as u32); let texture_info = skia_safe::gpu::d3d::TextureResourceInfo { resource: buffer, alloc: None, resource_state: D3D12_RESOURCE_STATE_PRESENT, format: DEFAULT_SURFACE_FORMAT, sample_count: 1, level_count: 1, sample_quality_pattern: DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN, protected: skia_safe::gpu::Protected::No, }; let backend_texture = skia_safe::gpu::BackendRenderTarget::new_d3d((width, height), &texture_info); skia_safe::gpu::surfaces::wrap_backend_render_target( gr_context, &backend_texture, skia_safe::gpu::SurfaceOrigin::TopLeft, skia_safe::ColorType::RGBA8888, None, None, ) .ok_or_else(|| format!("unable to create d3d skia backend render target")) }; Ok([make_surface(0)?, make_surface(1)?]) } fn resize( &mut self, width: u32, height: u32, ) -> Result<(), i_slint_core::platform::PlatformError> { self.gr_context.flush_submit_and_sync_cpu(); self.wait_for_buffer(0)?; self.wait_for_buffer(1)?; drop(self.surfaces.take()); unsafe { self.swap_chain.ResizeBuffers( 0, width, height, DEFAULT_SURFACE_FORMAT, DXGI_SWAP_CHAIN_FLAG(0), ) } .map_platform_error("Error resizing swap chain buffers")?; self.surfaces = Some(Self::create_surfaces( &self.swap_chain, &mut self.gr_context, width as i32, height as i32, )?); Ok(()) } fn wait_for_buffer(&mut self, buffer_index: usize) -> Result<(), PlatformError> { if unsafe { self.fence.GetCompletedValue() } < self.fence_values[buffer_index] { unsafe { self.fence.SetEventOnCompletion(self.fence_values[buffer_index], self.fence_event) } .map_platform_error("error setting event on command queue completion")?; unsafe { WaitForSingleObjectEx(self.fence_event, INFINITE, false); } } Ok(()) } } /// This surface renders into the given window using Direct 3D. The provided display /// argument is ignored, as it has no meaning on Windows. pub struct D3DSurface { swap_chain: RefCell, } impl super::Surface for D3DSurface { fn new( window_handle: Rc, _display_handle: Rc, size: PhysicalWindowSize, ) -> Result { let factory_flags = 0; /* let factory_flags = dxgi1_3::DXGI_CREATE_FACTORY_DEBUG; { let maybe_debug_interface: Result< ComPtr, HRESULT, > = resolve_interface(|iid, ptr| unsafe { d3d12::D3D12GetDebugInterface(iid, ptr) }); if let Ok(debug) = maybe_debug_interface { unsafe { debug.EnableDebugLayer() }; } } */ let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_FLAGS(factory_flags)) } .map_platform_error("unable to create DXGIFactory4")?; let mut software_adapter_index = None; let use_warp = std::env::var("SLINT_D3D_USE_WARP").is_ok(); let adapter = { let mut i = 0; loop { let adapter = match unsafe { dxgi_factory.EnumAdapters1(i) } { Ok(adapter) => adapter, Err(_) => break None, }; let Ok(desc) = (unsafe { adapter.GetDesc1() }) else { continue; }; let adapter_is_warp = (DXGI_ADAPTER_FLAG(desc.Flags as i32) & DXGI_ADAPTER_FLAG_SOFTWARE) != DXGI_ADAPTER_FLAG_NONE; if adapter_is_warp { if software_adapter_index.is_none() { software_adapter_index = Some(i); } if !use_warp { i += 1; // Select warp only if explicitly opted in via SLINT_D3D_USE_WARP continue; } // found warp adapter, requested warp? give it a try below } else if use_warp { // Don't select a non-warp adapter when warp is requested i += 1; continue; } // Check to see whether the adapter supports Direct3D 12, but don't // create the actual device yet. if unsafe { D3D12CreateDevice( &adapter, D3D_FEATURE_LEVEL_11_0, std::ptr::null_mut::>(), ) } .is_ok() { break Some(adapter); } i += 1; } }; let adapter = adapter.map_or_else( || { let software_adapter_index = software_adapter_index .ok_or_else(|| format!("unable to locate D3D software adapter"))?; unsafe { dxgi_factory.EnumAdapters1(software_adapter_index) } .map_err(|hr| format!("unable to create D3D software adapter: {hr}")) }, |adapter| Ok(adapter), )?; let mut device: Option = None; unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_11_0, &mut device) } .map_platform_error("error calling D3D12CreateDevice")?; let device = device.unwrap(); let queue: ID3D12CommandQueue = { let desc = D3D12_COMMAND_QUEUE_DESC { Type: D3D12_COMMAND_LIST_TYPE_DIRECT, ..Default::default() }; unsafe { device.CreateCommandQueue(&desc) } .map_platform_error("Creating command queue")? }; let backend_context = skia_safe::gpu::d3d::BackendContext { adapter, device: device.clone(), queue: queue.clone(), memory_allocator: None, protected_context: skia_safe::gpu::Protected::No, }; let gr_context = unsafe { skia_safe::gpu::DirectContext::new_d3d(&backend_context, None) } .ok_or_else(|| format!("unable to create Skia D3D DirectContext"))?; let window_handle = window_handle .window_handle() .map_err(|e| format!("error obtaining window handle for skia d3d renderer: {e}"))?; let swap_chain = RefCell::new(SwapChain::new( queue, &device, gr_context, window_handle, size, &dxgi_factory, )?); Ok(Self { swap_chain }) } fn name(&self) -> &'static str { "d3d" } fn resize_event( &self, size: PhysicalWindowSize, ) -> Result<(), i_slint_core::platform::PlatformError> { self.swap_chain.borrow_mut().resize(size.width, size.height) } fn render( &self, _size: PhysicalWindowSize, callback: &dyn Fn(&skia_safe::Canvas, Option<&mut skia_safe::gpu::DirectContext>), pre_present_callback: &RefCell>>, ) -> Result<(), i_slint_core::platform::PlatformError> { self.swap_chain.borrow_mut().render_and_present( |surface, gr_context| callback(surface.canvas(), Some(gr_context)), pre_present_callback, ) } fn bits_per_pixel(&self) -> Result { let desc = unsafe { self.swap_chain.borrow().swap_chain.GetDesc() } .map_platform_error("error getting swap chain description")?; Ok(match desc.BufferDesc.Format { DEFAULT_SURFACE_FORMAT => 32, fmt @ _ => { return Err( format!("Skia D3D Renderer: Unsupported buffer format found {fmt:?}").into() ) } }) } }