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<T> {
fn map_platform_error(self, msg: &str) -> std::result::Result<T, PlatformError>;
}
impl<T> MapToPlatformError<T> for windows::core::Result<T> {
fn map_platform_error(self, msg: &str) -> std::result::Result<T, PlatformError> {
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<Self, PlatformError> {
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<T>(
&mut self,
callback: impl FnOnce(&mut skia_safe::Surface, &mut skia_safe::gpu::DirectContext) -> T,
pre_present_callback: &RefCell<Option<Box<dyn FnMut()>>>,
) -> Result<T, PlatformError> {
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(())
}
}
pub struct D3DSurface {
swap_chain: RefCell<SwapChain>,
}
impl super::Surface for D3DSurface {
fn new(
window_handle: Rc<dyn raw_window_handle::HasWindowHandle>,
_display_handle: Rc<dyn raw_window_handle::HasDisplayHandle>,
size: PhysicalWindowSize,
) -> Result<Self, i_slint_core::platform::PlatformError> {
let factory_flags = 0;
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;
continue;
}
} else if use_warp {
i += 1;
continue;
}
if unsafe {
D3D12CreateDevice(
&adapter,
D3D_FEATURE_LEVEL_11_0,
std::ptr::null_mut::<Option<ID3D12Device>>(),
)
}
.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<ID3D12Device> = 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<Option<Box<dyn FnMut()>>>,
) -> 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<u8, i_slint_core::platform::PlatformError> {
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()
)
}
})
}
}