mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-24 19:42:24 +03:00
feat(nescapture): open the capture layer
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>
This commit is contained in:
@@ -0,0 +1,528 @@
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// 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()
|
||||
}
|
||||
Reference in New Issue
Block a user