Files
fluxer/fluxer_desktop/native/win-game-capture/vulkan-layer/src/lib.rs
T

2632 lines
88 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-or-later
#![cfg(target_os = "windows")]
#![allow(non_snake_case)]
#![allow(unsafe_op_in_unsafe_fn)]
#[path = "../../src/game_capture_abi.rs"]
mod game_capture_abi;
mod d3d11_interop;
use ash::{khr, vk};
use ash_layer::{
LayerFunction, NegotiateLayerInterface, PFN_vk_layerGetPhysicalDeviceProcAddr,
PFN_vkNegotiateLoaderLayerInterfaceVersion, get_device_chain_info, get_instance_chain_info,
};
use d3d11_interop::{D3d11Device, InteropFormat, SharedTexture, interop_format};
use dashmap::DashMap;
use game_capture_abi::{
ENV_FORCE_CPU, ENV_FORCE_SHARED_TEXTURE, GAME_CAPTURE_API_VULKAN, GAME_CAPTURE_BUFFER_COUNT,
GAME_CAPTURE_FALLBACK_EXTERNAL_MEMORY_UNSUPPORTED, GAME_CAPTURE_FALLBACK_FORCED_CPU,
GAME_CAPTURE_FALLBACK_FORMAT_UNSUPPORTED, GAME_CAPTURE_FALLBACK_NONE, GAME_CAPTURE_FLAG_HDR,
GAME_CAPTURE_FLAG_TEN_BIT, GAME_CAPTURE_FRAME_PREFIX, GAME_CAPTURE_INFO_PREFIX,
GAME_CAPTURE_KEEPALIVE_PREFIX, GAME_CAPTURE_MAGIC, GAME_CAPTURE_PRESENT_CLOCK_QPC,
GAME_CAPTURE_READY_PREFIX, GAME_CAPTURE_STATE_ACTIVE, GAME_CAPTURE_STATE_ERROR,
GAME_CAPTURE_STATE_RESIZE_REQUIRED, GAME_CAPTURE_STATE_STOPPED, GAME_CAPTURE_STOP_PREFIX,
GAME_CAPTURE_TRANSPORT_MEMORY, GAME_CAPTURE_TRANSPORT_SHARED_TEXTURE, GameCaptureSharedInfo,
env_flag_enabled, frame_buffer_size, host_supports_present_clock, mutex_name, object_name,
qpc_now_us,
};
use once_cell::sync::{Lazy, OnceCell};
use std::{
ffi::{CStr, c_char},
mem,
ptr::{null, null_mut},
sync::Mutex,
};
use windows_sys::Win32::{
Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE, WAIT_ABANDONED, WAIT_OBJECT_0},
System::{
Memory::{
FILE_MAP_ALL_ACCESS, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, OpenFileMappingW,
UnmapViewOfFile,
},
Threading::{
EVENT_ALL_ACCESS, MUTEX_ALL_ACCESS, OpenEventW, OpenMutexW, ReleaseMutex,
SYNCHRONIZATION_SYNCHRONIZE, SetEvent, WaitForSingleObject,
},
},
};
const LAYER_NAME: &[u8] = b"VK_LAYER_FLUXER_game_capture\0";
const FRAME_MUTEX_WAIT_MS: u32 = 0;
const CAPTURE_FENCE_TIMEOUT_NS: u64 = 250_000_000;
const DEVICE_EXT_EXTERNAL_MEMORY: &[u8] = b"VK_KHR_external_memory\0";
const DEVICE_EXT_EXTERNAL_MEMORY_WIN32: &[u8] = b"VK_KHR_external_memory_win32\0";
const DEVICE_EXT_DEDICATED_ALLOCATION: &[u8] = b"VK_KHR_dedicated_allocation\0";
const DEVICE_EXT_GET_MEMORY_REQUIREMENTS2: &[u8] = b"VK_KHR_get_memory_requirements2\0";
const INSTANCE_EXT_PHYSICAL_DEVICE_PROPERTIES2: &[u8] = b"VK_KHR_get_physical_device_properties2\0";
const INSTANCE_EXT_EXTERNAL_MEMORY_CAPABILITIES: &[u8] = b"VK_KHR_external_memory_capabilities\0";
const SHARED_HANDLE_TYPE: vk::ExternalMemoryHandleTypeFlags =
vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE_KMT;
static VERBOSE: Lazy<bool> =
Lazy::new(|| game_capture_abi::env_flag_enabled(game_capture_abi::ENV_VERBOSE));
macro_rules! vlog {
($($arg:tt)*) => {
if *VERBOSE {
eprintln!("[fluxer-vk-layer] {}", format!($($arg)*));
}
};
}
static GIPA: OnceCell<vk::PFN_vkGetInstanceProcAddr> = OnceCell::new();
static GPHYPA: OnceCell<PFN_vk_layerGetPhysicalDeviceProcAddr> = OnceCell::new();
static ENTRY: OnceCell<ash::Entry> = OnceCell::new();
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum CaptureMode {
Auto,
ForceCpu,
ForceSharedTexture,
}
fn capture_mode() -> CaptureMode {
static MODE: OnceCell<u8> = OnceCell::new();
let value = *MODE.get_or_init(|| {
if env_flag_enabled(ENV_FORCE_CPU) {
0
} else if env_flag_enabled(ENV_FORCE_SHARED_TEXTURE) {
2
} else {
1
}
});
match value {
0 => CaptureMode::ForceCpu,
2 => CaptureMode::ForceSharedTexture,
_ => CaptureMode::Auto,
}
}
static INSTANCE_MAP: Lazy<DashMap<vk::Instance, LayerInstance>> = Lazy::new(DashMap::new);
static PHY_TO_INSTANCE_MAP: Lazy<DashMap<vk::PhysicalDevice, vk::Instance>> =
Lazy::new(DashMap::new);
static GDPA_MAP: Lazy<DashMap<vk::Device, vk::PFN_vkGetDeviceProcAddr>> = Lazy::new(DashMap::new);
static DEVICE_MAP: Lazy<DashMap<vk::Device, LayerDevice>> = Lazy::new(DashMap::new);
static SURFACE_MAP: Lazy<DashMap<vk::SurfaceKHR, SurfaceState>> = Lazy::new(DashMap::new);
static SWAPCHAIN_MAP: Lazy<DashMap<vk::SwapchainKHR, SwapchainState>> = Lazy::new(DashMap::new);
static QUEUE_MAP: Lazy<DashMap<vk::Queue, QueueState>> = Lazy::new(DashMap::new);
static IPC_STATE: Lazy<Mutex<Option<IpcState>>> = Lazy::new(|| Mutex::new(None));
static D3D11_DEVICE: Lazy<Mutex<Option<D3d11Device>>> = Lazy::new(|| Mutex::new(None));
static FAST_PATH_DISABLED: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
#[derive(Clone)]
struct LayerInstance {
ash_instance: ash::Instance,
khr_surface: khr::surface::Instance,
khr_win32_surface: khr::win32_surface::Instance,
}
#[derive(Clone)]
struct LayerDevice {
instance: vk::Instance,
physical_device: vk::PhysicalDevice,
ash_device: ash::Device,
khr_swapchain: khr::swapchain::Device,
queue_families: Vec<vk::QueueFamilyProperties>,
khr_external_memory_win32: Option<khr::external_memory_win32::Device>,
external_memory_enabled: bool,
dedicated_allocation_enabled: bool,
}
#[derive(Clone, Copy)]
struct SurfaceState {
instance: vk::Instance,
hwnd: vk::HWND,
}
#[derive(Clone)]
struct SwapchainState {
device: vk::Device,
hwnd: vk::HWND,
extent: vk::Extent2D,
format: vk::Format,
images: Vec<vk::Image>,
can_capture: bool,
present_layout: vk::ImageLayout,
shared_texture_allowed: bool,
}
struct QueueState {
device: vk::Device,
family_index: u32,
supports_transfer: bool,
resources: Mutex<Option<ReadbackResources>>,
shared: Mutex<Option<SharedTextureResources>>,
}
struct ReadbackResources {
width: u32,
height: u32,
format_mode: FormatMode,
buffer: vk::Buffer,
memory: vk::DeviceMemory,
command_pool: vk::CommandPool,
command_buffer: vk::CommandBuffer,
fence: vk::Fence,
present_wait_semaphore: vk::Semaphore,
size: vk::DeviceSize,
}
struct SharedTextureResources {
width: u32,
height: u32,
format: InteropFormat,
shared_texture: SharedTexture,
image: vk::Image,
memory: vk::DeviceMemory,
command_pool: vk::CommandPool,
command_buffer: vk::CommandBuffer,
fence: vk::Fence,
present_wait_semaphore: vk::Semaphore,
initialised: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum FormatMode {
Bgra,
Rgba,
}
struct CaptureSubmit {
chained_wait: Option<vk::Semaphore>,
}
impl CaptureSubmit {
fn none() -> Self {
Self { chained_wait: None }
}
}
struct IpcState {
info_map: HANDLE,
frame_map: HANDLE,
ready_event: HANDLE,
stop_event: HANDLE,
mutexes: [HANDLE; GAME_CAPTURE_BUFFER_COUNT],
info: *mut GameCaptureSharedInfo,
frame_base: *mut u8,
frame_buffer_capacity: usize,
next_frame_index: usize,
last_frame_ns: u64,
keepalive_name: Vec<u16>,
}
unsafe impl Send for IpcState {}
fn wide(value: &str) -> Vec<u16> {
value.encode_utf16().chain(std::iter::once(0)).collect()
}
struct AugmentedExtensions {
pointers: Vec<*const c_char>,
}
impl AugmentedExtensions {
unsafe fn build(
existing: *const *const c_char,
existing_count: u32,
extra: &[&'static [u8]],
) -> Self {
let mut pointers: Vec<*const c_char> = Vec::new();
let mut present: Vec<&CStr> = Vec::new();
if !existing.is_null() {
let slice = std::slice::from_raw_parts(existing, existing_count as usize);
for &ptr in slice {
pointers.push(ptr);
if !ptr.is_null() {
present.push(CStr::from_ptr(ptr));
}
}
}
for &name in extra {
let cstr = CStr::from_bytes_with_nul_unchecked(name);
if !present.contains(&cstr) {
pointers.push(cstr.as_ptr());
}
}
Self { pointers }
}
fn count(&self) -> u32 {
self.pointers.len() as u32
}
fn as_ptr(&self) -> *const *const c_char {
self.pointers.as_ptr()
}
}
unsafe fn extension_in_list(list: *const *const c_char, count: u32, name: &'static [u8]) -> bool {
if list.is_null() {
return false;
}
let target = CStr::from_bytes_with_nul_unchecked(name);
std::slice::from_raw_parts(list, count as usize)
.iter()
.any(|&ptr| !ptr.is_null() && CStr::from_ptr(ptr) == target)
}
fn current_pid() -> u32 {
std::process::id()
}
fn now_ns() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos() as u64)
.unwrap_or(0)
}
fn now_us() -> i64 {
(now_ns() / 1_000) as i64
}
unsafe fn close_handle(handle: &mut HANDLE) {
if !handle.is_null() && *handle != INVALID_HANDLE_VALUE {
CloseHandle(*handle);
*handle = null_mut();
}
}
unsafe fn open_event(prefix: &str, pid: u32) -> HANDLE {
let name = wide(&object_name(prefix, pid));
OpenEventW(EVENT_ALL_ACCESS, 0, name.as_ptr())
}
unsafe fn open_mutex(name: &str) -> HANDLE {
let name = wide(name);
OpenMutexW(MUTEX_ALL_ACCESS, 0, name.as_ptr())
}
unsafe fn parent_alive(keepalive_name: &[u16]) -> bool {
let handle = OpenMutexW(SYNCHRONIZATION_SYNCHRONIZE, 0, keepalive_name.as_ptr());
if handle.is_null() {
return false;
}
CloseHandle(handle);
true
}
unsafe fn map_view<T>(handle: HANDLE, size: usize) -> *mut T {
let view = MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
view.Value.cast()
}
unsafe fn free_ipc_state(state: &mut IpcState) {
if !state.info.is_null() {
UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
Value: state.info.cast(),
});
state.info = null_mut();
}
if !state.frame_base.is_null() {
UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
Value: state.frame_base.cast(),
});
state.frame_base = null_mut();
}
close_handle(&mut state.mutexes[0]);
close_handle(&mut state.mutexes[1]);
close_handle(&mut state.stop_event);
close_handle(&mut state.ready_event);
close_handle(&mut state.frame_map);
close_handle(&mut state.info_map);
}
unsafe fn open_ipc() -> Option<IpcState> {
let pid = current_pid();
let info_name = wide(&object_name(GAME_CAPTURE_INFO_PREFIX, pid));
let frame_name = wide(&object_name(GAME_CAPTURE_FRAME_PREFIX, pid));
let info_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, info_name.as_ptr());
if info_map.is_null() {
return None;
}
let frame_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, frame_name.as_ptr());
if frame_map.is_null() {
let mut handle = info_map;
close_handle(&mut handle);
return None;
}
let info = map_view::<GameCaptureSharedInfo>(info_map, mem::size_of::<GameCaptureSharedInfo>());
if info.is_null() || (*info).magic != GAME_CAPTURE_MAGIC {
let mut info_map = info_map;
let mut frame_map = frame_map;
close_handle(&mut frame_map);
close_handle(&mut info_map);
return None;
}
let frame_buffer_capacity = frame_buffer_size((*info).max_width, (*info).max_height)?;
let frame_map_size = frame_buffer_capacity.checked_mul(GAME_CAPTURE_BUFFER_COUNT)?;
let frame_base = map_view::<u8>(frame_map, frame_map_size);
if frame_base.is_null() {
let mut info_map = info_map;
let mut frame_map = frame_map;
close_handle(&mut frame_map);
close_handle(&mut info_map);
return None;
}
let ready_event = open_event(GAME_CAPTURE_READY_PREFIX, pid);
let stop_event = open_event(GAME_CAPTURE_STOP_PREFIX, pid);
let mutex_names = [mutex_name(pid, 0), mutex_name(pid, 1)];
let mutexes = [open_mutex(&mutex_names[0]), open_mutex(&mutex_names[1])];
if ready_event.is_null()
|| stop_event.is_null()
|| mutexes.iter().any(|handle| handle.is_null())
{
let mut state = IpcState {
info_map,
frame_map,
ready_event,
stop_event,
mutexes,
info,
frame_base,
frame_buffer_capacity,
next_frame_index: 0,
last_frame_ns: 0,
keepalive_name: wide(&object_name(GAME_CAPTURE_KEEPALIVE_PREFIX, pid)),
};
free_ipc_state(&mut state);
return None;
}
Some(IpcState {
info_map,
frame_map,
ready_event,
stop_event,
mutexes,
info,
frame_base,
frame_buffer_capacity,
next_frame_index: 0,
last_frame_ns: 0,
keepalive_name: wide(&object_name(GAME_CAPTURE_KEEPALIVE_PREFIX, pid)),
})
}
unsafe fn capture_should_run(state: &mut IpcState) -> bool {
if WaitForSingleObject(state.stop_event, 0) == WAIT_OBJECT_0 {
(*state.info).state = GAME_CAPTURE_STATE_STOPPED;
return false;
}
if !parent_alive(&state.keepalive_name) {
(*state.info).state = GAME_CAPTURE_STATE_STOPPED;
return false;
}
true
}
unsafe fn frame_due(state: &mut IpcState) -> bool {
let interval = (*state.info).target_frame_interval_ns;
if interval == 0 {
return true;
}
let now = now_ns();
if now.saturating_sub(state.last_frame_ns) < interval {
return false;
}
state.last_frame_ns = now;
true
}
unsafe fn target_matches(state: &IpcState, hwnd: vk::HWND) -> bool {
let target = (*state.info).hwnd;
target == 0 || hwnd == 0 || target == hwnd as u64
}
unsafe fn write_rows_to_shared_memory(
state: &mut IpcState,
hwnd: vk::HWND,
width: u32,
height: u32,
src_base: *const u8,
src_row_pitch: usize,
format_mode: FormatMode,
fallback_reason: u32,
) -> bool {
if src_base.is_null() {
(*state.info).state = GAME_CAPTURE_STATE_ERROR;
(*state.info).last_error = 60;
return false;
}
let max_width = (*state.info).max_width;
let max_height = (*state.info).max_height;
if width == 0 || height == 0 || width > max_width || height > max_height {
(*state.info).state = GAME_CAPTURE_STATE_RESIZE_REQUIRED;
(*state.info).width = width;
(*state.info).height = height;
return false;
}
let Some(bytes) = frame_buffer_size(width, height) else {
(*state.info).state = GAME_CAPTURE_STATE_ERROR;
(*state.info).last_error = 61;
return false;
};
if bytes > state.frame_buffer_capacity {
(*state.info).state = GAME_CAPTURE_STATE_RESIZE_REQUIRED;
(*state.info).width = width;
(*state.info).height = height;
return false;
}
let mut index = state.next_frame_index;
let mut mutex = state.mutexes[index];
let mut wait = WaitForSingleObject(mutex, FRAME_MUTEX_WAIT_MS);
if wait != WAIT_OBJECT_0 && wait != WAIT_ABANDONED {
index = (index + 1) % GAME_CAPTURE_BUFFER_COUNT;
mutex = state.mutexes[index];
wait = WaitForSingleObject(mutex, FRAME_MUTEX_WAIT_MS);
}
if wait != WAIT_OBJECT_0 && wait != WAIT_ABANDONED {
return false;
}
let row_bytes = (width * 4) as usize;
let dst_base = state.frame_base.add(state.frame_buffer_capacity * index);
for y in 0..height as usize {
let src = src_base.add(y * src_row_pitch);
let dst = dst_base.add(y * row_bytes);
match format_mode {
FormatMode::Bgra => std::ptr::copy_nonoverlapping(src, dst, row_bytes),
FormatMode::Rgba => {
for x in 0..width as usize {
let src_px = src.add(x * 4);
let dst_px = dst.add(x * 4);
*dst_px.add(0) = *src_px.add(2);
*dst_px.add(1) = *src_px.add(1);
*dst_px.add(2) = *src_px.add(0);
*dst_px.add(3) = *src_px.add(3);
}
}
}
}
let info = &mut *state.info;
info.hwnd = hwnd as u64;
info.width = width;
info.height = height;
info.pitch = width * 4;
info.frame_index = index as u32;
info.frame_counter = info.frame_counter.wrapping_add(1);
info.timestamp_us = now_us();
info.transport = GAME_CAPTURE_TRANSPORT_MEMORY;
info.dxgi_format = 0;
info.texture_handle = 0;
info.api_type = GAME_CAPTURE_API_VULKAN;
info.fallback_reason = fallback_reason;
info.capture_flags = 0;
info.state = GAME_CAPTURE_STATE_ACTIVE;
state.next_frame_index = (index + 1) % GAME_CAPTURE_BUFFER_COUNT;
ReleaseMutex(mutex);
SetEvent(state.ready_event);
true
}
fn format_mode(format: vk::Format) -> Option<FormatMode> {
match format {
vk::Format::B8G8R8A8_UNORM | vk::Format::B8G8R8A8_SRGB => Some(FormatMode::Bgra),
vk::Format::R8G8B8A8_UNORM | vk::Format::R8G8B8A8_SRGB => Some(FormatMode::Rgba),
_ => None,
}
}
fn hdr_format_flags(format: vk::Format) -> u32 {
match format {
vk::Format::A2B10G10R10_UNORM_PACK32
| vk::Format::A2R10G10B10_UNORM_PACK32
| vk::Format::A2B10G10R10_SNORM_PACK32
| vk::Format::A2R10G10B10_SNORM_PACK32 => GAME_CAPTURE_FLAG_TEN_BIT | GAME_CAPTURE_FLAG_HDR,
vk::Format::R16G16B16A16_SFLOAT | vk::Format::R16G16B16A16_UNORM => GAME_CAPTURE_FLAG_HDR,
_ => 0,
}
}
unsafe fn destroy_shared_texture_resources(
device: &ash::Device,
resources: &mut SharedTextureResources,
) {
let _ = device.wait_for_fences(&[resources.fence], true, CAPTURE_FENCE_TIMEOUT_NS);
device.destroy_fence(resources.fence, None);
if resources.present_wait_semaphore != vk::Semaphore::null() {
device.destroy_semaphore(resources.present_wait_semaphore, None);
resources.present_wait_semaphore = vk::Semaphore::null();
}
device.destroy_command_pool(resources.command_pool, None);
device.destroy_image(resources.image, None);
device.free_memory(resources.memory, None);
resources.fence = vk::Fence::null();
resources.command_pool = vk::CommandPool::null();
resources.image = vk::Image::null();
resources.memory = vk::DeviceMemory::null();
}
unsafe fn query_external_image_support(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
format: vk::Format,
) -> Result<bool, vk::Result> {
let mut external_info =
vk::PhysicalDeviceExternalImageFormatInfo::default().handle_type(SHARED_HANDLE_TYPE);
let format_info = vk::PhysicalDeviceImageFormatInfo2::default()
.format(format)
.ty(vk::ImageType::TYPE_2D)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::TRANSFER_DST)
.flags(vk::ImageCreateFlags::empty())
.push_next(&mut external_info);
let mut external_props = vk::ExternalImageFormatProperties::default();
let mut props = vk::ImageFormatProperties2::default().push_next(&mut external_props);
instance.get_physical_device_image_format_properties2(
physical_device,
&format_info,
&mut props,
)?;
let features = external_props
.external_memory_properties
.external_memory_features;
if !features.contains(vk::ExternalMemoryFeatureFlags::IMPORTABLE) {
return Err(vk::Result::ERROR_FORMAT_NOT_SUPPORTED);
}
Ok(features.contains(vk::ExternalMemoryFeatureFlags::DEDICATED_ONLY))
}
unsafe fn create_shared_texture_resources(
device_state: &LayerDevice,
queue_family_index: u32,
width: u32,
height: u32,
format: InteropFormat,
) -> Result<SharedTextureResources, vk::Result> {
if !device_state.external_memory_enabled || device_state.khr_external_memory_win32.is_none() {
return Err(vk::Result::ERROR_EXTENSION_NOT_PRESENT);
}
let device = &device_state.ash_device;
let Some(instance_state) = INSTANCE_MAP.get(&device_state.instance) else {
return Err(vk::Result::ERROR_INITIALIZATION_FAILED);
};
let instance = instance_state.ash_instance.clone();
drop(instance_state);
let dedicated_required = match query_external_image_support(
&instance,
device_state.physical_device,
format.vk_format,
) {
Ok(v) => {
vlog!("step query_external_image_support OK (dedicated_required={v})");
v
}
Err(e) => {
vlog!(
"step query_external_image_support advisory-fail {e:?} (handle_type=D3D11_TEXTURE_KMT, format={:?}); attempting import anyway",
format.vk_format
);
true
}
};
let shared_texture = {
let mut guard = D3D11_DEVICE
.lock()
.map_err(|_| vk::Result::ERROR_INITIALIZATION_FAILED)?;
if guard.is_none() {
*guard = D3d11Device::create();
}
let d3d11 = guard
.as_ref()
.ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)?;
match d3d11.create_shared_texture(width, height, format) {
Some(tex) => {
vlog!("step create_shared_texture OK (handle={:#x})", tex.handle);
tex
}
None => {
vlog!("step create_shared_texture FAILED (D3D11 CreateTexture2D/GetSharedHandle)");
return Err(vk::Result::ERROR_OUT_OF_DEVICE_MEMORY);
}
}
};
let mut external_image_info =
vk::ExternalMemoryImageCreateInfo::default().handle_types(SHARED_HANDLE_TYPE);
let image_info = vk::ImageCreateInfo {
image_type: vk::ImageType::TYPE_2D,
format: format.vk_format,
extent: vk::Extent3D {
width,
height,
depth: 1,
},
mip_levels: 1,
array_layers: 1,
samples: vk::SampleCountFlags::TYPE_1,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::TRANSFER_DST,
sharing_mode: vk::SharingMode::EXCLUSIVE,
initial_layout: vk::ImageLayout::UNDEFINED,
..Default::default()
}
.push_next(&mut external_image_info);
let image = match device.create_image(&image_info, None) {
Ok(image) => image,
Err(e) => {
vlog!("step create_image (external) FAILED {e:?}");
return Err(e);
}
};
let cleanup_image = |device: &ash::Device, image: vk::Image, shared: SharedTexture| {
device.destroy_image(image, None);
drop(shared);
};
let requirements = device.get_image_memory_requirements(image);
let Some(instance_state) = INSTANCE_MAP.get(&device_state.instance) else {
cleanup_image(device, image, shared_texture);
return Err(vk::Result::ERROR_INITIALIZATION_FAILED);
};
let memory_properties = instance_state
.ash_instance
.get_physical_device_memory_properties(device_state.physical_device);
drop(instance_state);
let Some(memory_type_index) = find_memory_type(
&memory_properties,
requirements.memory_type_bits,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)
.or_else(|| {
find_memory_type(
&memory_properties,
requirements.memory_type_bits,
vk::MemoryPropertyFlags::empty(),
)
}) else {
cleanup_image(device, image, shared_texture);
return Err(vk::Result::ERROR_FEATURE_NOT_PRESENT);
};
let want_dedicated = dedicated_required || device_state.dedicated_allocation_enabled;
let mut dedicated_info = vk::MemoryDedicatedAllocateInfo::default().image(image);
let mut import_info = vk::ImportMemoryWin32HandleInfoKHR {
handle_type: SHARED_HANDLE_TYPE,
handle: shared_texture.handle as usize as vk::HANDLE,
..Default::default()
};
let mut allocate_info = vk::MemoryAllocateInfo {
allocation_size: requirements.size,
memory_type_index,
..Default::default()
}
.push_next(&mut import_info);
if want_dedicated {
allocate_info = allocate_info.push_next(&mut dedicated_info);
}
let memory = match device.allocate_memory(&allocate_info, None) {
Ok(memory) => memory,
Err(error) => {
vlog!(
"step allocate_memory (import KMT handle) FAILED {error:?} (want_dedicated={want_dedicated}, mem_type={memory_type_index})"
);
cleanup_image(device, image, shared_texture);
return Err(error);
}
};
if let Err(error) = device.bind_image_memory(image, memory, 0) {
vlog!("step bind_image_memory FAILED {error:?}");
device.free_memory(memory, None);
cleanup_image(device, image, shared_texture);
return Err(error);
}
vlog!("shared-texture resources created OK (fast path live)");
let pool_info = vk::CommandPoolCreateInfo {
flags: vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
queue_family_index,
..Default::default()
};
let command_pool = match device.create_command_pool(&pool_info, None) {
Ok(pool) => pool,
Err(error) => {
device.free_memory(memory, None);
cleanup_image(device, image, shared_texture);
return Err(error);
}
};
let alloc_info = vk::CommandBufferAllocateInfo {
command_pool,
level: vk::CommandBufferLevel::PRIMARY,
command_buffer_count: 1,
..Default::default()
};
let command_buffer = match device.allocate_command_buffers(&alloc_info) {
Ok(mut buffers) => buffers.remove(0),
Err(error) => {
device.destroy_command_pool(command_pool, None);
device.free_memory(memory, None);
cleanup_image(device, image, shared_texture);
return Err(error);
}
};
let fence = match device.create_fence(&vk::FenceCreateInfo::default(), None) {
Ok(fence) => fence,
Err(error) => {
device.destroy_command_pool(command_pool, None);
device.free_memory(memory, None);
cleanup_image(device, image, shared_texture);
return Err(error);
}
};
let present_wait_semaphore =
match device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None) {
Ok(semaphore) => semaphore,
Err(error) => {
device.destroy_fence(fence, None);
device.destroy_command_pool(command_pool, None);
device.free_memory(memory, None);
cleanup_image(device, image, shared_texture);
return Err(error);
}
};
Ok(SharedTextureResources {
width,
height,
format,
shared_texture,
image,
memory,
command_pool,
command_buffer,
fence,
present_wait_semaphore,
initialised: false,
})
}
unsafe fn ensure_shared_texture_resources<'a>(
device_state: &LayerDevice,
queue_state: &'a QueueState,
width: u32,
height: u32,
format: InteropFormat,
) -> Result<std::sync::MutexGuard<'a, Option<SharedTextureResources>>, vk::Result> {
let mut guard = queue_state
.shared
.lock()
.map_err(|_| vk::Result::ERROR_INITIALIZATION_FAILED)?;
let recreate = guard
.as_ref()
.map(|resources| {
resources.width != width
|| resources.height != height
|| resources.format.vk_format != format.vk_format
})
.unwrap_or(true);
if recreate {
if let Some(mut old) = guard.take() {
destroy_shared_texture_resources(&device_state.ash_device, &mut old);
}
*guard = Some(create_shared_texture_resources(
device_state,
queue_state.family_index,
width,
height,
format,
)?);
}
Ok(guard)
}
unsafe fn capture_swapchain_image_shared(
device_state: &LayerDevice,
queue_state: &QueueState,
queue: vk::Queue,
swapchain: &SwapchainState,
image_index: u32,
present_info: &vk::PresentInfoKHR,
ipc: &mut IpcState,
) -> Result<CaptureSubmit, vk::Result> {
if !swapchain.can_capture || !queue_state.supports_transfer {
return Err(vk::Result::ERROR_FEATURE_NOT_PRESENT);
}
if !device_state.external_memory_enabled {
return Err(vk::Result::ERROR_EXTENSION_NOT_PRESENT);
}
let Some(format) = interop_format(swapchain.format) else {
return Err(vk::Result::ERROR_FORMAT_NOT_SUPPORTED);
};
let Some(&image) = swapchain.images.get(image_index as usize) else {
return Err(vk::Result::ERROR_OUT_OF_DATE_KHR);
};
let width = swapchain.extent.width;
let height = swapchain.extent.height;
if width == 0 || height == 0 {
return Err(vk::Result::ERROR_OUT_OF_DATE_KHR);
}
let mut resources_guard =
ensure_shared_texture_resources(device_state, queue_state, width, height, format)?;
let resources = resources_guard
.as_mut()
.ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)?;
let device = &device_state.ash_device;
let dst_image = resources.image;
let shared_handle = resources.shared_texture.handle;
device.reset_fences(&[resources.fence])?;
device.reset_command_pool(resources.command_pool, vk::CommandPoolResetFlags::empty())?;
let begin_info = vk::CommandBufferBeginInfo {
flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT,
..Default::default()
};
device.begin_command_buffer(resources.command_buffer, &begin_info)?;
let range = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
let present_layout = swapchain.present_layout;
let shared_present = present_layout == vk::ImageLayout::SHARED_PRESENT_KHR;
let src_copy_layout = if shared_present {
vk::ImageLayout::SHARED_PRESENT_KHR
} else {
vk::ImageLayout::TRANSFER_SRC_OPTIMAL
};
let mut pre_barriers: Vec<vk::ImageMemoryBarrier> = Vec::with_capacity(2);
if !shared_present {
pre_barriers.push(vk::ImageMemoryBarrier {
src_access_mask: vk::AccessFlags::MEMORY_READ,
dst_access_mask: vk::AccessFlags::TRANSFER_READ,
old_layout: present_layout,
new_layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
image,
subresource_range: range,
..Default::default()
});
}
let dst_old_layout = if resources.initialised {
vk::ImageLayout::GENERAL
} else {
vk::ImageLayout::UNDEFINED
};
let (dst_pre_src_queue_family, dst_pre_dst_queue_family) =
shared_texture_dst_pre_queue_families(resources.initialised, queue_state.family_index);
pre_barriers.push(vk::ImageMemoryBarrier {
src_access_mask: vk::AccessFlags::empty(),
dst_access_mask: vk::AccessFlags::TRANSFER_WRITE,
old_layout: dst_old_layout,
new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
src_queue_family_index: dst_pre_src_queue_family,
dst_queue_family_index: dst_pre_dst_queue_family,
image: dst_image,
subresource_range: range,
..Default::default()
});
device.cmd_pipeline_barrier(
resources.command_buffer,
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&pre_barriers,
);
let copy = vk::ImageCopy {
src_subresource: vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
},
src_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
dst_subresource: vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
},
dst_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
extent: vk::Extent3D {
width,
height,
depth: 1,
},
};
device.cmd_copy_image(
resources.command_buffer,
image,
src_copy_layout,
dst_image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&[copy],
);
let mut post_barriers: Vec<vk::ImageMemoryBarrier> = Vec::with_capacity(2);
if !shared_present {
post_barriers.push(vk::ImageMemoryBarrier {
src_access_mask: vk::AccessFlags::TRANSFER_READ,
dst_access_mask: vk::AccessFlags::MEMORY_READ,
old_layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
new_layout: present_layout,
src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
image,
subresource_range: range,
..Default::default()
});
}
post_barriers.push(vk::ImageMemoryBarrier {
src_access_mask: vk::AccessFlags::TRANSFER_WRITE,
dst_access_mask: vk::AccessFlags::empty(),
old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
new_layout: vk::ImageLayout::GENERAL,
src_queue_family_index: queue_state.family_index,
dst_queue_family_index: vk::QUEUE_FAMILY_EXTERNAL,
image: dst_image,
subresource_range: range,
..Default::default()
});
device.cmd_pipeline_barrier(
resources.command_buffer,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
vk::DependencyFlags::empty(),
&[],
&[],
&post_barriers,
);
device.end_command_buffer(resources.command_buffer)?;
let chained_wait = submit_capture(
device,
queue,
resources.command_buffer,
resources.fence,
resources.present_wait_semaphore,
present_info,
)?;
resources.initialised = true;
if device
.wait_for_fences(&[resources.fence], true, CAPTURE_FENCE_TIMEOUT_NS)
.is_err()
{
(*ipc.info).dropped_frame_counter = (*ipc.info).dropped_frame_counter.wrapping_add(1);
return Ok(CaptureSubmit { chained_wait });
}
publish_shared_texture(ipc, swapchain.hwnd, width, height, format, shared_handle);
Ok(CaptureSubmit { chained_wait })
}
fn shared_texture_dst_pre_queue_families(initialised: bool, queue_family_index: u32) -> (u32, u32) {
if initialised {
(vk::QUEUE_FAMILY_EXTERNAL, queue_family_index)
} else {
(vk::QUEUE_FAMILY_IGNORED, vk::QUEUE_FAMILY_IGNORED)
}
}
unsafe fn submit_capture(
device: &ash::Device,
queue: vk::Queue,
command_buffer: vk::CommandBuffer,
fence: vk::Fence,
signal_semaphore: vk::Semaphore,
present_info: &vk::PresentInfoKHR,
) -> Result<Option<vk::Semaphore>, vk::Result> {
let wait_semaphores = present_wait_semaphores(present_info);
let wait_stages = vec![vk::PipelineStageFlags::TRANSFER; wait_semaphores.len()];
let chain = !wait_semaphores.is_empty();
let signal = if chain {
[signal_semaphore]
} else {
[vk::Semaphore::null(); 1]
};
let submit = vk::SubmitInfo {
wait_semaphore_count: wait_semaphores.len() as u32,
p_wait_semaphores: wait_semaphores.as_ptr(),
p_wait_dst_stage_mask: wait_stages.as_ptr(),
command_buffer_count: 1,
p_command_buffers: &command_buffer,
signal_semaphore_count: if chain { 1 } else { 0 },
p_signal_semaphores: if chain { signal.as_ptr() } else { null() },
..Default::default()
};
device.queue_submit(queue, &[submit], fence)?;
Ok(chain.then_some(signal_semaphore))
}
unsafe fn present_wait_semaphores<'a>(
present_info: &vk::PresentInfoKHR<'a>,
) -> &'a [vk::Semaphore] {
if present_info.wait_semaphore_count > 0 && !present_info.p_wait_semaphores.is_null() {
std::slice::from_raw_parts(
present_info.p_wait_semaphores,
present_info.wait_semaphore_count as usize,
)
} else {
&[]
}
}
unsafe fn publish_shared_texture(
state: &mut IpcState,
hwnd: vk::HWND,
width: u32,
height: u32,
format: InteropFormat,
shared_handle: u64,
) {
let info = &mut *state.info;
info.hwnd = hwnd as u64;
info.width = width;
info.height = height;
info.pitch = width * 4;
info.frame_index = 0;
info.frame_counter = info.frame_counter.wrapping_add(1);
info.timestamp_us = now_us();
info.transport = GAME_CAPTURE_TRANSPORT_SHARED_TEXTURE;
info.dxgi_format = format.dxgi_format.0 as u32;
info.texture_handle = shared_handle;
info.api_type = GAME_CAPTURE_API_VULKAN;
info.fallback_reason = GAME_CAPTURE_FALLBACK_NONE;
info.capture_flags = format.capture_flags;
info.state = GAME_CAPTURE_STATE_ACTIVE;
SetEvent(state.ready_event);
}
unsafe fn destroy_readback_resources(device: &ash::Device, resources: &mut ReadbackResources) {
let _ = device.wait_for_fences(&[resources.fence], true, CAPTURE_FENCE_TIMEOUT_NS);
device.destroy_fence(resources.fence, None);
if resources.present_wait_semaphore != vk::Semaphore::null() {
device.destroy_semaphore(resources.present_wait_semaphore, None);
resources.present_wait_semaphore = vk::Semaphore::null();
}
device.destroy_command_pool(resources.command_pool, None);
device.destroy_buffer(resources.buffer, None);
device.free_memory(resources.memory, None);
resources.fence = vk::Fence::null();
resources.command_pool = vk::CommandPool::null();
resources.buffer = vk::Buffer::null();
resources.memory = vk::DeviceMemory::null();
}
fn find_memory_type(
properties: &vk::PhysicalDeviceMemoryProperties,
type_bits: u32,
required: vk::MemoryPropertyFlags,
) -> Option<u32> {
for index in 0..properties.memory_type_count {
let supported = (type_bits & (1 << index)) != 0;
let flags = properties.memory_types[index as usize].property_flags;
if supported && flags.contains(required) {
return Some(index);
}
}
None
}
unsafe fn create_readback_resources(
device_state: &LayerDevice,
queue_family_index: u32,
width: u32,
height: u32,
format_mode: FormatMode,
) -> Result<ReadbackResources, vk::Result> {
let device = &device_state.ash_device;
let size = (width as vk::DeviceSize)
.checked_mul(height as vk::DeviceSize)
.and_then(|value| value.checked_mul(4))
.ok_or(vk::Result::ERROR_OUT_OF_HOST_MEMORY)?;
let buffer_info = vk::BufferCreateInfo {
size,
usage: vk::BufferUsageFlags::TRANSFER_DST,
sharing_mode: vk::SharingMode::EXCLUSIVE,
..Default::default()
};
let buffer = device.create_buffer(&buffer_info, None)?;
let requirements = device.get_buffer_memory_requirements(buffer);
let Some(instance_state) = INSTANCE_MAP.get(&device_state.instance) else {
device.destroy_buffer(buffer, None);
return Err(vk::Result::ERROR_INITIALIZATION_FAILED);
};
let memory_properties = instance_state
.ash_instance
.get_physical_device_memory_properties(device_state.physical_device);
let memory_type_index = match find_memory_type(
&memory_properties,
requirements.memory_type_bits,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
) {
Some(index) => index,
None => {
device.destroy_buffer(buffer, None);
return Err(vk::Result::ERROR_FEATURE_NOT_PRESENT);
}
};
let allocate_info = vk::MemoryAllocateInfo {
allocation_size: requirements.size,
memory_type_index,
..Default::default()
};
let memory = match device.allocate_memory(&allocate_info, None) {
Ok(memory) => memory,
Err(error) => {
device.destroy_buffer(buffer, None);
return Err(error);
}
};
if let Err(error) = device.bind_buffer_memory(buffer, memory, 0) {
device.free_memory(memory, None);
device.destroy_buffer(buffer, None);
return Err(error);
}
let pool_info = vk::CommandPoolCreateInfo {
flags: vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
queue_family_index,
..Default::default()
};
let command_pool = match device.create_command_pool(&pool_info, None) {
Ok(pool) => pool,
Err(error) => {
device.free_memory(memory, None);
device.destroy_buffer(buffer, None);
return Err(error);
}
};
let alloc_info = vk::CommandBufferAllocateInfo {
command_pool,
level: vk::CommandBufferLevel::PRIMARY,
command_buffer_count: 1,
..Default::default()
};
let command_buffer = match device.allocate_command_buffers(&alloc_info) {
Ok(mut buffers) => buffers.remove(0),
Err(error) => {
device.destroy_command_pool(command_pool, None);
device.free_memory(memory, None);
device.destroy_buffer(buffer, None);
return Err(error);
}
};
let fence = match device.create_fence(&vk::FenceCreateInfo::default(), None) {
Ok(fence) => fence,
Err(error) => {
device.destroy_command_pool(command_pool, None);
device.free_memory(memory, None);
device.destroy_buffer(buffer, None);
return Err(error);
}
};
let present_wait_semaphore =
match device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None) {
Ok(semaphore) => semaphore,
Err(error) => {
device.destroy_fence(fence, None);
device.destroy_command_pool(command_pool, None);
device.free_memory(memory, None);
device.destroy_buffer(buffer, None);
return Err(error);
}
};
Ok(ReadbackResources {
width,
height,
format_mode,
buffer,
memory,
command_pool,
command_buffer,
fence,
present_wait_semaphore,
size,
})
}
unsafe fn ensure_readback_resources<'a>(
device_state: &LayerDevice,
queue_state: &'a QueueState,
width: u32,
height: u32,
format_mode: FormatMode,
) -> Result<std::sync::MutexGuard<'a, Option<ReadbackResources>>, vk::Result> {
let mut guard = queue_state
.resources
.lock()
.map_err(|_| vk::Result::ERROR_INITIALIZATION_FAILED)?;
let recreate = guard
.as_ref()
.map(|resources| {
resources.width != width
|| resources.height != height
|| resources.format_mode != format_mode
})
.unwrap_or(true);
if recreate {
if let Some(mut old) = guard.take() {
destroy_readback_resources(&device_state.ash_device, &mut old);
}
*guard = Some(create_readback_resources(
device_state,
queue_state.family_index,
width,
height,
format_mode,
)?);
}
Ok(guard)
}
unsafe fn capture_swapchain_image(
device_state: &LayerDevice,
queue_state: &QueueState,
queue: vk::Queue,
swapchain: &SwapchainState,
image_index: u32,
present_info: &vk::PresentInfoKHR,
ipc: &mut IpcState,
fallback_reason: u32,
) -> Result<CaptureSubmit, vk::Result> {
if !swapchain.can_capture || !queue_state.supports_transfer {
return Err(vk::Result::ERROR_FEATURE_NOT_PRESENT);
}
let Some(format_mode) = format_mode(swapchain.format) else {
return Err(vk::Result::ERROR_FORMAT_NOT_SUPPORTED);
};
let Some(&image) = swapchain.images.get(image_index as usize) else {
return Err(vk::Result::ERROR_OUT_OF_DATE_KHR);
};
let width = swapchain.extent.width;
let height = swapchain.extent.height;
if width == 0 || height == 0 {
return Err(vk::Result::ERROR_OUT_OF_DATE_KHR);
}
let mut resources_guard =
ensure_readback_resources(device_state, queue_state, width, height, format_mode)?;
let resources = resources_guard
.as_mut()
.ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)?;
let device = &device_state.ash_device;
device.reset_fences(&[resources.fence])?;
device.reset_command_pool(resources.command_pool, vk::CommandPoolResetFlags::empty())?;
let begin_info = vk::CommandBufferBeginInfo {
flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT,
..Default::default()
};
device.begin_command_buffer(resources.command_buffer, &begin_info)?;
let range = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
let present_layout = swapchain.present_layout;
let shared_present = present_layout == vk::ImageLayout::SHARED_PRESENT_KHR;
let src_copy_layout = if shared_present {
vk::ImageLayout::SHARED_PRESENT_KHR
} else {
vk::ImageLayout::TRANSFER_SRC_OPTIMAL
};
if !shared_present {
let to_transfer = vk::ImageMemoryBarrier {
src_access_mask: vk::AccessFlags::MEMORY_READ,
dst_access_mask: vk::AccessFlags::TRANSFER_READ,
old_layout: present_layout,
new_layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
image,
subresource_range: range,
..Default::default()
};
device.cmd_pipeline_barrier(
resources.command_buffer,
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&[to_transfer],
);
}
let copy = vk::BufferImageCopy {
buffer_offset: 0,
buffer_row_length: 0,
buffer_image_height: 0,
image_subresource: vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
},
image_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
image_extent: vk::Extent3D {
width,
height,
depth: 1,
},
};
device.cmd_copy_image_to_buffer(
resources.command_buffer,
image,
src_copy_layout,
resources.buffer,
&[copy],
);
if !shared_present {
let to_present = vk::ImageMemoryBarrier {
src_access_mask: vk::AccessFlags::TRANSFER_READ,
dst_access_mask: vk::AccessFlags::MEMORY_READ,
old_layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
new_layout: present_layout,
src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
image,
subresource_range: range,
..Default::default()
};
device.cmd_pipeline_barrier(
resources.command_buffer,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
vk::DependencyFlags::empty(),
&[],
&[],
&[to_present],
);
}
device.end_command_buffer(resources.command_buffer)?;
let chained_wait = submit_capture(
device,
queue,
resources.command_buffer,
resources.fence,
resources.present_wait_semaphore,
present_info,
)?;
if device
.wait_for_fences(&[resources.fence], true, CAPTURE_FENCE_TIMEOUT_NS)
.is_err()
{
return Ok(CaptureSubmit { chained_wait });
}
let mapped = match device.map_memory(
resources.memory,
0,
resources.size,
vk::MemoryMapFlags::empty(),
) {
Ok(mapped) => mapped,
Err(_) => return Ok(CaptureSubmit { chained_wait }),
};
let _ = write_rows_to_shared_memory(
ipc,
swapchain.hwnd,
width,
height,
mapped.cast(),
(width * 4) as usize,
resources.format_mode,
fallback_reason,
);
device.unmap_memory(resources.memory);
Ok(CaptureSubmit { chained_wait })
}
unsafe fn capture_swapchain_for_present(
device_state: &LayerDevice,
queue_state: &QueueState,
queue: vk::Queue,
swapchain: &SwapchainState,
image_index: u32,
present_info: &vk::PresentInfoKHR,
ipc: &mut IpcState,
) -> Result<CaptureSubmit, vk::Result> {
if !target_matches(ipc, swapchain.hwnd) || !frame_due(ipc) {
return Err(vk::Result::NOT_READY);
}
let hdr_flags = hdr_format_flags(swapchain.format);
let fast_capturable = interop_format(swapchain.format).is_some();
let cpu_capturable = format_mode(swapchain.format).is_some();
if !fast_capturable && !cpu_capturable {
let info = &mut *ipc.info;
info.api_type = GAME_CAPTURE_API_VULKAN;
info.capture_flags = hdr_flags;
info.fallback_reason = GAME_CAPTURE_FALLBACK_FORMAT_UNSUPPORTED;
info.dropped_frame_counter = info.dropped_frame_counter.wrapping_add(1);
return Err(vk::Result::ERROR_FORMAT_NOT_SUPPORTED);
}
let mode = capture_mode();
let fast_path_possible = device_state.external_memory_enabled
&& fast_capturable
&& swapchain.shared_texture_allowed
&& !fast_path_permanently_disabled();
vlog!(
"present: mode={:?} fast_path_possible={} (ext_enabled={}, interop_format={:?}, shared_allowed={}, perm_disabled={}) swapchain_format={:?}",
mode,
fast_path_possible,
device_state.external_memory_enabled,
interop_format(swapchain.format).map(|f| f.dxgi_format.0),
swapchain.shared_texture_allowed,
fast_path_permanently_disabled(),
swapchain.format
);
if mode != CaptureMode::ForceCpu && fast_path_possible {
match capture_swapchain_image_shared(
device_state,
queue_state,
queue,
swapchain,
image_index,
present_info,
ipc,
) {
Ok(result) => return Ok(result),
Err(error) => {
vlog!("fast-path shared-texture FAILED: {:?}", error);
if matches!(
error,
vk::Result::ERROR_INITIALIZATION_FAILED
| vk::Result::ERROR_EXTENSION_NOT_PRESENT
| vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
| vk::Result::ERROR_FORMAT_NOT_SUPPORTED
| vk::Result::ERROR_FEATURE_NOT_PRESENT
| vk::Result::ERROR_INVALID_EXTERNAL_HANDLE
) {
disable_fast_path();
}
if mode == CaptureMode::ForceSharedTexture {
let info = &mut *ipc.info;
info.api_type = GAME_CAPTURE_API_VULKAN;
info.fallback_reason = GAME_CAPTURE_FALLBACK_EXTERNAL_MEMORY_UNSUPPORTED;
info.state = GAME_CAPTURE_STATE_ERROR;
info.last_error = 70;
info.dropped_frame_counter = info.dropped_frame_counter.wrapping_add(1);
return Err(error);
}
}
}
} else if mode == CaptureMode::ForceSharedTexture {
let info = &mut *ipc.info;
info.api_type = GAME_CAPTURE_API_VULKAN;
info.fallback_reason = if interop_format(swapchain.format).is_none() {
GAME_CAPTURE_FALLBACK_FORMAT_UNSUPPORTED
} else {
GAME_CAPTURE_FALLBACK_EXTERNAL_MEMORY_UNSUPPORTED
};
info.state = GAME_CAPTURE_STATE_ERROR;
info.last_error = 70;
info.dropped_frame_counter = info.dropped_frame_counter.wrapping_add(1);
return Err(vk::Result::ERROR_FEATURE_NOT_PRESENT);
}
if !cpu_capturable {
let info = &mut *ipc.info;
info.api_type = GAME_CAPTURE_API_VULKAN;
info.capture_flags = hdr_flags;
info.fallback_reason = if mode == CaptureMode::ForceCpu {
GAME_CAPTURE_FALLBACK_FORCED_CPU
} else {
GAME_CAPTURE_FALLBACK_FORMAT_UNSUPPORTED
};
info.dropped_frame_counter = info.dropped_frame_counter.wrapping_add(1);
return Err(vk::Result::ERROR_FORMAT_NOT_SUPPORTED);
}
let fallback_reason = if mode == CaptureMode::ForceCpu {
GAME_CAPTURE_FALLBACK_FORCED_CPU
} else {
GAME_CAPTURE_FALLBACK_EXTERNAL_MEMORY_UNSUPPORTED
};
capture_swapchain_image(
device_state,
queue_state,
queue,
swapchain,
image_index,
present_info,
ipc,
fallback_reason,
)
}
fn fast_path_permanently_disabled() -> bool {
FAST_PATH_DISABLED
.lock()
.map(|guard| *guard)
.unwrap_or(false)
}
fn disable_fast_path() {
if let Ok(mut guard) = FAST_PATH_DISABLED.lock() {
*guard = true;
}
}
unsafe fn try_capture_present(
queue: vk::Queue,
present_info: &vk::PresentInfoKHR,
) -> CaptureSubmit {
if present_info.swapchain_count == 0
|| present_info.p_swapchains.is_null()
|| present_info.p_image_indices.is_null()
{
return CaptureSubmit::none();
}
let Some(queue_state_ref) = QUEUE_MAP.get(&queue) else {
return CaptureSubmit::none();
};
let Some(device_state_ref) = DEVICE_MAP.get(&queue_state_ref.device) else {
return CaptureSubmit::none();
};
let mut ipc_guard = match IPC_STATE.lock() {
Ok(guard) => guard,
Err(_) => {
return CaptureSubmit::none();
}
};
if ipc_guard.is_none() {
*ipc_guard = open_ipc();
}
let Some(ipc) = ipc_guard.as_mut() else {
return CaptureSubmit::none();
};
if !capture_should_run(ipc) {
if let Some(mut state) = ipc_guard.take() {
free_ipc_state(&mut state);
}
return CaptureSubmit::none();
}
if host_supports_present_clock((*ipc.info).version) {
(*ipc.info).present_clock = GAME_CAPTURE_PRESENT_CLOCK_QPC;
(*ipc.info).last_present_timestamp_us = qpc_now_us();
} else {
(*ipc.info).last_present_timestamp_us = now_us();
}
let swapchains = std::slice::from_raw_parts(
present_info.p_swapchains,
present_info.swapchain_count as usize,
);
let indices = std::slice::from_raw_parts(
present_info.p_image_indices,
present_info.swapchain_count as usize,
);
for (swapchain_handle, image_index) in swapchains.iter().zip(indices.iter()) {
let Some(swapchain_ref) = SWAPCHAIN_MAP.get(swapchain_handle) else {
continue;
};
if swapchain_ref.device != queue_state_ref.device {
continue;
}
if let Ok(result) = capture_swapchain_for_present(
&device_state_ref,
&queue_state_ref,
queue,
&swapchain_ref,
*image_index,
present_info,
ipc,
) {
return result;
}
}
CaptureSubmit::none()
}
unsafe fn record_queue(device: vk::Device, queue: vk::Queue, family_index: u32) {
if queue == vk::Queue::null() {
return;
}
let supports_transfer = DEVICE_MAP
.get(&device)
.and_then(|device_state| {
device_state
.queue_families
.get(family_index as usize)
.copied()
})
.map(|properties| {
properties.queue_flags.intersects(
vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE | vk::QueueFlags::TRANSFER,
)
})
.unwrap_or(true);
QUEUE_MAP.insert(
queue,
QueueState {
device,
family_index,
supports_transfer,
resources: Mutex::new(None),
shared: Mutex::new(None),
},
);
}
unsafe fn destroy_device_state(device: vk::Device) -> Option<LayerDevice> {
let device_state = DEVICE_MAP.remove(&device).map(|(_, value)| value)?;
let queue_keys: Vec<vk::Queue> = QUEUE_MAP
.iter()
.filter_map(|entry| (entry.value().device == device).then_some(*entry.key()))
.collect();
for queue in queue_keys {
if let Some((_, queue_state)) = QUEUE_MAP.remove(&queue) {
if let Ok(mut resources) = queue_state.resources.lock() {
if let Some(mut resources) = resources.take() {
destroy_readback_resources(&device_state.ash_device, &mut resources);
}
}
if let Ok(mut shared) = queue_state.shared.lock() {
if let Some(mut shared) = shared.take() {
destroy_shared_texture_resources(&device_state.ash_device, &mut shared);
}
}
}
}
let swapchain_keys: Vec<vk::SwapchainKHR> = SWAPCHAIN_MAP
.iter()
.filter_map(|entry| (entry.value().device == device).then_some(*entry.key()))
.collect();
for swapchain in swapchain_keys {
SWAPCHAIN_MAP.remove(&swapchain);
}
GDPA_MAP.remove(&device);
Some(device_state)
}
unsafe extern "system" fn dispatch_next_vkGetInstanceProcAddr(
instance: vk::Instance,
p_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
if p_name.is_null() {
return None;
}
let name = CStr::from_ptr(p_name).to_bytes();
let pfn: *const () = match name {
b"vkGetInstanceProcAddr" => dispatch_next_vkGetInstanceProcAddr as _,
b"vkGetDeviceProcAddr" => dispatch_next_vkGetDeviceProcAddr as _,
b"vkEnumerateInstanceExtensionProperties"
| b"vkEnumerateInstanceLayerProperties"
| b"vkEnumerateInstanceVersion" => return None,
_ => {
let gipa = GIPA.get()?;
return gipa(instance, p_name);
}
};
Some(mem::transmute(pfn))
}
unsafe extern "system" fn dispatch_next_vkGetDeviceProcAddr(
device: vk::Device,
p_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
if p_name.is_null() {
return None;
}
let name = CStr::from_ptr(p_name).to_bytes();
if name == b"vkGetDeviceProcAddr" {
return Some(mem::transmute(
dispatch_next_vkGetDeviceProcAddr as *const (),
));
}
let gdpa = GDPA_MAP.get(&device)?;
gdpa(device, p_name)
}
#[unsafe(no_mangle)]
pub unsafe extern "system" fn vkNegotiateLoaderLayerInterfaceVersion(
p_version_struct: *mut NegotiateLayerInterface,
) -> vk::Result {
if p_version_struct.is_null() {
return vk::Result::ERROR_INITIALIZATION_FAILED;
}
let version_struct = &mut *p_version_struct;
version_struct.loader_layer_interface_version =
version_struct.loader_layer_interface_version.min(2);
version_struct.pfn_get_instance_proc_addr = Some(flux_vkGetInstanceProcAddr);
version_struct.pfn_get_device_proc_addr = Some(flux_vkGetDeviceProcAddr);
version_struct.pfn_get_physical_device_proc_addr = Some(flux_vk_layerGetPhysicalDeviceProcAddr);
vk::Result::SUCCESS
}
const _: PFN_vkNegotiateLoaderLayerInterfaceVersion = vkNegotiateLoaderLayerInterfaceVersion;
#[unsafe(no_mangle)]
pub unsafe extern "system" fn vkGetInstanceProcAddr(
instance: vk::Instance,
p_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
flux_vkGetInstanceProcAddr(instance, p_name)
}
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkGetInstanceProcAddr(
instance: vk::Instance,
p_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
if p_name.is_null() {
return None;
}
let name = CStr::from_ptr(p_name).to_bytes();
let pfn: *const () = match name {
b"vkGetInstanceProcAddr" => flux_vkGetInstanceProcAddr as _,
b"vkGetDeviceProcAddr" => flux_vkGetDeviceProcAddr as _,
b"vkCreateInstance" => flux_vkCreateInstance as _,
b"vkDestroyInstance" => flux_vkDestroyInstance as _,
b"vkCreateDevice" => flux_vkCreateDevice as _,
b"vkDestroyDevice" => flux_vkDestroyDevice as _,
b"vkCreateWin32SurfaceKHR" => flux_vkCreateWin32SurfaceKHR as _,
b"vkDestroySurfaceKHR" => flux_vkDestroySurfaceKHR as _,
b"vk_layerGetPhysicalDeviceProcAddr" => flux_vk_layerGetPhysicalDeviceProcAddr as _,
_ => {
let gipa = GIPA.get()?;
return gipa(instance, p_name);
}
};
Some(mem::transmute(pfn))
}
const _: vk::PFN_vkGetInstanceProcAddr = flux_vkGetInstanceProcAddr;
#[unsafe(no_mangle)]
pub unsafe extern "system" fn vkGetDeviceProcAddr(
device: vk::Device,
p_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
flux_vkGetDeviceProcAddr(device, p_name)
}
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkGetDeviceProcAddr(
device: vk::Device,
p_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
if p_name.is_null() {
return None;
}
let name = CStr::from_ptr(p_name).to_bytes();
let pfn: *const () = match name {
b"vkGetDeviceProcAddr" => flux_vkGetDeviceProcAddr as _,
b"vkCreateDevice" => flux_vkCreateDevice as _,
b"vkDestroyDevice" => flux_vkDestroyDevice as _,
b"vkCreateSwapchainKHR" => flux_vkCreateSwapchainKHR as _,
b"vkDestroySwapchainKHR" => flux_vkDestroySwapchainKHR as _,
b"vkGetSwapchainImagesKHR" => flux_vkGetSwapchainImagesKHR as _,
b"vkQueuePresentKHR" => flux_vkQueuePresentKHR as _,
b"vkGetDeviceQueue" => flux_vkGetDeviceQueue as _,
b"vkGetDeviceQueue2" => flux_vkGetDeviceQueue2 as _,
_ => {
let gdpa = GDPA_MAP.get(&device)?;
return gdpa(device, p_name);
}
};
Some(mem::transmute(pfn))
}
const _: vk::PFN_vkGetDeviceProcAddr = flux_vkGetDeviceProcAddr;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vk_layerGetPhysicalDeviceProcAddr(
instance: vk::Instance,
p_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
if p_name.is_null() {
return None;
}
let name = CStr::from_ptr(p_name).to_bytes();
if name == b"vkCreateDevice" {
return Some(mem::transmute(flux_vkCreateDevice as *const ()));
}
let gphypa = GPHYPA.get()?;
gphypa(instance, p_name)
}
const _: PFN_vk_layerGetPhysicalDeviceProcAddr = flux_vk_layerGetPhysicalDeviceProcAddr;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkCreateInstance(
p_create_info: *const vk::InstanceCreateInfo,
p_allocator: *const vk::AllocationCallbacks,
p_instance: *mut vk::Instance,
) -> vk::Result {
if p_create_info.is_null() || p_instance.is_null() {
return vk::Result::ERROR_INITIALIZATION_FAILED;
}
let create_info = p_create_info.read();
let Some(mut chain_info) =
get_instance_chain_info(&create_info, LayerFunction::LAYER_LINK_INFO)
else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let chain_info = chain_info.as_mut();
let layer_info = chain_info.u.p_layer_info.read();
chain_info.u.p_layer_info = layer_info.p_next;
let Some(gipa) = layer_info.pfn_next_get_instance_proc_addr else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
if let Some(gphypa) = layer_info.pfn_next_get_physical_device_proc_addr {
let _ = GPHYPA.set(gphypa);
}
let create_name = CStr::from_bytes_with_nul_unchecked(b"vkCreateInstance\0");
let Some(create_instance_ptr) = gipa(vk::Instance::null(), create_name.as_ptr()) else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let create_instance: vk::PFN_vkCreateInstance = mem::transmute(create_instance_ptr);
let augmented = AugmentedExtensions::build(
create_info.pp_enabled_extension_names,
create_info.enabled_extension_count,
&[
INSTANCE_EXT_PHYSICAL_DEVICE_PROPERTIES2,
INSTANCE_EXT_EXTERNAL_MEMORY_CAPABILITIES,
],
);
let mut augmented_info = create_info;
augmented_info.enabled_extension_count = augmented.count();
augmented_info.pp_enabled_extension_names = augmented.as_ptr();
let result = if capture_mode() != CaptureMode::ForceCpu {
let augmented_result = create_instance(&augmented_info, p_allocator, p_instance);
if augmented_result == vk::Result::SUCCESS {
augmented_result
} else {
create_instance(p_create_info, p_allocator, p_instance)
}
} else {
create_instance(p_create_info, p_allocator, p_instance)
};
if result != vk::Result::SUCCESS {
return result;
}
let instance = *p_instance;
let _ = GIPA.set(gipa);
let entry = ash::Entry::from_static_fn(ash::StaticFn {
get_instance_proc_addr: dispatch_next_vkGetInstanceProcAddr,
});
let _ = ENTRY.set(entry.clone());
let ash_instance = ash::Instance::load(entry.static_fn(), instance);
let khr_surface = khr::surface::Instance::new(&entry, &ash_instance);
let khr_win32_surface = khr::win32_surface::Instance::new(&entry, &ash_instance);
if let Ok(physical_devices) = ash_instance.enumerate_physical_devices() {
for physical_device in physical_devices {
PHY_TO_INSTANCE_MAP.insert(physical_device, instance);
}
}
INSTANCE_MAP.insert(
instance,
LayerInstance {
ash_instance,
khr_surface,
khr_win32_surface,
},
);
vk::Result::SUCCESS
}
const _: vk::PFN_vkCreateInstance = flux_vkCreateInstance;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkDestroyInstance(
instance: vk::Instance,
p_allocator: *const vk::AllocationCallbacks,
) {
let Some((_, instance_state)) = INSTANCE_MAP.remove(&instance) else {
return;
};
if let Ok(physical_devices) = instance_state.ash_instance.enumerate_physical_devices() {
for physical_device in physical_devices {
PHY_TO_INSTANCE_MAP.remove(&physical_device);
}
}
let surfaces: Vec<vk::SurfaceKHR> = SURFACE_MAP
.iter()
.filter_map(|entry| (entry.value().instance == instance).then_some(*entry.key()))
.collect();
for surface in surfaces {
SURFACE_MAP.remove(&surface);
}
(instance_state.ash_instance.fp_v1_0().destroy_instance)(instance, p_allocator);
}
const _: vk::PFN_vkDestroyInstance = flux_vkDestroyInstance;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkCreateDevice(
physical_device: vk::PhysicalDevice,
p_create_info: *const vk::DeviceCreateInfo,
p_allocator: *const vk::AllocationCallbacks,
p_device: *mut vk::Device,
) -> vk::Result {
if p_create_info.is_null() || p_device.is_null() {
return vk::Result::ERROR_INITIALIZATION_FAILED;
}
let instance = PHY_TO_INSTANCE_MAP
.get(&physical_device)
.map(|entry| *entry)
.or_else(|| INSTANCE_MAP.iter().next().map(|entry| *entry.key()));
let Some(instance) = instance else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let Some(instance_state) = INSTANCE_MAP.get(&instance) else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let instance_fn = instance_state.ash_instance.fp_v1_0();
let create_info = p_create_info.read();
let Some(mut chain_info) = get_device_chain_info(&create_info, LayerFunction::LAYER_LINK_INFO)
else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let chain_info = chain_info.as_mut();
let layer_info = chain_info.u.p_layer_info.read();
chain_info.u.p_layer_info = layer_info.p_next;
let Some(gdpa) = layer_info.pfn_next_get_device_proc_addr else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let want_external_memory = capture_mode() != CaptureMode::ForceCpu;
let augmented = AugmentedExtensions::build(
create_info.pp_enabled_extension_names,
create_info.enabled_extension_count,
&[
DEVICE_EXT_EXTERNAL_MEMORY,
DEVICE_EXT_EXTERNAL_MEMORY_WIN32,
DEVICE_EXT_DEDICATED_ALLOCATION,
DEVICE_EXT_GET_MEMORY_REQUIREMENTS2,
],
);
let mut augmented_info = create_info;
augmented_info.enabled_extension_count = augmented.count();
augmented_info.pp_enabled_extension_names = augmented.as_ptr();
let mut external_memory_enabled = false;
let result = if want_external_memory {
let augmented_result =
(instance_fn.create_device)(physical_device, &augmented_info, p_allocator, p_device);
if augmented_result == vk::Result::SUCCESS {
external_memory_enabled = true;
augmented_result
} else {
(instance_fn.create_device)(physical_device, p_create_info, p_allocator, p_device)
}
} else {
(instance_fn.create_device)(physical_device, p_create_info, p_allocator, p_device)
};
if result != vk::Result::SUCCESS {
return result;
}
let app_external_memory_win32 = extension_in_list(
create_info.pp_enabled_extension_names,
create_info.enabled_extension_count,
DEVICE_EXT_EXTERNAL_MEMORY_WIN32,
);
let external_memory_enabled = external_memory_enabled || app_external_memory_win32;
vlog!(
"vkCreateDevice: external_memory_enabled={external_memory_enabled} (want={want_external_memory}, app_enabled={app_external_memory_win32})"
);
let dedicated_allocation_enabled = external_memory_enabled
|| extension_in_list(
create_info.pp_enabled_extension_names,
create_info.enabled_extension_count,
DEVICE_EXT_DEDICATED_ALLOCATION,
);
let device = *p_device;
GDPA_MAP.insert(device, gdpa);
let ash_device = ash::Device::load(instance_fn, device);
let khr_swapchain = khr::swapchain::Device::new(&instance_state.ash_instance, &ash_device);
let khr_external_memory_win32 = external_memory_enabled.then(|| {
khr::external_memory_win32::Device::new(&instance_state.ash_instance, &ash_device)
});
let queue_families = instance_state
.ash_instance
.get_physical_device_queue_family_properties(physical_device);
DEVICE_MAP.insert(
device,
LayerDevice {
instance,
physical_device,
ash_device: ash_device.clone(),
khr_swapchain,
queue_families,
khr_external_memory_win32,
external_memory_enabled,
dedicated_allocation_enabled,
},
);
if !create_info.p_queue_create_infos.is_null() {
let queue_infos = std::slice::from_raw_parts(
create_info.p_queue_create_infos,
create_info.queue_create_info_count as usize,
);
for queue_info in queue_infos {
for queue_index in 0..queue_info.queue_count {
let mut queue = vk::Queue::null();
(ash_device.fp_v1_0().get_device_queue)(
device,
queue_info.queue_family_index,
queue_index,
&mut queue,
);
record_queue(device, queue, queue_info.queue_family_index);
}
}
}
vk::Result::SUCCESS
}
const _: vk::PFN_vkCreateDevice = flux_vkCreateDevice;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkDestroyDevice(
device: vk::Device,
p_allocator: *const vk::AllocationCallbacks,
) {
let Some(device_state) = destroy_device_state(device) else {
return;
};
(device_state.ash_device.fp_v1_0().destroy_device)(device, p_allocator);
}
const _: vk::PFN_vkDestroyDevice = flux_vkDestroyDevice;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkCreateWin32SurfaceKHR(
instance: vk::Instance,
p_create_info: *const vk::Win32SurfaceCreateInfoKHR,
p_allocator: *const vk::AllocationCallbacks,
p_surface: *mut vk::SurfaceKHR,
) -> vk::Result {
let Some(instance_state) = INSTANCE_MAP.get(&instance) else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let result = (instance_state
.khr_win32_surface
.fp()
.create_win32_surface_khr)(instance, p_create_info, p_allocator, p_surface);
if result == vk::Result::SUCCESS && !p_surface.is_null() && !p_create_info.is_null() {
let surface = *p_surface;
SURFACE_MAP.insert(
surface,
SurfaceState {
instance,
hwnd: (*p_create_info).hwnd,
},
);
}
result
}
const _: vk::PFN_vkCreateWin32SurfaceKHR = flux_vkCreateWin32SurfaceKHR;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkDestroySurfaceKHR(
instance: vk::Instance,
surface: vk::SurfaceKHR,
p_allocator: *const vk::AllocationCallbacks,
) {
SURFACE_MAP.remove(&surface);
if let Some(instance_state) = INSTANCE_MAP.get(&instance) {
(instance_state.khr_surface.fp().destroy_surface_khr)(instance, surface, p_allocator);
}
}
const _: vk::PFN_vkDestroySurfaceKHR = flux_vkDestroySurfaceKHR;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkCreateSwapchainKHR(
device: vk::Device,
p_create_info: *const vk::SwapchainCreateInfoKHR,
p_allocator: *const vk::AllocationCallbacks,
p_swapchain: *mut vk::SwapchainKHR,
) -> vk::Result {
if p_create_info.is_null() || p_swapchain.is_null() {
return vk::Result::ERROR_INITIALIZATION_FAILED;
}
let Some(device_state) = DEVICE_MAP.get(&device) else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let original_info = p_create_info.read();
let original_can_capture = original_info
.image_usage
.contains(vk::ImageUsageFlags::TRANSFER_SRC);
let create_info = swapchain_create_info_with_transfer_src(original_info);
let mut used_transfer_src = true;
let mut result = (device_state.khr_swapchain.fp().create_swapchain_khr)(
device,
&create_info,
p_allocator,
p_swapchain,
);
if result != vk::Result::SUCCESS {
used_transfer_src = original_can_capture;
let retry_info = swapchain_retry_create_info(original_info);
result = (device_state.khr_swapchain.fp().create_swapchain_khr)(
device,
&retry_info,
p_allocator,
p_swapchain,
);
}
if result != vk::Result::SUCCESS {
return result;
}
if let Some(old_swapchain) =
old_swapchain_to_remove_after_create(result, original_info.old_swapchain)
{
SWAPCHAIN_MAP.remove(&old_swapchain);
}
let swapchain = *p_swapchain;
let mut count = 0u32;
let get_result = (device_state.khr_swapchain.fp().get_swapchain_images_khr)(
device,
swapchain,
&mut count,
null_mut(),
);
let mut images = Vec::new();
if get_result == vk::Result::SUCCESS && count > 0 {
images.resize(count as usize, vk::Image::null());
let image_result = (device_state.khr_swapchain.fp().get_swapchain_images_khr)(
device,
swapchain,
&mut count,
images.as_mut_ptr(),
);
if image_result == vk::Result::SUCCESS || image_result == vk::Result::INCOMPLETE {
images.truncate(count as usize);
} else {
images.clear();
}
}
let hwnd = SURFACE_MAP
.get(&original_info.surface)
.map(|surface| surface.hwnd)
.unwrap_or(0);
let present_layout = present_layout_for_mode(original_info.present_mode);
let shared_texture_allowed =
shared_texture_allowed_for_present_mode(original_info.present_mode);
let have_images = !images.is_empty();
SWAPCHAIN_MAP.insert(
swapchain,
SwapchainState {
device,
hwnd,
extent: original_info.image_extent,
format: original_info.image_format,
images,
can_capture: used_transfer_src && hwnd != 0 && have_images,
present_layout,
shared_texture_allowed,
},
);
vk::Result::SUCCESS
}
const _: vk::PFN_vkCreateSwapchainKHR = flux_vkCreateSwapchainKHR;
fn swapchain_create_info_with_transfer_src(
mut create_info: vk::SwapchainCreateInfoKHR,
) -> vk::SwapchainCreateInfoKHR {
create_info.image_usage |= vk::ImageUsageFlags::TRANSFER_SRC;
create_info
}
fn swapchain_retry_create_info(
original_info: vk::SwapchainCreateInfoKHR,
) -> vk::SwapchainCreateInfoKHR {
original_info
}
fn old_swapchain_to_remove_after_create(
result: vk::Result,
old_swapchain: vk::SwapchainKHR,
) -> Option<vk::SwapchainKHR> {
(result == vk::Result::SUCCESS && old_swapchain != vk::SwapchainKHR::null())
.then_some(old_swapchain)
}
fn present_layout_for_mode(present_mode: vk::PresentModeKHR) -> vk::ImageLayout {
match present_mode {
vk::PresentModeKHR::SHARED_DEMAND_REFRESH
| vk::PresentModeKHR::SHARED_CONTINUOUS_REFRESH => vk::ImageLayout::SHARED_PRESENT_KHR,
_ => vk::ImageLayout::PRESENT_SRC_KHR,
}
}
fn shared_texture_allowed_for_present_mode(present_mode: vk::PresentModeKHR) -> bool {
present_mode != vk::PresentModeKHR::IMMEDIATE
}
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkDestroySwapchainKHR(
device: vk::Device,
swapchain: vk::SwapchainKHR,
p_allocator: *const vk::AllocationCallbacks,
) {
SWAPCHAIN_MAP.remove(&swapchain);
if let Some(device_state) = DEVICE_MAP.get(&device) {
(device_state.khr_swapchain.fp().destroy_swapchain_khr)(device, swapchain, p_allocator);
}
}
const _: vk::PFN_vkDestroySwapchainKHR = flux_vkDestroySwapchainKHR;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkGetSwapchainImagesKHR(
device: vk::Device,
swapchain: vk::SwapchainKHR,
p_swapchain_image_count: *mut u32,
p_swapchain_images: *mut vk::Image,
) -> vk::Result {
let Some(device_state) = DEVICE_MAP.get(&device) else {
return vk::Result::ERROR_INITIALIZATION_FAILED;
};
let result = (device_state.khr_swapchain.fp().get_swapchain_images_khr)(
device,
swapchain,
p_swapchain_image_count,
p_swapchain_images,
);
if (result == vk::Result::SUCCESS || result == vk::Result::INCOMPLETE)
&& !p_swapchain_image_count.is_null()
&& !p_swapchain_images.is_null()
{
let count = *p_swapchain_image_count as usize;
if let Some(mut swapchain_state) = SWAPCHAIN_MAP.get_mut(&swapchain) {
swapchain_state.images = std::slice::from_raw_parts(p_swapchain_images, count).to_vec();
}
}
result
}
const _: vk::PFN_vkGetSwapchainImagesKHR = flux_vkGetSwapchainImagesKHR;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkQueuePresentKHR(
queue: vk::Queue,
p_present_info: *const vk::PresentInfoKHR,
) -> vk::Result {
if p_present_info.is_null() {
return vk::Result::ERROR_INITIALIZATION_FAILED;
}
let present_info = *p_present_info;
let capture = try_capture_present(queue, &present_info);
let Some(queue_state) = QUEUE_MAP.get(&queue) else {
return vk::Result::ERROR_DEVICE_LOST;
};
let Some(device_state) = DEVICE_MAP.get(&queue_state.device) else {
return vk::Result::ERROR_DEVICE_LOST;
};
let chained_slot = capture.chained_wait;
if let Some(chained) = chained_slot {
let modified = present_info_with_chained_wait(present_info, &chained);
return (device_state.khr_swapchain.fp().queue_present_khr)(queue, &modified);
}
(device_state.khr_swapchain.fp().queue_present_khr)(queue, p_present_info)
}
const _: vk::PFN_vkQueuePresentKHR = flux_vkQueuePresentKHR;
fn present_info_with_chained_wait<'a>(
mut present_info: vk::PresentInfoKHR<'a>,
chained: &'a vk::Semaphore,
) -> vk::PresentInfoKHR<'a> {
present_info.wait_semaphore_count = 1;
present_info.p_wait_semaphores = chained;
present_info
}
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkGetDeviceQueue(
device: vk::Device,
queue_family_index: u32,
queue_index: u32,
p_queue: *mut vk::Queue,
) {
if let Some(device_state) = DEVICE_MAP.get(&device) {
(device_state.ash_device.fp_v1_0().get_device_queue)(
device,
queue_family_index,
queue_index,
p_queue,
);
if !p_queue.is_null() {
record_queue(device, *p_queue, queue_family_index);
}
}
}
const _: vk::PFN_vkGetDeviceQueue = flux_vkGetDeviceQueue;
#[unsafe(no_mangle)]
unsafe extern "system" fn flux_vkGetDeviceQueue2(
device: vk::Device,
p_queue_info: *const vk::DeviceQueueInfo2,
p_queue: *mut vk::Queue,
) {
if let Some(device_state) = DEVICE_MAP.get(&device) {
(device_state.ash_device.fp_v1_1().get_device_queue2)(device, p_queue_info, p_queue);
if !p_queue_info.is_null() && !p_queue.is_null() {
record_queue(device, *p_queue, (*p_queue_info).queue_family_index);
}
}
}
const _: vk::PFN_vkGetDeviceQueue2 = flux_vkGetDeviceQueue2;
#[cfg(test)]
mod tests {
use super::*;
use ash::vk::Handle;
#[test]
fn format_mode_accepts_obs_capture_8bit_formats() {
assert_eq!(
format_mode(vk::Format::B8G8R8A8_UNORM),
Some(FormatMode::Bgra)
);
assert_eq!(
format_mode(vk::Format::B8G8R8A8_SRGB),
Some(FormatMode::Bgra)
);
assert_eq!(
format_mode(vk::Format::R8G8B8A8_UNORM),
Some(FormatMode::Rgba)
);
assert_eq!(
format_mode(vk::Format::R8G8B8A8_SRGB),
Some(FormatMode::Rgba)
);
assert_eq!(format_mode(vk::Format::A2B10G10R10_UNORM_PACK32), None);
}
#[test]
fn hdr_format_flags_mark_ten_bit_and_float_formats() {
assert_eq!(
hdr_format_flags(vk::Format::A2B10G10R10_UNORM_PACK32),
GAME_CAPTURE_FLAG_TEN_BIT | GAME_CAPTURE_FLAG_HDR
);
assert_eq!(
hdr_format_flags(vk::Format::A2R10G10B10_UNORM_PACK32),
GAME_CAPTURE_FLAG_TEN_BIT | GAME_CAPTURE_FLAG_HDR
);
assert_eq!(
hdr_format_flags(vk::Format::R16G16B16A16_SFLOAT),
GAME_CAPTURE_FLAG_HDR
);
assert_eq!(hdr_format_flags(vk::Format::B8G8R8A8_UNORM), 0);
}
#[test]
fn present_layout_uses_shared_present_only_for_shared_modes() {
assert_eq!(
present_layout_for_mode(vk::PresentModeKHR::SHARED_DEMAND_REFRESH),
vk::ImageLayout::SHARED_PRESENT_KHR
);
assert_eq!(
present_layout_for_mode(vk::PresentModeKHR::SHARED_CONTINUOUS_REFRESH),
vk::ImageLayout::SHARED_PRESENT_KHR
);
assert_eq!(
present_layout_for_mode(vk::PresentModeKHR::FIFO),
vk::ImageLayout::PRESENT_SRC_KHR
);
assert_eq!(
present_layout_for_mode(vk::PresentModeKHR::MAILBOX),
vk::ImageLayout::PRESENT_SRC_KHR
);
}
#[test]
fn immediate_present_mode_uses_cpu_fallback_after_real_shared_handle_failure() {
assert!(!shared_texture_allowed_for_present_mode(
vk::PresentModeKHR::IMMEDIATE
));
assert!(shared_texture_allowed_for_present_mode(
vk::PresentModeKHR::FIFO
));
assert!(shared_texture_allowed_for_present_mode(
vk::PresentModeKHR::MAILBOX
));
}
#[test]
fn shared_texture_reacquires_external_ownership_after_first_frame() {
let queue_family_index = 7;
assert_eq!(
shared_texture_dst_pre_queue_families(false, queue_family_index),
(vk::QUEUE_FAMILY_IGNORED, vk::QUEUE_FAMILY_IGNORED)
);
assert_eq!(
shared_texture_dst_pre_queue_families(true, queue_family_index),
(vk::QUEUE_FAMILY_EXTERNAL, queue_family_index)
);
}
#[test]
fn augmented_swapchain_create_info_preserves_old_swapchain() {
let old = vk::SwapchainKHR::from_raw(0x55);
let original = vk::SwapchainCreateInfoKHR {
image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT,
old_swapchain: old,
..Default::default()
};
let augmented = swapchain_create_info_with_transfer_src(original);
assert!(
augmented
.image_usage
.contains(vk::ImageUsageFlags::TRANSFER_SRC)
);
assert!(
augmented
.image_usage
.contains(vk::ImageUsageFlags::COLOR_ATTACHMENT)
);
assert_eq!(augmented.old_swapchain.as_raw(), old.as_raw());
}
#[test]
fn retry_swapchain_create_info_keeps_app_usage_and_old_swapchain() {
let old = vk::SwapchainKHR::from_raw(0x66);
let original = vk::SwapchainCreateInfoKHR {
image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT,
old_swapchain: old,
..Default::default()
};
let retry = swapchain_retry_create_info(original);
assert_eq!(retry.image_usage, original.image_usage);
assert_eq!(retry.old_swapchain.as_raw(), old.as_raw());
}
#[test]
fn old_swapchain_is_removed_only_after_successful_recreate() {
let old = vk::SwapchainKHR::from_raw(0x77);
assert_eq!(
old_swapchain_to_remove_after_create(vk::Result::SUCCESS, old).map(|h| h.as_raw()),
Some(old.as_raw())
);
assert_eq!(
old_swapchain_to_remove_after_create(vk::Result::ERROR_INITIALIZATION_FAILED, old)
.map(|h| h.as_raw()),
None
);
assert_eq!(
old_swapchain_to_remove_after_create(vk::Result::SUCCESS, vk::SwapchainKHR::null())
.map(|h| h.as_raw()),
None
);
}
#[test]
fn present_wait_semaphore_slice_rejects_missing_pointer() {
let waits = [vk::Semaphore::from_raw(0x11), vk::Semaphore::from_raw(0x12)];
let present_info = vk::PresentInfoKHR {
wait_semaphore_count: waits.len() as u32,
p_wait_semaphores: waits.as_ptr(),
..Default::default()
};
let slice = unsafe { present_wait_semaphores(&present_info) };
assert_eq!(
slice.iter().map(|s| s.as_raw()).collect::<Vec<_>>(),
vec![0x11, 0x12]
);
let missing_pointer = vk::PresentInfoKHR {
wait_semaphore_count: waits.len() as u32,
p_wait_semaphores: null(),
..Default::default()
};
assert!(unsafe { present_wait_semaphores(&missing_pointer) }.is_empty());
}
#[test]
fn chained_present_wait_replaces_wait_list_but_preserves_present_payload() {
let original_waits = [vk::Semaphore::from_raw(0x21), vk::Semaphore::from_raw(0x22)];
let swapchains = [vk::SwapchainKHR::from_raw(0x31)];
let image_indices = [3_u32];
let mut results = [vk::Result::SUCCESS];
let present_info = vk::PresentInfoKHR {
wait_semaphore_count: original_waits.len() as u32,
p_wait_semaphores: original_waits.as_ptr(),
swapchain_count: swapchains.len() as u32,
p_swapchains: swapchains.as_ptr(),
p_image_indices: image_indices.as_ptr(),
p_results: results.as_mut_ptr(),
..Default::default()
};
let chained = vk::Semaphore::from_raw(0x44);
let modified = present_info_with_chained_wait(present_info, &chained);
assert_eq!(modified.wait_semaphore_count, 1);
assert_eq!(
unsafe { *modified.p_wait_semaphores }.as_raw(),
chained.as_raw()
);
assert_eq!(modified.swapchain_count, present_info.swapchain_count);
assert_eq!(modified.p_swapchains, present_info.p_swapchains);
assert_eq!(modified.p_image_indices, present_info.p_image_indices);
assert_eq!(modified.p_results, present_info.p_results);
}
}
#[unsafe(no_mangle)]
unsafe extern "system" fn vkEnumerateInstanceLayerProperties(
p_property_count: *mut u32,
p_properties: *mut vk::LayerProperties,
) -> vk::Result {
if p_property_count.is_null() {
return vk::Result::ERROR_INITIALIZATION_FAILED;
}
if p_properties.is_null() {
*p_property_count = 1;
return vk::Result::SUCCESS;
}
if *p_property_count == 0 {
return vk::Result::INCOMPLETE;
}
*p_property_count = 1;
let mut properties = vk::LayerProperties {
spec_version: vk::API_VERSION_1_0,
implementation_version: 1,
..Default::default()
};
let name = LAYER_NAME;
std::ptr::copy_nonoverlapping(
name.as_ptr() as *const c_char,
properties.layer_name.as_mut_ptr(),
name.len().min(properties.layer_name.len()),
);
*p_properties = properties;
vk::Result::SUCCESS
}