slint/internal/renderers/skia/d3d_surface.rs
Simon Hausmann 01d2efce4e Move the Skia renderer into a separate crate
The crate is an internal crate until the API has been polished and
documented, after which we can call slint-renderer-skia for example.

This also duplicates a little bit of the glutin setup code, because
that would otherwise have to go into another shared crate.
2022-12-09 17:04:54 +01:00

437 lines
14 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
use std::cell::RefCell;
use winapi::{
shared::{dxgi, dxgi1_2, dxgi1_3, dxgi1_4, dxgiformat},
shared::{
dxgitype,
guiddef::GUID,
winerror::{HRESULT, S_OK},
},
um::{d3d12, d3dcommon},
Interface,
};
use wio::com::ComPtr;
fn resolve_interface<T: Interface>(
f: impl FnOnce(&GUID, &mut *mut std::ffi::c_void) -> HRESULT,
) -> Result<ComPtr<T>, HRESULT> {
let mut ptr: *mut std::ffi::c_void = std::ptr::null_mut();
let r = f(&T::uuidof(), &mut ptr);
if r == S_OK {
Ok(unsafe { ComPtr::from_raw(ptr as *mut T) })
} else {
Err(r)
}
}
fn resolve_specific<T: Interface>(
f: impl FnOnce(&mut *mut T) -> HRESULT,
) -> Result<ComPtr<T>, HRESULT> {
let mut ptr: *mut T = std::ptr::null_mut();
let r = f(&mut ptr);
if r == S_OK {
Ok(unsafe { ComPtr::from_raw(ptr) })
} else {
Err(r)
}
}
trait ExpectOk<T> {
fn expect_ok(self, msg: &str) -> T;
}
impl<T> ExpectOk<T> for Result<T, HRESULT> {
fn expect_ok(self, msg: &str) -> T {
match self {
Ok(r) => r,
Err(hr) => panic!("{} failed. {:x}", msg, hr),
}
}
}
const DEFAULT_SURFACE_FORMAT: dxgiformat::DXGI_FORMAT = dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM;
struct SwapChain {
command_queue: ComPtr<d3d12::ID3D12CommandQueue>,
swap_chain: ComPtr<dxgi1_4::IDXGISwapChain3>,
surfaces: Option<[skia_safe::Surface; 2]>,
current_buffer_index: usize,
fence: ComPtr<d3d12::ID3D12Fence>,
fence_values: [u64; 2],
fence_event: *mut std::ffi::c_void,
gr_context: skia_safe::gpu::DirectContext,
}
impl SwapChain {
fn new(
command_queue: ComPtr<d3d12::ID3D12CommandQueue>,
device: &ComPtr<d3d12::ID3D12Device>,
mut gr_context: skia_safe::gpu::DirectContext,
window: &dyn raw_window_handle::HasRawWindowHandle,
size: PhysicalWindowSize,
dxgi_factory: &ComPtr<dxgi1_4::IDXGIFactory4>,
) -> Self {
let swap_chain_desc = dxgi1_2::DXGI_SWAP_CHAIN_DESC1 {
Width: size.width,
Height: size.height,
Format: DEFAULT_SURFACE_FORMAT,
BufferCount: 2,
BufferUsage: dxgitype::DXGI_USAGE_RENDER_TARGET_OUTPUT,
SwapEffect: dxgi::DXGI_SWAP_EFFECT_FLIP_DISCARD,
SampleDesc: dxgitype::DXGI_SAMPLE_DESC { Count: 1, ..Default::default() },
..Default::default()
};
let hwnd = match window.raw_window_handle() {
raw_window_handle::RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle {
hwnd,
..
}) => hwnd,
_ => panic!("Metal surface is only supported with Win32WindowHandle"),
};
let swap_chain1 = resolve_specific(|ptr| unsafe {
dxgi_factory.CreateSwapChainForHwnd(
command_queue.as_raw() as _,
hwnd as _,
&swap_chain_desc,
std::ptr::null(),
std::ptr::null_mut(),
ptr,
)
})
.expect_ok("unable to create D3D swap chain");
let swap_chain: ComPtr<dxgi1_4::IDXGISwapChain3> =
swap_chain1.cast().expect_ok("unable to cast swap chain 1 to v3");
let fence = resolve_interface(|iid, ptr| unsafe {
device.CreateFence(0, d3d12::D3D12_FENCE_FLAG_NONE, iid, ptr)
})
.expect_ok("unable to create D3D12 fence");
let fence_values = [0, 0];
let fence_event = unsafe {
winapi::um::synchapi::CreateEventW(std::ptr::null_mut(), 0, 0, std::ptr::null())
};
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 _,
));
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,
) -> T {
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();
surface.flush_with_access_info(skia_safe::surface::BackendSurfaceAccess::Present, &info);
drop(surface);
self.gr_context.submit(None);
let present_result = unsafe { self.swap_chain.Present(1, 0) };
if present_result != S_OK {
panic!("Error presenting d3d swap chain: {:x}", present_result);
}
let signal_result = unsafe {
self.command_queue
.Signal(self.fence.as_raw() as _, self.fence_values[self.current_buffer_index])
};
if signal_result != S_OK {
panic!(
"error setting up completion signal for d3d12 command queue: {:x}",
signal_result
)
}
result
}
fn create_surfaces(
swap_chain: &ComPtr<dxgi1_4::IDXGISwapChain3>,
gr_context: &mut skia_safe::gpu::DirectContext,
width: i32,
height: i32,
) -> [skia_safe::Surface; 2] {
let mut make_surface = |buffer_index| {
let buffer: ComPtr<d3d12::ID3D12Resource> = resolve_interface(|iid, ptr| unsafe {
swap_chain.GetBuffer(buffer_index, iid, ptr)
})
.expect_ok("unable to retrieve swap chain back buffer");
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::D3D12_RESOURCE_STATE_PRESENT,
format: DEFAULT_SURFACE_FORMAT,
sample_count: 1,
level_count: 1,
sample_quality_pattern: dxgitype::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::Surface::from_backend_render_target(
gr_context,
&backend_texture,
skia_safe::gpu::SurfaceOrigin::TopLeft,
skia_safe::ColorType::RGBA8888,
None,
None,
)
.expect("unable to create skia backend render target")
};
[make_surface(0), make_surface(1)]
}
fn resize(&mut self, width: u32, height: u32) {
self.gr_context.flush_submit_and_sync_cpu();
self.wait_for_buffer(0);
self.wait_for_buffer(1);
drop(self.surfaces.take());
unsafe {
let resize_result =
self.swap_chain.ResizeBuffers(0, width, height, DEFAULT_SURFACE_FORMAT, 0);
if resize_result != S_OK {
panic!("Error resizing swap chain buffers: {:x}", resize_result);
}
}
self.surfaces = Some(Self::create_surfaces(
&self.swap_chain,
&mut self.gr_context,
width as i32,
height as i32,
));
}
fn wait_for_buffer(&mut self, buffer_index: usize) {
if unsafe { self.fence.GetCompletedValue() } < self.fence_values[buffer_index] {
let set_completion_result = unsafe {
self.fence.SetEventOnCompletion(self.fence_values[buffer_index], self.fence_event)
};
if set_completion_result != S_OK {
panic!(
"error setting event on command queue completion: {:x}",
set_completion_result
)
}
unsafe {
winapi::um::synchapi::WaitForSingleObjectEx(
self.fence_event,
winapi::um::winbase::INFINITE,
0,
);
}
}
}
}
pub struct D3DSurface {
swap_chain: RefCell<SwapChain>,
}
impl super::Surface for D3DSurface {
const SUPPORTS_GRAPHICS_API: bool = false;
fn new(
window: &dyn raw_window_handle::HasRawWindowHandle,
_display: &dyn raw_window_handle::HasRawDisplayHandle,
size: PhysicalWindowSize,
) -> Self {
let factory_flags = 0;
/*
let factory_flags = dxgi1_3::DXGI_CREATE_FACTORY_DEBUG;
{
let maybe_debug_interface: Result<
ComPtr<winapi::um::d3d12sdklayers::ID3D12Debug>,
HRESULT,
> = resolve_interface(|iid, ptr| unsafe { d3d12::D3D12GetDebugInterface(iid, ptr) });
if let Ok(debug) = maybe_debug_interface {
unsafe { debug.EnableDebugLayer() };
}
}
*/
let dxgi_factory: ComPtr<dxgi1_4::IDXGIFactory4> = resolve_interface(|iid, ptr| unsafe {
dxgi1_3::CreateDXGIFactory2(factory_flags, iid, ptr)
})
.expect("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 resolve_specific(|ptr| unsafe { dxgi_factory.EnumAdapters1(i, ptr) }) {
Ok(adapter) => adapter,
Err(_) => break None,
};
let mut desc = dxgi::DXGI_ADAPTER_DESC1::default();
unsafe { adapter.GetDesc1(&mut desc) };
let adapter_is_warp =
(desc.Flags & dxgi::DXGI_ADAPTER_FLAG_SOFTWARE) != dxgi::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 {
d3d12::D3D12CreateDevice(
adapter.as_raw() as _,
d3dcommon::D3D_FEATURE_LEVEL_11_0,
&d3d12::ID3D12Device::uuidof(),
std::ptr::null_mut(),
)
} == S_OK
{
break Some(adapter);
}
i += 1;
}
};
let adapter = adapter.unwrap_or_else(|| {
let software_adapter_index =
software_adapter_index.expect("unable to locate D3D software adapter");
resolve_specific(|ptr| unsafe {
dxgi_factory.EnumAdapters1(software_adapter_index, ptr)
})
.expect("unable to create D3D software adapter")
});
let device: ComPtr<d3d12::ID3D12Device> = resolve_interface(|iid, ptr| unsafe {
d3d12::D3D12CreateDevice(
adapter.as_raw() as _,
d3dcommon::D3D_FEATURE_LEVEL_11_0,
iid,
ptr,
)
})
.expect_ok("error calling D3D12CreateDevice");
let queue: ComPtr<d3d12::ID3D12CommandQueue> = {
let desc = d3d12::D3D12_COMMAND_QUEUE_DESC {
Type: d3d12::D3D12_COMMAND_LIST_TYPE_DIRECT,
Priority: d3d12::D3D12_COMMAND_QUEUE_PRIORITY_NORMAL as _,
Flags: d3d12::D3D12_COMMAND_QUEUE_FLAG_NONE,
NodeMask: 0,
};
resolve_interface(|iid, ptr| unsafe { device.CreateCommandQueue(&desc, iid, ptr) })
.expect("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) }
.expect("unable to create Skia D3D DirectContext");
let swap_chain =
RefCell::new(SwapChain::new(queue, &device, gr_context, &window, size, &dxgi_factory));
Self { swap_chain }
}
fn name(&self) -> &'static str {
"d3d"
}
fn with_graphics_api(&self, _cb: impl FnOnce(i_slint_core::api::GraphicsAPI<'_>)) {
unimplemented!()
}
fn resize_event(&self, size: PhysicalWindowSize) {
self.swap_chain.borrow_mut().resize(size.width, size.height);
}
fn render(
&self,
_size: PhysicalWindowSize,
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
) {
self.swap_chain
.borrow_mut()
.render_and_present(|surface, gr_context| callback(surface.canvas(), gr_context))
}
fn bits_per_pixel(&self) -> u8 {
let mut desc = dxgi::DXGI_SWAP_CHAIN_DESC::default();
unsafe { self.swap_chain.borrow().swap_chain.GetDesc(&mut desc) };
match desc.BufferDesc.Format {
DEFAULT_SURFACE_FORMAT => 32,
_ => 0, // Not mapped yet
}
}
}