From 4aaec3267a16f7dd15352791fcafa1c5ccadbab7 Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 4 Sep 2024 11:10:15 +0700 Subject: [PATCH] feat: add vulkan driver --- Cargo.toml | 60 +++- crates/engine/src/skia.rs | 97 ++----- crates/renderer/Cargo.toml | 5 +- crates/renderer/src/drivers/mod.rs | 21 +- crates/renderer/src/drivers/vulkan.rs | 403 ++++++++++++++++++++++++++ 5 files changed, 489 insertions(+), 97 deletions(-) create mode 100644 crates/renderer/src/drivers/vulkan.rs diff --git a/Cargo.toml b/Cargo.toml index 848b75c32..785e68154 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,23 @@ version = "0.0.0" edition = "2021" [workspace] -members = ["crates/renderer", "crates/state", "crates/freya", "crates/elements", "crates/components", "crates/hooks", "crates/common", "crates/core", "crates/testing", "crates/devtools", "crates/torin", "crates/engine", "./examples/installer", "crates/native-core", "crates/native-core-macro"] +members = [ + "crates/renderer", + "crates/state", + "crates/freya", + "crates/elements", + "crates/components", + "crates/hooks", + "crates/common", + "crates/core", + "crates/testing", + "crates/devtools", + "crates/torin", + "crates/engine", + "./examples/installer", + "crates/native-core", + "crates/native-core-macro", +] [features] tracing-subscriber = ["freya/tracing-subscriber"] @@ -42,31 +58,55 @@ torin = { path = "crates/torin", version = "0.2" } freya-native-core-macro = { path = "crates/native-core-macro", version = "0.2" } freya-native-core = { path = "crates/native-core", version = "0.2" } -dioxus = { version = "0.5", default-features = false, features = ["macro", "signals", "hooks"]} +dioxus = { version = "0.5", default-features = false, features = [ + "macro", + "signals", + "hooks", +] } dioxus-rsx = { version = "0.5", features = ["hot_reload"] } dioxus-core-macro = { version = "0.5" } dioxus-hooks = { version = "0.5" } dioxus-signals = { version = "0.5" } dioxus-core = { version = "0.5" } -dioxus-hot-reload = { version = "0.5", features = ["file_watcher"], default-features = false } +dioxus-hot-reload = { version = "0.5", features = [ + "file_watcher", +], default-features = false } dioxus-router = { version = "0.5", default-features = false } -dioxus-sdk = { version = "0.5", features = ["clipboard"]} +dioxus-sdk = { version = "0.5", features = ["clipboard"] } -skia-safe = { version = "0.75.0", features = ["gl", "textlayout", "svg"] } +skia-safe = { version = "0.75.0", features = [ + "gl", + "vulkan", + "egl", + "x11", + "wayland", + "textlayout", + "svg", + "webp", +] } gl = "0.14.0" glutin = "0.32.0" glutin-winit = "0.5.0" raw-window-handle = "0.6.0" winit = "0.30.0" -tokio = { version = "1.33.0", features = ["sync", "rt-multi-thread", "time", "macros"] } -accesskit = { version = "0.16.0", features = ["serde"]} +tokio = { version = "1.33.0", features = [ + "sync", + "rt-multi-thread", + "time", + "macros", +] } +accesskit = { version = "0.16.0", features = ["serde"] } accesskit_winit = "0.22.0" -shipyard = { version = "0.6.2", features = ["proc", "std", "parallel"], default-features = false } +shipyard = { version = "0.6.2", features = [ + "proc", + "std", + "parallel", +], default-features = false } smallvec = "1.13.1" euclid = "0.22.9" -uuid = { version = "1.4.1", features = ["v4"]} +uuid = { version = "1.4.1", features = ["v4"] } futures-util = "0.3.30" futures-task = "0.3.30" tracing = "0.1" @@ -75,7 +115,7 @@ rustc-hash = "2.0.0" [dev-dependencies] skia-safe = { workspace = true } -tokio = { workspace = true, features = ["fs"]} +tokio = { workspace = true, features = ["fs"] } dioxus = { workspace = true } freya = { workspace = true } freya-core = { workspace = true } diff --git a/crates/engine/src/skia.rs b/crates/engine/src/skia.rs index f3524415a..06af407db 100644 --- a/crates/engine/src/skia.rs +++ b/crates/engine/src/skia.rs @@ -1,27 +1,18 @@ pub use skia_safe::{ - font_style::{ - Slant, - Weight, - Width, - }, + font_style::{Slant, Weight, Width}, gpu::{ - backend_render_targets, - direct_contexts, - gl::{ - Format, - FramebufferInfo, - Interface, - }, + backend_render_targets, direct_contexts, + gl::{Format, FramebufferInfo, Interface}, surfaces::wrap_backend_render_target, - BackendRenderTarget, - DirectContext, - RecordingContext, - SurfaceOrigin, + vk::{ + Alloc, BackendContext, Format as VkFormat, GetProcOf, ImageInfo as VkImageInfo, + ImageLayout, ImageTiling, + }, + BackendRenderTarget, DirectContext, RecordingContext, SurfaceOrigin, }, gradient_shader::GradientShaderColors, graphics::{ - set_resource_cache_single_allocation_byte_limit, - set_resource_cache_total_bytes_limit, + set_resource_cache_single_allocation_byte_limit, set_resource_cache_total_bytes_limit, }, path::ArcSize, rrect::Corner, @@ -29,66 +20,14 @@ pub use skia_safe::{ surfaces::raster_n32_premul, svg, textlayout::{ - paragraph::GlyphClusterInfo, - Decoration, - FontCollection, - FontFeature, - LineMetrics, - Paragraph, - ParagraphBuilder, - ParagraphStyle, - PlaceholderStyle, - PositionWithAffinity, - RectHeightStyle, - RectWidthStyle, - StrutStyle, - TextAlign, - TextBaseline, - TextBox, - TextDecoration, - TextDecorationStyle, - TextDirection, - TextHeightBehavior, - TextIndex, - TextRange, - TextShadow, - TextStyle, - TypefaceFontProvider, + paragraph::GlyphClusterInfo, Decoration, FontCollection, FontFeature, LineMetrics, + Paragraph, ParagraphBuilder, ParagraphStyle, PlaceholderStyle, PositionWithAffinity, + RectHeightStyle, RectWidthStyle, StrutStyle, TextAlign, TextBaseline, TextBox, + TextDecoration, TextDecorationStyle, TextDirection, TextHeightBehavior, TextIndex, + TextRange, TextShadow, TextStyle, TypefaceFontProvider, }, - Bitmap, - BlurStyle, - Canvas, - ClipOp, - Color, - ColorSpace, - ColorType, - Data, - EncodedImageFormat, - FilterMode, - FontArguments, - FontMgr, - FontStyle, - IPoint, - IRect, - Image, - ImageInfo, - MaskFilter, - Matrix, - Paint, - PaintStyle, - Path, - PathDirection, - Point, - RRect, - Rect, - RuntimeEffect, - SamplingOptions, - Shader, - Surface, - TileMode, - Typeface, - HSV, - M44, - RGB, - V3, + Bitmap, BlurStyle, Canvas, ClipOp, Color, ColorSpace, ColorType, Data, EncodedImageFormat, + FilterMode, FontArguments, FontMgr, FontStyle, IPoint, IRect, Image, ImageInfo, MaskFilter, + Matrix, Paint, PaintStyle, Path, PathDirection, Point, RRect, Rect, RuntimeEffect, + SamplingOptions, Shader, Surface, TileMode, Typeface, HSV, M44, RGB, V3, }; diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index ec243f9fe..7f5ea543b 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -40,10 +40,13 @@ winit = { workspace = true } accesskit = { workspace = true } accesskit_winit = { workspace = true } tracing = { workspace = true } -futures-task ={ workspace = true } +futures-task = { workspace = true } futures-util = { workspace = true } itertools = "0.13.0" uuid = { workspace = true } image = "0.25.0" pin-utils = "0.1.0" + +ash = { version = "^0.37.2" } +vulkano = { version = "^0.34.0" } diff --git a/crates/renderer/src/drivers/mod.rs b/crates/renderer/src/drivers/mod.rs index b0accda04..7bfb214d1 100644 --- a/crates/renderer/src/drivers/mod.rs +++ b/crates/renderer/src/drivers/mod.rs @@ -1,21 +1,22 @@ mod gl; +mod vulkan; -use freya_engine::prelude::Surface as SkiaSurface; pub use gl::*; + +use freya_engine::prelude::Surface as SkiaSurface; use glutin::surface::GlSurface; +use vulkan::VulkanDriver; use winit::{ dpi::PhysicalSize, event_loop::ActiveEventLoop, - window::{ - Window, - WindowAttributes, - }, + window::{Window, WindowAttributes}, }; use crate::LaunchConfig; pub enum GraphicsDriver { OpenGl(OpenGLDriver), + Vulkan(VulkanDriver), } impl GraphicsDriver { @@ -24,13 +25,15 @@ impl GraphicsDriver { window_attributes: WindowAttributes, config: &LaunchConfig, ) -> (Self, Window, SkiaSurface) { - let (driver, window, surface) = OpenGLDriver::new(event_loop, window_attributes, config); - (Self::OpenGl(driver), window, surface) + let (driver, window, surface) = VulkanDriver::new(event_loop, window_attributes, config); + + (Self::Vulkan(driver), window, surface) } pub fn make_current(&mut self) { match self { Self::OpenGl(gl) => gl.make_current(), + Self::Vulkan(_) => {} } } @@ -40,12 +43,16 @@ impl GraphicsDriver { gl.gr_context.flush_and_submit(); gl.gl_surface.swap_buffers(&gl.gl_context).unwrap(); } + Self::Vulkan(vk) => { + vk.gr_context.flush_and_submit(); + } } } pub fn resize(&mut self, size: PhysicalSize) -> (SkiaSurface, SkiaSurface) { match self { Self::OpenGl(gl) => gl.resize(size), + Self::Vulkan(vk) => vk.resize(size), } } } diff --git a/crates/renderer/src/drivers/vulkan.rs b/crates/renderer/src/drivers/vulkan.rs new file mode 100644 index 000000000..88bbdf9b7 --- /dev/null +++ b/crates/renderer/src/drivers/vulkan.rs @@ -0,0 +1,403 @@ +use freya_engine::prelude::{ + backend_render_targets, direct_contexts, wrap_backend_render_target, Alloc, BackendContext, + Canvas, ColorType, DirectContext, GetProcOf, ImageLayout, ImageTiling, Surface as SkiaSurface, + SurfaceOrigin, VkFormat, VkImageInfo, +}; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; +use std::{ + cell::{Cell, RefCell}, + sync::Arc, +}; +use vulkano::{ + device::{ + physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, Queue, + QueueCreateInfo, QueueFlags, + }, + image::{view::ImageView, Image, ImageUsage}, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + swapchain::{Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo}, + sync::{self, GpuFuture}, + Handle, Validated, VulkanError, VulkanLibrary, VulkanObject, +}; +use winit::{ + dpi::PhysicalSize, + event_loop::ActiveEventLoop, + window::{Window, WindowAttributes}, +}; + +use crate::{size::WinitSize, LaunchConfig}; + +/// Graphics driver using Vulkan. +pub struct VulkanDriver { + pub(crate) gr_context: DirectContext, + pub(crate) recreate_swapchain: Cell, + pub(crate) device: Arc, + pub(crate) previous_frame_end: RefCell>>, + pub(crate) queue: Arc, + pub(crate) swapchain: RefCell>, + pub(crate) swapchain_images: RefCell>>, + pub(crate) swapchain_image_views: RefCell>>, +} + +impl VulkanDriver { + pub fn new( + event_loop: &ActiveEventLoop, + window_attributes: WindowAttributes, + _config: &LaunchConfig, + ) -> (Self, Window, SkiaSurface) { + let window = event_loop + .create_window(window_attributes) + .expect("Could not create window with Vulkan context"); + + let library = VulkanLibrary::new().expect("Could not create Vulkan library"); + let required_extensions = InstanceExtensions { + khr_surface: true, + mvk_macos_surface: true, + ext_metal_surface: true, + khr_wayland_surface: true, + khr_xlib_surface: true, + khr_xcb_surface: true, + khr_win32_surface: true, + khr_get_surface_capabilities2: true, + khr_get_physical_device_properties2: true, + ..InstanceExtensions::empty() + } + .intersection(library.supported_extensions()); + + let instance = Instance::new( + library.clone(), + InstanceCreateInfo { + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: required_extensions, + ..Default::default() + }, + ) + .expect("Could not create Vulkan instance"); + + let window_handle = window.window_handle().unwrap(); + let display_handle = window.display_handle().unwrap(); + + let surface = create_surface(&instance, window_handle, display_handle) + .expect("Could not create Vulkan surface"); + + let device_extensions = DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::empty() + }; + + let (physical_device, queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| p.supported_extensions().contains(&device_extensions)) + .filter_map(|p| { + p.queue_family_properties() + .iter() + .enumerate() + .position(|(i, q)| { + q.queue_flags.intersects(QueueFlags::GRAPHICS) + && p.surface_support(i as u32, &surface).unwrap_or(false) + }) + .map(|i| (p, i as u32)) + }) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) + .unwrap(); + + let (device, mut queues) = Device::new( + physical_device.clone(), + DeviceCreateInfo { + enabled_extensions: DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::empty() + }, + queue_create_infos: vec![QueueCreateInfo { + queue_family_index, + ..Default::default() + }], + ..Default::default() + }, + ) + .unwrap(); + + let size = window.inner_size(); + let queue = queues.next().unwrap(); + + let (swapchain, swapchain_images) = { + let surface_capabilities = device + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + let image_format = vulkano::format::Format::B8G8R8A8_UNORM; + + Swapchain::new( + device.clone(), + surface.clone(), + SwapchainCreateInfo { + min_image_count: surface_capabilities.min_image_count, + image_format, + image_extent: [size.width, size.height], + image_usage: ImageUsage::COLOR_ATTACHMENT, + composite_alpha: surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(), + ..Default::default() + }, + ) + .unwrap() + }; + + let mut swapchain_image_views = Vec::with_capacity(swapchain_images.len()); + + for image in &swapchain_images { + swapchain_image_views.push(ImageView::new_default(image.clone()).unwrap()); + } + + let instance = physical_device.instance(); + let library = instance.library(); + + let get_proc = |of| unsafe { + let result = match of { + GetProcOf::Instance(instance, name) => { + library.get_instance_proc_addr(ash::vk::Instance::from_raw(instance as _), name) + } + GetProcOf::Device(device, name) => (instance.fns().v1_0.get_device_proc_addr)( + ash::vk::Device::from_raw(device as _), + name, + ), + }; + + match result { + Some(f) => f as _, + None => { + //println!("resolve of {} failed", of.name().to_str().unwrap()); + core::ptr::null() + } + } + }; + + let backend_context = unsafe { + BackendContext::new( + instance.handle().as_raw() as _, + physical_device.handle().as_raw() as _, + device.handle().as_raw() as _, + (queue.handle().as_raw() as _, queue.id_within_family() as _), + &get_proc, + ) + }; + + let previous_frame_end = RefCell::new(Some(sync::now(device.clone()).boxed())); + let image_info = VkImageInfo::default(); + let render_target = backend_render_targets::make_vk(size.to_skia(), &image_info); + + let mut gr_context = direct_contexts::make_vulkan(&backend_context, None) + .expect("Could not create direct context"); + + let skia_surface = wrap_backend_render_target( + &mut gr_context, + &render_target, + SurfaceOrigin::TopLeft, + ColorType::BGRA8888, + None, + None, + ) + .expect("Could not create skia surface"); + + let driver = VulkanDriver { + gr_context, + recreate_swapchain: Cell::new(false), + device, + previous_frame_end, + queue, + swapchain: RefCell::new(swapchain), + swapchain_images: RefCell::new(swapchain_images), + swapchain_image_views: RefCell::new(swapchain_image_views), + }; + + (driver, window, skia_surface) + } + + pub fn resize(&mut self, size: PhysicalSize) -> (SkiaSurface, SkiaSurface) { + let gr_context = &mut self.gr_context; + + let device = self.device.clone(); + + self.previous_frame_end + .borrow_mut() + .as_mut() + .unwrap() + .cleanup_finished(); + + let swapchain = self.swapchain.borrow().clone(); + + let (image_index, suboptimal, acquire_future) = + vulkano::swapchain::acquire_next_image(swapchain.clone(), None).unwrap(); + + if suboptimal { + self.recreate_swapchain.set(true); + } + + let width = swapchain.image_extent()[0]; + let width: i32 = width.try_into().unwrap(); + let height = swapchain.image_extent()[1]; + let height: i32 = height.try_into().unwrap(); + + let image_view = self.swapchain_image_views.borrow()[image_index as usize].clone(); + let image_object = image_view.image(); + + let (vk_format, color_type) = (VkFormat::B8G8R8A8_UNORM, ColorType::BGRA8888); + + let alloc = Alloc::default(); + let image_info = &unsafe { + VkImageInfo::new( + image_object.handle().as_raw() as _, + alloc, + ImageTiling::OPTIMAL, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + vk_format, + 1, + None, + None, + None, + None, + ) + }; + + let render_target = &backend_render_targets::make_vk((width, height), image_info); + + let mut surface = wrap_backend_render_target( + gr_context, + render_target, + SurfaceOrigin::TopLeft, + color_type, + None, + None, + ) + .expect("Could not create skia surface"); + + let dirty_surface = surface.new_surface_with_dimensions(size.to_skia()).unwrap(); + + gr_context.submit(None); + + let future = self + .previous_frame_end + .borrow_mut() + .take() + .unwrap() + .join(acquire_future) + .then_swapchain_present( + self.queue.clone(), + SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index), + ) + .then_signal_fence_and_flush(); + + match future.map_err(Validated::unwrap) { + Ok(future) => { + *self.previous_frame_end.borrow_mut() = Some(future.boxed()); + } + Err(VulkanError::OutOfDate) => { + self.recreate_swapchain.set(true); + *self.previous_frame_end.borrow_mut() = Some(sync::now(device.clone()).boxed()); + } + Err(_) => { + *self.previous_frame_end.borrow_mut() = Some(sync::now(device.clone()).boxed()); + } + } + + (surface, dirty_surface) + } +} + +fn create_surface( + instance: &Arc, + window_handle: raw_window_handle::WindowHandle<'_>, + display_handle: raw_window_handle::DisplayHandle<'_>, +) -> Result, vulkano::Validated> { + match (window_handle.as_raw(), display_handle.as_raw()) { + #[cfg(target_os = "macos")] + ( + raw_window_handle::RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle { + ns_view, + .. + }), + _, + ) => unsafe { + use cocoa::{appkit::NSView, base::id as cocoa_id}; + use objc::runtime::YES; + + let layer = metal::MetalLayer::new(); + layer.set_opaque(false); + layer.set_presents_with_transaction(false); + let view = ns_view.as_ptr() as cocoa_id; + view.setWantsLayer(YES); + view.setLayer(layer.as_ref() as *const _ as _); + Surface::from_metal(instance.clone(), layer.as_ref(), None) + }, + ( + raw_window_handle::RawWindowHandle::Xlib(raw_window_handle::XlibWindowHandle { + window, + .. + }), + raw_window_handle::RawDisplayHandle::Xlib(display), + ) => unsafe { + Surface::from_xlib( + instance.clone(), + display.display.unwrap().as_ptr(), + window, + None, + ) + }, + ( + raw_window_handle::RawWindowHandle::Xcb(raw_window_handle::XcbWindowHandle { + window, + .. + }), + raw_window_handle::RawDisplayHandle::Xcb(raw_window_handle::XcbDisplayHandle { + connection, + .. + }), + ) => unsafe { + Surface::from_xcb( + instance.clone(), + connection.unwrap().as_ptr(), + window.get(), + None, + ) + }, + ( + raw_window_handle::RawWindowHandle::Wayland(raw_window_handle::WaylandWindowHandle { + surface, + .. + }), + raw_window_handle::RawDisplayHandle::Wayland(raw_window_handle::WaylandDisplayHandle { + display, + .. + }), + ) => unsafe { + Surface::from_wayland(instance.clone(), display.as_ptr(), surface.as_ptr(), None) + }, + ( + raw_window_handle::RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle { + hwnd, + hinstance, + .. + }), + _, + ) => unsafe { + Surface::from_win32( + instance.clone(), + hinstance.unwrap().get() as *const std::ffi::c_void, + hwnd.get() as *const std::ffi::c_void, + None, + ) + }, + _ => unimplemented!(), + } +}