use anyhow::Context;
use asdf_overlay_event::{SurfaceInfo, SurfaceType};
use dashmap::Entry;
use once_cell::sync::Lazy;
use scopeguard::defer;
use tracing::{Level, info, trace};
use windows::{
Win32::Graphics::{
Direct3D::D3D_FEATURE_LEVEL_11_0,
Direct3D11::{
D3D11_1_CREATE_DEVICE_CONTEXT_STATE_SINGLETHREADED, D3D11_CREATE_DEVICE_SINGLETHREADED,
D3D11_SDK_VERSION, ID3D11Device, ID3D11Device1, ID3D11Texture2D,
ID3DDeviceContextState,
},
Dxgi::{IDXGIDevice, IDXGISwapChain, IDXGISwapChain1},
},
core::Interface,
};
use crate::{
hook::dx::dxgi::callback::register_swapchain_destruction_callback,
interop::DxInterop,
renderer::dx11::Dx11Renderer,
surface::{SurfaceState, Surfaces},
types::IntDashMap,
};
static RENDERERS: Lazy<IntDashMap<usize, RendererData>> = Lazy::new(IntDashMap::default);
struct RendererData {
renderer: Dx11Renderer,
state: ID3DDeviceContextState,
}
#[inline]
fn with_or_init_renderer_data<R>(
swapchain: &IDXGISwapChain1,
f: impl FnOnce(&mut RendererData) -> anyhow::Result<R>,
) -> anyhow::Result<R> {
let mut data = match RENDERERS.entry(swapchain.as_raw() as _) {
Entry::Occupied(entry) => entry.into_ref(),
Entry::Vacant(entry) => {
info!("Initializing Direct3D11 renderer");
let device = unsafe { swapchain.GetDevice::<ID3D11Device1>()? };
let state = unsafe {
let mut state = None;
let flag = if device.GetCreationFlags() & D3D11_CREATE_DEVICE_SINGLETHREADED.0 != 0
{
D3D11_1_CREATE_DEVICE_CONTEXT_STATE_SINGLETHREADED.0 as u32
} else {
0
};
device
.CreateDeviceContextState(
flag,
&[D3D_FEATURE_LEVEL_11_0],
D3D11_SDK_VERSION,
&ID3D11Device::IID,
None,
Some(&mut state),
)
.expect("CreateDeviceContextState failed");
state.unwrap()
};
let ref_mut = entry.insert(RendererData {
renderer: Dx11Renderer::new(&device)?,
state,
});
register_swapchain_destruction_callback(swapchain, cleanup_swapchain);
ref_mut
}
};
f(&mut data)
}
pub fn draw_overlay(
state: &SurfaceState,
device: &ID3D11Device1,
swapchain: &IDXGISwapChain1,
) -> anyhow::Result<()> {
let Some(size) = state.texture_size() else {
return Ok(());
};
let position = state.position();
let screen = state.size();
with_or_init_renderer_data(swapchain, move |data| {
trace!("Using Direct3D11 renderer");
if state.texture.take_update() {
data.renderer
.update_texture(device, state.texture.get().as_ref())
.context("renderer texture update")?;
}
let cx = unsafe { device.GetImmediateContext1()? };
let mut prev_state = None;
unsafe {
cx.SwapDeviceContextState(&data.state, Some(&mut prev_state));
}
let prev_state = prev_state.unwrap();
defer!(unsafe {
cx.SwapDeviceContextState(&prev_state, None);
});
let back_buffer =
unsafe { swapchain.GetBuffer::<ID3D11Texture2D>(0) }.context("get backbuffer")?;
let mut rtv = None;
unsafe { device.CreateRenderTargetView(&back_buffer, None, Some(&mut rtv)) }
.context("get backbuffer rtv")?;
let rtv = rtv.unwrap();
unsafe { cx.OMSetRenderTargets(Some(&[Some(rtv)]), None) };
defer!(unsafe { cx.OMSetRenderTargets(None, None) });
data.renderer
.draw(device, &cx, position, size, screen)
.context("renderer draw")
})
}
pub(super) fn setup_fn(
device: &ID3D11Device,
swapchain: &IDXGISwapChain,
) -> anyhow::Result<SurfaceState> {
let adapter = unsafe { device.cast::<IDXGIDevice>().unwrap().GetAdapter().ok() };
let desc = unsafe { swapchain.GetDesc() }?;
let window_id = if desc.OutputWindow.is_invalid() {
None
} else {
Some(desc.OutputWindow.0 as u32)
};
let interop = DxInterop::new(adapter.as_ref())?;
let gpu_id = interop.gpu_id;
SurfaceState::new(
interop,
(desc.BufferDesc.Width, desc.BufferDesc.Height),
SurfaceInfo {
api: SurfaceType::Direct3D11 { window_id },
gpu_id,
},
)
}
#[tracing::instrument(level = Level::TRACE)]
fn cleanup_swapchain(swapchain: usize) {
if RENDERERS.remove(&swapchain).is_none() {
return;
};
info!("Direct3D11 renderer cleanup");
Surfaces::cleanup_state(swapchain as _);
}