// 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
}
