mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
A Vulkan implicit layer that captures frames from inside the workload's own process and encodes them on the GPU they were drawn on. Fourth and last of this batch, imported as a tree from `nestrilabs/nescapture` on the same terms. Filed under `apps/` rather than `crates/` despite building a cdylib. The rule here is what a thing *is*, not what it compiles to: this is a finished artefact that gets installed into an image beside its layer manifest, not a library another crate in this tree depends on. `crates/` is for the latter, and putting this there would make the distinction useless the first time someone looked. Wired to the workspace, `nesprotocol` by path. Its description named the transport component; that reads better as what it actually is — where the frames go — so it says that instead. Whole workspace builds and tests: 21 across four members. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
529 lines
20 KiB
Rust
529 lines
20 KiB
Rust
// ─────────────────────────────────────────────────────────────────────────────
|
|
// device.rs — vkCreateDevice, vkDestroyDevice, vkGetDeviceQueue
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
use crate::config;
|
|
use crate::dispatch::{NextDeviceFn, PFN_vkCreateDevice, PFN_vkGetInstanceProcAddr};
|
|
use crate::state::{DEVICE_STATE, DeviceState, INSTANCE_STATE, QUEUE_TO_DEVICE_KEY};
|
|
use crate::{
|
|
VkLayerDeviceCreateInfo, dispatch_key, find_layer_link, load_device_fn, try_load_device_fn,
|
|
};
|
|
use ash::vk::{self, Handle};
|
|
use dashmap::DashMap;
|
|
use std::os::raw::c_void;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
const VK_LAYER_LINK_INFO: u32 = 0;
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "system" fn vkCreateDevice(
|
|
physical_device: vk::PhysicalDevice,
|
|
p_create_info: *const vk::DeviceCreateInfo,
|
|
p_allocator: *const vk::AllocationCallbacks,
|
|
p_device: *mut vk::Device,
|
|
) -> vk::Result {
|
|
let layer_info: *mut VkLayerDeviceCreateInfo = match unsafe {
|
|
find_layer_link((*p_create_info).p_next as *const c_void, VK_LAYER_LINK_INFO)
|
|
} {
|
|
Some(p) => p,
|
|
None => return vk::Result::ERROR_INITIALIZATION_FAILED,
|
|
};
|
|
|
|
let dev_link = unsafe { (*layer_info).u.pDeviceLayerInfo };
|
|
let next_gipa: PFN_vkGetInstanceProcAddr =
|
|
match unsafe { (*dev_link).pfnNextGetInstanceProcAddr } {
|
|
Some(f) => f,
|
|
None => return vk::Result::ERROR_INITIALIZATION_FAILED,
|
|
};
|
|
let next_gdpa = unsafe {
|
|
match (*dev_link).pfnNextGetDeviceProcAddr {
|
|
Some(f) => f,
|
|
None => return vk::Result::ERROR_INITIALIZATION_FAILED,
|
|
}
|
|
};
|
|
unsafe { (*layer_info).u.pDeviceLayerInfo = (*dev_link).pNext };
|
|
|
|
let inst_key = unsafe { dispatch_key(physical_device.as_raw() as *const c_void) };
|
|
let istate = match INSTANCE_STATE.get(&inst_key) {
|
|
Some(s) => s.clone(),
|
|
None => {
|
|
let next_create: PFN_vkCreateDevice = unsafe {
|
|
crate::load_instance_fn(next_gipa, vk::Instance::null(), b"vkCreateDevice\0")
|
|
};
|
|
return unsafe { next_create(physical_device, p_create_info, p_allocator, p_device) };
|
|
}
|
|
};
|
|
|
|
// ── Inject DMA-BUF extensions for zero-copy capture ──────────────────
|
|
let ci = unsafe { &*p_create_info };
|
|
|
|
// Collect the game's original extensions.
|
|
let original_extensions: Vec<*const libc::c_char> =
|
|
if ci.enabled_extension_count > 0 && !ci.pp_enabled_extension_names.is_null() {
|
|
unsafe {
|
|
std::slice::from_raw_parts(
|
|
ci.pp_enabled_extension_names,
|
|
ci.enabled_extension_count as usize,
|
|
)
|
|
}
|
|
.to_vec()
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
// Extensions we need — static byte strings so pointers stay valid.
|
|
const EXT_EXTERNAL_MEMORY: &[u8] = b"VK_KHR_external_memory\0";
|
|
const EXT_EXTERNAL_MEMORY_FD: &[u8] = b"VK_KHR_external_memory_fd\0";
|
|
const EXT_EXTERNAL_MEMORY_DMABUF: &[u8] = b"VK_EXT_external_memory_dma_buf\0";
|
|
|
|
let needed: &[&[u8]] = &[
|
|
EXT_EXTERNAL_MEMORY,
|
|
EXT_EXTERNAL_MEMORY_FD,
|
|
EXT_EXTERNAL_MEMORY_DMABUF,
|
|
];
|
|
|
|
// Build extended list: original + any of ours not already present.
|
|
let mut extended = original_extensions.clone();
|
|
for &ext in needed {
|
|
let name_cstr = unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(ext) };
|
|
let already = extended
|
|
.iter()
|
|
.any(|&ptr| unsafe { std::ffi::CStr::from_ptr(ptr) == name_cstr });
|
|
if !already {
|
|
extended.push(ext.as_ptr() as *const libc::c_char);
|
|
}
|
|
}
|
|
|
|
// ── Bump queue count for dedicated capture queue ──────────────────
|
|
// Add one extra queue to the first queue family so capture
|
|
// submissions don't compete with game rendering.
|
|
let mut capture_queue_index = 0u32;
|
|
let queue_infos: Vec<vk::DeviceQueueCreateInfo> = if ci.queue_create_info_count > 0
|
|
&& !ci.p_queue_create_infos.is_null()
|
|
{
|
|
let slice = unsafe {
|
|
std::slice::from_raw_parts(ci.p_queue_create_infos, ci.queue_create_info_count as usize)
|
|
};
|
|
let mut qis = slice.to_vec();
|
|
if let Some(first) = qis.first_mut() {
|
|
capture_queue_index = first.queue_count; // use the NEXT index
|
|
first.queue_count += 1;
|
|
}
|
|
qis
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
// Try with injected extensions first.
|
|
let mut modified_ci = *ci;
|
|
modified_ci.enabled_extension_count = extended.len() as u32;
|
|
modified_ci.pp_enabled_extension_names = extended.as_ptr();
|
|
modified_ci.queue_create_info_count = queue_infos.len() as u32;
|
|
modified_ci.p_queue_create_infos = queue_infos.as_ptr();
|
|
|
|
let mut dmabuf_available = true;
|
|
let result =
|
|
unsafe { (istate.create_device)(physical_device, &modified_ci, p_allocator, p_device) };
|
|
|
|
let result = if result != vk::Result::SUCCESS {
|
|
// Driver rejected our extensions — retry with original create info.
|
|
log::warn!(
|
|
"vkCreateDevice with DMA-BUF extensions failed ({:?}), \
|
|
retrying without — CPU readback fallback will be used",
|
|
result
|
|
);
|
|
dmabuf_available = false;
|
|
unsafe { (istate.create_device)(physical_device, p_create_info, p_allocator, p_device) }
|
|
} else {
|
|
log::info!("DMA-BUF extensions injected successfully");
|
|
result
|
|
};
|
|
if result != vk::Result::SUCCESS {
|
|
return result;
|
|
}
|
|
|
|
if !dmabuf_available {
|
|
log::warn!("DMA-BUF extensions missing — will use CPU readback fallback (expensive!)");
|
|
}
|
|
|
|
let device = unsafe { *p_device };
|
|
|
|
// Cache physical device memory properties
|
|
let mut mem_props = vk::PhysicalDeviceMemoryProperties::default();
|
|
unsafe { (istate.get_physical_device_memory_properties)(physical_device, &mut mem_props) };
|
|
|
|
macro_rules! load {
|
|
($name:literal) => {
|
|
unsafe { load_device_fn(next_gdpa, device, $name) }
|
|
};
|
|
}
|
|
macro_rules! try_load {
|
|
($name:literal) => {
|
|
unsafe { try_load_device_fn(next_gdpa, device, $name) }
|
|
};
|
|
}
|
|
|
|
let fp = NextDeviceFn {
|
|
// Infrastructure
|
|
get_device_proc_addr: next_gdpa,
|
|
destroy_device: load!(b"vkDestroyDevice\0"),
|
|
get_device_queue: load!(b"vkGetDeviceQueue\0"),
|
|
queue_present_khr: try_load!(b"vkQueuePresentKHR\0"),
|
|
|
|
// Phase 1
|
|
create_shader_module: load!(b"vkCreateShaderModule\0"),
|
|
destroy_shader_module: load!(b"vkDestroyShaderModule\0"),
|
|
create_graphics_pipelines: load!(b"vkCreateGraphicsPipelines\0"),
|
|
destroy_pipeline: load!(b"vkDestroyPipeline\0"),
|
|
|
|
// Phase 2
|
|
create_image_view: load!(b"vkCreateImageView\0"),
|
|
destroy_image_view: load!(b"vkDestroyImageView\0"),
|
|
create_framebuffer: load!(b"vkCreateFramebuffer\0"),
|
|
destroy_framebuffer: load!(b"vkDestroyFramebuffer\0"),
|
|
allocate_command_buffers: load!(b"vkAllocateCommandBuffers\0"),
|
|
free_command_buffers: load!(b"vkFreeCommandBuffers\0"),
|
|
cmd_bind_pipeline: load!(b"vkCmdBindPipeline\0"),
|
|
cmd_begin_render_pass: load!(b"vkCmdBeginRenderPass\0"),
|
|
cmd_end_render_pass: load!(b"vkCmdEndRenderPass\0"),
|
|
cmd_begin_rendering_khr: try_load!(b"vkCmdBeginRenderingKHR\0"),
|
|
cmd_end_rendering_khr: try_load!(b"vkCmdEndRenderingKHR\0"),
|
|
|
|
// Phase 4 — capture images
|
|
create_image: load!(b"vkCreateImage\0"),
|
|
destroy_image: load!(b"vkDestroyImage\0"),
|
|
allocate_memory: load!(b"vkAllocateMemory\0"),
|
|
free_memory: load!(b"vkFreeMemory\0"),
|
|
bind_image_memory: load!(b"vkBindImageMemory\0"),
|
|
get_image_memory_requirements: load!(b"vkGetImageMemoryRequirements\0"),
|
|
map_memory: load!(b"vkMapMemory\0"),
|
|
unmap_memory: load!(b"vkUnmapMemory\0"),
|
|
cmd_pipeline_barrier: load!(b"vkCmdPipelineBarrier\0"),
|
|
cmd_copy_image: load!(b"vkCmdCopyImage\0"),
|
|
get_image_subresource_layout: load!(b"vkGetImageSubresourceLayout\0"),
|
|
get_memory_fd_khr: try_load!(b"vkGetMemoryFdKHR\0"),
|
|
|
|
// Phase 4 — synchronisation
|
|
create_fence: load!(b"vkCreateFence\0"),
|
|
destroy_fence: load!(b"vkDestroyFence\0"),
|
|
create_command_pool: load!(b"vkCreateCommandPool\0"),
|
|
destroy_command_pool: load!(b"vkDestroyCommandPool\0"),
|
|
reset_command_pool: load!(b"vkResetCommandPool\0"),
|
|
begin_command_buffer: load!(b"vkBeginCommandBuffer\0"),
|
|
end_command_buffer: load!(b"vkEndCommandBuffer\0"),
|
|
reset_command_buffer: load!(b"vkResetCommandBuffer\0"), // needed for double-buffered capture
|
|
queue_submit: load!(b"vkQueueSubmit\0"),
|
|
wait_for_fences: load!(b"vkWaitForFences\0"),
|
|
reset_fences: load!(b"vkResetFences\0"),
|
|
|
|
// Phase 4 — swapchain
|
|
create_swapchain_khr: try_load!(b"vkCreateSwapchainKHR\0"),
|
|
destroy_swapchain_khr: try_load!(b"vkDestroySwapchainKHR\0"),
|
|
get_swapchain_images_khr: try_load!(b"vkGetSwapchainImagesKHR\0"),
|
|
|
|
// Phase 6 — draw commands
|
|
cmd_draw: load!(b"vkCmdDraw\0"),
|
|
cmd_draw_indexed: load!(b"vkCmdDrawIndexed\0"),
|
|
cmd_draw_indirect: load!(b"vkCmdDrawIndirect\0"),
|
|
cmd_draw_indexed_indirect: load!(b"vkCmdDrawIndexedIndirect\0"),
|
|
cmd_draw_indirect_count: try_load!(b"vkCmdDrawIndirectCount\0"),
|
|
cmd_draw_indexed_indirect_count: try_load!(b"vkCmdDrawIndexedIndirectCount\0"),
|
|
};
|
|
|
|
// ── Retrieve capture queue from the bumped slot ───────────────────
|
|
let mut capture_queue = vk::Queue::null();
|
|
if queue_infos
|
|
.first()
|
|
.map(|q| q.queue_count > 1)
|
|
.unwrap_or(false)
|
|
{
|
|
let qi = &queue_infos[0];
|
|
unsafe {
|
|
(fp.get_device_queue)(
|
|
device,
|
|
qi.queue_family_index,
|
|
capture_queue_index,
|
|
&mut capture_queue,
|
|
);
|
|
}
|
|
log::info!(
|
|
"capture queue: family={} index={capture_queue_index}",
|
|
qi.queue_family_index
|
|
);
|
|
}
|
|
|
|
let key = unsafe { dispatch_key(device.as_raw() as *const c_void) };
|
|
|
|
// Phase 3: load shader hash config
|
|
let shader_hashes = config::resolve_config_path()
|
|
.as_ref()
|
|
.and_then(|p| config::load_config(p));
|
|
|
|
if let Some(ref set) = shader_hashes {
|
|
log::info!(
|
|
"config loaded — {} hud_frag, {} hud_vert, {} skip_frag",
|
|
set.hud_fragment_shaders.len(),
|
|
set.hud_vertex_shaders.len(),
|
|
set.skip_fragment_shaders.len(),
|
|
);
|
|
} else {
|
|
log::warn!("no config — draw suppression disabled");
|
|
}
|
|
|
|
let dev_state = Arc::new(DeviceState {
|
|
raw: device,
|
|
physical_device,
|
|
fp,
|
|
|
|
shader_registry: DashMap::new(),
|
|
pipeline_registry: DashMap::new(),
|
|
pipeline_state: DashMap::new(),
|
|
|
|
view_to_image: DashMap::new(),
|
|
view_format: DashMap::new(),
|
|
framebuffer_to_views: DashMap::new(),
|
|
framebuffer_extent: DashMap::new(),
|
|
|
|
shader_hashes,
|
|
|
|
hudless_image: std::sync::Mutex::new(None),
|
|
hudless_memory: std::sync::Mutex::new(None),
|
|
hudless_size: std::sync::Mutex::new((0, 0, vk::Format::UNDEFINED)),
|
|
final_image: std::sync::Mutex::new(None),
|
|
final_memory: std::sync::Mutex::new(None),
|
|
final_size: std::sync::Mutex::new((0, 0, vk::Format::UNDEFINED)),
|
|
final_stride: std::sync::atomic::AtomicU32::new(0),
|
|
swapchain: std::sync::Mutex::new(None),
|
|
swapchain_images: std::sync::Mutex::new(Vec::new()),
|
|
swapchain_format: std::sync::Mutex::new(vk::Format::UNDEFINED),
|
|
swapchain_extent: std::sync::Mutex::new(vk::Extent2D {
|
|
width: 0,
|
|
height: 0,
|
|
}),
|
|
swapchain_colorspace: std::sync::atomic::AtomicU32::new(0),
|
|
frame_counter: std::sync::atomic::AtomicU64::new(0),
|
|
largest_extent: std::sync::Mutex::new(vk::Extent2D {
|
|
width: 0,
|
|
height: 0,
|
|
}),
|
|
|
|
hud_detected_frame: std::sync::atomic::AtomicBool::new(false),
|
|
pending_capture_frame: std::sync::atomic::AtomicBool::new(false),
|
|
capture_injected_frame: std::sync::atomic::AtomicBool::new(false),
|
|
skipped_draws_frame: std::sync::atomic::AtomicU32::new(0),
|
|
|
|
encoder: std::sync::Mutex::new(None),
|
|
|
|
capture_resources: std::sync::Mutex::new(None),
|
|
capture_queue: std::sync::Mutex::new(capture_queue),
|
|
fake_images: std::sync::Mutex::new(Vec::new()),
|
|
fake_memories: std::sync::Mutex::new(Vec::new()),
|
|
fake_fds: std::sync::Mutex::new(Vec::new()),
|
|
fake_strides: std::sync::Mutex::new(Vec::new()),
|
|
fake_available: std::sync::Mutex::new(Vec::new()),
|
|
fake_image_count: std::sync::atomic::AtomicU32::new(0),
|
|
fake_swapchain: std::sync::Mutex::new(None),
|
|
signal_queue: std::sync::Mutex::new(vk::Queue::null()),
|
|
next_acquire: std::sync::atomic::AtomicU32::new(0),
|
|
memory_properties: std::sync::Mutex::new(mem_props),
|
|
acquire_dummy_pool: std::sync::Mutex::new(vk::CommandPool::null()),
|
|
acquire_dummy_cb: std::sync::Mutex::new(vk::CommandBuffer::null()),
|
|
cached_dmabuf_fd: std::sync::atomic::AtomicI32::new(-1),
|
|
|
|
target_fps: std::sync::atomic::AtomicU32::new(0),
|
|
last_capture_time: std::sync::Mutex::new(None),
|
|
capture_tx: std::sync::Mutex::new(None),
|
|
});
|
|
|
|
DEVICE_STATE.insert(key, dev_state);
|
|
|
|
log::info!("vkCreateDevice OK — key {:#x}", key);
|
|
vk::Result::SUCCESS
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "system" fn vkDestroyDevice(
|
|
device: vk::Device,
|
|
p_allocator: *const vk::AllocationCallbacks,
|
|
) {
|
|
let key = unsafe { dispatch_key(device.as_raw() as *const c_void) };
|
|
let ds = match DEVICE_STATE.remove(&key) {
|
|
Some((_, ds)) => ds,
|
|
None => return,
|
|
};
|
|
|
|
log::info!(
|
|
"vkDestroyDevice — {} shaders, {} pipelines evicted",
|
|
ds.shader_registry.len(),
|
|
ds.pipeline_registry.len(),
|
|
);
|
|
|
|
// ── 1. Shut down encoder pipeline (unblocks encoder + RTP threads) ───
|
|
{
|
|
let mut enc_guard = ds.encoder.lock().unwrap();
|
|
if let Some(handle) = enc_guard.take() {
|
|
handle.shutdown();
|
|
// `handle` is dropped here → drops `frame_tx` → encoder thread's
|
|
// recv_timeout returns Disconnected → encoder thread drops
|
|
// `encoded_tx` → RTP thread exits too.
|
|
//
|
|
// Give threads a moment to drain. In production you'd join the
|
|
// JoinHandles, but since we don't store them, a short sleep +
|
|
// the AtomicBool shutdown flag is sufficient.
|
|
log::info!("encoder pipeline shutdown signaled");
|
|
}
|
|
}
|
|
// Brief yield to let threads notice the disconnect.
|
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
|
|
|
// ── 2. Clean up capture resources (double-buffered cmd pool + fences) ─
|
|
{
|
|
let mut res_guard = ds.capture_resources.lock().unwrap();
|
|
if let Some(res) = res_guard.take() {
|
|
unsafe {
|
|
// Wait for any in-flight capture commands to finish before
|
|
// destroying the fences / command pool.
|
|
let _ = (ds.fp.wait_for_fences)(
|
|
ds.raw,
|
|
res.fences.len() as u32,
|
|
res.fences.as_ptr(),
|
|
vk::TRUE,
|
|
5_000_000_000, // 5 seconds — should be instant
|
|
);
|
|
for &f in &res.fences {
|
|
(ds.fp.destroy_fence)(ds.raw, f, std::ptr::null());
|
|
}
|
|
(ds.fp.destroy_command_pool)(ds.raw, res.command_pool, std::ptr::null());
|
|
}
|
|
log::debug!("capture resources destroyed");
|
|
}
|
|
}
|
|
|
|
// ── 3. Free final_image / final_memory ────────────────────────────────
|
|
{
|
|
let img = ds.final_image.lock().unwrap().take();
|
|
let mem = ds.final_memory.lock().unwrap().take();
|
|
if let Some(i) = img {
|
|
unsafe { (ds.fp.destroy_image)(ds.raw, i, std::ptr::null()) };
|
|
}
|
|
if let Some(m) = mem {
|
|
unsafe { (ds.fp.free_memory)(ds.raw, m, std::ptr::null()) };
|
|
}
|
|
}
|
|
|
|
// ── 4. Free hudless_image / hudless_memory ────────────────────────────
|
|
{
|
|
let img = ds.hudless_image.lock().unwrap().take();
|
|
let mem = ds.hudless_memory.lock().unwrap().take();
|
|
if let Some(i) = img {
|
|
unsafe { (ds.fp.destroy_image)(ds.raw, i, std::ptr::null()) };
|
|
}
|
|
if let Some(m) = mem {
|
|
unsafe { (ds.fp.free_memory)(ds.raw, m, std::ptr::null()) };
|
|
}
|
|
}
|
|
|
|
// ── 5. Close cached DMA-BUF fd ────────────────────────────────────────
|
|
{
|
|
let fd = ds.cached_dmabuf_fd.load(Ordering::Relaxed);
|
|
if fd >= 0 {
|
|
unsafe { libc::close(fd) };
|
|
log::debug!("cached DMA-BUF fd {} closed", fd);
|
|
}
|
|
}
|
|
|
|
// ── 6. Clean up queue → device key mappings for this device ───────────
|
|
QUEUE_TO_DEVICE_KEY.retain(|_, dk| *dk != key);
|
|
|
|
// ── 7. Call the real vkDestroyDevice ──────────────────────────────────
|
|
unsafe { (ds.fp.destroy_device)(device, p_allocator) };
|
|
|
|
log::info!("vkDestroyDevice complete");
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "system" fn vkGetDeviceQueue(
|
|
device: vk::Device,
|
|
queue_family_index: u32,
|
|
queue_index: u32,
|
|
p_queue: *mut vk::Queue,
|
|
) {
|
|
let key = unsafe { dispatch_key(device.as_raw() as *const c_void) };
|
|
if let Some(ds) = DEVICE_STATE.get(&key) {
|
|
unsafe { (ds.fp.get_device_queue)(device, queue_family_index, queue_index, p_queue) };
|
|
let queue = unsafe { *p_queue };
|
|
QUEUE_TO_DEVICE_KEY.insert(queue.as_raw(), key);
|
|
// Store first queue for acquire semaphore signaling
|
|
let mut sq = ds.signal_queue.lock().unwrap();
|
|
if *sq == vk::Queue::null() {
|
|
*sq = queue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Enumerate device extensions supported by the physical device.
|
|
unsafe fn enumerate_device_extensions(
|
|
istate: &crate::dispatch::NextInstanceFn,
|
|
physical_device: vk::PhysicalDevice,
|
|
) -> Vec<std::ffi::CString> {
|
|
// We need vkEnumerateDeviceExtensionProperties. Load it from the
|
|
// instance dispatch since it's a physical-device-level function.
|
|
// For simplicity, use ash's raw function signature.
|
|
type PFN_vkEnumerateDeviceExtensionProperties = unsafe extern "system" fn(
|
|
vk::PhysicalDevice,
|
|
*const libc::c_char,
|
|
*mut u32,
|
|
*mut vk::ExtensionProperties,
|
|
) -> vk::Result;
|
|
|
|
let func: Option<PFN_vkEnumerateDeviceExtensionProperties> = {
|
|
let raw = unsafe {
|
|
(istate.get_instance_proc_addr)(
|
|
vk::Instance::null(),
|
|
b"vkEnumerateDeviceExtensionProperties\0".as_ptr() as *const libc::c_char,
|
|
)
|
|
};
|
|
raw.map(|f| unsafe { std::mem::transmute(f) })
|
|
};
|
|
|
|
let Some(enumerate) = func else {
|
|
log::warn!("could not load vkEnumerateDeviceExtensionProperties");
|
|
return Vec::new();
|
|
};
|
|
|
|
let mut count = 0u32;
|
|
if unsafe {
|
|
enumerate(
|
|
physical_device,
|
|
std::ptr::null(),
|
|
&mut count,
|
|
std::ptr::null_mut(),
|
|
)
|
|
} != vk::Result::SUCCESS
|
|
{
|
|
return Vec::new();
|
|
}
|
|
|
|
let mut props = vec![vk::ExtensionProperties::default(); count as usize];
|
|
if unsafe {
|
|
enumerate(
|
|
physical_device,
|
|
std::ptr::null(),
|
|
&mut count,
|
|
props.as_mut_ptr(),
|
|
)
|
|
} != vk::Result::SUCCESS
|
|
{
|
|
return Vec::new();
|
|
}
|
|
|
|
props
|
|
.iter()
|
|
.filter_map(|p| {
|
|
let cstr = unsafe { std::ffi::CStr::from_ptr(p.extension_name.as_ptr()) };
|
|
Some(cstr.to_owned())
|
|
})
|
|
.collect()
|
|
}
|