mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2025-12-20 16:00:08 +01:00
Merge branch 'vulkan/rust' into 'main'
Draft: Add infrastructure for Vulkan drivers written in Rust See merge request mesa/mesa!20298
This commit is contained in:
commit
e6135d2e28
8 changed files with 490 additions and 0 deletions
|
|
@ -86,6 +86,7 @@ vulkan_wsi_deps += idep_vulkan_wsi_defines
|
||||||
subdir('util')
|
subdir('util')
|
||||||
subdir('runtime')
|
subdir('runtime')
|
||||||
subdir('wsi')
|
subdir('wsi')
|
||||||
|
subdir('rust')
|
||||||
if with_vulkan_overlay_layer
|
if with_vulkan_overlay_layer
|
||||||
subdir('overlay-layer')
|
subdir('overlay-layer')
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
1
src/vulkan/rust/.rustfmt.toml
Normal file
1
src/vulkan/rust/.rustfmt.toml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
max_width = 80
|
||||||
335
src/vulkan/rust/boxed.rs
Normal file
335
src/vulkan/rust/boxed.rs
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2022 Collabora, Ltd.
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
use vulkan_h::*;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
use std::alloc::Layout;
|
||||||
|
use std::convert::{AsMut, AsRef};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::os::raw::c_void;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
trait VkAllocScoped {
|
||||||
|
unsafe fn alloc_scoped_raw(
|
||||||
|
&self,
|
||||||
|
layout: Layout,
|
||||||
|
scope: VkSystemAllocationScope,
|
||||||
|
) -> *mut u8;
|
||||||
|
|
||||||
|
unsafe fn alloc_scoped<T>(&self, scope: VkSystemAllocationScope) -> *mut T {
|
||||||
|
self.alloc_scoped_raw(Layout::new::<T>(), scope) as *mut T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait VkFree {
|
||||||
|
unsafe fn free_raw(&self, mem: *mut u8);
|
||||||
|
|
||||||
|
unsafe fn free<T>(&self, mem: *mut T) {
|
||||||
|
self.free_raw(mem as *mut u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkAllocScoped for VkAllocationCallbacks {
|
||||||
|
unsafe fn alloc_scoped_raw(
|
||||||
|
&self,
|
||||||
|
layout: Layout,
|
||||||
|
scope: VkSystemAllocationScope,
|
||||||
|
) -> *mut u8 {
|
||||||
|
self.pfnAllocation.unwrap()(
|
||||||
|
self.pUserData,
|
||||||
|
layout.align(),
|
||||||
|
layout.size(),
|
||||||
|
scope,
|
||||||
|
) as *mut u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkFree for VkAllocationCallbacks {
|
||||||
|
unsafe fn free_raw(&self, mem: *mut u8) {
|
||||||
|
self.pfnFree.unwrap()(self.pUserData, mem as *mut c_void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct FreeCb {
|
||||||
|
user_data: *mut c_void,
|
||||||
|
free: unsafe extern "C" fn(pUserData: *mut c_void, pMemory: *mut c_void),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FreeCb {
|
||||||
|
pub fn new(alloc: &VkAllocationCallbacks) -> FreeCb {
|
||||||
|
FreeCb {
|
||||||
|
user_data: alloc.pUserData,
|
||||||
|
free: alloc.pfnFree.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkFree for FreeCb {
|
||||||
|
unsafe fn free_raw(&self, mem: *mut u8) {
|
||||||
|
(self.free)(self.user_data, mem as *mut c_void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VkBox<T> {
|
||||||
|
ptr: NonNull<T>,
|
||||||
|
free: FreeCb,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> VkBox<MaybeUninit<T>> {
|
||||||
|
pub unsafe fn assume_init(self) -> VkBox<T> {
|
||||||
|
let ub = std::mem::ManuallyDrop::new(self);
|
||||||
|
VkBox {
|
||||||
|
ptr: NonNull::new_unchecked(ub.ptr.as_ptr() as *mut T),
|
||||||
|
free: ub.free,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_uninit(
|
||||||
|
alloc: &VkAllocationCallbacks,
|
||||||
|
scope: VkSystemAllocationScope,
|
||||||
|
) -> Result<VkBox<MaybeUninit<T>>> {
|
||||||
|
unsafe {
|
||||||
|
let ptr = alloc.alloc_scoped::<MaybeUninit<T>>(scope);
|
||||||
|
if let Some(ptr) = NonNull::new(ptr) {
|
||||||
|
Ok(VkBox {
|
||||||
|
ptr: ptr,
|
||||||
|
free: FreeCb::new(alloc),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(VK_ERROR_OUT_OF_HOST_MEMORY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self, x: T) -> VkBox<T> {
|
||||||
|
unsafe {
|
||||||
|
(self.ptr.as_ptr() as *mut T).write(x);
|
||||||
|
self.assume_init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> VkBox<T> {
|
||||||
|
pub fn new(
|
||||||
|
x: T,
|
||||||
|
alloc: &VkAllocationCallbacks,
|
||||||
|
scope: VkSystemAllocationScope,
|
||||||
|
) -> Result<VkBox<T>> {
|
||||||
|
match VkBox::<MaybeUninit<T>>::new_uninit(alloc, scope) {
|
||||||
|
Ok(b) => Ok(b.write(x)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn new_cb<F: FnOnce(NonNull<T>) -> VkResult>(
|
||||||
|
alloc: &VkAllocationCallbacks,
|
||||||
|
scope: VkSystemAllocationScope,
|
||||||
|
f: F,
|
||||||
|
) -> Result<VkBox<T>> {
|
||||||
|
match VkBox::<MaybeUninit<T>>::new_uninit(alloc, scope) {
|
||||||
|
Ok(b) => {
|
||||||
|
match f(NonNull::new_unchecked(b.ptr.as_ptr() as *mut T)) {
|
||||||
|
VK_SUCCESS => Ok(b.assume_init()),
|
||||||
|
e => Err(e),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new2(
|
||||||
|
x: T,
|
||||||
|
parent_alloc: &VkAllocationCallbacks,
|
||||||
|
alloc: *const VkAllocationCallbacks,
|
||||||
|
scope: VkSystemAllocationScope,
|
||||||
|
) -> Result<VkBox<T>> {
|
||||||
|
let alloc = if alloc.is_null() {
|
||||||
|
parent_alloc
|
||||||
|
} else {
|
||||||
|
unsafe {&*alloc }
|
||||||
|
};
|
||||||
|
VkBox::new(x, alloc, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for VkBox<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
std::ptr::drop_in_place(self.ptr.as_ptr());
|
||||||
|
self.free.free(self.ptr.as_ptr() as *mut c_void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsRef<T> for VkBox<T> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
unsafe { self.ptr.as_ref() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsMut<T> for VkBox<T> {
|
||||||
|
fn as_mut(&mut self) -> &mut T {
|
||||||
|
unsafe { self.ptr.as_mut() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for VkBox<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for VkBox<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
self.as_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type VkFinishFn<V> = unsafe extern "C" fn(obj: *mut V);
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct VkObj<V, T> {
|
||||||
|
vk: V,
|
||||||
|
finish: VkFinishFn<V>,
|
||||||
|
data: Option<T>,
|
||||||
|
_pin: std::marker::PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> VkObj<V, T> {
|
||||||
|
unsafe fn init_ptr<F: FnOnce(NonNull<V>) -> VkResult>(
|
||||||
|
mut ptr: NonNull<Self>,
|
||||||
|
finish: VkFinishFn<V>,
|
||||||
|
f: F,
|
||||||
|
) -> VkResult {
|
||||||
|
let vk_ptr = (&mut ptr.as_mut().vk) as *mut V;
|
||||||
|
let finish_ptr = (&mut ptr.as_mut().finish) as *mut VkFinishFn<V>;
|
||||||
|
let data_ptr = (&mut ptr.as_mut().data) as *mut Option<T>;
|
||||||
|
|
||||||
|
match f(NonNull::new_unchecked(vk_ptr)) {
|
||||||
|
VK_SUCCESS => {
|
||||||
|
finish_ptr.write(finish);
|
||||||
|
data_ptr.write(None);
|
||||||
|
VK_SUCCESS
|
||||||
|
}
|
||||||
|
err => err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vk(&self) -> &V {
|
||||||
|
&self.vk
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vk_mut(&mut self) -> &mut V {
|
||||||
|
&mut self.vk
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn vk_ptr(&self) -> *mut V {
|
||||||
|
&self.vk as *const V as *mut V
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> Drop for VkObj<V, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.data.take();
|
||||||
|
unsafe { (self.finish)(&mut self.vk) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> Deref for VkObj<V, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
self.data.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> DerefMut for VkObj<V, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
self.data.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VkObjBaseBox<V, T> {
|
||||||
|
obj: VkBox<VkObj<V, T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> VkObjBaseBox<V, T> {
|
||||||
|
pub fn new_cb<F: FnOnce(NonNull<V>) -> VkResult>(
|
||||||
|
alloc: &VkAllocationCallbacks,
|
||||||
|
finish: unsafe extern "C" fn(obj: *mut V),
|
||||||
|
f: F,
|
||||||
|
) -> Result<VkObjBaseBox<V, T>> {
|
||||||
|
let obj = unsafe {
|
||||||
|
VkBox::new_cb(alloc, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, |ptr| {
|
||||||
|
VkObj::init_ptr(ptr, finish, f)
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
Ok(VkObjBaseBox { obj: obj })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new2_cb<F: FnOnce(NonNull<V>) -> VkResult>(
|
||||||
|
parent_alloc: &VkAllocationCallbacks,
|
||||||
|
alloc: *const VkAllocationCallbacks,
|
||||||
|
finish: unsafe extern "C" fn(obj: *mut V),
|
||||||
|
f: F,
|
||||||
|
) -> Result<VkObjBaseBox<V, T>> {
|
||||||
|
let alloc = if alloc.is_null() {
|
||||||
|
parent_alloc
|
||||||
|
} else {
|
||||||
|
unsafe {&*alloc }
|
||||||
|
};
|
||||||
|
let obj = unsafe {
|
||||||
|
VkBox::new_cb(alloc, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, |ptr| {
|
||||||
|
VkObj::init_ptr(ptr, finish, f)
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
Ok(VkObjBaseBox { obj: obj })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> Deref for VkObjBaseBox<V, T> {
|
||||||
|
type Target = V;
|
||||||
|
|
||||||
|
fn deref(&self) -> &V {
|
||||||
|
&self.obj.vk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> DerefMut for VkObjBaseBox<V, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut V {
|
||||||
|
&mut self.obj.vk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VkObjBox<V, T> {
|
||||||
|
base: VkObjBaseBox<V, T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> VkObjBox<V, T> {
|
||||||
|
pub fn new(mut base: VkObjBaseBox<V, T>, x: T) -> VkObjBox<V, T> {
|
||||||
|
base.obj.data.replace(x);
|
||||||
|
VkObjBox { base: base }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> Deref for VkObjBox<V, T> {
|
||||||
|
type Target = VkObj<V, T>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &VkObj<V, T> {
|
||||||
|
&self.base.obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, T> DerefMut for VkObjBox<V, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut VkObj<V, T> {
|
||||||
|
&mut self.base.obj
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/vulkan/rust/examples.rs
Normal file
43
src/vulkan/rust/examples.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2022 Collabora, Ltd.
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
use vulkan_h::*;
|
||||||
|
use vulkan_runtime::*;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::boxed::*;
|
||||||
|
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Image<'a> {
|
||||||
|
dev: &'a VkObj<vk_device, Device>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_image(
|
||||||
|
dev: &VkObj<vk_device, Device>,
|
||||||
|
info: *const VkImageCreateInfo,
|
||||||
|
alloc: *const VkAllocationCallbacks,
|
||||||
|
) -> Result<VkObjBox<vk_image, Image>> {
|
||||||
|
let vk = unsafe {
|
||||||
|
VkObjBaseBox::new2_cb(
|
||||||
|
&dev.vk().alloc,
|
||||||
|
alloc,
|
||||||
|
vk_image_finish,
|
||||||
|
&|vk: NonNull<vk_image>| {
|
||||||
|
vk_image_init(dev.vk_ptr(), vk.as_ptr(), info);
|
||||||
|
VK_SUCCESS
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
/* Stuff which may use vk */
|
||||||
|
|
||||||
|
Ok(VkObjBox::new(vk, Image {
|
||||||
|
dev: dev,
|
||||||
|
}))
|
||||||
|
}
|
||||||
83
src/vulkan/rust/meson.build
Normal file
83
src/vulkan/rust/meson.build
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Copyright © 2022 Collabora, Ltd.
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
add_languages('rust', required: true)
|
||||||
|
rust = import('unstable-rust')
|
||||||
|
|
||||||
|
assert(
|
||||||
|
meson.version().version_compare('>= 1.0.0'),
|
||||||
|
'Meson 1.0 is required for Rust Vulkan bindings'
|
||||||
|
)
|
||||||
|
|
||||||
|
_vulkan_h_rs = rust.bindgen(
|
||||||
|
input : ['vulkan_h_bindgen.h'],
|
||||||
|
output : 'vulkan_h.rs',
|
||||||
|
include_directories : [inc_include, inc_src],
|
||||||
|
c_args : [
|
||||||
|
pre_args,
|
||||||
|
],
|
||||||
|
args : [
|
||||||
|
'--allowlist-type', 'PFN_vk.*',
|
||||||
|
'--allowlist-type', 'Vk.*',
|
||||||
|
'--allowlist-var', 'VK_.*',
|
||||||
|
'--no-prepend-enum-name',
|
||||||
|
'--raw-line', '#![allow(non_camel_case_types)]',
|
||||||
|
'--raw-line', '#![allow(non_snake_case)]',
|
||||||
|
'--raw-line', '#![allow(non_upper_case_globals)]',
|
||||||
|
'--size_t-is-usize',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
libvulkan_h_rs = static_library(
|
||||||
|
'vulkan_h',
|
||||||
|
_vulkan_h_rs,
|
||||||
|
gnu_symbol_visibility : 'hidden',
|
||||||
|
rust_crate_type : 'rlib',
|
||||||
|
)
|
||||||
|
|
||||||
|
_vulkan_runtime_rs = rust.bindgen(
|
||||||
|
input : ['vulkan_runtime_bindgen.h'],
|
||||||
|
output : 'vulkan_runtime.rs',
|
||||||
|
c_args : [
|
||||||
|
pre_args,
|
||||||
|
],
|
||||||
|
args : [
|
||||||
|
'--allowlist-function', 'vk_.*',
|
||||||
|
'--allowlist-type', 'vk_.*',
|
||||||
|
'--allowlist-var', 'vk_.*',
|
||||||
|
'--blocklist-type', 'PFN_vk.*',
|
||||||
|
'--blocklist-type', 'Vk.*',
|
||||||
|
'--no-prepend-enum-name',
|
||||||
|
'--raw-line', '#![allow(non_camel_case_types)]',
|
||||||
|
'--raw-line', '#![allow(non_snake_case)]',
|
||||||
|
'--raw-line', '#![allow(non_upper_case_globals)]',
|
||||||
|
'--raw-line', 'extern crate vulkan_h;',
|
||||||
|
'--raw-line', 'use vulkan_h::*;',
|
||||||
|
'--size_t-is-usize',
|
||||||
|
],
|
||||||
|
dependencies : [
|
||||||
|
idep_mesautil,
|
||||||
|
idep_vulkan_runtime_headers,
|
||||||
|
idep_vulkan_runtime,
|
||||||
|
idep_vulkan_util_headers,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
libvulkan_runtime_rs = static_library(
|
||||||
|
'vulkan_runtime',
|
||||||
|
_vulkan_runtime_rs,
|
||||||
|
gnu_symbol_visibility : 'hidden',
|
||||||
|
link_with : [libvulkan_h_rs],
|
||||||
|
rust_crate_type : 'rlib',
|
||||||
|
)
|
||||||
|
|
||||||
|
libvk_rs = static_library(
|
||||||
|
'vk_rs',
|
||||||
|
'vk.rs',
|
||||||
|
gnu_symbol_visibility : 'hidden',
|
||||||
|
rust_crate_type : 'rlib',
|
||||||
|
link_with : [
|
||||||
|
libvulkan_h_rs,
|
||||||
|
libvulkan_runtime_rs,
|
||||||
|
]
|
||||||
|
)
|
||||||
14
src/vulkan/rust/vk.rs
Normal file
14
src/vulkan/rust/vk.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2022 Collabora, Ltd.
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern crate vulkan_h;
|
||||||
|
extern crate vulkan_runtime;
|
||||||
|
|
||||||
|
mod boxed;
|
||||||
|
mod examples;
|
||||||
|
|
||||||
|
use vulkan_h::VkResult;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, VkResult>;
|
||||||
5
src/vulkan/rust/vulkan_h_bindgen.h
Normal file
5
src/vulkan/rust/vulkan_h_bindgen.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2022 Collabora, Ltd.
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
#include "vulkan/vulkan_core.h"
|
||||||
8
src/vulkan/rust/vulkan_runtime_bindgen.h
Normal file
8
src/vulkan/rust/vulkan_runtime_bindgen.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2022 Collabora, Ltd.
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
#include "vk_device.h"
|
||||||
|
#include "vk_image.h"
|
||||||
|
#include "vk_instance.h"
|
||||||
|
#include "vk_physical_device.h"
|
||||||
Loading…
Add table
Reference in a new issue