diff --git a/Cargo.toml b/Cargo.toml index 6d5fe7a6..24813dba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hudhook" -version = "0.5.0" +version = "0.6.0" edition = "2021" description = "A graphics API hook with dear imgui render loop. Supports DirectX 9, 11, 12, and OpenGL 3." homepage = "https://github.com/veeenu/hudhook" @@ -11,7 +11,7 @@ authors = ["Andrea Venuta "] [package.metadata.docs.rs] default-target = "x86_64-pc-windows-msvc" -targets = ["x86_64-pc-windows-msvc"] +targets = ["x86_64-pc-windows-msvc", "i686-pc-windows-msvc"] [features] default = ["dx9", "dx11", "dx12", "opengl3", "inject"] @@ -54,26 +54,6 @@ crate-type = ["cdylib"] name = "dx11_host" crate-type = ["bin"] -# Renderers examples -# -# These examples contain code that creates a window and a rendering surface and -# plainly renders some imgui code with the appropriate renderer. - -[[example]] -name = "renderer-dx9" -path = "examples/renderers/dx9.rs" -crate-type = ["bin"] - -[[example]] -name = "renderer-dx11" -path = "examples/renderers/dx11.rs" -crate-type = ["bin"] - -[[example]] -name = "renderer-dx12" -path = "examples/renderers/dx12.rs" -crate-type = ["bin"] - [dependencies] imgui = "0.11" imgui-opengl = "0.1" @@ -99,6 +79,7 @@ windows = { version = "0.51.0", features = [ "Win32_Graphics_Direct3D12", "Win32_Graphics_Direct3D_Fxc", "Win32_Graphics_Direct3D", + "Win32_Graphics_DirectComposition", "Win32_Graphics_Gdi", "Win32_Graphics_OpenGL", "Win32_UI_Input", @@ -108,6 +89,7 @@ windows = { version = "0.51.0", features = [ tracing = { version = "0.1", features = ["log"] } memoffset = "0.9.0" once_cell = "1.18.0" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } [dev-dependencies] tracing-subscriber = "0.3" diff --git a/README.md b/README.md index 8559c916..d6c1c445 100644 --- a/README.md +++ b/README.md @@ -36,25 +36,25 @@ impl ImguiRenderLoop for MyRenderLoop { { // Use this if hooking into a DirectX 9 application. use hudhook::hooks::dx9::ImguiDx9Hooks; - hudhook!(MyRenderLoop.into_hook::()); + hudhook!(ImguiDx9Hooks, MyRenderLoop); } { // Use this if hooking into a DirectX 11 application. use hudhook::hooks::dx11::ImguiDx11Hooks; - hudhook!(MyRenderLoop.into_hook::()); + hudhook!(ImguiDx11Hooks, MyRenderLoop); } { // Use this if hooking into a DirectX 12 application. use hudhook::hooks::dx12::ImguiDx12Hooks; - hudhook!(MyRenderLoop.into_hook::()); + hudhook!(ImguiDx12Hooks, MyRenderLoop); } { // Use this if hooking into an OpenGL 3 application. use hudhook::hooks::opengl3::ImguiOpenGl3Hooks; - hudhook!(MyRenderLoop.into_hook::()); + hudhook!(ImguiOpenGl3Hooks, MyRenderLoop); } ``` diff --git a/examples/dx11_hook.rs b/examples/dx11_hook.rs index 81a2436a..d38e0b9c 100644 --- a/examples/dx11_hook.rs +++ b/examples/dx11_hook.rs @@ -1,5 +1,7 @@ +// Please ignore these examples; they are broken. + use hudhook::hooks::dx11::ImguiDx11Hooks; -use hudhook::hooks::ImguiRenderLoop; +use hudhook::*; use imgui::Condition; use tracing::metadata::LevelFilter; struct Dx11HookExample; @@ -7,7 +9,7 @@ struct Dx11HookExample; impl Dx11HookExample { fn new() -> Self { println!("Initializing"); - hudhook::alloc_console().expect("AllocConsole"); + hudhook::alloc_console().ok(); hudhook::enable_console_colors(); tracing_subscriber::fmt() @@ -35,4 +37,4 @@ impl ImguiRenderLoop for Dx11HookExample { } } -hudhook::hudhook!(Dx11HookExample::new().into_hook::()); +hudhook::hudhook!(ImguiDx11Hooks, Dx11HookExample::new()); diff --git a/examples/dx11_host.rs b/examples/dx11_host.rs index a3d6e25f..380af358 100644 --- a/examples/dx11_host.rs +++ b/examples/dx11_host.rs @@ -59,7 +59,7 @@ pub fn main(_argc: i32, _argv: *const *const u8) { let mut dll_path = std::env::current_exe().unwrap(); dll_path.pop(); - dll_path.push("hook_you.dll"); + dll_path.push("dx11_hook.dll"); println!("{:?}", dll_path.canonicalize()); unsafe { diff --git a/examples/dx12_hook.rs b/examples/dx12_hook.rs index de8f33c2..5df54ffb 100644 --- a/examples/dx12_hook.rs +++ b/examples/dx12_hook.rs @@ -1,5 +1,5 @@ use hudhook::hooks::dx12::ImguiDx12Hooks; -use hudhook::hooks::ImguiRenderLoop; +use hudhook::*; use imgui::Condition; use tracing::metadata::LevelFilter; struct Dx12HookExample; @@ -35,4 +35,4 @@ impl ImguiRenderLoop for Dx12HookExample { } } -hudhook::hudhook!(Dx12HookExample::new().into_hook::()); +hudhook::hudhook!(ImguiDx12Hooks, Dx12HookExample::new()); diff --git a/examples/dx9_hook.rs b/examples/dx9_hook.rs index 605f5678..031ef64a 100644 --- a/examples/dx9_hook.rs +++ b/examples/dx9_hook.rs @@ -1,5 +1,5 @@ use hudhook::hooks::dx9::ImguiDx9Hooks; -use hudhook::hooks::ImguiRenderLoop; +use hudhook::*; use imgui::Condition; use tracing::metadata::LevelFilter; struct Dx9HookExample; @@ -35,4 +35,4 @@ impl ImguiRenderLoop for Dx9HookExample { } } -hudhook::hudhook!(Dx9HookExample::new().into_hook::()); +hudhook::hudhook!(ImguiDx9Hooks, Dx9HookExample::new()); diff --git a/examples/opengl3_hook.rs b/examples/opengl3_hook.rs index 8e72b09f..18354ff1 100644 --- a/examples/opengl3_hook.rs +++ b/examples/opengl3_hook.rs @@ -1,5 +1,5 @@ use hudhook::hooks::opengl3::ImguiOpenGl3Hooks; -use hudhook::hooks::ImguiRenderLoop; +use hudhook::*; use imgui::Condition; use tracing::metadata::LevelFilter; struct HookYou; @@ -35,4 +35,4 @@ impl ImguiRenderLoop for HookYou { } } -hudhook::hudhook!(HookYou::new().into_hook::()); +hudhook::hudhook!(ImguiOpenGl3Hooks, HookYou::new()); diff --git a/examples/renderers/dx11.rs b/examples/renderers/dx11.rs deleted file mode 100644 index 669b3bc4..00000000 --- a/examples/renderers/dx11.rs +++ /dev/null @@ -1,182 +0,0 @@ -// Mandatory reference: -// https://www.codeslow.com/2019/12/tiny-windows-executable-in-rust.html - -#![no_main] - -use std::mem::MaybeUninit; -use std::ptr::null_mut; - -use hudhook::renderers::imgui_dx11::RenderEngine; -use imgui::Condition; -use tracing::metadata::LevelFilter; -use windows::core::PCSTR; -use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; -use windows::Win32::Graphics::Dxgi::{ - DXGIGetDebugInterface1, IDXGIInfoQueue, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE, -}; -use windows::Win32::Graphics::Gdi::{ - BeginPaint, DrawTextA, EndPaint, DT_CENTER, DT_SINGLELINE, DT_VCENTER, HBRUSH, -}; -use windows::Win32::System::LibraryLoader::GetModuleHandleA; -use windows::Win32::UI::WindowsAndMessaging::{ - CreateWindowExA, DefWindowProcA, DispatchMessageA, GetClientRect, GetMessageA, PostQuitMessage, - RegisterClassA, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, HCURSOR, HICON, HMENU, - WINDOW_EX_STYLE, WM_DESTROY, WM_PAINT, WM_QUIT, WNDCLASSA, WS_OVERLAPPEDWINDOW, WS_VISIBLE, -}; - -#[no_mangle] -pub fn main(_argc: i32, _argv: *const *const u8) { - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true) - .with_thread_names(true) - .init(); - - let hinstance = unsafe { GetModuleHandleA(None).unwrap() }; - let wnd_class = WNDCLASSA { - style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW, - lpfnWndProc: Some(window_proc), - hInstance: hinstance.into(), - lpszClassName: PCSTR("MyClass\0".as_ptr()), - cbClsExtra: 0, - cbWndExtra: 0, - hIcon: HICON(0), - hCursor: HCURSOR(0), - hbrBackground: HBRUSH(0), - lpszMenuName: PCSTR(null_mut()), - }; - unsafe { RegisterClassA(&wnd_class) }; - let handle = unsafe { - CreateWindowExA( - WINDOW_EX_STYLE(0), // dwExStyle - PCSTR("MyClass\0".as_ptr()), // class we registered. - PCSTR("MiniWIN\0".as_ptr()), // title - WS_OVERLAPPEDWINDOW | WS_VISIBLE, // dwStyle - // size and position - 100, - 100, - 800, - 600, - HWND(0), // hWndParent - HMENU(0), // hMenu - hinstance, // hInstance - None, - ) - }; // lpParam - - let mut ctx = imgui::Context::create(); - ctx.set_ini_filename(None); - ctx.io_mut().display_size = [800., 600.]; - - eprintln!("Creating renderer"); - let mut renderer = RenderEngine::new(handle, &mut ctx); - - let diq: IDXGIInfoQueue = unsafe { DXGIGetDebugInterface1(0) }.unwrap(); - - loop { - unsafe { - for i in 0..diq.GetNumStoredMessages(DXGI_DEBUG_ALL) { - eprintln!("Debug Message {i}"); - let mut msg_len: usize = 0; - diq.GetMessage(DXGI_DEBUG_ALL, i, None, &mut msg_len as _).unwrap(); - let diqm = vec![0u8; msg_len]; - let pdiqm = diqm.as_ptr() as *mut DXGI_INFO_QUEUE_MESSAGE; - diq.GetMessage(DXGI_DEBUG_ALL, i, Some(pdiqm), &mut msg_len as _).unwrap(); - let diqm = pdiqm.as_ref().unwrap(); - eprintln!( - "{}", - String::from_utf8_lossy(std::slice::from_raw_parts( - diqm.pDescription, - diqm.DescriptionByteLength - )) - ); - } - diq.ClearStoredMessages(DXGI_DEBUG_ALL); - } - - let ui = ctx.frame(); - ui.window("Hello world").size([640.0, 480.0], Condition::Always).build(|| { - ui.text("Hello world!"); - ui.text("こんにちは世界!"); - ui.text("This...is...imgui-rs!"); - ui.separator(); - let mouse_pos = ui.io().mouse_pos; - ui.text(format!("Mouse Position: ({:.1},{:.1})", mouse_pos[0], mouse_pos[1])); - - imgui::ListBox::new("##listbox").size([300., 150.]).build(ui, || { - ui.selectable("test1"); - ui.selectable("test2"); - ui.selectable_config("test3").selected(true).build(); - ui.selectable("test4"); - ui.selectable("test5"); - }); - - if ui.begin_combo("##combo", "test").is_some() { - ui.selectable("test1"); - ui.selectable("test2"); - ui.selectable_config("test3").selected(true).build(); - ui.selectable("test4"); - ui.selectable("test5"); - }; - ui.open_popup("##combo"); - }); - - renderer.render_draw_data(ctx.render()).unwrap(); - - eprintln!("Present..."); - renderer.present(); - - eprintln!("Handle message"); - if !handle_message(handle) { - break; - } - } -} - -// Winapi things - -fn handle_message(window: HWND) -> bool { - unsafe { - let mut msg = MaybeUninit::uninit(); - if GetMessageA(msg.as_mut_ptr(), window, 0, 0).0 > 0 { - TranslateMessage(msg.as_ptr()); - DispatchMessageA(msg.as_ptr()); - msg.as_ptr().as_ref().map(|m| m.message != WM_QUIT).unwrap_or(true) - } else { - false - } - } -} - -/// # Safety -pub unsafe extern "system" fn window_proc( - hwnd: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - match msg { - WM_PAINT => { - let mut paint_struct = MaybeUninit::uninit(); - let mut rect = MaybeUninit::uninit(); - let hdc = BeginPaint(hwnd, paint_struct.as_mut_ptr()); - GetClientRect(hwnd, rect.as_mut_ptr()).expect("GetClientRect"); - DrawTextA( - hdc, - &mut b"Test\0".to_vec(), - rect.as_mut_ptr(), - DT_SINGLELINE | DT_CENTER | DT_VCENTER, - ); - EndPaint(hwnd, paint_struct.as_mut_ptr()); - }, - WM_DESTROY => { - PostQuitMessage(0); - }, - _ => { - return DefWindowProcA(hwnd, msg, wparam, lparam); - }, - } - LRESULT(0) -} diff --git a/examples/renderers/dx12.rs b/examples/renderers/dx12.rs deleted file mode 100644 index 89ee1768..00000000 --- a/examples/renderers/dx12.rs +++ /dev/null @@ -1,283 +0,0 @@ -// Mandatory reference: -// https://www.codeslow.com/2019/12/tiny-windows-executable-in-rust.html - -#![no_main] - -use std::mem::MaybeUninit; -use std::ptr::null; - -use hudhook::renderers::imgui_dx12::RenderEngine; -use imgui::Condition; -use tracing::metadata::LevelFilter; -use tracing::trace; -use windows::core::{s, ComInterface, PCSTR}; -use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, WPARAM}; -use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0; -use windows::Win32::Graphics::Direct3D12::*; -use windows::Win32::Graphics::Dxgi::Common::*; -use windows::Win32::Graphics::Dxgi::*; -use windows::Win32::Graphics::Gdi::*; -use windows::Win32::System::LibraryLoader::GetModuleHandleA; -use windows::Win32::UI::WindowsAndMessaging::*; - -#[no_mangle] -pub fn main(_argc: i32, _argv: *const *const u8) { - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true) - .with_thread_names(true) - .init(); - - let hinstance = unsafe { GetModuleHandleA(PCSTR(null())).unwrap() }; - let wnd_class = WNDCLASSA { - style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW, - lpfnWndProc: Some(window_proc), - hInstance: hinstance.into(), - lpszClassName: s!("MyClass\0"), - cbClsExtra: 0, - cbWndExtra: 0, - hIcon: HICON::default(), - hCursor: HCURSOR::default(), - hbrBackground: HBRUSH::default(), - lpszMenuName: PCSTR(null()), - }; - unsafe { RegisterClassA(&wnd_class) }; - let hwnd = unsafe { - CreateWindowExA( - WINDOW_EX_STYLE::default(), - PCSTR("MyClass\0".as_ptr()), - PCSTR("MiniWIN\0".as_ptr()), - WS_OVERLAPPEDWINDOW | WS_VISIBLE, // dwStyle - // size and position - 100, - 100, - 800, - 600, - HWND::default(), // hWndParent - HMENU::default(), // hMenu - hinstance, // hInstance - None, - ) - }; // lpParam - - let mut debug_interface: Option = None; - unsafe { D3D12GetDebugInterface(&mut debug_interface) }.unwrap(); - unsafe { debug_interface.as_ref().unwrap().EnableDebugLayer() }; - - let factory: IDXGIFactory = unsafe { CreateDXGIFactory() }.unwrap(); - let adapter = unsafe { factory.EnumAdapters(0) }.unwrap(); - - let mut dev: Option = None; - unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_11_0, &mut dev) }.unwrap(); - let dev = dev.unwrap(); - - let queue_desc = D3D12_COMMAND_QUEUE_DESC { - Type: D3D12_COMMAND_LIST_TYPE_DIRECT, - Priority: 0, - Flags: D3D12_COMMAND_QUEUE_FLAG_NONE, - NodeMask: 0, - }; - - let command_queue: ID3D12CommandQueue = - unsafe { dev.CreateCommandQueue(&queue_desc as *const _) }.unwrap(); - let command_alloc: ID3D12CommandAllocator = - unsafe { dev.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }.unwrap(); - let command_list: ID3D12GraphicsCommandList = - unsafe { dev.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &command_alloc, None) } - .unwrap(); - - let swap_chain_desc = DXGI_SWAP_CHAIN_DESC { - BufferDesc: DXGI_MODE_DESC { - Width: 100, - Height: 100, - RefreshRate: DXGI_RATIONAL { Numerator: 60, Denominator: 1 }, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - ScanlineOrdering: DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, - Scaling: DXGI_MODE_SCALING_UNSPECIFIED, - }, - SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, - BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, - BufferCount: 2, - OutputWindow: hwnd, - Windowed: BOOL::from(true), - SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD, - Flags: DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.0 as u32, - }; - - let mut swap_chain: Option = None; - unsafe { factory.CreateSwapChain(&command_queue, &swap_chain_desc, &mut swap_chain as *mut _) } - .unwrap(); - let swap_chain: IDXGISwapChain3 = swap_chain.unwrap().cast().unwrap(); - let desc = unsafe { - let mut desc = Default::default(); - swap_chain.GetDesc(&mut desc).unwrap(); - desc - }; - - let renderer_heap: ID3D12DescriptorHeap = unsafe { - dev.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { - Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - NumDescriptors: desc.BufferCount, - Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, - NodeMask: 0, - }) - .unwrap() - }; - - let rtv_heap: ID3D12DescriptorHeap = unsafe { - dev.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { - Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV, - NumDescriptors: desc.BufferCount, - Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE, - NodeMask: 1, - }) - .unwrap() - }; - - let rtv_heap_inc_size = - unsafe { dev.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) }; - let rtv_start = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() }; - - let _frames = (0..desc.BufferCount) - .map(|i| unsafe { - let buf: ID3D12Resource = swap_chain.GetBuffer(i).unwrap(); - let rtv_handle = D3D12_CPU_DESCRIPTOR_HANDLE { - ptr: rtv_start.ptr + (i * rtv_heap_inc_size) as usize, - }; - dev.CreateRenderTargetView(&buf, None, rtv_handle); - (buf, rtv_handle) - }) - .collect::>(); - - let cpu_dh = unsafe { renderer_heap.GetCPUDescriptorHandleForHeapStart() }; - let gpu_dh = unsafe { renderer_heap.GetGPUDescriptorHandleForHeapStart() }; - - let mut ctx = imgui::Context::create(); - let mut renderer = RenderEngine::new( - &mut ctx, - dev, - 2, - DXGI_FORMAT_R8G8B8A8_UNORM, - renderer_heap, - cpu_dh, - gpu_dh, - ); - - let diq: IDXGIInfoQueue = unsafe { DXGIGetDebugInterface1(0) }.unwrap(); - - ctx.io_mut().display_size = [800., 600.]; - - loop { - trace!("Debug"); - unsafe { - for i in 0..diq.GetNumStoredMessages(DXGI_DEBUG_ALL) { - let mut msg_len: usize = 0; - diq.GetMessage(DXGI_DEBUG_ALL, i, None, &mut msg_len as _).unwrap(); - let diqm = vec![0u8; msg_len]; - let pdiqm = diqm.as_ptr() as *mut DXGI_INFO_QUEUE_MESSAGE; - diq.GetMessage(DXGI_DEBUG_ALL, i, Some(pdiqm), &mut msg_len as _).unwrap(); - let diqm = pdiqm.as_ref().unwrap(); - println!( - "{}", - String::from_utf8_lossy(std::slice::from_raw_parts( - diqm.pDescription, - diqm.DescriptionByteLength - )) - ); - } - diq.ClearStoredMessages(DXGI_DEBUG_ALL); - } - - trace!("New frame"); - renderer.new_frame(&mut ctx); - - let ui = ctx.frame(); - ui.window("Hello world").size([640.0, 480.0], Condition::Always).build(|| { - ui.text("Hello world!"); - ui.text("こんにちは世界!"); - ui.text("This...is...imgui-rs!"); - ui.separator(); - let mouse_pos = ui.io().mouse_pos; - ui.text(format!("Mouse Position: ({:.1},{:.1})", mouse_pos[0], mouse_pos[1])); - - imgui::ListBox::new("##listbox").size([300., 150.]).build(ui, || { - ui.selectable("test1"); - ui.selectable("test2"); - ui.selectable_config("test3").selected(true).build(); - ui.selectable("test4"); - ui.selectable("test5"); - }); - - if ui.begin_combo("##combo", "test").is_some() { - ui.selectable("test1"); - ui.selectable("test2"); - ui.selectable_config("test3").selected(true).build(); - ui.selectable("test4"); - ui.selectable("test5"); - }; - ui.open_popup("##combo"); - }); - trace!("Render draw data"); - renderer - .render_draw_data(ctx.render(), &command_list, unsafe { - swap_chain.GetCurrentBackBufferIndex() - } as _) - .unwrap(); - trace!("Present"); - unsafe { swap_chain.Present(1, 0) }.unwrap(); - - trace!("Handle message"); - if !handle_message(hwnd) { - break; - } - } -} - -// Winapi things -// - -fn handle_message(window: HWND) -> bool { - unsafe { - let mut msg = MaybeUninit::uninit(); - if GetMessageA(msg.as_mut_ptr(), window, 0, 0).as_bool() { - TranslateMessage(msg.as_ptr()); - DispatchMessageA(msg.as_ptr()); - msg.as_ptr().as_ref().map(|m| m.message != WM_QUIT).unwrap_or(true) - } else { - false - } - } -} - -/// # Safety -pub unsafe extern "system" fn window_proc( - hwnd: HWND, - msg: u32, - w_param: WPARAM, - l_param: LPARAM, -) -> LRESULT { - match msg { - WM_PAINT => { - let mut paint_struct = MaybeUninit::uninit(); - let mut rect = MaybeUninit::uninit(); - let hdc = BeginPaint(hwnd, paint_struct.as_mut_ptr()); - GetClientRect(hwnd, rect.as_mut_ptr()).expect("GetClientRect"); - DrawTextA( - hdc, - &mut b"Test\0".to_vec(), - rect.as_mut_ptr(), - DT_SINGLELINE | DT_CENTER | DT_VCENTER, - ); - EndPaint(hwnd, paint_struct.as_mut_ptr()); - }, - WM_DESTROY => { - PostQuitMessage(0); - }, - _ => { - return DefWindowProcA(hwnd, msg, w_param, l_param); - }, - } - LRESULT(0) -} diff --git a/examples/renderers/dx9.rs b/examples/renderers/dx9.rs deleted file mode 100644 index 6ddb8d43..00000000 --- a/examples/renderers/dx9.rs +++ /dev/null @@ -1,241 +0,0 @@ -// Based on https://github.com/Veykril/imgui-dx9-renderer -// -// Copyright (c) 2019 Lukas Wirth -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#![no_main] - -use std::mem::MaybeUninit; -use std::ptr::null_mut; - -use hudhook::renderers::imgui_dx9::Renderer; -use imgui::Condition; -use tracing::metadata::LevelFilter; -use windows::core::PCSTR; -use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, RECT, WPARAM}; -use windows::Win32::Graphics::Direct3D9::{ - Direct3DCreate9, IDirect3D9, IDirect3DDevice9, D3DADAPTER_DEFAULT, D3DCLEAR_TARGET, - D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DDEVTYPE_HAL, D3DFMT_R5G6B5, D3DMULTISAMPLE_NONE, - D3DPRESENT_INTERVAL_DEFAULT, D3DPRESENT_PARAMETERS, D3DPRESENT_RATE_DEFAULT, - D3DSWAPEFFECT_DISCARD, D3D_SDK_VERSION, -}; -use windows::Win32::Graphics::Dxgi::{ - DXGIGetDebugInterface1, IDXGIInfoQueue, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE, -}; -use windows::Win32::Graphics::Gdi::HBRUSH; -use windows::Win32::System::LibraryLoader::GetModuleHandleA; -use windows::Win32::UI::WindowsAndMessaging::{ - AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, - PostQuitMessage, RegisterClassA, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, HCURSOR, - HICON, HMENU, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, WS_OVERLAPPEDWINDOW, WS_VISIBLE, -}; - -const WINDOW_WIDTH: f64 = 760.0; -const WINDOW_HEIGHT: f64 = 760.0; - -unsafe fn setup_dx_context(hwnd: HWND) -> (IDirect3D9, IDirect3DDevice9) { - let d9_option = Direct3DCreate9(D3D_SDK_VERSION); - match d9_option { - Some(d9) => { - let mut present_params = D3DPRESENT_PARAMETERS { - BackBufferWidth: WINDOW_WIDTH as _, - BackBufferHeight: WINDOW_HEIGHT as _, - BackBufferFormat: D3DFMT_R5G6B5, - BackBufferCount: 1, - MultiSampleType: D3DMULTISAMPLE_NONE, - MultiSampleQuality: 0, - SwapEffect: D3DSWAPEFFECT_DISCARD, - hDeviceWindow: hwnd, - Windowed: BOOL(1), - EnableAutoDepthStencil: BOOL(0), - Flags: 0, - FullScreen_RefreshRateInHz: D3DPRESENT_RATE_DEFAULT, - PresentationInterval: D3DPRESENT_INTERVAL_DEFAULT as u32, - ..core::mem::zeroed() - }; - let mut device: Option = None; - match d9.CreateDevice( - D3DADAPTER_DEFAULT, - D3DDEVTYPE_HAL, - hwnd, - D3DCREATE_SOFTWARE_VERTEXPROCESSING as u32, - &mut present_params, - &mut device, - ) { - Ok(_) => (d9, device.unwrap()), - _ => panic!("CreateDevice failed"), - } - }, - None => panic!("Direct3DCreate9 failed"), - } -} - -#[no_mangle] -pub fn main(_argc: i32, _argv: *const *const u8) { - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true) - .with_thread_names(true) - .init(); - - let hinstance = unsafe { GetModuleHandleA(None).unwrap() }; - let wnd_class = WNDCLASSA { - style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW, - lpfnWndProc: Some(window_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: hinstance.into(), - hIcon: HICON(0), - hCursor: HCURSOR(0), - hbrBackground: HBRUSH(0), - lpszMenuName: PCSTR(null_mut()), - lpszClassName: PCSTR("MyClass\0".as_ptr()), - }; - unsafe { RegisterClassA(&wnd_class) }; - let mut rect = RECT { left: 0, top: 0, right: WINDOW_WIDTH as _, bottom: WINDOW_HEIGHT as _ }; - unsafe { - AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW | WS_VISIBLE, BOOL::from(false)) - .expect("GetWindowRect") - }; - let handle = unsafe { - CreateWindowExA( - WINDOW_EX_STYLE(0), - PCSTR("MyClass\0".as_ptr()), - PCSTR("MiniWIN\0".as_ptr()), - WS_OVERLAPPEDWINDOW | WS_VISIBLE, - // size and position - 100, - 100, - rect.right - rect.left, - rect.bottom - rect.top, - HWND(0), - HMENU(0), - hinstance, - None, - ) - }; - - let (_, device) = unsafe { setup_dx_context(handle) }; - - let mut ctx = imgui::Context::create(); - ctx.set_ini_filename(None); - ctx.io_mut().display_size = [800., 600.]; - - eprintln!("Creating renderer"); - let mut renderer = unsafe { Renderer::new(&mut ctx, device.clone()).unwrap() }; - - let diq: IDXGIInfoQueue = unsafe { DXGIGetDebugInterface1(0) }.unwrap(); - - loop { - unsafe { - for i in 0..diq.GetNumStoredMessages(DXGI_DEBUG_ALL) { - eprintln!("Debug Message {i}"); - let mut msg_len: usize = 0; - diq.GetMessage(DXGI_DEBUG_ALL, i, None, &mut msg_len as _).unwrap(); - let diqm = vec![0u8; msg_len]; - let pdiqm = diqm.as_ptr() as *mut DXGI_INFO_QUEUE_MESSAGE; - diq.GetMessage(DXGI_DEBUG_ALL, i, Some(pdiqm), &mut msg_len as _).unwrap(); - let diqm = pdiqm.as_ref().unwrap(); - eprintln!( - "{}", - String::from_utf8_lossy(std::slice::from_raw_parts( - diqm.pDescription, - diqm.DescriptionByteLength - )) - ); - } - diq.ClearStoredMessages(DXGI_DEBUG_ALL); - } - - let ui = ctx.frame(); - ui.window("Hello world").size([640.0, 480.0], Condition::Always).build(|| { - ui.text("Hello world!"); - ui.text("こんにちは世界!"); - ui.text("This...is...imgui-rs!"); - ui.separator(); - let mouse_pos = ui.io().mouse_pos; - ui.text(format!("Mouse Position: ({:.1},{:.1})", mouse_pos[0], mouse_pos[1])); - - imgui::ListBox::new("##listbox").size([300., 150.]).build(ui, || { - ui.selectable("test1"); - ui.selectable("test2"); - ui.selectable_config("test3").selected(true).build(); - ui.selectable("test4"); - ui.selectable("test5"); - }); - - if ui.begin_combo("##combo", "test").is_some() { - ui.selectable("test1"); - ui.selectable("test2"); - ui.selectable_config("test3").selected(true).build(); - ui.selectable("test4"); - ui.selectable("test5"); - }; - ui.open_popup("##combo"); - }); - unsafe { - device.Clear(0, null_mut(), D3DCLEAR_TARGET as u32, 0xFFAA_AAAA, 1.0, 0).unwrap(); - device.BeginScene().unwrap(); - } - - renderer.render(ctx.render()).unwrap(); - unsafe { - device.EndScene().unwrap(); - device.Present(null_mut(), null_mut(), None, null_mut()).unwrap(); - }; - - if !handle_message(handle) { - break; - } - } -} - -fn handle_message(window: HWND) -> bool { - unsafe { - let mut msg = MaybeUninit::uninit(); - if GetMessageA(msg.as_mut_ptr(), window, 0, 0).0 > 0 { - TranslateMessage(msg.as_ptr()); - DispatchMessageA(msg.as_ptr()); - msg.as_ptr().as_ref().map(|m| m.message != WM_QUIT).unwrap_or(true) - } else { - false - } - } -} - -#[allow(clippy::missing_safety_doc)] -pub unsafe extern "system" fn window_proc( - hwnd: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - match msg { - WM_DESTROY => { - PostQuitMessage(0); - }, - _ => { - return DefWindowProcA(hwnd, msg, wparam, lparam); - }, - } - LRESULT(0) -} diff --git a/src/hooks/common/mod.rs b/src/hooks/common/mod.rs deleted file mode 100644 index 0ac81e9b..00000000 --- a/src/hooks/common/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::mem; -use std::ptr::null; - -use imgui::Key; -use tracing::{debug, error}; -use windows::core::{w, PCWSTR}; -use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; -use windows::Win32::Graphics::Gdi::HBRUSH; -use windows::Win32::System::LibraryLoader::GetModuleHandleW; -use windows::Win32::UI::Input::KeyboardAndMouse::*; -use windows::Win32::UI::WindowsAndMessaging::{ - CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassExW, UnregisterClassW, CS_HREDRAW, - CS_VREDRAW, HCURSOR, HICON, HWND_MESSAGE, WNDCLASSEXW, WS_OVERLAPPEDWINDOW, -}; - -pub use crate::hooks::common::wnd_proc::*; -use crate::hooks::ImguiRenderLoop; -use crate::mh::MhHook; - -pub mod wnd_proc; - -pub type WndProcType = - unsafe extern "system" fn(hwnd: HWND, umsg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT; - -/// Generic trait for platform-specific hooks. -/// -/// Implement this if you are building a custom renderer. -/// -/// Check out first party implementations ([`crate::hooks::dx9`], -/// [`crate::hooks::dx11`], [`crate::hooks::dx12`], [`crate::hooks::opengl3`]) -/// for guidance on how to implement the methods. -pub trait Hooks { - fn from_render_loop(t: T) -> Box - where - Self: Sized, - T: ImguiRenderLoop + Send + Sync + 'static; - - /// Return the list of hooks to be enabled, in order. - fn hooks(&self) -> &[MhHook]; - - /// Cleanup global data and disable the hooks. - /// - /// # Safety - /// - /// Is most definitely UB. - unsafe fn unhook(&mut self); -} - -/// Implement this if you are building a custom renderer. -/// -/// Check out first party implementations ([`crate::hooks::dx9`], -/// [`crate::hooks::dx11`], [`crate::hooks::dx12`], [`crate::hooks::opengl3`]) -/// for guidance on how to implement the methods. -pub trait ImguiWindowsEventHandler { - fn io(&self) -> &imgui::Io; - fn io_mut(&mut self) -> &mut imgui::Io; - - fn wnd_proc(&self) -> WndProcType; - - fn setup_io(&mut self) { - let io = ImguiWindowsEventHandler::io_mut(self); - - io.nav_active = true; - io.nav_visible = true; - - // Initialize keys - io[Key::Tab] = VK_TAB.0 as _; - io[Key::LeftArrow] = VK_LEFT.0 as _; - io[Key::RightArrow] = VK_RIGHT.0 as _; - io[Key::UpArrow] = VK_UP.0 as _; - io[Key::DownArrow] = VK_DOWN.0 as _; - io[Key::PageUp] = VK_PRIOR.0 as _; - io[Key::PageDown] = VK_NEXT.0 as _; - io[Key::Home] = VK_HOME.0 as _; - io[Key::End] = VK_END.0 as _; - io[Key::Insert] = VK_INSERT.0 as _; - io[Key::Delete] = VK_DELETE.0 as _; - io[Key::Backspace] = VK_BACK.0 as _; - io[Key::Space] = VK_SPACE.0 as _; - io[Key::Enter] = VK_RETURN.0 as _; - io[Key::Escape] = VK_ESCAPE.0 as _; - io[Key::A] = VK_A.0 as _; - io[Key::C] = VK_C.0 as _; - io[Key::V] = VK_V.0 as _; - io[Key::X] = VK_X.0 as _; - io[Key::Y] = VK_Y.0 as _; - io[Key::Z] = VK_Z.0 as _; - } -} - -/// A RAII dummy window. -/// -/// Registers a class and creates a window on instantiation. -/// Destroys the window and unregisters the class on drop. -pub struct DummyHwnd(HWND, WNDCLASSEXW); - -impl Default for DummyHwnd { - fn default() -> Self { - Self::new() - } -} - -impl DummyHwnd { - pub fn new() -> Self { - // The window procedure for the class just calls `DefWindowProcW`. - unsafe extern "system" fn wnd_proc( - hwnd: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, - ) -> LRESULT { - DefWindowProcW(hwnd, msg, wparam, lparam) - } - - // Create and register the class. - let wndclass = WNDCLASSEXW { - cbSize: mem::size_of::() as u32, - style: CS_HREDRAW | CS_VREDRAW, - lpfnWndProc: Some(wnd_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: unsafe { GetModuleHandleW(None).unwrap().into() }, - hIcon: HICON(0), - hCursor: HCURSOR(0), - hbrBackground: HBRUSH(0), - lpszMenuName: PCWSTR(null()), - lpszClassName: w!("HUDHOOK"), - hIconSm: HICON(0), - }; - debug!("{:?}", wndclass); - unsafe { RegisterClassExW(&wndclass) }; - - // Create the window. - let hwnd = unsafe { - CreateWindowExW( - Default::default(), - wndclass.lpszClassName, - w!("HUDHOOK"), - WS_OVERLAPPEDWINDOW, - 0, - 0, - 100, - 100, - HWND_MESSAGE, - None, - wndclass.hInstance, - None, - ) - }; - debug!("{:?}", hwnd); - - Self(hwnd, wndclass) - } - - // Retrieve the window handle. - pub fn hwnd(&self) -> HWND { - self.0 - } -} - -impl Drop for DummyHwnd { - fn drop(&mut self) { - // Destroy the window and unregister the class. - unsafe { - if let Err(e) = DestroyWindow(self.0) { - error!("DestroyWindow: {e}"); - } - if let Err(e) = UnregisterClassW(self.1.lpszClassName, self.1.hInstance) { - error!("UnregisterClass: {e}"); - } - } - } -} diff --git a/src/hooks/dx11.rs b/src/hooks/dx11.rs index e8067dd0..fb8659bd 100644 --- a/src/hooks/dx11.rs +++ b/src/hooks/dx11.rs @@ -2,13 +2,9 @@ use std::ffi::c_void; use std::mem; use std::sync::OnceLock; -use imgui::Context; -use parking_lot::Mutex; -use tracing::{debug, error, trace}; +use tracing::{info, trace}; use windows::core::{Interface, HRESULT}; -use windows::Win32::Foundation::{ - GetLastError, BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, WPARAM, -}; +use windows::Win32::Foundation::BOOL; use windows::Win32::Graphics::Direct3D::{ D3D_DRIVER_TYPE_NULL, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_11_0, }; @@ -17,270 +13,56 @@ use windows::Win32::Graphics::Direct3D11::{ D3D11_SDK_VERSION, }; use windows::Win32::Graphics::Dxgi::Common::{ - DXGI_FORMAT, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_DESC, DXGI_MODE_SCALING_UNSPECIFIED, + DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_DESC, DXGI_MODE_SCALING_UNSPECIFIED, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, DXGI_SAMPLE_DESC, }; use windows::Win32::Graphics::Dxgi::{ IDXGISwapChain, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_EFFECT_DISCARD, DXGI_USAGE_RENDER_TARGET_OUTPUT, }; -use windows::Win32::Graphics::Gdi::ScreenToClient; -#[cfg(target_arch = "x86")] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongA; -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrA; -use windows::Win32::UI::WindowsAndMessaging::*; -use crate::hooks::common::{imgui_wnd_proc_impl, DummyHwnd, ImguiWindowsEventHandler, WndProcType}; -use crate::hooks::{Hooks, ImguiRenderLoop}; +use super::DummyHwnd; +use crate::hooks::render::RenderState; use crate::mh::MhHook; -use crate::renderers::imgui_dx11; -use crate::util::try_out_param; +use crate::{Hooks, ImguiRenderLoop}; type DXGISwapChainPresentType = unsafe extern "system" fn(This: IDXGISwapChain, SyncInterval: u32, Flags: u32) -> HRESULT; -type DXGISwapChainResizeBuffersType = unsafe extern "system" fn( - This: IDXGISwapChain, - buffer_count: u32, - width: u32, - height: u32, - new_format: DXGI_FORMAT, - flags: u32, -) -> HRESULT; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Data structures and traits -//////////////////////////////////////////////////////////////////////////////////////////////////// - -trait Renderer { - /// Invoked once per frame. - fn render(&mut self); +struct Trampolines { + dxgi_swap_chain_present: DXGISwapChainPresentType, } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Global singletons -//////////////////////////////////////////////////////////////////////////////////////////////////// +static mut TRAMPOLINES: OnceLock = OnceLock::new(); -static TRAMPOLINE: OnceLock<(DXGISwapChainPresentType, DXGISwapChainResizeBuffersType)> = - OnceLock::new(); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Hook entry points -//////////////////////////////////////////////////////////////////////////////////////////////////// - -static mut IMGUI_RENDER_LOOP: OnceLock> = OnceLock::new(); -static mut IMGUI_RENDERER: OnceLock>> = OnceLock::new(); - -unsafe extern "system" fn imgui_dxgi_swap_chain_present_impl( +unsafe extern "system" fn dxgi_swap_chain_present_impl( p_this: IDXGISwapChain, sync_interval: u32, flags: u32, ) -> HRESULT { - let (trampoline, _) = - TRAMPOLINE.get().expect("IDXGISwapChain::Present trampoline uninitialized"); - - let mut renderer = IMGUI_RENDERER - .get_or_init(|| Mutex::new(Box::new(ImguiRenderer::new(p_this.clone())))) - .lock(); - - renderer.render(Some(p_this.clone())); - drop(renderer); - - trace!("Invoking IDXGISwapChain::Present trampoline"); - let r = trampoline(p_this, sync_interval, flags); - trace!("Trampoline returned {:?}", r); - - r -} - -unsafe extern "system" fn imgui_resize_buffers_impl( - swap_chain: IDXGISwapChain, - buffer_count: u32, - width: u32, - height: u32, - new_format: DXGI_FORMAT, - flags: u32, -) -> HRESULT { - trace!("IDXGISwapChain::ResizeBuffers invoked"); - let (_, trampoline) = - TRAMPOLINE.get().expect("IDXGISwapChain::ResizeBuffer trampoline uninitialized"); - - if let Some(mutex) = IMGUI_RENDERER.take() { - mutex.lock().cleanup(Some(swap_chain.clone())); - }; - - trampoline(swap_chain, buffer_count, width, height, new_format, flags) -} - -unsafe extern "system" fn imgui_wnd_proc( - hwnd: HWND, - umsg: u32, - WPARAM(wparam): WPARAM, - LPARAM(lparam): LPARAM, -) -> LRESULT { - match IMGUI_RENDERER.get().map(Mutex::try_lock) { - Some(Some(imgui_renderer)) => imgui_wnd_proc_impl( - hwnd, - umsg, - WPARAM(wparam), - LPARAM(lparam), - imgui_renderer, - IMGUI_RENDER_LOOP.get().unwrap(), - ), - Some(None) => { - debug!("Could not lock in WndProc"); - DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) - }, - None => { - debug!("WndProc called before hook was set"); - DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) - }, - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Render loops -//////////////////////////////////////////////////////////////////////////////////////////////////// - -struct ImguiRenderer { - ctx: Context, - engine: imgui_dx11::RenderEngine, - wnd_proc: WndProcType, - swap_chain: IDXGISwapChain, -} - -impl ImguiRenderer { - unsafe fn new(swap_chain: IDXGISwapChain) -> Self { - trace!("Initializing imgui context"); - - let mut ctx = Context::create(); - ctx.set_ini_filename(None); - - IMGUI_RENDER_LOOP.get_mut().unwrap().initialize(&mut ctx); - - trace!("Initializing renderer"); - - let dev: ID3D11Device = swap_chain.GetDevice().expect("GetDevice"); - let dev_ctx: ID3D11DeviceContext = dev.GetImmediateContext().expect("GetImmediateContext"); - let sd: DXGI_SWAP_CHAIN_DESC = try_out_param(|sd| swap_chain.GetDesc(sd)).expect("GetDesc"); - - let engine = - imgui_dx11::RenderEngine::new_with_ptrs(dev, dev_ctx, swap_chain.clone(), &mut ctx); - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongPtrA( - sd.OutputWindow, - GWLP_WNDPROC, - imgui_wnd_proc as usize as isize, - )); - - #[cfg(target_arch = "x86")] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongA( - sd.OutputWindow, - GWLP_WNDPROC, - imgui_wnd_proc as usize as i32, - )); + let Trampolines { dxgi_swap_chain_present } = + TRAMPOLINES.get().expect("DirectX 11 trampolines uninitialized"); - trace!("Renderer initialized"); - let mut renderer = ImguiRenderer { ctx, engine, wnd_proc, swap_chain }; - - ImguiWindowsEventHandler::setup_io(&mut renderer); - - renderer - } - - unsafe fn render(&mut self, swap_chain: Option) { - trace!("Present impl: Rendering"); - - let swap_chain = self.store_swap_chain(swap_chain); - let sd: DXGI_SWAP_CHAIN_DESC = try_out_param(|sd| swap_chain.GetDesc(sd)).expect("GetDesc"); - - if let Some(rect) = self.engine.get_client_rect() { - let io = self.ctx_mut().io_mut(); - - io.display_size = [(rect.right - rect.left) as f32, (rect.bottom - rect.top) as f32]; - - let mut pos = POINT { x: 0, y: 0 }; - - let active_window = GetForegroundWindow(); - if !HANDLE(active_window.0).is_invalid() - && (active_window == sd.OutputWindow - || IsChild(active_window, sd.OutputWindow).as_bool()) - { - let gcp = GetCursorPos(&mut pos as *mut _); - if gcp.is_ok() && ScreenToClient(sd.OutputWindow, &mut pos as *mut _).as_bool() { - io.mouse_pos[0] = pos.x as _; - io.mouse_pos[1] = pos.y as _; - } - } - } else { - trace!("GetWindowRect error: {:?}", GetLastError()); - } - - let ctx = &mut self.ctx; - let ui = ctx.frame(); - IMGUI_RENDER_LOOP.get_mut().unwrap().render(ui); - let draw_data = ctx.render(); - - if let Err(e) = self.engine.render_draw_data(draw_data) { - error!("ImGui renderer error: {:?}", e); - } - } - - fn store_swap_chain(&mut self, swap_chain: Option) -> IDXGISwapChain { - if let Some(swap_chain) = swap_chain { - self.swap_chain = swap_chain; - } - - self.swap_chain.clone() - } - - unsafe fn cleanup(&mut self, swap_chain: Option) { - let swap_chain = self.store_swap_chain(swap_chain); - let sd: DXGI_SWAP_CHAIN_DESC = try_out_param(|sd| swap_chain.GetDesc(sd)).expect("GetDesc"); - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - SetWindowLongPtrA(sd.OutputWindow, GWLP_WNDPROC, self.wnd_proc as usize as isize); - - #[cfg(target_arch = "x86")] - SetWindowLongA(sd.OutputWindow, GWLP_WNDPROC, self.wnd_proc as usize as i32); - } - - fn ctx(&self) -> &imgui::Context { - &self.ctx + // Don't attempt a render if one is already underway: it might be that the + // renderer itself is currently invoking `Present`. + if RenderState::is_locked() { + return dxgi_swap_chain_present(p_this, sync_interval, flags); } - fn ctx_mut(&mut self) -> &mut imgui::Context { - &mut self.ctx - } -} - -impl ImguiWindowsEventHandler for ImguiRenderer { - fn io(&self) -> &imgui::Io { - self.ctx().io() - } + let hwnd = RenderState::setup(|| { + let mut desc = Default::default(); + p_this.GetDesc(&mut desc).unwrap(); + info!("Output window: {:?}", p_this); + info!("Desc: {:?}", desc); + desc.OutputWindow + }); - fn io_mut(&mut self) -> &mut imgui::Io { - self.ctx_mut().io_mut() - } + RenderState::render(hwnd); - fn wnd_proc(&self) -> WndProcType { - self.wnd_proc - } + trace!("Call IDXGISwapChain::Present trampoline"); + dxgi_swap_chain_present(p_this, sync_interval, flags) } -unsafe impl Send for ImguiRenderer {} -unsafe impl Sync for ImguiRenderer {} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Function address finders -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/// Get the `IDXGISwapChain::Present` function address. -/// -/// Creates a swap chain + device instance and looks up its -/// vtable to find the address. -fn get_present_addr() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersType) { +fn get_target_addrs() -> DXGISwapChainPresentType { let mut p_device: Option = None; let mut p_context: Option = None; let mut p_swap_chain: Option = None; @@ -303,7 +85,7 @@ fn get_present_addr() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersTy }, BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, BufferCount: 1, - OutputWindow: dummy_hwnd.hwnd(), // GetDesktopWindow(), + OutputWindow: dummy_hwnd.hwnd(), Windowed: BOOL(1), SwapEffect: DXGI_SWAP_EFFECT_DISCARD, SampleDesc: DXGI_SAMPLE_DESC { Count: 1, ..Default::default() }, @@ -319,45 +101,43 @@ fn get_present_addr() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersTy let swap_chain = p_swap_chain.unwrap(); - let present_ptr = swap_chain.vtable().Present; - let resize_buffers_ptr = swap_chain.vtable().ResizeBuffers; + let present_ptr: DXGISwapChainPresentType = + unsafe { mem::transmute(swap_chain.vtable().Present) }; - unsafe { (std::mem::transmute(present_ptr), std::mem::transmute(resize_buffers_ptr)) } + present_ptr } -pub struct ImguiDx11Hooks([MhHook; 2]); +pub struct ImguiDx11Hooks([MhHook; 1]); impl ImguiDx11Hooks { - /// Construct a [`RawDetour`] that will render UI via the provided - /// `ImguiRenderLoop`. + /// Construct a set of [`crate::mh::MhHook`]s that will render UI via the + /// provided [`ImguiRenderLoop`]. + /// + /// The following functions are hooked: + /// - `IDXGISwapChain::Present` /// /// # Safety /// /// yolo pub unsafe fn new(t: T) -> Self where - T: Send + Sync + ImguiRenderLoop, + T: ImguiRenderLoop + Send + Sync, { - let (present_addr, resize_buffers_addr) = get_present_addr(); - debug!("IDXGISwapChain::Present = {:p}", present_addr as *mut c_void); - debug!("IDXGISwapChain::ResizeBuffers = {:p}", resize_buffers_addr as *mut c_void); + let dxgi_swap_chain_present_addr = get_target_addrs(); - let hook_present = - MhHook::new(present_addr as *mut _, imgui_dxgi_swap_chain_present_impl as *mut _) - .expect("couldn't create IDXGISwapChain::Present hook"); - let hook_resize_buffers = - MhHook::new(resize_buffers_addr as *mut _, imgui_resize_buffers_impl as *mut _) - .expect("couldn't create IDXGISwapChain::ResizeBuffers hook"); + trace!("IDXGISwapChain::Present = {:p}", dxgi_swap_chain_present_addr as *const c_void); + let hook_present = MhHook::new( + dxgi_swap_chain_present_addr as *mut _, + dxgi_swap_chain_present_impl as *mut _, + ) + .expect("couldn't create IDXGISwapChain::Present hook"); - IMGUI_RENDER_LOOP.get_or_init(|| Box::new(t)); - TRAMPOLINE.get_or_init(|| { - ( - mem::transmute(hook_present.trampoline()), - mem::transmute(hook_resize_buffers.trampoline()), - ) + RenderState::set_render_loop(t); + TRAMPOLINES.get_or_init(|| Trampolines { + dxgi_swap_chain_present: mem::transmute(hook_present.trampoline()), }); - Self([hook_present, hook_resize_buffers]) + Self([hook_present]) } } @@ -367,7 +147,7 @@ impl Hooks for ImguiDx11Hooks { Self: Sized, T: ImguiRenderLoop + Send + Sync + 'static, { - Box::new(unsafe { ImguiDx11Hooks::new(t) }) + Box::new(unsafe { Self::new(t) }) } fn hooks(&self) -> &[MhHook] { @@ -375,13 +155,7 @@ impl Hooks for ImguiDx11Hooks { } unsafe fn unhook(&mut self) { - trace!("Disabling hooks..."); - - trace!("Cleaning up renderer..."); - if let Some(renderer) = IMGUI_RENDERER.take() { - renderer.lock().cleanup(None); - } - - drop(IMGUI_RENDER_LOOP.take()); + RenderState::cleanup(); + TRAMPOLINES.take(); } } diff --git a/src/hooks/dx12.rs b/src/hooks/dx12.rs index f777b86c..ea3c38dd 100644 --- a/src/hooks/dx12.rs +++ b/src/hooks/dx12.rs @@ -1,145 +1,94 @@ -//! Hook for DirectX 12 applications. use std::ffi::c_void; -use std::mem::{self, ManuallyDrop}; -use std::ptr::null; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::time::{Duration, Instant}; -use std::{hint, thread}; +use std::mem; +use std::sync::OnceLock; -use imgui::Context; -use once_cell::sync::OnceCell; -use parking_lot::Mutex; -use tracing::{debug, error, info, trace}; -use windows::core::{w, ComInterface, Interface, HRESULT, PCWSTR}; -use windows::Win32::Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}; +use tracing::{debug, info, trace}; +use windows::core::{Interface, HRESULT}; +use windows::Win32::Foundation::BOOL; use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0; -use windows::Win32::Graphics::Direct3D12::*; -use windows::Win32::Graphics::Dxgi::Common::*; +use windows::Win32::Graphics::Direct3D12::{ + D3D12CreateDevice, ID3D12CommandQueue, ID3D12Device, D3D12_COMMAND_LIST_TYPE_DIRECT, + D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, +}; +use windows::Win32::Graphics::Dxgi::Common::{ + DXGI_FORMAT, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_DESC, DXGI_MODE_SCALING_UNSPECIFIED, + DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, DXGI_RATIONAL, DXGI_SAMPLE_DESC, +}; use windows::Win32::Graphics::Dxgi::{ CreateDXGIFactory1, DXGIGetDebugInterface1, IDXGIFactory1, IDXGIInfoQueue, IDXGISwapChain, IDXGISwapChain3, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH, DXGI_SWAP_EFFECT_FLIP_DISCARD, DXGI_USAGE_RENDER_TARGET_OUTPUT, }; -use windows::Win32::Graphics::Gdi::ScreenToClient; -use windows::Win32::System::Threading::{ - CreateEventExW, WaitForSingleObjectEx, CREATE_EVENT, INFINITE, -}; -#[cfg(target_arch = "x86")] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongA; -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrA; -use windows::Win32::UI::WindowsAndMessaging::*; -use crate::hooks::common::{imgui_wnd_proc_impl, DummyHwnd, ImguiWindowsEventHandler, WndProcType}; -use crate::hooks::{Hooks, ImguiRenderLoop}; +use super::DummyHwnd; +use crate::hooks::render::RenderState; use crate::mh::MhHook; -use crate::renderers::imgui_dx12::RenderEngine; -use crate::util::{try_out_param, try_out_ptr}; +use crate::util::try_out_ptr; +use crate::{Hooks, ImguiRenderLoop}; -//////////////////////////////////////////////////////////////////////////////// -// Utilities -//////////////////////////////////////////////////////////////////////////////// - -/// Spin-loop based synchronization struct. -/// -/// Call [`Fence::lock`] in a thread to indicate some operation is in progress, -/// and [`Fence::wait`] on a different thread to create a spin-loop that waits -/// for the lock to be dropped. -struct Fence(AtomicBool); - -impl Fence { - const fn new() -> Self { - Self(AtomicBool::new(false)) - } +type DXGISwapChainPresentType = + unsafe extern "system" fn(This: IDXGISwapChain3, SyncInterval: u32, Flags: u32) -> HRESULT; - /// Create a [`FenceGuard`]. - fn lock(&self) -> FenceGuard<'_> { - FenceGuard::new(self) - } +type DXGISwapChainResizeBuffersType = unsafe extern "system" fn( + This: IDXGISwapChain3, + buffer_count: u32, + width: u32, + height: u32, + new_format: DXGI_FORMAT, + flags: u32, +) -> HRESULT; - /// Wait in a spin-loop for the [`FenceGuard`] created by [`Fence::lock`] to - /// be dropped. - fn wait(&self) { - while self.0.load(Ordering::SeqCst) { - hint::spin_loop(); - } - } +struct Trampolines { + dxgi_swap_chain_present: DXGISwapChainPresentType, + dxgi_swap_chain_resize_buffers: DXGISwapChainResizeBuffersType, } -/// A RAII implementation of a spin-loop for a [`Fence`]. When this is dropped, -/// the wait on a [`Fence`] will terminate. -struct FenceGuard<'a>(&'a Fence); +static mut TRAMPOLINES: OnceLock = OnceLock::new(); -impl<'a> FenceGuard<'a> { - fn new(fence: &'a Fence) -> Self { - fence.0.store(true, Ordering::SeqCst); - Self(fence) - } -} +unsafe extern "system" fn dxgi_swap_chain_present_impl( + p_this: IDXGISwapChain3, + sync_interval: u32, + flags: u32, +) -> HRESULT { + let Trampolines { dxgi_swap_chain_present, .. } = + TRAMPOLINES.get().expect("DirectX 12 trampolines uninitialized"); -impl<'a> Drop for FenceGuard<'a> { - fn drop(&mut self) { - self.0 .0.store(false, Ordering::SeqCst); + // Don't attempt a render if one is already underway: it might be that the + // renderer itself is currently invoking `Present`. + if RenderState::is_locked() { + return dxgi_swap_chain_present(p_this, sync_interval, flags); } -} -//////////////////////////////////////////////////////////////////////////////// -// Type aliases -//////////////////////////////////////////////////////////////////////////////// + let hwnd = RenderState::setup(|| { + let mut desc = Default::default(); + p_this.GetDesc(&mut desc).unwrap(); + info!("Output window: {:?}", p_this); + info!("Desc: {:?}", desc); + desc.OutputWindow + }); -type DXGISwapChainPresentType = - unsafe extern "system" fn(This: IDXGISwapChain3, SyncInterval: u32, Flags: u32) -> HRESULT; + RenderState::render(hwnd); -type ExecuteCommandListsType = unsafe extern "system" fn( - This: ID3D12CommandQueue, - num_command_lists: u32, - command_lists: *mut ID3D12CommandList, -); + trace!("Call IDXGISwapChain::Present trampoline"); + dxgi_swap_chain_present(p_this, sync_interval, flags) +} -type ResizeBuffersType = unsafe extern "system" fn( - This: IDXGISwapChain3, +unsafe extern "system" fn dxgi_swap_chain_resize_buffers_impl( + p_this: IDXGISwapChain3, buffer_count: u32, width: u32, height: u32, new_format: DXGI_FORMAT, flags: u32, -) -> HRESULT; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Data structures and traits -//////////////////////////////////////////////////////////////////////////////////////////////////// +) -> HRESULT { + let Trampolines { dxgi_swap_chain_resize_buffers, .. } = + TRAMPOLINES.get().expect("DirectX 12 trampolines uninitialized"); -trait Renderer { - /// Invoked once per frame. - fn render(&mut self); + trace!("Call IDXGISwapChain::ResizeBuffers trampoline"); + dxgi_swap_chain_resize_buffers(p_this, buffer_count, width, height, new_format, flags) } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Global singletons -//////////////////////////////////////////////////////////////////////////////////////////////////// - -static TRAMPOLINE: OnceCell<( - DXGISwapChainPresentType, - ExecuteCommandListsType, - ResizeBuffersType, -)> = OnceCell::new(); - -const COMMAND_ALLOCATOR_NAMES: [PCWSTR; 8] = [ - w!("hudhook Command allocator #0"), - w!("hudhook Command allocator #1"), - w!("hudhook Command allocator #2"), - w!("hudhook Command allocator #3"), - w!("hudhook Command allocator #4"), - w!("hudhook Command allocator #5"), - w!("hudhook Command allocator #6"), - w!("hudhook Command allocator #7"), -]; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Debugging -//////////////////////////////////////////////////////////////////////////////////////////////////// - unsafe fn print_dxgi_debug_messages() { let diq: IDXGIInfoQueue = DXGIGetDebugInterface1(0).unwrap(); @@ -161,490 +110,26 @@ unsafe fn print_dxgi_debug_messages() { diq.ClearStoredMessages(DXGI_DEBUG_ALL); } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Hook entry points -//////////////////////////////////////////////////////////////////////////////////////////////////// - -static mut IMGUI_RENDER_LOOP: OnceCell> = OnceCell::new(); -static mut IMGUI_RENDERER: OnceCell>> = OnceCell::new(); -static mut COMMAND_QUEUE_GUARD: OnceCell<()> = OnceCell::new(); -static DXGI_DEBUG_ENABLED: AtomicBool = AtomicBool::new(false); - -static CQECL_RUNNING: Fence = Fence::new(); -static PRESENT_RUNNING: Fence = Fence::new(); -static RBUF_RUNNING: Fence = Fence::new(); - -#[derive(Debug)] -struct FrameContext { - back_buffer: ID3D12Resource, - desc_handle: D3D12_CPU_DESCRIPTOR_HANDLE, - command_allocator: ID3D12CommandAllocator, - fence: ID3D12Fence, - fence_val: u64, - fence_event: HANDLE, -} - -impl FrameContext { - fn incr(&mut self) { - static FENCE_MAX: AtomicU64 = AtomicU64::new(0); - self.fence_val = FENCE_MAX.fetch_add(1, Ordering::SeqCst); - } - - fn wait_fence(&mut self) { - unsafe { - if self.fence.GetCompletedValue() < self.fence_val { - self.fence.SetEventOnCompletion(self.fence_val, self.fence_event).unwrap(); - WaitForSingleObjectEx(self.fence_event, INFINITE, false); - } - } - } -} - -unsafe extern "system" fn imgui_execute_command_lists_impl( - cmd_queue: ID3D12CommandQueue, - num_command_lists: u32, - command_lists: *mut ID3D12CommandList, -) { - let _fence = CQECL_RUNNING.lock(); - - trace!( - "ID3D12CommandQueue::ExecuteCommandLists({cmd_queue:?}, {num_command_lists}, \ - {command_lists:p}) invoked" - ); - COMMAND_QUEUE_GUARD - .get_or_try_init(|| { - let desc = cmd_queue.GetDesc(); - trace!("CommandQueue description: {:?}", desc); - - if desc.Type.0 != 0 { - trace!("Skipping CommandQueue"); - return Err(()); - } - - if let Some(renderer) = IMGUI_RENDERER.get() { - trace!("cmd_queue ptr was set"); - renderer.lock().command_queue = Some(cmd_queue.clone()); - Ok(()) - } else { - trace!("cmd_queue ptr was not set: renderer not initialized"); - Err(()) - } - }) - .ok(); - - let (_, trampoline, _) = - TRAMPOLINE.get().expect("ID3D12CommandQueue::ExecuteCommandLists trampoline uninitialized"); - trampoline(cmd_queue, num_command_lists, command_lists); -} - -unsafe extern "system" fn imgui_dxgi_swap_chain_present_impl( - swap_chain: IDXGISwapChain3, - sync_interval: u32, - flags: u32, -) -> HRESULT { - let _fence = PRESENT_RUNNING.lock(); - - let (trampoline_present, ..) = - TRAMPOLINE.get().expect("IDXGISwapChain::Present trampoline uninitialized"); - - trace!("IDXGISwapChain3::Present({swap_chain:?}, {sync_interval}, {flags}) invoked"); - - let renderer = - IMGUI_RENDERER.get_or_init(|| Mutex::new(Box::new(ImguiRenderer::new(swap_chain.clone())))); - - { - renderer.lock().render(Some(swap_chain.clone())); - } - - trace!("Invoking IDXGISwapChain3::Present trampoline"); - let r = trampoline_present(swap_chain, sync_interval, flags); - trace!("Trampoline returned {:?}", r); - - // Windows + R -> dxcpl.exe - // Edit list... -> add eldenring.exe - // DXGI debug layer -> Force On - if DXGI_DEBUG_ENABLED.load(Ordering::SeqCst) { - print_dxgi_debug_messages(); - } - - r -} - -unsafe extern "system" fn imgui_resize_buffers_impl( - swap_chain: IDXGISwapChain3, - buffer_count: u32, - width: u32, - height: u32, - new_format: DXGI_FORMAT, - flags: u32, -) -> HRESULT { - let _fence = RBUF_RUNNING.lock(); - - trace!("IDXGISwapChain3::ResizeBuffers invoked"); - let (_, _, trampoline) = - TRAMPOLINE.get().expect("IDXGISwapChain3::ResizeBuffer trampoline uninitialized"); - - if let Some(mutex) = IMGUI_RENDERER.take() { - mutex.lock().cleanup(Some(swap_chain.clone())); - }; - - COMMAND_QUEUE_GUARD.take(); - - trampoline(swap_chain, buffer_count, width, height, new_format, flags) -} - -unsafe extern "system" fn imgui_wnd_proc( - hwnd: HWND, - umsg: u32, - WPARAM(wparam): WPARAM, - LPARAM(lparam): LPARAM, -) -> LRESULT { - trace!("Entering WndProc {:x} {:x} {:x} {:x}", hwnd.0, umsg, wparam, lparam); - - match IMGUI_RENDERER.get().map(Mutex::try_lock) { - Some(Some(imgui_renderer)) => imgui_wnd_proc_impl( - hwnd, - umsg, - WPARAM(wparam), - LPARAM(lparam), - imgui_renderer, - IMGUI_RENDER_LOOP.get().unwrap(), - ), - Some(None) => { - debug!("Could not lock in WndProc"); - DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) - }, - None => { - debug!("WndProc called before hook was set"); - DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) - }, - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Render loops -//////////////////////////////////////////////////////////////////////////////////////////////////// - -struct ImguiRenderer { - ctx: Context, - engine: RenderEngine, - wnd_proc: WndProcType, - frame_contexts: Vec, - _rtv_heap: ID3D12DescriptorHeap, - renderer_heap: ID3D12DescriptorHeap, - command_queue: Option, - command_list: ID3D12GraphicsCommandList, - swap_chain: IDXGISwapChain3, -} - -impl ImguiRenderer { - unsafe fn new(swap_chain: IDXGISwapChain3) -> Self { - trace!("Initializing renderer"); - let dev = swap_chain.GetDevice::().expect("GetDevice"); - let sd = try_out_param(|sd| swap_chain.GetDesc(sd)).expect("GetDesc"); - - let renderer_heap: ID3D12DescriptorHeap = dev - .CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { - Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - NumDescriptors: sd.BufferCount, - Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, - NodeMask: 0, - }) - .unwrap(); - - let command_allocator: ID3D12CommandAllocator = - dev.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT).unwrap(); - - let command_list: ID3D12GraphicsCommandList = dev - .CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &command_allocator, None) - .unwrap(); - command_list.Close().unwrap(); - - command_list - .SetName(PCWSTR(w!("hudhook Command List").as_ptr())) - .expect("Couldn't set command list name"); - - let rtv_heap: ID3D12DescriptorHeap = dev - .CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { - Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV, - NumDescriptors: sd.BufferCount, - Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE, - NodeMask: 1, - }) - .unwrap(); - - let rtv_heap_inc_size = - dev.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - - let rtv_handle_start = rtv_heap.GetCPUDescriptorHandleForHeapStart(); - trace!("rtv_handle_start ptr {:x}", rtv_handle_start.ptr); - - let frame_contexts: Vec = (0..sd.BufferCount) - .map(|i| { - let desc_handle = D3D12_CPU_DESCRIPTOR_HANDLE { - ptr: rtv_handle_start.ptr + (i * rtv_heap_inc_size) as usize, - }; - trace!("desc handle {i} ptr {:x}", desc_handle.ptr); - - let back_buffer: ID3D12Resource = swap_chain.GetBuffer(i).expect("GetBuffer"); - dev.CreateRenderTargetView(&back_buffer, None, desc_handle); - - let command_allocator: ID3D12CommandAllocator = - dev.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT).unwrap(); - let command_allocator_name = COMMAND_ALLOCATOR_NAMES - [usize::min(COMMAND_ALLOCATOR_NAMES.len() - 1, i as usize)]; - - command_allocator - .SetName(PCWSTR(command_allocator_name.as_ptr())) - .expect("Couldn't set command allocator name"); - - FrameContext { - desc_handle, - back_buffer, - command_allocator, - fence: dev.CreateFence(0, D3D12_FENCE_FLAG_NONE).unwrap(), - fence_val: 0, - fence_event: CreateEventExW(None, PCWSTR(null()), CREATE_EVENT(0), 0x1F0003) - .unwrap(), - } - }) - .collect(); - - trace!("number of frame contexts: {}", frame_contexts.len()); - - let mut ctx = Context::create(); - let cpu_desc = renderer_heap.GetCPUDescriptorHandleForHeapStart(); - let gpu_desc = renderer_heap.GetGPUDescriptorHandleForHeapStart(); - let engine = RenderEngine::new( - &mut ctx, - dev, - sd.BufferCount, - DXGI_FORMAT_R8G8B8A8_UNORM, - renderer_heap.clone(), - cpu_desc, - gpu_desc, - ); - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongPtrA( - sd.OutputWindow, - GWLP_WNDPROC, - imgui_wnd_proc as usize as isize, - )); - - #[cfg(target_arch = "x86")] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongA( - sd.OutputWindow, - GWLP_WNDPROC, - imgui_wnd_proc as usize as i32, - )); - - ctx.set_ini_filename(None); - - IMGUI_RENDER_LOOP.get_mut().unwrap().initialize(&mut ctx); - - debug!("Done init"); - let mut renderer = ImguiRenderer { - ctx, - command_queue: None, - command_list, - engine, - wnd_proc, - _rtv_heap: rtv_heap, - renderer_heap, - frame_contexts, - swap_chain, - }; - - ImguiWindowsEventHandler::setup_io(&mut renderer); - - renderer - } - - fn store_swap_chain(&mut self, swap_chain: Option) -> IDXGISwapChain3 { - if let Some(swap_chain) = swap_chain { - self.swap_chain = swap_chain; - } - - self.swap_chain.clone() - } - - fn render(&mut self, swap_chain: Option) -> Option<()> { - let render_start = Instant::now(); - - let swap_chain = self.store_swap_chain(swap_chain); - - let frame_contexts_idx = unsafe { swap_chain.GetCurrentBackBufferIndex() } as usize; - let frame_context = &mut self.frame_contexts[frame_contexts_idx]; - - trace!("Rendering started"); - let sd = try_out_param(|sd| unsafe { swap_chain.GetDesc(sd) }).expect("GetDesc"); - let rect: Result = - try_out_param(|rect| unsafe { GetClientRect(sd.OutputWindow, rect) }); - - match rect { - Ok(rect) => { - let io = self.ctx.io_mut(); - - io.display_size = - [(rect.right - rect.left) as f32, (rect.bottom - rect.top) as f32]; - - let mut pos = POINT { x: 0, y: 0 }; - - let active_window = unsafe { GetForegroundWindow() }; - if !HANDLE(active_window.0).is_invalid() - && (active_window == sd.OutputWindow - || unsafe { IsChild(active_window, sd.OutputWindow) }.as_bool()) - { - let gcp = unsafe { GetCursorPos(&mut pos as *mut _) }; - if gcp.is_ok() - && unsafe { ScreenToClient(sd.OutputWindow, &mut pos as *mut _) }.as_bool() - { - io.mouse_pos[0] = pos.x as _; - io.mouse_pos[1] = pos.y as _; - } - } - }, - Err(e) => { - trace!("GetClientRect error: {e:?}"); - }, - } - - let command_queue = match self.command_queue.as_ref() { - Some(cq) => cq, - None => { - error!("Null command queue"); - return None; - }, - }; - - self.engine.new_frame(&mut self.ctx); - let ctx = &mut self.ctx; - let ui = ctx.frame(); - unsafe { IMGUI_RENDER_LOOP.get_mut() }.unwrap().render(ui); - let draw_data = ctx.render(); - - let back_buffer = ManuallyDrop::new(Some(frame_context.back_buffer.clone())); - let transition_barrier = ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER { - pResource: back_buffer, - Subresource: D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, - StateBefore: D3D12_RESOURCE_STATE_PRESENT, - StateAfter: D3D12_RESOURCE_STATE_RENDER_TARGET, - }); - - let mut barrier = D3D12_RESOURCE_BARRIER { - Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, - Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE, - Anonymous: D3D12_RESOURCE_BARRIER_0 { Transition: transition_barrier }, - }; - - frame_context.wait_fence(); - frame_context.incr(); - let command_allocator = &frame_context.command_allocator; - - unsafe { - command_allocator.Reset().unwrap(); - self.command_list.Reset(command_allocator, None).unwrap(); - self.command_list.ResourceBarrier(&[barrier.clone()]); - self.command_list.OMSetRenderTargets( - 1, - Some(&frame_context.desc_handle), - BOOL::from(false), - None, - ); - self.command_list.SetDescriptorHeaps(&[Some(self.renderer_heap.clone())]); - }; - - if let Err(e) = - self.engine.render_draw_data(draw_data, &self.command_list, frame_contexts_idx) - { - trace!("{}", e); - if DXGI_DEBUG_ENABLED.load(Ordering::SeqCst) { - unsafe { print_dxgi_debug_messages() } - }; - }; - - // Explicit auto deref necessary because this is ManuallyDrop. - #[allow(clippy::explicit_auto_deref)] - unsafe { - (*barrier.Anonymous.Transition).StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; - (*barrier.Anonymous.Transition).StateAfter = D3D12_RESOURCE_STATE_PRESENT; - } - - let barriers = vec![barrier]; - - unsafe { - self.command_list.ResourceBarrier(&barriers); - self.command_list.Close().unwrap(); - command_queue.ExecuteCommandLists(&[Some(self.command_list.cast().unwrap())]); - command_queue.Signal(&frame_context.fence, frame_context.fence_val).unwrap(); - } - - let barrier = barriers.into_iter().next().unwrap(); - - let transition = ManuallyDrop::into_inner(unsafe { barrier.Anonymous.Transition }); - let _ = ManuallyDrop::into_inner(transition.pResource); - - trace!("Rendering done in {:?}", render_start.elapsed()); - None - } - - unsafe fn cleanup(&mut self, swap_chain: Option) { - let swap_chain = self.store_swap_chain(swap_chain); - let sd = try_out_param(|sd| swap_chain.GetDesc(sd)).expect("GetDesc"); - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - SetWindowLongPtrA(sd.OutputWindow, GWLP_WNDPROC, self.wnd_proc as usize as isize); - - #[cfg(target_arch = "x86")] - SetWindowLongA(sd.OutputWindow, GWLP_WNDPROC, self.wnd_proc as usize as i32); - } -} - -impl ImguiWindowsEventHandler for ImguiRenderer { - fn io(&self) -> &imgui::Io { - self.ctx.io() - } - - fn io_mut(&mut self) -> &mut imgui::Io { - self.ctx.io_mut() - } - - fn wnd_proc(&self) -> WndProcType { - self.wnd_proc - } -} - -unsafe impl Send for ImguiRenderer {} -unsafe impl Sync for ImguiRenderer {} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Function address finders -//////////////////////////////////////////////////////////////////////////////////////////////////// +fn get_target_addrs() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersType) { + let dummy_hwnd = DummyHwnd::new(); -/// Get the `IDXGISwapChain::Present` function address. -/// -/// Creates a swap chain + device instance and looks up its -/// vtable to find the address. -fn get_present_addr() -> (DXGISwapChainPresentType, ExecuteCommandListsType, ResizeBuffersType) { let factory: IDXGIFactory1 = unsafe { CreateDXGIFactory1() }.unwrap(); let adapter = unsafe { factory.EnumAdapters(0) }.unwrap(); let dev: ID3D12Device = try_out_ptr(|v| unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_11_0, v) }) - .expect("D3D12CreateDevice"); - - let queue_desc = D3D12_COMMAND_QUEUE_DESC { - Type: D3D12_COMMAND_LIST_TYPE_DIRECT, - Priority: 0, - Flags: D3D12_COMMAND_QUEUE_FLAG_NONE, - NodeMask: 0, - }; - - let command_queue: ID3D12CommandQueue = - unsafe { dev.CreateCommandQueue(&queue_desc as *const _) }.unwrap(); + .expect("D3D12CreateDevice failed"); + + let command_queue: ID3D12CommandQueue = unsafe { + dev.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC { + Type: D3D12_COMMAND_LIST_TYPE_DIRECT, + Priority: 0, + Flags: D3D12_COMMAND_QUEUE_FLAG_NONE, + NodeMask: 0, + }) + } + .unwrap(); - let dummy_hwnd = DummyHwnd::new(); let swap_chain: IDXGISwapChain = match try_out_ptr(|v| unsafe { factory .CreateSwapChain( @@ -671,48 +156,29 @@ fn get_present_addr() -> (DXGISwapChainPresentType, ExecuteCommandListsType, Res .ok() }) { Ok(swap_chain) => swap_chain, - Err(e) => unsafe { - print_dxgi_debug_messages(); + Err(e) => { + unsafe { print_dxgi_debug_messages() }; panic!("{e:?}"); }, }; - let present_ptr = swap_chain.vtable().Present; - let ecl_ptr = command_queue.vtable().ExecuteCommandLists; - let rbuf_ptr = swap_chain.vtable().ResizeBuffers; + let present_ptr: DXGISwapChainPresentType = + unsafe { mem::transmute(swap_chain.vtable().Present) }; + let resize_buffers_ptr: DXGISwapChainResizeBuffersType = + unsafe { mem::transmute(swap_chain.vtable().ResizeBuffers) }; - unsafe { - ( - std::mem::transmute(present_ptr), - std::mem::transmute(ecl_ptr), - std::mem::transmute(rbuf_ptr), - ) - } + (present_ptr, resize_buffers_ptr) } -/// Globally enables DXGI debug messages. -pub fn enable_dxgi_debug() { - info!("DXGI debugging enabled"); - DXGI_DEBUG_ENABLED.store(true, Ordering::SeqCst); -} - -/// Globally disables DXGI debug messages. -pub fn disable_dxgi_debug() { - info!("DXGI debugging disabled"); - DXGI_DEBUG_ENABLED.store(false, Ordering::SeqCst); -} - -/// Stores hook detours and implements the [`Hooks`] trait. -pub struct ImguiDx12Hooks([MhHook; 3]); +pub struct ImguiDx12Hooks([MhHook; 2]); impl ImguiDx12Hooks { - /// Construct a set of [`RawDetour`]s that will render UI via the provided - /// [`ImguiRenderLoop`]. + /// Construct a set of [`crate::mh::MhHook`]s that will render UI via the + /// provided [`ImguiRenderLoop`]. /// /// The following functions are hooked: - /// - `IDXGISwapChain::Present` - /// - `IDXGISwapChain::ResizeBuffers` - /// - `ID3D12CommandQueue::ExecuteCommandLists` + /// - `IDXGISwapChain3::Present` + /// - `IDXGISwapChain3::ResizeBuffers` /// /// # Safety /// @@ -721,48 +187,28 @@ impl ImguiDx12Hooks { where T: ImguiRenderLoop + Send + Sync, { - let (dxgi_swap_chain_present_addr, execute_command_lists_addr, resize_buffers_addr) = - get_present_addr(); + let (dxgi_swap_chain_present_addr, dxgi_swap_chain_resize_buffers_addr) = + get_target_addrs(); - trace!( - "IDXGISwapChain::Present = {:p}", - dxgi_swap_chain_present_addr as *const c_void - ); - trace!( - "ID3D12CommandQueue::ExecuteCommandLists = {:p}", - execute_command_lists_addr as *const c_void - ); - trace!( - "IDXGISwapChain::ResizeBuffers = {:p}", - resize_buffers_addr as *const c_void - ); - - let hook_dscp = MhHook::new( + trace!("IDXGISwapChain::Present = {:p}", dxgi_swap_chain_present_addr as *const c_void); + let hook_present = MhHook::new( dxgi_swap_chain_present_addr as *mut _, - imgui_dxgi_swap_chain_present_impl as *mut _, + dxgi_swap_chain_present_impl as *mut _, ) .expect("couldn't create IDXGISwapChain::Present hook"); - - let hook_cqecl = MhHook::new( - execute_command_lists_addr as *mut _, - imgui_execute_command_lists_impl as *mut _, + let hook_resize_buffers = MhHook::new( + dxgi_swap_chain_resize_buffers_addr as *mut _, + dxgi_swap_chain_resize_buffers_impl as *mut _, ) - .expect("couldn't create ID3D12CommandQueue::ExecuteCommandLists hook"); - - let hook_rbuf = - MhHook::new(resize_buffers_addr as *mut _, imgui_resize_buffers_impl as *mut _) - .expect("couldn't create IDXGISwapChain::ResizeBuffers hook"); + .expect("couldn't create IDXGISwapChain::ResizeBuffers hook"); - IMGUI_RENDER_LOOP.get_or_init(|| Box::new(t)); - TRAMPOLINE.get_or_init(|| { - ( - mem::transmute(hook_dscp.trampoline()), - mem::transmute(hook_cqecl.trampoline()), - mem::transmute(hook_rbuf.trampoline()), - ) + RenderState::set_render_loop(t); + TRAMPOLINES.get_or_init(|| Trampolines { + dxgi_swap_chain_present: mem::transmute(hook_present.trampoline()), + dxgi_swap_chain_resize_buffers: mem::transmute(hook_resize_buffers.trampoline()), }); - Self([hook_dscp, hook_cqecl, hook_rbuf]) + Self([hook_present, hook_resize_buffers]) } } @@ -772,7 +218,7 @@ impl Hooks for ImguiDx12Hooks { Self: Sized, T: ImguiRenderLoop + Send + Sync + 'static, { - Box::new(unsafe { ImguiDx12Hooks::new(t) }) + Box::new(unsafe { Self::new(t) }) } fn hooks(&self) -> &[MhHook] { @@ -780,31 +226,7 @@ impl Hooks for ImguiDx12Hooks { } unsafe fn unhook(&mut self) { - trace!("Disabling hooks..."); - - CQECL_RUNNING.wait(); - PRESENT_RUNNING.wait(); - RBUF_RUNNING.wait(); - - trace!("Cleaning up renderer..."); - if let Some(renderer) = IMGUI_RENDERER.take() { - let mut renderer = renderer.lock(); - // XXX - // This is a hack for solving this concurrency issue: - // https://github.com/veeenu/hudhook/issues/34 - // We should investigate deeper into this and find a way of synchronizing with - // the moment the actual resources involved in the rendering are - // dropped. Using a condvar like above does not work, and still - // leads clients to crash. - // - // The 34ms value was chosen because it's a bit more than 1 frame @ 30fps. - thread::sleep(Duration::from_millis(34)); - renderer.cleanup(None); - } - - drop(IMGUI_RENDER_LOOP.take()); - COMMAND_QUEUE_GUARD.take(); - - DXGI_DEBUG_ENABLED.store(false, Ordering::SeqCst); + RenderState::cleanup(); + TRAMPOLINES.take(); } } diff --git a/src/hooks/dx9.rs b/src/hooks/dx9.rs index 61a468cf..bf330480 100644 --- a/src/hooks/dx9.rs +++ b/src/hooks/dx9.rs @@ -1,68 +1,24 @@ +use std::ffi::c_void; use std::mem; use std::sync::OnceLock; -use imgui::Context; -use parking_lot::Mutex; -use tracing::{debug, trace}; +use tracing::trace; use windows::core::{Interface, HRESULT}; -use windows::Win32::Foundation::{ - GetLastError, BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM, -}; +use windows::Win32::Foundation::{BOOL, HWND, RECT}; use windows::Win32::Graphics::Direct3D9::{ - Direct3DCreate9, IDirect3DDevice9, D3DADAPTER_DEFAULT, D3DBACKBUFFER_TYPE_MONO, - D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DDEVTYPE_NULLREF, D3DDISPLAYMODE, D3DFORMAT, - D3DPRESENT_PARAMETERS, D3DSWAPEFFECT_DISCARD, D3D_SDK_VERSION, -}; -use windows::Win32::Graphics::Gdi::{ScreenToClient, RGNDATA}; -#[cfg(target_arch = "x86")] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongA; -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrA; -use windows::Win32::UI::WindowsAndMessaging::{ - DefWindowProcW, GetCursorPos, GetForegroundWindow, IsChild, GWLP_WNDPROC, + Direct3DCreate9, IDirect3DDevice9, D3DADAPTER_DEFAULT, D3DCREATE_SOFTWARE_VERTEXPROCESSING, + D3DDEVTYPE_NULLREF, D3DDISPLAYMODE, D3DFORMAT, D3DPRESENT_PARAMETERS, D3DSWAPEFFECT_DISCARD, + D3D_SDK_VERSION, }; +use windows::Win32::Graphics::Gdi::RGNDATA; -use crate::hooks::common::{imgui_wnd_proc_impl, DummyHwnd, ImguiWindowsEventHandler, WndProcType}; -use crate::hooks::{Hooks, ImguiRenderLoop}; +use super::DummyHwnd; +use crate::hooks::render::RenderState; use crate::mh::MhHook; -use crate::renderers::imgui_dx9; use crate::util::try_out_ptr; +use crate::{Hooks, ImguiRenderLoop}; -unsafe fn draw(this: &IDirect3DDevice9) { - let mut imgui_renderer = IMGUI_RENDERER - .get_or_init(|| { - let mut context = imgui::Context::create(); - context.set_ini_filename(None); - IMGUI_RENDER_LOOP.get_mut().unwrap().initialize(&mut context); - let renderer = imgui_dx9::Renderer::new(&mut context, this.clone()).unwrap(); - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongPtrA( - renderer.get_hwnd(), - GWLP_WNDPROC, - imgui_wnd_proc as usize as isize, - )); - - #[cfg(target_arch = "x86")] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongA( - renderer.get_hwnd(), - GWLP_WNDPROC, - imgui_wnd_proc as usize as i32, - )); - - Mutex::new(Box::new(ImguiRenderer { ctx: context, renderer, wnd_proc })) - }) - .lock(); - - imgui_renderer.render(); -} - -type Dx9EndSceneFn = unsafe extern "system" fn(this: IDirect3DDevice9) -> HRESULT; - -type Dx9ResetFn = - unsafe extern "system" fn(this: IDirect3DDevice9, *const D3DPRESENT_PARAMETERS) -> HRESULT; - -type Dx9PresentFn = unsafe extern "system" fn( +type Dx9PresentType = unsafe extern "system" fn( this: IDirect3DDevice9, psourcerect: *const RECT, pdestrect: *const RECT, @@ -70,193 +26,101 @@ type Dx9PresentFn = unsafe extern "system" fn( pdirtyregion: *const RGNDATA, ) -> HRESULT; -unsafe extern "system" fn imgui_dx9_reset_impl( - this: IDirect3DDevice9, - present_params: *const D3DPRESENT_PARAMETERS, -) -> HRESULT { - trace!( - "IDirect3DDevice9::Reset invoked ({} x {})", - (*present_params).BackBufferWidth, - (*present_params).BackBufferHeight - ); - - if let Some(renderer) = IMGUI_RENDERER.take() { - renderer.lock().cleanup(); - } - - let (_, _, trampoline_reset) = - TRAMPOLINE.get().expect("IDirect3DDevice9::Reset trampoline uninitialized"); - trampoline_reset(this, present_params) +struct Trampolines { + dx9_present: Dx9PresentType, } -unsafe extern "system" fn imgui_dx9_end_scene_impl(this: IDirect3DDevice9) -> HRESULT { - trace!("IDirect3DDevice9::EndScene invoked"); +static mut TRAMPOLINES: OnceLock = OnceLock::new(); - let mut viewport = core::mem::zeroed(); - this.GetViewport(&mut viewport).unwrap(); - let render_target_surface = this.GetRenderTarget(0).unwrap(); - let mut render_target_desc = core::mem::zeroed(); - render_target_surface.GetDesc(&mut render_target_desc).unwrap(); - - let backbuffer_surface = this.GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO).unwrap(); - let mut backbuffer_desc = core::mem::zeroed(); - backbuffer_surface.GetDesc(&mut backbuffer_desc).unwrap(); - - trace!("Viewport: {:?}", viewport); - trace!("Render target desc: {:?}", render_target_desc); - trace!("Backbuffer desc: {:?}", backbuffer_desc); - - let (trampoline_end_scene, ..) = - TRAMPOLINE.get().expect("IDirect3DDevice9::EndScene trampoline uninitialized"); - - trampoline_end_scene(this) -} - -unsafe extern "system" fn imgui_wnd_proc( - hwnd: HWND, - umsg: u32, - WPARAM(wparam): WPARAM, - LPARAM(lparam): LPARAM, -) -> LRESULT { - if let Some(imgui_renderer) = IMGUI_RENDERER.get_mut().and_then(|m| m.try_lock()) { - imgui_wnd_proc_impl( - hwnd, - umsg, - WPARAM(wparam), - LPARAM(lparam), - imgui_renderer, - IMGUI_RENDER_LOOP.get().unwrap(), - ) - } else { - debug!("WndProc called before hook was set"); - DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) - } -} - -unsafe extern "system" fn imgui_dx9_present_impl( - this: IDirect3DDevice9, +unsafe extern "system" fn dx9_present_impl( + p_this: IDirect3DDevice9, psourcerect: *const RECT, pdestrect: *const RECT, hdestwindowoverride: HWND, pdirtyregion: *const RGNDATA, ) -> HRESULT { - trace!("IDirect3DDevice9::Present invoked"); + let Trampolines { dx9_present } = + TRAMPOLINES.get().expect("DirectX 12 trampolines uninitialized"); - this.BeginScene().unwrap(); - draw(&this); - this.EndScene().unwrap(); - - let (_, trampoline_present, _) = - TRAMPOLINE.get().expect("IDirect3DDevice9::Present trampoline uninitialized"); + // Don't attempt a render if one is already underway: it might be that the + // renderer itself is currently invoking `Present`. + if RenderState::is_locked() { + return dx9_present(p_this, psourcerect, pdestrect, hdestwindowoverride, pdirtyregion); + } - trampoline_present(this, psourcerect, pdestrect, hdestwindowoverride, pdirtyregion) -} + let hwnd = RenderState::setup(|| { + let mut creation_parameters = Default::default(); + let _ = p_this.GetCreationParameters(&mut creation_parameters); + creation_parameters.hFocusWindow + }); -static mut IMGUI_RENDER_LOOP: OnceLock> = OnceLock::new(); -static mut IMGUI_RENDERER: OnceLock>> = OnceLock::new(); -static TRAMPOLINE: OnceLock<(Dx9EndSceneFn, Dx9PresentFn, Dx9ResetFn)> = OnceLock::new(); + RenderState::render(hwnd); -struct ImguiRenderer { - ctx: Context, - renderer: imgui_dx9::Renderer, - wnd_proc: WndProcType, + trace!("Call IDirect3DDevice9::Present trampoline"); + dx9_present(p_this, psourcerect, pdestrect, hdestwindowoverride, pdirtyregion) } -impl ImguiRenderer { - unsafe fn render(&mut self) { - if let Some(rect) = self.renderer.get_client_rect() { - let io = self.ctx.io_mut(); +fn get_target_addrs() -> Dx9PresentType { + let d9 = unsafe { Direct3DCreate9(D3D_SDK_VERSION).unwrap() }; - io.display_size = [(rect.right - rect.left) as f32, (rect.bottom - rect.top) as f32]; + let mut d3d_display_mode = + D3DDISPLAYMODE { Width: 0, Height: 0, RefreshRate: 0, Format: D3DFORMAT(0) }; + unsafe { d9.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mut d3d_display_mode).unwrap() }; - let mut pos = POINT { x: 0, y: 0 }; + let mut present_params = D3DPRESENT_PARAMETERS { + Windowed: BOOL(1), + SwapEffect: D3DSWAPEFFECT_DISCARD, + BackBufferFormat: d3d_display_mode.Format, + ..Default::default() + }; - let active_window = GetForegroundWindow(); - if !HANDLE(active_window.0).is_invalid() - && (active_window == self.renderer.get_hwnd() - || IsChild(active_window, self.renderer.get_hwnd()).as_bool()) - { - let gcp = GetCursorPos(&mut pos as *mut _); - if gcp.is_ok() - && ScreenToClient(self.renderer.get_hwnd(), &mut pos as *mut _).as_bool() - { - io.mouse_pos[0] = pos.x as _; - io.mouse_pos[1] = pos.y as _; - } - } - } else { - trace!("GetWindowRect error: {:?}", GetLastError()); + let dummy_hwnd = DummyHwnd::new(); + let device: IDirect3DDevice9 = try_out_ptr(|v| { + unsafe { + d9.CreateDevice( + D3DADAPTER_DEFAULT, + D3DDEVTYPE_NULLREF, + dummy_hwnd.hwnd(), // GetDesktopWindow(), + D3DCREATE_SOFTWARE_VERTEXPROCESSING as u32, + &mut present_params, + v, + ) } + }) + .expect("IDirect3DDevice9::CreateDevice: failed to create device"); - let ui = self.ctx.frame(); - - IMGUI_RENDER_LOOP.get_mut().unwrap().render(ui); - let draw_data = self.ctx.render(); - self.renderer.render(draw_data).unwrap(); - } - - unsafe fn cleanup(&mut self) { - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - SetWindowLongPtrA(self.renderer.get_hwnd(), GWLP_WNDPROC, self.wnd_proc as usize as isize); - - #[cfg(target_arch = "x86")] - SetWindowLongA(self.renderer.get_hwnd(), GWLP_WNDPROC, self.wnd_proc as usize as i32); - } -} - -impl ImguiWindowsEventHandler for ImguiRenderer { - fn io(&self) -> &imgui::Io { - self.ctx.io() - } - - fn io_mut(&mut self) -> &mut imgui::Io { - self.ctx.io_mut() - } + let present_ptr = device.vtable().Present; - fn wnd_proc(&self) -> WndProcType { - self.wnd_proc - } + unsafe { mem::transmute(present_ptr) } } -unsafe impl Send for ImguiRenderer {} -unsafe impl Sync for ImguiRenderer {} -/// Stores hook detours and implements the [`Hooks`] trait. -pub struct ImguiDx9Hooks([MhHook; 3]); +pub struct ImguiDx9Hooks([MhHook; 1]); impl ImguiDx9Hooks { + /// Construct a set of [`crate::mh::MhHook`]s that will render UI via the + /// provided [`ImguiRenderLoop`]. + /// + /// The following functions are hooked: + /// - `IDirect3DDevice9::Present` + /// /// # Safety /// - /// Is most likely undefined behavior, as it modifies function pointers at - /// runtime. + /// yolo pub unsafe fn new(t: T) -> Self where T: ImguiRenderLoop + Send + Sync, { - let (hook_dx9_end_scene_address, dx9_present_address, dx9_reset_address) = - get_dx9_present_addr(); + let dx9_present_addr = get_target_addrs(); - let hook_dx9_end_scene = - MhHook::new(hook_dx9_end_scene_address as *mut _, imgui_dx9_end_scene_impl as *mut _) - .expect("couldn't create IDirect3DDevice9::EndScene hook"); + trace!("IDirect3DDevice9::Present = {:p}", dx9_present_addr as *const c_void); + let hook_present = MhHook::new(dx9_present_addr as *mut _, dx9_present_impl as *mut _) + .expect("couldn't create IDirect3DDevice9::Present hook"); - let hook_dx9_present = - MhHook::new(dx9_present_address as *mut _, imgui_dx9_present_impl as *mut _) - .expect("couldn't create IDirect3DDevice9::Present hook"); + RenderState::set_render_loop(t); + TRAMPOLINES + .get_or_init(|| Trampolines { dx9_present: mem::transmute(hook_present.trampoline()) }); - let hook_dx9_reset = - MhHook::new(dx9_reset_address as *mut _, imgui_dx9_reset_impl as *mut _) - .expect("couldn't create IDirect3DDevice9::Reset hook"); - - IMGUI_RENDER_LOOP.get_or_init(|| Box::new(t)); - TRAMPOLINE.get_or_init(|| { - ( - mem::transmute(hook_dx9_end_scene.trampoline()), - mem::transmute(hook_dx9_present.trampoline()), - mem::transmute(hook_dx9_reset.trampoline()), - ) - }); - - Self([hook_dx9_end_scene, hook_dx9_present, hook_dx9_reset]) + Self([hook_present]) } } @@ -266,7 +130,7 @@ impl Hooks for ImguiDx9Hooks { Self: Sized, T: ImguiRenderLoop + Send + Sync + 'static, { - Box::new(unsafe { ImguiDx9Hooks::new(t) }) + Box::new(unsafe { Self::new(t) }) } fn hooks(&self) -> &[MhHook] { @@ -274,51 +138,7 @@ impl Hooks for ImguiDx9Hooks { } unsafe fn unhook(&mut self) { - if let Some(renderer) = IMGUI_RENDERER.take() { - renderer.lock().cleanup(); - } - drop(IMGUI_RENDER_LOOP.take()); + RenderState::cleanup(); + TRAMPOLINES.take(); } } - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Function address finders -//////////////////////////////////////////////////////////////////////////////////////////////////// - -unsafe fn get_dx9_present_addr() -> (Dx9EndSceneFn, Dx9PresentFn, Dx9ResetFn) { - let d9 = Direct3DCreate9(D3D_SDK_VERSION).unwrap(); - - let mut d3d_display_mode = - D3DDISPLAYMODE { Width: 0, Height: 0, RefreshRate: 0, Format: D3DFORMAT(0) }; - d9.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mut d3d_display_mode).unwrap(); - - let mut present_params = D3DPRESENT_PARAMETERS { - Windowed: BOOL(1), - SwapEffect: D3DSWAPEFFECT_DISCARD, - BackBufferFormat: d3d_display_mode.Format, - ..core::mem::zeroed() - }; - - let dummy_hwnd = DummyHwnd::new(); - let device: IDirect3DDevice9 = try_out_ptr(|v| { - d9.CreateDevice( - D3DADAPTER_DEFAULT, - D3DDEVTYPE_NULLREF, - dummy_hwnd.hwnd(), // GetDesktopWindow(), - D3DCREATE_SOFTWARE_VERTEXPROCESSING as u32, - &mut present_params, - v, - ) - }) - .expect("IDirect3DDevice9::CreateDevice: failed to create device"); - - let end_scene_ptr = device.vtable().EndScene; - let present_ptr = device.vtable().Present; - let reset_ptr = device.vtable().Reset; - - ( - std::mem::transmute(end_scene_ptr), - std::mem::transmute(present_ptr), - std::mem::transmute(reset_ptr), - ) -} diff --git a/src/hooks/common/wnd_proc.rs b/src/hooks/input/mod.rs similarity index 89% rename from src/hooks/common/wnd_proc.rs rename to src/hooks/input/mod.rs index 8c2ef559..e7d1ad1e 100644 --- a/src/hooks/common/wnd_proc.rs +++ b/src/hooks/input/mod.rs @@ -6,19 +6,25 @@ use std::mem::size_of; use imgui::Io; use parking_lot::MutexGuard; use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; -use windows::Win32::UI::Input::KeyboardAndMouse::{ - MapVirtualKeyA, MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_CONTROL, VK_LBUTTON, VK_LCONTROL, VK_LMENU, - VK_LSHIFT, VK_LWIN, VK_MBUTTON, VK_MENU, VK_RBUTTON, VK_RCONTROL, VK_RMENU, VK_RSHIFT, VK_RWIN, - VK_SHIFT, VK_XBUTTON1, VK_XBUTTON2, -}; +use windows::Win32::UI::Input::KeyboardAndMouse::*; use windows::Win32::UI::Input::{ GetRawInputData, HRAWINPUT, RAWINPUT, RAWINPUTHEADER, RAWKEYBOARD, RAWMOUSE_0_0, RID_DEVICE_INFO_TYPE, RID_INPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }; use windows::Win32::UI::WindowsAndMessaging::*; -use super::{ImguiRenderLoop, ImguiWindowsEventHandler}; -use crate::hooks::{get_wheel_delta_wparam, hiword}; +use super::render::RenderState; +use crate::renderer::dx12::RenderEngine; +use crate::ImguiRenderLoop; + +pub type WndProcType = + unsafe extern "system" fn(hwnd: HWND, umsg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT; + +// Replication of the Win32 HIWORD macro. +#[inline] +fn hiword(l: u32) -> u16 { + ((l >> 16) & 0xffff) as u16 +} //////////////////////////////////////////////////////////////////////////////// // Raw input @@ -238,13 +244,15 @@ pub fn imgui_wnd_proc_impl( umsg: u32, WPARAM(wparam): WPARAM, LPARAM(lparam): LPARAM, - mut imgui_renderer: MutexGuard>, + wnd_proc: WndProcType, + mut render_engine: MutexGuard, imgui_render_loop: T, ) -> LRESULT where T: AsRef, { - let io = imgui_renderer.io_mut(); + let mut ctx = render_engine.ctx(); + let io = ctx.io_mut(); match umsg { WM_INPUT => handle_raw_input(io, WPARAM(wparam), LPARAM(lparam)), state @ (WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP) if wparam < 256 => { @@ -277,26 +285,33 @@ where io.mouse_down[btn] = false; }, WM_MOUSEWHEEL => { - let wheel_delta_wparam = get_wheel_delta_wparam(wparam as _); + // This `hiword` call is equivalent to GET_WHEEL_DELTA_WPARAM + let wheel_delta_wparam = hiword(wparam as _); let wheel_delta = WHEEL_DELTA as f32; io.mouse_wheel += (wheel_delta_wparam as i16 as f32) / wheel_delta; }, WM_MOUSEHWHEEL => { - let wheel_delta_wparam = get_wheel_delta_wparam(wparam as _); + // This `hiword` call is equivalent to GET_WHEEL_DELTA_WPARAM + let wheel_delta_wparam = hiword(wparam as _); let wheel_delta = WHEEL_DELTA as f32; io.mouse_wheel_h += (wheel_delta_wparam as i16 as f32) / wheel_delta; }, WM_CHAR => io.add_input_character(wparam as u8 as char), + WM_SIZE => { + drop(ctx); + drop(render_engine); + RenderState::resize(); + return LRESULT(1); + }, _ => {}, }; - let wnd_proc = imgui_renderer.wnd_proc(); - let should_block_messages = - imgui_render_loop.as_ref().should_block_messages(imgui_renderer.io()); + let should_block_messages = imgui_render_loop.as_ref().should_block_messages(io); imgui_render_loop.as_ref().on_wnd_proc(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)); - drop(imgui_renderer); + drop(ctx); + drop(render_engine); if should_block_messages { return LRESULT(1); diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 077e1536..e3571433 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -1,68 +1,147 @@ -//! Implementation of platform-specific hooks. +//! # How to implement a hook //! -//! Currently DirectX 11 and DirectX 12 hooks with [`imgui`] renderers are -//! available. +//! The code structure of a hook should follow this scheme: //! -//! [`imgui`]: https://docs.rs/imgui/0.8.0/imgui/ +//! 1. `use` statements +//! 2. Type aliases for hooked functions +//! 3. A `struct Trampolines` that should hold all needed trampolines +//! 4. `static mut` objects, favoring `OnceLock`s and keeping heavy sync +//! primitives like mutexes to the minimum absolute necessary +//! 5. Hook function implementations, one for each trampoline +//! 6. An `imgui_wnd_proc` implementation +//! 7. A `render` function that locks the render engine and uses it to draw and +//! swap. Possibly implement a critical section with an `AtomicBool` to +//! prevent double invocations +//! 8. A `get_target_addrs` function that retrieves hooked function addresses +//! 9. A `ImguiHooks` type that holds necessary hook state (generally +//! just a static array of `MhHook` objects); implement a `new` function and +//! all necessary methods/associated functions, then implement the `Hooks` +//! trait below it +use std::mem; +use std::sync::OnceLock; -use imgui::{Context, Io, Ui}; -use windows::Win32::Foundation::{HWND, LPARAM, WPARAM}; +use tracing::{debug, error}; +use windows::core::w; +use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, WPARAM}; +use windows::Win32::System::LibraryLoader::GetModuleHandleW; +use windows::Win32::System::Threading::GetCurrentProcessId; +use windows::Win32::UI::WindowsAndMessaging::{ + CreateWindowExW, DefWindowProcW, DestroyWindow, EnumWindows, GetWindowThreadProcessId, + RegisterClassExW, UnregisterClassW, CS_HREDRAW, CS_VREDRAW, WNDCLASSEXW, + WS_EX_OVERLAPPEDWINDOW, WS_OVERLAPPEDWINDOW, +}; -pub use crate::hooks::common::Hooks; - -pub mod common; -#[cfg(feature = "dx11")] pub mod dx11; -#[cfg(feature = "dx12")] pub mod dx12; -#[cfg(feature = "dx9")] pub mod dx9; -#[cfg(feature = "opengl3")] +mod input; pub mod opengl3; +mod render; -/// Implement your `imgui` rendering logic via this trait. -pub trait ImguiRenderLoop { - /// Called once at the first occurrence of the hook. Implement this to - /// initialize your data. - fn initialize(&mut self, _ctx: &mut Context) {} - - /// Called every frame. Use the provided `ui` object to build your UI. - fn render(&mut self, ui: &mut Ui); +pub fn find_process_hwnd() -> Option { + static mut FOUND_HWND: OnceLock = OnceLock::new(); - /// Called during the window procedure. - fn on_wnd_proc(&self, _hwnd: HWND, _umsg: u32, _wparam: WPARAM, _lparam: LPARAM) {} - - /// If this function returns true, the WndProc function will not call the - /// procedure of the parent window. - fn should_block_messages(&self, _io: &Io) -> bool { - false + unsafe extern "system" fn enum_callback(hwnd: HWND, _: LPARAM) -> BOOL { + let mut pid = 0; + GetWindowThreadProcessId(hwnd, Some(&mut pid)); + tracing::debug!("hwnd {hwnd:?} has pid {pid} vs {}", GetCurrentProcessId()); + if pid == GetCurrentProcessId() { + FOUND_HWND.get_or_init(|| hwnd); + BOOL::from(false) + } else { + BOOL::from(true) + } } - fn into_hook(self) -> Box - where - T: Hooks, - Self: Send + Sync + Sized + 'static, - { - T::from_render_loop(self) + unsafe { + FOUND_HWND.take(); + EnumWindows(Some(enum_callback), LPARAM(0)).ok(); } + + unsafe { FOUND_HWND.get().copied() } } -// #[inline] -// fn loword(l: u32) -> u16 { -// (l & 0xffff) as u16 -// } -#[inline] -fn hiword(l: u32) -> u16 { - ((l >> 16) & 0xffff) as u16 +/// A RAII dummy window. +/// +/// Registers a class and creates a window on instantiation. +/// Destroys the window and unregisters the class on drop. +pub struct DummyHwnd(HWND, WNDCLASSEXW); + +impl Default for DummyHwnd { + fn default() -> Self { + Self::new() + } } -#[inline] -fn get_wheel_delta_wparam(wparam: u32) -> u16 { - hiword(wparam) +impl DummyHwnd { + pub fn new() -> Self { + // The window procedure for the class just calls `DefWindowProcW`. + unsafe extern "system" fn wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + DefWindowProcW(hwnd, msg, wparam, lparam) + } + + // Create and register the class. + let wndclass = WNDCLASSEXW { + cbSize: mem::size_of::() as u32, + style: CS_HREDRAW | CS_VREDRAW, + lpfnWndProc: Some(wnd_proc), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: unsafe { GetModuleHandleW(None).unwrap().into() }, + // hIcon: HICON(0), + // hCursor: HCURSOR(0), + // hbrBackground: HBRUSH(0), + // lpszMenuName: PCWSTR(null()), + lpszClassName: w!("HUDHOOK"), + // hIconSm: HICON(0), + ..Default::default() + }; + debug!("{:?}", wndclass); + unsafe { RegisterClassExW(&wndclass) }; + + // Create the window. + let hwnd = unsafe { + CreateWindowExW( + WS_EX_OVERLAPPEDWINDOW, + wndclass.lpszClassName, + w!("HUDHOOK"), + WS_OVERLAPPEDWINDOW, + 0, + 0, + 100, + 100, + None, + None, + wndclass.hInstance, + None, + ) + }; + debug!("{:?}", hwnd); + + Self(hwnd, wndclass) + } + + // Retrieve the window handle. + pub fn hwnd(&self) -> HWND { + self.0 + } } -#[allow(dead_code)] -#[inline] -fn get_xbutton_wparam(wparam: u32) -> u16 { - hiword(wparam) +impl Drop for DummyHwnd { + fn drop(&mut self) { + // Destroy the window and unregister the class. + unsafe { + if let Err(e) = DestroyWindow(self.0) { + error!("DestroyWindow: {e}"); + } + if let Err(e) = UnregisterClassW(self.1.lpszClassName, self.1.hInstance) { + error!("UnregisterClass: {e}"); + } + } + } } diff --git a/src/hooks/opengl3.rs b/src/hooks/opengl3.rs index 8c5f7388..95cd68a1 100644 --- a/src/hooks/opengl3.rs +++ b/src/hooks/opengl3.rs @@ -1,246 +1,44 @@ use std::ffi::CString; +use std::mem; use std::sync::OnceLock; -use std::time::Instant; -use imgui::Context; -use parking_lot::Mutex; -use tracing::{debug, trace}; +use tracing::trace; use windows::core::PCSTR; -use windows::Win32::Foundation::{ - GetLastError, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM, -}; -use windows::Win32::Graphics::Gdi::{ScreenToClient, WindowFromDC, HDC}; -use windows::Win32::Graphics::OpenGL::{glClearColor, glGetIntegerv, GL_VIEWPORT}; +use windows::Win32::Graphics::Gdi::{WindowFromDC, HDC}; use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; -#[cfg(target_arch = "x86")] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongA; -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] -use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrA; -use windows::Win32::UI::WindowsAndMessaging::{ - DefWindowProcW, GetClientRect, GetCursorPos, GetForegroundWindow, IsChild, GWLP_WNDPROC, -}; -use crate::hooks::common::{imgui_wnd_proc_impl, ImguiWindowsEventHandler, WndProcType}; -use crate::hooks::{Hooks, ImguiRenderLoop}; +use crate::hooks::render::RenderState; use crate::mh::MhHook; -use crate::renderers::imgui_opengl3::get_proc_address; +use crate::{Hooks, ImguiRenderLoop}; -type OpenGl32wglSwapBuffers = unsafe extern "system" fn(HDC) -> (); +type OpenGl32wglSwapBuffersType = unsafe extern "system" fn(HDC) -> (); -unsafe fn draw(dc: HDC) { - // Get the imgui renderer, or create it if it does not exist - let mut imgui_renderer = IMGUI_RENDERER - .get_or_insert_with(|| { - // Create ImGui context - let mut context = imgui::Context::create(); - context.set_ini_filename(None); - - // Initialize the render loop with the context - IMGUI_RENDER_LOOP.get_mut().unwrap().initialize(&mut context); - - let renderer = imgui_opengl::Renderer::new(&mut context, |s| { - get_proc_address(CString::new(s).unwrap()) as _ - }); - - // Grab the HWND from the DC - let hwnd = WindowFromDC(dc); - - // Set the new wnd proc, and assign the old one to a variable for further - // storing - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongPtrA( - hwnd, - GWLP_WNDPROC, - imgui_wnd_proc as usize as isize, - )); - #[cfg(target_arch = "x86")] - let wnd_proc = std::mem::transmute::<_, WndProcType>(SetWindowLongA( - hwnd, - GWLP_WNDPROC, - imgui_wnd_proc as usize as i32, - )); - - // Create the imgui rendererer - let mut imgui_renderer = ImguiRenderer { - ctx: context, - renderer, - wnd_proc, - game_hwnd: hwnd, - resolution_and_rect: None, - }; - - // Initialize window events on the imgui renderer - ImguiWindowsEventHandler::setup_io(&mut imgui_renderer); - - // Return the imgui renderer as a mutex - Mutex::new(Box::new(imgui_renderer)) - }) - .lock(); - - imgui_renderer.render(); -} - -unsafe extern "system" fn imgui_wnd_proc( - hwnd: HWND, - umsg: u32, - WPARAM(wparam): WPARAM, - LPARAM(lparam): LPARAM, -) -> LRESULT { - if IMGUI_RENDERER.is_some() { - match IMGUI_RENDERER.as_mut().unwrap().try_lock() { - Some(imgui_renderer) => imgui_wnd_proc_impl( - hwnd, - umsg, - WPARAM(wparam), - LPARAM(lparam), - imgui_renderer, - IMGUI_RENDER_LOOP.get().unwrap(), - ), - None => { - debug!("Could not lock in WndProc"); - DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) - }, - } - } else { - debug!("WndProc called before hook was set"); - DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) - } +struct Trampolines { + opengl32_wgl_swap_buffers: OpenGl32wglSwapBuffersType, } -#[allow(non_snake_case)] -unsafe extern "system" fn imgui_opengl32_wglSwapBuffers_impl(dc: HDC) { - trace!("opengl32.wglSwapBuffers invoked"); +static mut TRAMPOLINES: OnceLock = OnceLock::new(); - // Draw ImGui - draw(dc); +unsafe extern "system" fn opengl32_wgl_swap_buffers_impl(dc: HDC) { + let Trampolines { opengl32_wgl_swap_buffers } = + TRAMPOLINES.get().expect("OpenGL3 trampolines uninitialized"); - // If resolution or window rect changes - reset ImGui - reset(dc); - - // Get the trampoline - let trampoline_wglswapbuffers = - TRAMPOLINE.get().expect("opengl32.wglSwapBuffers trampoline uninitialized"); - - // Call the original function - trampoline_wglswapbuffers(dc) -} - -unsafe fn reset(hdc: HDC) { - if IMGUI_RENDERER.is_none() { - return; + // Don't attempt a render if one is already underway: it might be that the + // renderer itself is currently invoking `Present`. + if RenderState::is_locked() { + return opengl32_wgl_swap_buffers(dc); } - if let Some(mut renderer) = IMGUI_RENDERER.as_mut().unwrap().try_lock() { - // Get resolution - let viewport = &mut [0; 4]; - glGetIntegerv(GL_VIEWPORT, viewport.as_mut_ptr()); - - let hwnd = WindowFromDC(hdc); - let rect = get_client_rect(&hwnd).unwrap(); + let hwnd = RenderState::setup(|| WindowFromDC(dc)); - let (resolution, window_rect) = - renderer.resolution_and_rect.get_or_insert(([viewport[2], viewport[3]], rect)); + RenderState::render(hwnd); - // Compare previously saved to current - if viewport[2] != resolution[0] - || viewport[3] != resolution[1] - || rect.right != window_rect.right - || rect.bottom != window_rect.bottom - { - renderer.cleanup(); - glClearColor(0.0, 0.0, 0.0, 1.0); - IMGUI_RENDERER.take(); - } - } + trace!("Call OpenGL3 wglSwapBuffers trampoline"); + opengl32_wgl_swap_buffers(dc); } -static mut IMGUI_RENDER_LOOP: OnceLock> = OnceLock::new(); -static mut IMGUI_RENDERER: Option>> = None; -static TRAMPOLINE: OnceLock = OnceLock::new(); - -struct ImguiRenderer { - ctx: Context, - renderer: imgui_opengl::Renderer, - wnd_proc: WndProcType, - game_hwnd: HWND, - resolution_and_rect: Option<([i32; 2], RECT)>, -} - -fn get_client_rect(hwnd: &HWND) -> Option { - unsafe { - let mut rect: RECT = core::mem::zeroed(); - if GetClientRect(*hwnd, &mut rect).is_ok() { - Some(rect) - } else { - None - } - } -} - -static mut LAST_FRAME: Option> = None; - -impl ImguiRenderer { - unsafe fn render(&mut self) { - if let Some(rect) = get_client_rect(&self.game_hwnd) { - let io = self.ctx.io_mut(); - io.display_size = [(rect.right - rect.left) as f32, (rect.bottom - rect.top) as f32]; - let mut pos = POINT { x: 0, y: 0 }; - - let active_window = GetForegroundWindow(); - if !HANDLE(active_window.0).is_invalid() - && (active_window == self.game_hwnd - || IsChild(active_window, self.game_hwnd).as_bool()) - { - let gcp = GetCursorPos(&mut pos as *mut _); - if gcp.is_ok() && ScreenToClient(self.game_hwnd, &mut pos as *mut _).as_bool() { - io.mouse_pos[0] = pos.x as _; - io.mouse_pos[1] = pos.y as _; - } - } - } else { - trace!("GetClientRect error: {:?}", GetLastError()); - } - - // Update the delta time of ImGui as to tell it how long has elapsed since the - // last frame - let last_frame = LAST_FRAME.get_or_insert_with(|| Mutex::new(Instant::now())).get_mut(); - let now = Instant::now(); - self.ctx.io_mut().update_delta_time(now.duration_since(*last_frame)); - *last_frame = now; - - let ui = self.ctx.frame(); - - IMGUI_RENDER_LOOP.get_mut().unwrap().render(ui); - self.renderer.render(&mut self.ctx); - } - - unsafe fn cleanup(&mut self) { - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - SetWindowLongPtrA(self.game_hwnd, GWLP_WNDPROC, self.wnd_proc as usize as isize); - - #[cfg(target_arch = "x86")] - SetWindowLongA(self.game_hwnd, GWLP_WNDPROC, self.wnd_proc as usize as i32); - } -} - -impl ImguiWindowsEventHandler for ImguiRenderer { - fn io(&self) -> &imgui::Io { - self.ctx.io() - } - - fn io_mut(&mut self) -> &mut imgui::Io { - self.ctx.io_mut() - } - - fn wnd_proc(&self) -> WndProcType { - self.wnd_proc - } -} -unsafe impl Send for ImguiRenderer {} -unsafe impl Sync for ImguiRenderer {} - // Get the address of wglSwapBuffers in opengl32.dll -unsafe fn get_opengl_wglswapbuffers_addr() -> OpenGl32wglSwapBuffers { +unsafe fn get_opengl_wglswapbuffers_addr() -> OpenGl32wglSwapBuffersType { // Grab a handle to opengl32.dll let opengl32dll = CString::new("opengl32.dll").unwrap(); let opengl32module = GetModuleHandleA(PCSTR(opengl32dll.as_ptr() as *mut _)) @@ -251,34 +49,43 @@ unsafe fn get_opengl_wglswapbuffers_addr() -> OpenGl32wglSwapBuffers { let wglswapbuffers_func = GetProcAddress(opengl32module, PCSTR(wglswapbuffers.as_ptr() as *mut _)).unwrap(); - std::mem::transmute(wglswapbuffers_func) + mem::transmute(wglswapbuffers_func) } /// Stores hook detours and implements the [`Hooks`] trait. pub struct ImguiOpenGl3Hooks([MhHook; 1]); impl ImguiOpenGl3Hooks { + /// Construct a set of [`crate::mh::MhHook`]s that will render UI via the + /// provided [`ImguiRenderLoop`]. + /// + /// The following functions are hooked: + /// - `opengl32::wglSwapBuffers` + /// /// # Safety /// - /// Is most likely undefined behavior, as it modifies function pointers at - /// runtime. + /// yolo pub unsafe fn new(t: T) -> Self where T: ImguiRenderLoop + Send + Sync, { // Grab the addresses - let hook_opengl_swapbuffers_address = get_opengl_wglswapbuffers_addr(); + let hook_opengl_swap_buffers_address = get_opengl_wglswapbuffers_addr(); // Create detours let hook_opengl_wgl_swap_buffers = MhHook::new( - hook_opengl_swapbuffers_address as *mut _, - imgui_opengl32_wglSwapBuffers_impl as *mut _, + hook_opengl_swap_buffers_address as *mut _, + opengl32_wgl_swap_buffers_impl as *mut _, ) .expect("couldn't create opengl32.wglSwapBuffers hook"); // Initialize the render loop and store detours - IMGUI_RENDER_LOOP.get_or_init(|| Box::new(t)); - TRAMPOLINE.get_or_init(|| std::mem::transmute(hook_opengl_wgl_swap_buffers.trampoline())); + RenderState::set_render_loop(t); + TRAMPOLINES.get_or_init(|| Trampolines { + opengl32_wgl_swap_buffers: std::mem::transmute( + hook_opengl_wgl_swap_buffers.trampoline(), + ), + }); Self([hook_opengl_wgl_swap_buffers]) } @@ -298,9 +105,7 @@ impl Hooks for ImguiOpenGl3Hooks { } unsafe fn unhook(&mut self) { - if let Some(renderer) = IMGUI_RENDERER.take() { - renderer.lock().cleanup(); - } - drop(IMGUI_RENDER_LOOP.take()); + RenderState::cleanup(); + TRAMPOLINES.take(); } } diff --git a/src/hooks/render.rs b/src/hooks/render.rs new file mode 100644 index 00000000..088dcf71 --- /dev/null +++ b/src/hooks/render.rs @@ -0,0 +1,155 @@ +use std::mem; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::OnceLock; + +use parking_lot::Mutex; +use tracing::{debug, error, trace}; +use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; +#[cfg(target_arch = "x86")] +use windows::Win32::UI::WindowsAndMessaging::SetWindowLongA; +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] +use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrA; +use windows::Win32::UI::WindowsAndMessaging::{DefWindowProcW, GWLP_WNDPROC}; + +use crate::hooks::input::{imgui_wnd_proc_impl, WndProcType}; +use crate::renderer::dx12::RenderEngine; +use crate::ImguiRenderLoop; + +static mut GAME_HWND: OnceLock = OnceLock::new(); +static mut WND_PROC: OnceLock = OnceLock::new(); +static mut RENDER_ENGINE: OnceLock> = OnceLock::new(); +static mut RENDER_LOOP: OnceLock> = OnceLock::new(); +static RENDER_LOCK: AtomicBool = AtomicBool::new(false); + +pub(super) struct RenderState; + +impl RenderState { + pub(super) fn setup HWND>(f: F) -> HWND { + let hwnd = unsafe { *GAME_HWND.get_or_init(f) }; + + unsafe { + WND_PROC.get_or_init(|| { + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + let wnd_proc = mem::transmute(SetWindowLongPtrA( + hwnd, + GWLP_WNDPROC, + imgui_wnd_proc as usize as isize, + )); + + #[cfg(target_arch = "x86")] + let wnd_proc = mem::transmute(SetWindowLongA( + hwnd, + GWLP_WNDPROC, + imgui_wnd_proc as usize as i32, + )); + + wnd_proc + }) + }; + + hwnd + } + + pub(super) fn set_render_loop(t: T) { + unsafe { RENDER_LOOP.get_or_init(|| Box::new(t)) }; + } + + pub(super) fn is_locked() -> bool { + RENDER_LOCK.load(Ordering::SeqCst) + } + + pub(super) fn render(hwnd: HWND) { + RENDER_LOCK.store(true, Ordering::SeqCst); + + let Some(render_loop) = (unsafe { RENDER_LOOP.get_mut() }) else { + error!("Could not obtain render loop"); + return; + }; + + let render_engine = unsafe { + RENDER_ENGINE.get_or_init(|| { + let mut render_engine = RenderEngine::new(hwnd).unwrap(); + render_loop.initialize(&mut render_engine.ctx()); + Mutex::new(render_engine) + }) + }; + + let Some(mut render_engine) = render_engine.try_lock() else { + error!("Could not lock render engine"); + return; + }; + + render_loop.before_render(&mut render_engine.ctx()); + + if let Err(e) = render_engine.render(|ui| render_loop.render(ui)) { + error!("Render: {e:?}"); + } + + RENDER_LOCK.store(false, Ordering::SeqCst); + } + + pub(super) fn resize() { + // TODO sometimes it doesn't lock. + if let Some(Some(mut render_engine)) = unsafe { RENDER_ENGINE.get().map(Mutex::try_lock) } { + trace!("Resizing"); + if let Err(e) = render_engine.resize() { + error!("Couldn't resize: {e:?}"); + } + } + } + + pub(super) fn cleanup() { + unsafe { + if let (Some(wnd_proc), Some(hwnd)) = (WND_PROC.take(), GAME_HWND.take()) { + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + SetWindowLongPtrA(hwnd, GWLP_WNDPROC, wnd_proc as usize as isize); + + #[cfg(target_arch = "x86")] + SetWindowLongA(hwnd, GWLP_WNDPROC, wnd_proc as usize as i32); + } + + RENDER_ENGINE.take(); + RENDER_LOOP.take(); + RENDER_LOCK.store(false, Ordering::SeqCst); + } + } +} + +unsafe extern "system" fn imgui_wnd_proc( + hwnd: HWND, + umsg: u32, + WPARAM(wparam): WPARAM, + LPARAM(lparam): LPARAM, +) -> LRESULT { + let render_engine = match RENDER_ENGINE.get().map(Mutex::try_lock) { + Some(Some(render_engine)) => render_engine, + Some(None) => { + debug!("Could not lock in WndProc"); + return DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)); + }, + None => { + debug!("WndProc called before hook was set"); + return DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)); + }, + }; + + let Some(render_loop) = RENDER_LOOP.get() else { + debug!("Could not get render loop"); + return DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)); + }; + + let Some(&wnd_proc) = WND_PROC.get() else { + debug!("Could not get original WndProc"); + return DefWindowProcW(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)); + }; + + imgui_wnd_proc_impl( + hwnd, + umsg, + WPARAM(wparam), + LPARAM(lparam), + wnd_proc, + render_engine, + render_loop, + ) +} diff --git a/src/lib.rs b/src/lib.rs index 95416d7b..937c12fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,11 +45,10 @@ //! //! ##### Example //! -//! Implement the [`hooks::ImguiRenderLoop`] trait: +//! Implement the [`ImguiRenderLoop`] trait: //! //! ```no_run //! // lib.rs -//! use hudhook::hooks::ImguiRenderLoop; //! use hudhook::*; //! //! pub struct MyRenderLoop; @@ -68,25 +67,25 @@ //! { //! // Use this if hooking into a DirectX 9 application. //! use hudhook::hooks::dx9::ImguiDx9Hooks; -//! hudhook!(MyRenderLoop.into_hook::()); +//! hudhook!(ImguiDx9Hooks, MyRenderLoop); //! } //! //! { //! // Use this if hooking into a DirectX 11 application. //! use hudhook::hooks::dx11::ImguiDx11Hooks; -//! hudhook!(MyRenderLoop.into_hook::()); +//! hudhook!(ImguiDx11Hooks, MyRenderLoop); //! } //! //! { //! // Use this if hooking into a DirectX 12 application. //! use hudhook::hooks::dx12::ImguiDx12Hooks; -//! hudhook!(MyRenderLoop.into_hook::()); +//! hudhook!(ImguiDx12Hooks, MyRenderLoop); //! } //! //! { //! // Use this if hooking into a OpenGL 3 application. //! use hudhook::hooks::opengl3::ImguiOpenGl3Hooks; -//! hudhook!(MyRenderLoop.into_hook::()); +//! hudhook!(ImguiOpenGl3Hooks, MyRenderLoop); //! } //! ``` //! @@ -114,10 +113,12 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; +use imgui::{Context, Io, Ui}; use once_cell::sync::OnceCell; use tracing::error; use windows::core::Error; pub use windows::Win32::Foundation::HINSTANCE; +use windows::Win32::Foundation::{HWND, LPARAM, WPARAM}; use windows::Win32::System::Console::{ AllocConsole, FreeConsole, GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_VIRTUAL_TERMINAL_PROCESSING, STD_OUTPUT_HANDLE, @@ -126,16 +127,13 @@ use windows::Win32::System::LibraryLoader::FreeLibraryAndExitThread; pub use windows::Win32::System::SystemServices::{DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH}; pub use {imgui, tracing}; -use crate::hooks::Hooks; -pub use crate::hooks::ImguiRenderLoop; use crate::mh::{MH_ApplyQueued, MH_Initialize, MH_Uninitialize, MhHook, MH_STATUS}; pub mod hooks; -pub mod mh; -pub mod renderers; - #[cfg(feature = "inject")] pub mod inject; +pub mod mh; +pub mod renderer; mod util; @@ -211,6 +209,53 @@ pub fn eject() { }); } +/// Implement your `imgui` rendering logic via this trait. +pub trait ImguiRenderLoop { + /// Called once at the first occurrence of the hook. Implement this to + /// initialize your data. + fn initialize(&mut self, _ctx: &mut Context) {} + + /// Called every frame. Use the provided `ui` object to build your UI. + fn render(&mut self, ui: &mut Ui); + + // Called before rendering frame. Use _ctx object to modify imgui settings + // before rendering ui. + fn before_render(&mut self, _ctx: &mut Context) {} + + /// Called during the window procedure. + fn on_wnd_proc(&self, _hwnd: HWND, _umsg: u32, _wparam: WPARAM, _lparam: LPARAM) {} + + /// If this function returns true, the WndProc function will not call the + /// procedure of the parent window. + fn should_block_messages(&self, _io: &Io) -> bool { + false + } +} + +/// Generic trait for platform-specific hooks. +/// +/// Implement this if you are building a custom renderer. +/// +/// Check out first party implementations ([`crate::hooks::dx9`], +/// [`crate::hooks::dx11`], [`crate::hooks::dx12`], [`crate::hooks::opengl3`]) +/// for guidance on how to implement the methods. +pub trait Hooks { + fn from_render_loop(t: T) -> Box + where + Self: Sized, + T: ImguiRenderLoop + Send + Sync + 'static; + + /// Return the list of hooks to be enabled, in order. + fn hooks(&self) -> &[MhHook]; + + /// Cleanup global data and disable the hooks. + /// + /// # Safety + /// + /// Is most definitely UB. + unsafe fn unhook(&mut self); +} + /// Holds all the activated hooks and manages their lifetime. pub struct Hudhook(Vec>); unsafe impl Send for Hudhook {} @@ -311,8 +356,11 @@ pub struct HudhookBuilder(Hudhook); impl HudhookBuilder { /// Add a hook object. - pub fn with(mut self, hook: Box) -> Self { - self.0 .0.push(hook); + pub fn with( + mut self, + render_loop: impl ImguiRenderLoop + Send + Sync + 'static, + ) -> Self { + self.0 .0.push(T::from_render_loop(render_loop)); self } @@ -352,7 +400,7 @@ impl HudhookBuilder { /// ``` #[macro_export] macro_rules! hudhook { - ($hooks:expr) => { + ($t:ty, $hooks:expr) => { use hudhook::tracing::*; use hudhook::*; @@ -364,13 +412,16 @@ macro_rules! hudhook { _: *mut ::std::ffi::c_void, ) { if reason == DLL_PROCESS_ATTACH { - trace!("DllMain()"); + ::tracing::trace!("DllMain()"); ::std::thread::spawn(move || { - if let Err(e) = - Hudhook::builder().with({ $hooks }).with_hmodule(hmodule).build().apply() + if let Err(e) = ::hudhook::Hudhook::builder() + .with::<$t>({ $hooks }) + .with_hmodule(hmodule) + .build() + .apply() { - error!("Couldn't apply hooks: {e:?}"); - eject(); + ::tracing::error!("Couldn't apply hooks: {e:?}"); + ::hudhook::eject(); } }); } diff --git a/src/renderers/imgui_dx12.rs b/src/renderer/dx12.rs similarity index 51% rename from src/renderers/imgui_dx12.rs rename to src/renderer/dx12.rs index 53fb1c98..bd342c0c 100644 --- a/src/renderers/imgui_dx12.rs +++ b/src/renderer/dx12.rs @@ -1,263 +1,603 @@ +use std::cell::{RefCell, RefMut}; use std::ffi::c_void; use std::mem::{size_of, ManuallyDrop}; use std::ptr::{null, null_mut}; +use std::rc::Rc; +use std::sync::atomic::{AtomicU64, Ordering}; pub use imgui; use imgui::internal::RawWrapper; -use imgui::{BackendFlags, DrawCmd, DrawData, DrawIdx, DrawVert, TextureId}; +use imgui::{BackendFlags, Context, DrawCmd, DrawData, DrawIdx, DrawVert, TextureId, Ui}; use memoffset::offset_of; use tracing::{error, trace}; -use windows::core::{s, w, ComInterface, Result, PCSTR}; -use windows::Win32::Foundation::{CloseHandle, BOOL, RECT}; +use windows::core::{s, w, ComInterface, Result, PCSTR, PCWSTR}; +use windows::Win32::Foundation::{CloseHandle, BOOL, HANDLE, HWND, RECT}; use windows::Win32::Graphics::Direct3D::Fxc::D3DCompile; use windows::Win32::Graphics::Direct3D::{ - ID3DBlob, ID3DInclude, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, + ID3DBlob, ID3DInclude, D3D_FEATURE_LEVEL_11_1, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, }; use windows::Win32::Graphics::Direct3D12::*; +use windows::Win32::Graphics::DirectComposition::{ + DCompositionCreateDevice, IDCompositionDevice, IDCompositionTarget, IDCompositionVisual, +}; use windows::Win32::Graphics::Dxgi::Common::*; -use windows::Win32::System::Threading::{CreateEventA, WaitForSingleObject}; +use windows::Win32::Graphics::Dxgi::{ + CreateDXGIFactory2, IDXGIAdapter, IDXGIFactory2, IDXGISwapChain3, DXGI_CREATE_FACTORY_DEBUG, + DXGI_SWAP_CHAIN_DESC1, DXGI_SWAP_EFFECT_FLIP_DISCARD, DXGI_USAGE_RENDER_TARGET_OUTPUT, +}; +use windows::Win32::Graphics::Gdi::ScreenToClient; +use windows::Win32::System::Threading::{ + CreateEventA, CreateEventExW, WaitForSingleObject, WaitForSingleObjectEx, CREATE_EVENT, + INFINITE, +}; +use windows::Win32::UI::WindowsAndMessaging::{GetCursorPos, GetForegroundWindow, IsChild}; +use super::keys; use crate::util::{try_out_param, try_out_ptr}; +const COMMAND_ALLOCATOR_NAMES: [PCWSTR; 8] = [ + w!("hudhook Command allocator #0"), + w!("hudhook Command allocator #1"), + w!("hudhook Command allocator #2"), + w!("hudhook Command allocator #3"), + w!("hudhook Command allocator #4"), + w!("hudhook Command allocator #5"), + w!("hudhook Command allocator #6"), + w!("hudhook Command allocator #7"), +]; + +#[derive(Debug)] +struct FrameContext { + desc_handle: D3D12_CPU_DESCRIPTOR_HANDLE, + back_buffer: ID3D12Resource, + command_allocator: ID3D12CommandAllocator, + fence: ID3D12Fence, + fence_val: u64, + fence_event: HANDLE, +} + +impl FrameContext { + unsafe fn new( + device: &ID3D12Device, + swap_chain: &IDXGISwapChain3, + handle_start: D3D12_CPU_DESCRIPTOR_HANDLE, + heap_inc_size: u32, + index: u32, + ) -> Result { + let desc_handle = D3D12_CPU_DESCRIPTOR_HANDLE { + ptr: handle_start.ptr + (index * heap_inc_size) as usize, + }; + + let back_buffer: ID3D12Resource = swap_chain.GetBuffer(index)?; + device.CreateRenderTargetView(&back_buffer, None, desc_handle); + + let command_allocator: ID3D12CommandAllocator = + device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?; + command_allocator.SetName(COMMAND_ALLOCATOR_NAMES[index as usize % 8])?; + + Ok(FrameContext { + desc_handle, + back_buffer, + command_allocator, + fence: device.CreateFence(0, D3D12_FENCE_FLAG_NONE).unwrap(), + fence_val: 0, + fence_event: CreateEventExW(None, None, CREATE_EVENT(0), 0x1F0003).unwrap(), + }) + } + + fn incr(&mut self) { + static FENCE_MAX: AtomicU64 = AtomicU64::new(0); + self.fence_val = FENCE_MAX.fetch_add(1, Ordering::SeqCst); + } + + fn wait_fence(&mut self) { + unsafe { + if self.fence.GetCompletedValue() < self.fence_val { + self.fence.SetEventOnCompletion(self.fence_val, self.fence_event).unwrap(); + WaitForSingleObjectEx(self.fence_event, INFINITE, false); + } + } + } +} + +struct FrameResources { + index_buffer: Option, + vertex_buffer: Option, + index_buffer_size: usize, + vertex_buffer_size: usize, + vertices: Vec, + indices: Vec, +} + +impl FrameResources { + fn resize(&mut self, dev: &ID3D12Device, indices: usize, vertices: usize) -> Result<()> { + if self.vertex_buffer.is_none() || self.vertex_buffer_size < vertices { + drop(self.vertex_buffer.take()); + + self.vertex_buffer_size = vertices + 5000; + let props = D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_UPLOAD, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }; + let desc = D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 65536, + Width: (self.vertex_buffer_size * size_of::()) as u64, + Height: 1, + DepthOrArraySize: 1, + MipLevels: 1, + Format: DXGI_FORMAT_UNKNOWN, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + Flags: D3D12_RESOURCE_FLAG_NONE, + }; + + unsafe { + dev.CreateCommittedResource( + &props, + D3D12_HEAP_FLAG_NONE, + &desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + None, + &mut self.vertex_buffer, + ) + } + .map_err(|e| { + error!("Resizing index buffer: {:?}", e); + e + })?; + } + + if self.index_buffer.is_none() || self.index_buffer_size < indices { + drop(self.index_buffer.take()); + self.index_buffer_size = indices + 10000; + let props = D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_UPLOAD, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }; + let desc = D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 0, + Width: (self.index_buffer_size * size_of::()) as u64, + Height: 1, + DepthOrArraySize: 1, + MipLevels: 1, + Format: DXGI_FORMAT_UNKNOWN, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + Flags: D3D12_RESOURCE_FLAG_NONE, + }; + + unsafe { + dev.CreateCommittedResource( + &props, + D3D12_HEAP_FLAG_NONE, + &desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + None, + &mut self.index_buffer, + ) + } + .map_err(|e| { + error!("Resizing index buffer: {:?}", e); + e + })?; + } + Ok(()) + } +} + +impl Drop for FrameResources { + fn drop(&mut self) { + drop(self.vertex_buffer.take()); + drop(self.index_buffer.take()); + } +} + +impl Default for FrameResources { + fn default() -> Self { + Self { + index_buffer: None, + vertex_buffer: None, + index_buffer_size: 10000, + vertex_buffer_size: 5000, + vertices: Default::default(), + indices: Default::default(), + } + } +} + +struct Barrier; + +impl Barrier { + fn create( + buf: ID3D12Resource, + before: D3D12_RESOURCE_STATES, + after: D3D12_RESOURCE_STATES, + ) -> Vec { + let transition_barrier = ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER { + pResource: ManuallyDrop::new(Some(buf)), + Subresource: D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + StateBefore: before, + StateAfter: after, + }); + + let barrier = D3D12_RESOURCE_BARRIER { + Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE, + Anonymous: D3D12_RESOURCE_BARRIER_0 { Transition: transition_barrier }, + }; + + vec![barrier] + } + + fn drop(barriers: Vec) { + for barrier in barriers { + let transition = ManuallyDrop::into_inner(unsafe { barrier.Anonymous.Transition }); + let _ = ManuallyDrop::into_inner(transition.pResource); + } + } +} + +struct Compositor { + dcomp_dev: IDCompositionDevice, + _dcomp_target: IDCompositionTarget, + root_visual: IDCompositionVisual, +} + +impl Compositor { + unsafe fn new(target_hwnd: HWND) -> Result { + let dcomp_dev: IDCompositionDevice = DCompositionCreateDevice(None)?; + let dcomp_target = dcomp_dev.CreateTargetForHwnd(target_hwnd, BOOL::from(true))?; + + let root_visual = dcomp_dev.CreateVisual()?; + dcomp_target.SetRoot(&root_visual)?; + dcomp_dev.Commit()?; + + Ok(Self { dcomp_dev, _dcomp_target: dcomp_target, root_visual }) + } + + unsafe fn render(&self, swap_chain: &IDXGISwapChain3) -> Result<()> { + self.root_visual.SetContent(swap_chain)?; + self.dcomp_dev.Commit()?; + + Ok(()) + } +} + pub struct RenderEngine { - dev: ID3D12Device, - rtv_format: DXGI_FORMAT, - font_srv_cpu_desc_handle: D3D12_CPU_DESCRIPTOR_HANDLE, - font_srv_gpu_desc_handle: D3D12_GPU_DESCRIPTOR_HANDLE, - font_texture_resource: Option, + target_hwnd: HWND, + + _dxgi_factory: IDXGIFactory2, + _dxgi_adapter: IDXGIAdapter, + + device: ID3D12Device, + swap_chain: IDXGISwapChain3, + + command_queue: ID3D12CommandQueue, + command_list: ID3D12GraphicsCommandList, + + renderer_heap: ID3D12DescriptorHeap, + rtv_heap: ID3D12DescriptorHeap, + + cpu_desc: D3D12_CPU_DESCRIPTOR_HANDLE, + gpu_desc: D3D12_GPU_DESCRIPTOR_HANDLE, frame_resources: Vec, + frame_contexts: Vec, const_buf: [[f32; 4]; 4], + ctx: Rc>, + + font_texture_resource: Option, root_signature: Option, pipeline_state: Option, + + compositor: Compositor, } impl RenderEngine { - pub fn new( - ctx: &mut imgui::Context, - dev: ID3D12Device, - num_frames_in_flight: u32, - rtv_format: DXGI_FORMAT, - _cb_svr_heap: ID3D12DescriptorHeap, - font_srv_cpu_desc_handle: D3D12_CPU_DESCRIPTOR_HANDLE, - font_srv_gpu_desc_handle: D3D12_GPU_DESCRIPTOR_HANDLE, - ) -> Self { - ctx.io_mut().backend_flags |= BackendFlags::RENDERER_HAS_VTX_OFFSET; - ctx.set_renderer_name(String::from(concat!("imgui-dx12@", env!("CARGO_PKG_VERSION")))); + pub fn new(target_hwnd: HWND) -> Result { + // Build device and swap chain. + let dxgi_factory: IDXGIFactory2 = unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG) }?; + + let dxgi_adapter = unsafe { dxgi_factory.EnumAdapters(0) }?; + + let mut device: Option = None; + unsafe { D3D12CreateDevice(&dxgi_adapter, D3D_FEATURE_LEVEL_11_1, &mut device) }?; + let device = device.unwrap(); + + let queue_desc = D3D12_COMMAND_QUEUE_DESC { + Type: D3D12_COMMAND_LIST_TYPE_DIRECT, + Priority: 0, + Flags: D3D12_COMMAND_QUEUE_FLAG_NONE, + NodeMask: 0, + }; + + let command_queue: ID3D12CommandQueue = + unsafe { device.CreateCommandQueue(&queue_desc as *const _) }.unwrap(); + + let (width, height) = crate::util::win_size(target_hwnd); + + let sd = DXGI_SWAP_CHAIN_DESC1 { + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + Width: width as _, + Height: height as _, + BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount: 2, + SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED, + ..Default::default() + }; + + let swap_chain = + unsafe { dxgi_factory.CreateSwapChainForComposition(&command_queue, &sd, None) }? + .cast::()?; + + // Build descriptor heaps. + let renderer_heap: ID3D12DescriptorHeap = unsafe { + device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { + Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + NumDescriptors: sd.BufferCount, + Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + NodeMask: 0, + }) + }?; + + let rtv_heap: ID3D12DescriptorHeap = unsafe { + device + .CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { + Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + NumDescriptors: sd.BufferCount, + Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + NodeMask: 1, + }) + .unwrap() + }; + + let rtv_heap_inc_size = + unsafe { device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) }; + + let handle_start = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() }; + + // Build frame contexts. + let frame_contexts: Vec = (0..sd.BufferCount) + .map(|i| unsafe { + FrameContext::new(&device, &swap_chain, handle_start, rtv_heap_inc_size, i) + }) + .collect::>>()?; let frame_resources = - (0..num_frames_in_flight).map(|_| FrameResources::default()).collect::>(); + (0..sd.BufferCount).map(|_| FrameResources::default()).collect::>(); + + // Build command objects. + let command_allocator: ID3D12CommandAllocator = + unsafe { device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }?; + + let command_list: ID3D12GraphicsCommandList = unsafe { + device.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &command_allocator, None) + }?; + unsafe { + command_list.Close().unwrap(); + command_list.SetName(w!("hudhook Command List"))?; + }; + + let cpu_desc = unsafe { renderer_heap.GetCPUDescriptorHandleForHeapStart() }; + let gpu_desc = unsafe { renderer_heap.GetGPUDescriptorHandleForHeapStart() }; - RenderEngine { - dev, - rtv_format, - font_srv_cpu_desc_handle, - font_srv_gpu_desc_handle, + let mut ctx = Context::create(); + ctx.set_ini_filename(None); + ctx.io_mut().backend_flags |= BackendFlags::RENDERER_HAS_VTX_OFFSET; + ctx.set_renderer_name(String::from(concat!("imgui-dx12@", env!("CARGO_PKG_VERSION")))); + + let ctx = Rc::new(RefCell::new(ctx)); + let compositor = unsafe { Compositor::new(target_hwnd) }?; + + Ok(Self { + target_hwnd, + _dxgi_factory: dxgi_factory, + _dxgi_adapter: dxgi_adapter, + device, + swap_chain, + command_queue, + command_list, + renderer_heap, + rtv_heap, + cpu_desc, + gpu_desc, frame_resources, + frame_contexts, const_buf: [[0f32; 4]; 4], + ctx, + compositor, + font_texture_resource: None, root_signature: None, pipeline_state: None, - font_texture_resource: None, - } + }) } - fn setup_render_state( - &mut self, - draw_data: &imgui::DrawData, - cmd_list: &ID3D12GraphicsCommandList, - frame_resources_idx: usize, - ) { - let display_pos = draw_data.display_pos; - let display_size = draw_data.display_size; + pub fn resize(&mut self) -> Result<()> { + let (width, height) = crate::util::win_size(self.target_hwnd); - let frame_resources = &self.frame_resources[frame_resources_idx]; - self.const_buf = { - let [l, t, r, b] = [ - display_pos[0], - display_pos[1], - display_pos[0] + display_size[0], - display_pos[1] + display_size[1], - ]; + self.frame_contexts.drain(..).for_each(drop); + self.frame_resources.drain(..).for_each(drop); - [[2. / (r - l), 0., 0., 0.], [0., 2. / (t - b), 0., 0.], [0., 0., 0.5, 0.], [ - (r + l) / (l - r), - (t + b) / (b - t), - 0.5, - 1.0, - ]] - }; - - trace!("Display size {}x{}", display_size[0], display_size[1]); + let mut sd = Default::default(); + unsafe { self.swap_chain.GetDesc(&mut sd)? }; unsafe { - cmd_list.RSSetViewports(&[D3D12_VIEWPORT { - TopLeftX: 0f32, - TopLeftY: 0f32, - Width: display_size[0], - Height: display_size[1], - MinDepth: 0f32, - MaxDepth: 1f32, - }]) + self.swap_chain.ResizeBuffers( + sd.BufferCount, + width as _, + height as _, + sd.BufferDesc.Format, + sd.Flags, + )? }; + let rtv_heap_inc_size = + unsafe { self.device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) }; + + let handle_start = unsafe { self.rtv_heap.GetCPUDescriptorHandleForHeapStart() }; + + // Build frame contexts. + self.frame_contexts = (0..sd.BufferCount) + .map(|i| unsafe { + FrameContext::new( + &self.device, + &self.swap_chain, + handle_start, + rtv_heap_inc_size, + i, + ) + }) + .collect::>>()?; + + self.frame_resources = (0..sd.BufferCount).map(|_| FrameResources::default()).collect(); + + Ok(()) + } + + pub fn render(&mut self, mut render_loop: F) -> Result<()> { + unsafe { self.setup_io()? }; + + // Create device objects if necessary. + if self.pipeline_state.is_none() { + unsafe { self.create_device_objects() }?; + } + + let idx = unsafe { self.swap_chain.GetCurrentBackBufferIndex() } as usize; + self.frame_contexts[idx].wait_fence(); + self.frame_contexts[idx].incr(); + + let command_allocator = &self.frame_contexts[idx].command_allocator; + + // Reset command allocator and list state. unsafe { - cmd_list.IASetVertexBuffers( - 0, - Some(&[D3D12_VERTEX_BUFFER_VIEW { - BufferLocation: frame_resources - .vertex_buffer - .as_ref() - .unwrap() - .GetGPUVirtualAddress(), - SizeInBytes: (frame_resources.vertex_buffer_size * size_of::()) as _, - StrideInBytes: size_of::() as _, - }]), - ) - }; + command_allocator.Reset().unwrap(); + self.command_list.Reset(command_allocator, None).unwrap(); + } + + // Setup a barrier that waits for the back buffer to transition to a render + // target. + let back_buffer_to_rt_barrier = Barrier::create( + self.frame_contexts[idx].back_buffer.clone(), + D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATE_RENDER_TARGET, + ); unsafe { - cmd_list.IASetIndexBuffer(Some(&D3D12_INDEX_BUFFER_VIEW { - BufferLocation: frame_resources - .index_buffer - .as_ref() - .unwrap() - .GetGPUVirtualAddress(), - SizeInBytes: (frame_resources.index_buffer_size * size_of::()) as _, - Format: if size_of::() == 2 { - DXGI_FORMAT_R16_UINT - } else { - DXGI_FORMAT_R32_UINT - }, - })); - cmd_list.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - cmd_list.SetPipelineState(self.pipeline_state.as_ref().unwrap()); - cmd_list.SetGraphicsRootSignature(self.root_signature.as_ref().unwrap()); - cmd_list.SetGraphicsRoot32BitConstants( - 0, - 16, - self.const_buf.as_ptr() as *const c_void, - 0, + self.command_list.ResourceBarrier(&back_buffer_to_rt_barrier); + + // Setup the back buffer as a render target and clear it. + self.command_list.OMSetRenderTargets( + 1, + Some(&self.frame_contexts[idx].desc_handle), + BOOL::from(false), + None, + ); + self.command_list.SetDescriptorHeaps(&[Some(self.renderer_heap.clone())]); + self.command_list.ClearRenderTargetView( + self.frame_contexts[idx].desc_handle, + &[0.0, 0.0, 0.0, 0.0], + None, ); - cmd_list.OMSetBlendFactor(Some(&[0f32; 4])); } - } - pub fn render_draw_data( - &mut self, - draw_data: &DrawData, - cmd_list: &ID3D12GraphicsCommandList, - frame_resources_idx: usize, - ) -> Result<()> { - let print_device_removed_reason = |e: windows::core::Error| -> windows::core::Error { - trace!("Device removed reason: {:?}", unsafe { self.dev.GetDeviceRemovedReason() }); - e + // Draw data loop. + let ctx = Rc::clone(&self.ctx); + let mut ctx = ctx.borrow_mut(); + let ui = ctx.frame(); + render_loop(ui); + let draw_data = ctx.render(); + + if let Err(e) = unsafe { self.render_draw_data(draw_data, idx) } { + eprintln!("{}", e); }; - if draw_data.display_size[0] <= 0f32 || draw_data.display_size[1] <= 0f32 { - trace!( - "Insufficent display size {}x{}, skip rendering", - draw_data.display_size[0], - draw_data.display_size[1] - ); - return Ok(()); + // Setup a barrier to wait for the back buffer to transition to presentable + // state. + let back_buffer_to_present_barrier = Barrier::create( + self.frame_contexts[idx].back_buffer.clone(), + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT, + ); + + unsafe { + self.command_list.ResourceBarrier(&back_buffer_to_present_barrier); + + // Close and execute the command list. + self.command_list.Close()?; + self.command_queue.ExecuteCommandLists(&[Some(self.command_list.cast()?)]); + self.command_queue + .Signal(&self.frame_contexts[idx].fence, self.frame_contexts[idx].fence_val)?; } - self.frame_resources[frame_resources_idx] - .resize( - &self.dev, - draw_data.total_idx_count as usize, - draw_data.total_vtx_count as usize, - ) - .map_err(print_device_removed_reason)?; + unsafe { + // Present the content. + self.swap_chain.Present(1, 0).ok()?; + }; - let range = D3D12_RANGE::default(); - let mut vtx_resource: *mut imgui::DrawVert = null_mut(); - let mut idx_resource: *mut imgui::DrawIdx = null_mut(); + // Drop the barriers. + Barrier::drop(back_buffer_to_rt_barrier); + Barrier::drop(back_buffer_to_present_barrier); - // I allocate vectors every single frame SwoleDoge - let (vertices, indices): (Vec, Vec) = draw_data - .draw_lists() - .map(|m| (m.vtx_buffer().iter(), m.idx_buffer().iter())) - .fold((Vec::new(), Vec::new()), |(mut ov, mut oi), (v, i)| { - ov.extend(v); - oi.extend(i); - (ov, oi) - }); + // Composite the frame over the hwnd. + unsafe { self.compositor.render(&self.swap_chain) }?; - { - let frame_resources = &self.frame_resources[frame_resources_idx]; + Ok(()) + } - if let Some(vb) = frame_resources.vertex_buffer.as_ref() { - unsafe { - vb.Map(0, Some(&range), Some(&mut vtx_resource as *mut _ as *mut *mut c_void)) - .map_err(print_device_removed_reason)?; - std::ptr::copy_nonoverlapping(vertices.as_ptr(), vtx_resource, vertices.len()); - vb.Unmap(0, Some(&range)); - } - }; + pub fn hwnd(&self) -> HWND { + self.target_hwnd + } - if let Some(ib) = frame_resources.index_buffer.as_ref() { - unsafe { - ib.Map(0, Some(&range), Some(&mut idx_resource as *mut _ as *mut *mut c_void)) - .map_err(print_device_removed_reason)?; - std::ptr::copy_nonoverlapping(indices.as_ptr(), idx_resource, indices.len()); - ib.Unmap(0, Some(&range)); - } - }; - } + pub fn ctx(&mut self) -> RefMut { + self.ctx.borrow_mut() + } - { - self.setup_render_state(draw_data, cmd_list, frame_resources_idx); - } + unsafe fn setup_io(&mut self) -> Result<()> { + let sd = try_out_param(|sd| unsafe { self.swap_chain.GetDesc1(sd) })?; - let mut vtx_offset = 0usize; - let mut idx_offset = 0usize; + let mut ctx = self.ctx.borrow_mut(); - for cl in draw_data.draw_lists() { - for cmd in cl.commands() { - match cmd { - DrawCmd::Elements { count, cmd_params } => { - let [cx, cy, cw, ch] = cmd_params.clip_rect; - let [x, y] = draw_data.display_pos; - let r = RECT { - left: (cx - x) as i32, - top: (cy - y) as i32, - right: (cw - x) as i32, - bottom: (ch - y) as i32, - }; + // Setup display size and cursor position. + let io = ctx.io_mut(); - if r.right > r.left && r.bottom > r.top { - let tex_handle = D3D12_GPU_DESCRIPTOR_HANDLE { - ptr: cmd_params.texture_id.id() as _, - }; - unsafe { - cmd_list.SetGraphicsRootDescriptorTable(1, tex_handle); - cmd_list.RSSetScissorRects(&[r]); - cmd_list.DrawIndexedInstanced( - count as _, - 1, - (cmd_params.idx_offset + idx_offset) as _, - (cmd_params.vtx_offset + vtx_offset) as _, - 0, - ); - } - } - }, - DrawCmd::ResetRenderState => { - self.setup_render_state(draw_data, cmd_list, frame_resources_idx); - }, - DrawCmd::RawCallback { callback, raw_cmd } => unsafe { - callback(cl.raw(), raw_cmd) - }, - } + io.display_size = [sd.Width as f32, sd.Height as f32]; + + let active_window = unsafe { GetForegroundWindow() }; + if !HANDLE(active_window.0).is_invalid() + && (active_window == self.target_hwnd + || unsafe { IsChild(active_window, self.target_hwnd) }.as_bool()) + { + let mut pos = Default::default(); + let gcp = unsafe { GetCursorPos(&mut pos) }; + if gcp.is_ok() + && unsafe { ScreenToClient(self.target_hwnd, &mut pos as *mut _) }.as_bool() + { + io.mouse_pos[0] = pos.x as _; + io.mouse_pos[1] = pos.y as _; } - idx_offset += cl.idx_buffer().len(); - vtx_offset += cl.vtx_buffer().len(); + } + + io.nav_active = true; + io.nav_visible = true; + + // Map key indices to the virtual key codes + for i in keys::KEYS { + io[i.0] = i.1 .0 as _; } Ok(()) } - fn create_device_objects(&mut self, ctx: &mut imgui::Context) { + unsafe fn create_device_objects(&mut self) -> Result<()> { if self.pipeline_state.is_some() { self.invalidate_device_objects(); } @@ -322,36 +662,27 @@ impl RenderEngine { }; let mut blob: Option = None; let mut err_blob: Option = None; - if let Err(e) = unsafe { - D3D12SerializeRootSignature( - &root_signature_desc, - D3D_ROOT_SIGNATURE_VERSION_1_0, - &mut blob, - Some(&mut err_blob), - ) - } { + if let Err(e) = D3D12SerializeRootSignature( + &root_signature_desc, + D3D_ROOT_SIGNATURE_VERSION_1_0, + &mut blob, + Some(&mut err_blob), + ) { if let Some(err_blob) = err_blob { let buf_ptr = unsafe { err_blob.GetBufferPointer() } as *mut u8; let buf_size = unsafe { err_blob.GetBufferSize() }; let s = unsafe { String::from_raw_parts(buf_ptr, buf_size, buf_size + 1) }; error!("Serializing root signature: {}: {}", e, s); } + return Err(e); } - let blob = blob.unwrap(); - self.root_signature = Some( - unsafe { - self.dev.CreateRootSignature( - 0, - std::slice::from_raw_parts( - blob.GetBufferPointer() as *const u8, - blob.GetBufferSize(), - ), - ) - } - .unwrap(), - ); - + let blob = blob.unwrap(); + self.root_signature = Some(self.device.CreateRootSignature( + 0, + std::slice::from_raw_parts(blob.GetBufferPointer() as *const u8, blob.GetBufferSize()), + )?); + let vs = r#" cbuffer vertexBuffer : register(b0) { @@ -441,7 +772,7 @@ impl RenderEngine { Flags: D3D12_PIPELINE_STATE_FLAG_NONE, ..Default::default() }; - pso_desc.RTVFormats[0] = self.rtv_format; + pso_desc.RTVFormats[0] = DXGI_FORMAT_B8G8R8A8_UNORM; pso_desc.DSVFormat = DXGI_FORMAT_D32_FLOAT; pso_desc.VS = D3D12_SHADER_BYTECODE { @@ -514,36 +845,22 @@ impl RenderEngine { ConservativeRaster: D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF, }; - let pipeline_state = unsafe { self.dev.CreateGraphicsPipelineState(&pso_desc) }; + let pipeline_state = unsafe { self.device.CreateGraphicsPipelineState(&pso_desc) }; self.pipeline_state = Some(pipeline_state.unwrap()); - self.create_font_texture(ctx).unwrap(); - } - - fn invalidate_device_objects(&mut self) { - if let Some(root_signature) = self.root_signature.take() { - drop(root_signature); - } - if let Some(pipeline_state) = self.pipeline_state.take() { - drop(pipeline_state); - } - if let Some(font_texture_resource) = self.font_texture_resource.take() { - drop(font_texture_resource); - } + self.create_font_texture()?; - self.frame_resources.iter_mut().for_each(|fr| { - drop(fr.index_buffer.take()); - drop(fr.vertex_buffer.take()); - }); + Ok(()) } - fn create_font_texture(&mut self, ctx: &mut imgui::Context) -> Result<()> { + unsafe fn create_font_texture(&mut self) -> Result<()> { + let mut ctx = self.ctx.borrow_mut(); let fonts = ctx.fonts(); let texture = fonts.build_rgba32_texture(); - let p_texture: ManuallyDrop> = ManuallyDrop::new( - try_out_param(|v| unsafe { - self.dev.CreateCommittedResource( + let p_texture: ManuallyDrop> = + ManuallyDrop::new(try_out_param(|v| unsafe { + self.device.CreateCommittedResource( &D3D12_HEAP_PROPERTIES { Type: D3D12_HEAP_TYPE_DEFAULT, CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, @@ -568,41 +885,36 @@ impl RenderEngine { None, v, ) - }) - .expect("CreateCommittedResource p_texture"), - ); + })?); let mut upload_buffer: Option = None; let upload_pitch = texture.width * 4; let upload_size = texture.height * upload_pitch; - unsafe { - self.dev.CreateCommittedResource( - &D3D12_HEAP_PROPERTIES { - Type: D3D12_HEAP_TYPE_UPLOAD, - CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, - MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, - CreationNodeMask: Default::default(), - VisibleNodeMask: Default::default(), - }, - D3D12_HEAP_FLAG_NONE, - &D3D12_RESOURCE_DESC { - Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, - Alignment: 0, - Width: upload_size as _, - Height: 1, - DepthOrArraySize: 1, - MipLevels: 1, - Format: DXGI_FORMAT_UNKNOWN, - SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, - Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, - Flags: D3D12_RESOURCE_FLAG_NONE, - }, - D3D12_RESOURCE_STATE_GENERIC_READ, - None, - &mut upload_buffer, - ) - } - .expect("CreateCommittedResource upload_buffer"); + self.device.CreateCommittedResource( + &D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_UPLOAD, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: Default::default(), + VisibleNodeMask: Default::default(), + }, + D3D12_HEAP_FLAG_NONE, + &D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 0, + Width: upload_size as _, + Height: 1, + DepthOrArraySize: 1, + MipLevels: 1, + Format: DXGI_FORMAT_UNKNOWN, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + Flags: D3D12_RESOURCE_FLAG_NONE, + }, + D3D12_RESOURCE_STATE_GENERIC_READ, + None, + &mut upload_buffer, + )?; let upload_buffer = ManuallyDrop::new(upload_buffer); let range = D3D12_RANGE { Begin: 0, End: upload_size as usize }; @@ -615,34 +927,24 @@ impl RenderEngine { } }; - let fence: ID3D12Fence = unsafe { self.dev.CreateFence(0, D3D12_FENCE_FLAG_NONE) }.unwrap(); + let fence: ID3D12Fence = self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?; let event = unsafe { CreateEventA(None, BOOL::from(false), BOOL::from(false), PCSTR(null())) }?; - let cmd_queue: ID3D12CommandQueue = unsafe { - self.dev.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC { - Type: D3D12_COMMAND_LIST_TYPE_DIRECT, - Priority: Default::default(), - Flags: D3D12_COMMAND_QUEUE_FLAG_NONE, - NodeMask: 1, - }) - } - .expect("CreateCommandQueue"); - - unsafe { cmd_queue.SetName(w!("hudhook font texture Command Queue")) }.unwrap(); - let cmd_allocator: ID3D12CommandAllocator = - unsafe { self.dev.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }.unwrap(); + self.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?; - unsafe { cmd_allocator.SetName(w!("hudhook font texture Command Allocator")) }.unwrap(); + cmd_allocator.SetName(w!("hudhook font texture Command Allocator"))?; - let cmd_list: ID3D12GraphicsCommandList = unsafe { - self.dev.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &cmd_allocator, None) - } - .expect("CreateCommandList"); + let cmd_list: ID3D12GraphicsCommandList = self.device.CreateCommandList( + 0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + &cmd_allocator, + None, + )?; - unsafe { cmd_list.SetName(w!("hudhook font texture Command List")) }.unwrap(); + cmd_list.SetName(w!("hudhook font texture Command List"))?; let src_location = D3D12_TEXTURE_COPY_LOCATION { pResource: upload_buffer, @@ -684,9 +986,9 @@ impl RenderEngine { cmd_list.CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, None); cmd_list.ResourceBarrier(&[barrier]); cmd_list.Close().unwrap(); - cmd_queue.ExecuteCommandLists(&[Some(cmd_list.cast().unwrap())]); - cmd_queue.Signal(&fence, 1).unwrap(); - fence.SetEventOnCompletion(1, event).unwrap(); + self.command_queue.ExecuteCommandLists(&[Some(cmd_list.cast()?)]); + self.command_queue.Signal(&fence, 1)?; + fence.SetEventOnCompletion(1, event)?; WaitForSingleObject(event, u32::MAX); }; @@ -707,133 +1009,202 @@ impl RenderEngine { unsafe { CloseHandle(event)? }; unsafe { - self.dev.CreateShaderResourceView( - p_texture.as_ref(), - Some(&srv_desc), - self.font_srv_cpu_desc_handle, - ) + self.device.CreateShaderResourceView(p_texture.as_ref(), Some(&srv_desc), self.cpu_desc) }; drop(self.font_texture_resource.take()); self.font_texture_resource = ManuallyDrop::into_inner(p_texture); - fonts.tex_id = TextureId::from(self.font_srv_gpu_desc_handle.ptr as usize); + fonts.tex_id = TextureId::from(self.gpu_desc.ptr as usize); drop(ManuallyDrop::into_inner(src_location.pResource)); Ok(()) } - pub fn new_frame(&mut self, ctx: &mut imgui::Context) { - if self.pipeline_state.is_none() { - self.create_device_objects(ctx); + fn invalidate_device_objects(&mut self) { + if let Some(root_signature) = self.root_signature.take() { + drop(root_signature); + } + if let Some(pipeline_state) = self.pipeline_state.take() { + drop(pipeline_state); + } + if let Some(font_texture_resource) = self.font_texture_resource.take() { + drop(font_texture_resource); } + + self.frame_resources.iter_mut().for_each(|fr| { + drop(fr.index_buffer.take()); + drop(fr.vertex_buffer.take()); + }); } -} -struct FrameResources { - index_buffer: Option, - vertex_buffer: Option, - index_buffer_size: usize, - vertex_buffer_size: usize, -} + unsafe fn setup_render_state(&mut self, draw_data: &DrawData, idx: usize) { + let display_pos = draw_data.display_pos; + let display_size = draw_data.display_size; -impl FrameResources { - fn resize(&mut self, dev: &ID3D12Device, indices: usize, vertices: usize) -> Result<()> { - if self.vertex_buffer.is_none() || self.vertex_buffer_size < vertices { - drop(self.vertex_buffer.take()); + let frame_resources = &self.frame_resources[idx]; + self.const_buf = { + let [l, t, r, b] = [ + display_pos[0], + display_pos[1], + display_pos[0] + display_size[0], + display_pos[1] + display_size[1], + ]; - self.vertex_buffer_size = vertices + 5000; - let props = D3D12_HEAP_PROPERTIES { - Type: D3D12_HEAP_TYPE_UPLOAD, - CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, - MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, - CreationNodeMask: 0, - VisibleNodeMask: 0, - }; - let desc = D3D12_RESOURCE_DESC { - Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, - Alignment: 65536, - Width: (self.vertex_buffer_size * size_of::()) as u64, - Height: 1, - DepthOrArraySize: 1, - MipLevels: 1, - Format: DXGI_FORMAT_UNKNOWN, - SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, - Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, - Flags: D3D12_RESOURCE_FLAG_NONE, - }; + [[2. / (r - l), 0., 0., 0.], [0., 2. / (t - b), 0., 0.], [0., 0., 0.5, 0.], [ + (r + l) / (l - r), + (t + b) / (b - t), + 0.5, + 1.0, + ]] + }; - unsafe { - dev.CreateCommittedResource( - &props, - D3D12_HEAP_FLAG_NONE, - &desc, - D3D12_RESOURCE_STATE_GENERIC_READ, - None, - &mut self.vertex_buffer, - ) - } - .map_err(|e| { - error!("Resizing index buffer: {:?}", e); - e - })?; + trace!("Display size {}x{}", display_size[0], display_size[1]); + self.command_list.RSSetViewports(&[D3D12_VIEWPORT { + TopLeftX: 0f32, + TopLeftY: 0f32, + Width: display_size[0], + Height: display_size[1], + MinDepth: 0f32, + MaxDepth: 1f32, + }]); + + self.command_list.IASetVertexBuffers( + 0, + Some(&[D3D12_VERTEX_BUFFER_VIEW { + BufferLocation: frame_resources + .vertex_buffer + .as_ref() + .unwrap() + .GetGPUVirtualAddress(), + SizeInBytes: (frame_resources.vertex_buffer_size * size_of::()) as _, + StrideInBytes: size_of::() as _, + }]), + ); + + self.command_list.IASetIndexBuffer(Some(&D3D12_INDEX_BUFFER_VIEW { + BufferLocation: frame_resources.index_buffer.as_ref().unwrap().GetGPUVirtualAddress(), + SizeInBytes: (frame_resources.index_buffer_size * size_of::()) as _, + Format: if size_of::() == 2 { + DXGI_FORMAT_R16_UINT + } else { + DXGI_FORMAT_R32_UINT + }, + })); + self.command_list.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + self.command_list.SetPipelineState(self.pipeline_state.as_ref().unwrap()); + self.command_list.SetGraphicsRootSignature(self.root_signature.as_ref().unwrap()); + self.command_list.SetGraphicsRoot32BitConstants( + 0, + 16, + self.const_buf.as_ptr() as *const c_void, + 0, + ); + self.command_list.OMSetBlendFactor(Some(&[0f32; 4])); + } + + unsafe fn render_draw_data(&mut self, draw_data: &DrawData, idx: usize) -> Result<()> { + let print_device_removed_reason = |e: windows::core::Error| -> windows::core::Error { + trace!("Device removed reason: {:?}", unsafe { self.device.GetDeviceRemovedReason() }); + e + }; + + if draw_data.display_size[0] <= 0f32 || draw_data.display_size[1] <= 0f32 { + trace!( + "Insufficent display size {}x{}, skip rendering", + draw_data.display_size[0], + draw_data.display_size[1] + ); + return Ok(()); } - if self.index_buffer.is_none() || self.index_buffer_size < indices { - drop(self.index_buffer.take()); - self.index_buffer_size = indices + 10000; - let props = D3D12_HEAP_PROPERTIES { - Type: D3D12_HEAP_TYPE_UPLOAD, - CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, - MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, - CreationNodeMask: 0, - VisibleNodeMask: 0, - }; - let desc = D3D12_RESOURCE_DESC { - Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, - Alignment: 0, - Width: (self.index_buffer_size * size_of::()) as u64, - Height: 1, - DepthOrArraySize: 1, - MipLevels: 1, - Format: DXGI_FORMAT_UNKNOWN, - SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, - Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, - Flags: D3D12_RESOURCE_FLAG_NONE, + self.frame_resources[idx] + .resize( + &self.device, + draw_data.total_idx_count as usize, + draw_data.total_vtx_count as usize, + ) + .map_err(print_device_removed_reason)?; + + let range = D3D12_RANGE::default(); + let mut vtx_resource: *mut imgui::DrawVert = null_mut(); + let mut idx_resource: *mut imgui::DrawIdx = null_mut(); + + self.frame_resources[idx].vertices.clear(); + self.frame_resources[idx].indices.clear(); + draw_data.draw_lists().map(|m| (m.vtx_buffer().iter(), m.idx_buffer().iter())).for_each( + |(v, i)| { + self.frame_resources[idx].vertices.extend(v); + self.frame_resources[idx].indices.extend(i); + }, + ); + + let vertices = &self.frame_resources[idx].vertices; + let indices = &self.frame_resources[idx].indices; + + { + let frame_resources = &self.frame_resources[idx]; + + if let Some(vb) = frame_resources.vertex_buffer.as_ref() { + vb.Map(0, Some(&range), Some(&mut vtx_resource as *mut _ as *mut *mut c_void)) + .map_err(print_device_removed_reason)?; + std::ptr::copy_nonoverlapping(vertices.as_ptr(), vtx_resource, vertices.len()); + vb.Unmap(0, Some(&range)); }; - unsafe { - dev.CreateCommittedResource( - &props, - D3D12_HEAP_FLAG_NONE, - &desc, - D3D12_RESOURCE_STATE_GENERIC_READ, - None, - &mut self.index_buffer, - ) - } - .map_err(|e| { - error!("Resizing index buffer: {:?}", e); - e - })?; + if let Some(ib) = frame_resources.index_buffer.as_ref() { + ib.Map(0, Some(&range), Some(&mut idx_resource as *mut _ as *mut *mut c_void)) + .map_err(print_device_removed_reason)?; + std::ptr::copy_nonoverlapping(indices.as_ptr(), idx_resource, indices.len()); + ib.Unmap(0, Some(&range)); + }; } - Ok(()) - } -} -impl Drop for FrameResources { - fn drop(&mut self) { - drop(self.vertex_buffer.take()); - drop(self.index_buffer.take()); - } -} + self.setup_render_state(draw_data, idx); -impl Default for FrameResources { - fn default() -> Self { - Self { - index_buffer: None, - vertex_buffer: None, - index_buffer_size: 10000, - vertex_buffer_size: 5000, + let mut vtx_offset = 0usize; + let mut idx_offset = 0usize; + + for cl in draw_data.draw_lists() { + for cmd in cl.commands() { + match cmd { + DrawCmd::Elements { count, cmd_params } => { + let [cx, cy, cw, ch] = cmd_params.clip_rect; + let [x, y] = draw_data.display_pos; + let r = RECT { + left: (cx - x) as i32, + top: (cy - y) as i32, + right: (cw - x) as i32, + bottom: (ch - y) as i32, + }; + + if r.right > r.left && r.bottom > r.top { + let tex_handle = D3D12_GPU_DESCRIPTOR_HANDLE { + ptr: cmd_params.texture_id.id() as _, + }; + unsafe { + self.command_list.SetGraphicsRootDescriptorTable(1, tex_handle); + self.command_list.RSSetScissorRects(&[r]); + self.command_list.DrawIndexedInstanced( + count as _, + 1, + (cmd_params.idx_offset + idx_offset) as _, + (cmd_params.vtx_offset + vtx_offset) as _, + 0, + ); + } + } + }, + DrawCmd::ResetRenderState => { + self.setup_render_state(draw_data, idx); + }, + DrawCmd::RawCallback { callback, raw_cmd } => unsafe { + callback(cl.raw(), raw_cmd) + }, + } + } + idx_offset += cl.idx_buffer().len(); + vtx_offset += cl.vtx_buffer().len(); } + Ok(()) } } diff --git a/src/renderer/keys.rs b/src/renderer/keys.rs new file mode 100644 index 00000000..28447d10 --- /dev/null +++ b/src/renderer/keys.rs @@ -0,0 +1,169 @@ +use imgui::Key; +use windows::Win32::UI::Input::KeyboardAndMouse::{ + VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_A, VK_ADD, VK_B, + VK_BACK, VK_C, VK_CAPITAL, VK_D, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_E, VK_END, + VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F, VK_F1, VK_F10, VK_F11, VK_F12, VK_F2, VK_F3, VK_F4, + VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_G, VK_GAMEPAD_A, VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN, + VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP, VK_GAMEPAD_LEFT_SHOULDER, + VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT, + VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP, VK_GAMEPAD_LEFT_TRIGGER, + VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN, + VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT, + VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW, VK_GAMEPAD_X, + VK_GAMEPAD_Y, VK_H, VK_HOME, VK_I, VK_INSERT, VK_J, VK_K, VK_L, VK_LBUTTON, VK_LCONTROL, + VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_M, VK_MBUTTON, VK_MENU, VK_MULTIPLY, VK_N, VK_NEXT, + VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, + VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_O, VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, + VK_OEM_6, VK_OEM_7, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_P, VK_PAUSE, + VK_PRIOR, VK_Q, VK_R, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, + VK_RWIN, VK_S, VK_SCROLL, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_T, VK_TAB, VK_U, VK_UP, VK_V, + VK_W, VK_X, VK_XBUTTON1, VK_XBUTTON2, VK_Y, VK_Z, +}; + +pub(crate) const KEYS: [(Key, VIRTUAL_KEY); 132] = [ + (Key::Tab, VK_TAB), + (Key::LeftArrow, VK_LEFT), + (Key::RightArrow, VK_RIGHT), + (Key::UpArrow, VK_UP), + (Key::DownArrow, VK_DOWN), + (Key::PageUp, VK_PRIOR), + (Key::PageDown, VK_NEXT), + (Key::Home, VK_HOME), + (Key::End, VK_END), + (Key::Insert, VK_INSERT), + (Key::Delete, VK_DELETE), + (Key::Backspace, VK_BACK), + (Key::Space, VK_SPACE), + (Key::Enter, VK_RETURN), + (Key::Escape, VK_ESCAPE), + (Key::LeftCtrl, VK_LCONTROL), + (Key::LeftShift, VK_LSHIFT), + (Key::LeftAlt, VK_LMENU), + (Key::LeftSuper, VK_LWIN), + (Key::RightCtrl, VK_RCONTROL), + (Key::RightShift, VK_RSHIFT), + (Key::RightAlt, VK_RMENU), + (Key::RightSuper, VK_RWIN), + (Key::Menu, VK_MENU), + (Key::Alpha0, VK_0), + (Key::Alpha1, VK_1), + (Key::Alpha2, VK_2), + (Key::Alpha3, VK_3), + (Key::Alpha4, VK_4), + (Key::Alpha5, VK_5), + (Key::Alpha6, VK_6), + (Key::Alpha7, VK_7), + (Key::Alpha8, VK_8), + (Key::Alpha9, VK_9), + (Key::A, VK_A), + (Key::B, VK_B), + (Key::C, VK_C), + (Key::D, VK_D), + (Key::E, VK_E), + (Key::F, VK_F), + (Key::G, VK_G), + (Key::H, VK_H), + (Key::I, VK_I), + (Key::J, VK_J), + (Key::K, VK_K), + (Key::L, VK_L), + (Key::M, VK_M), + (Key::N, VK_N), + (Key::O, VK_O), + (Key::P, VK_P), + (Key::Q, VK_Q), + (Key::R, VK_R), + (Key::S, VK_S), + (Key::T, VK_T), + (Key::U, VK_U), + (Key::V, VK_V), + (Key::W, VK_W), + (Key::X, VK_X), + (Key::Y, VK_Y), + (Key::Z, VK_Z), + (Key::F1, VK_F1), + (Key::F2, VK_F2), + (Key::F3, VK_F3), + (Key::F4, VK_F4), + (Key::F5, VK_F5), + (Key::F6, VK_F6), + (Key::F7, VK_F7), + (Key::F8, VK_F8), + (Key::F9, VK_F9), + (Key::F10, VK_F10), + (Key::F11, VK_F11), + (Key::F12, VK_F12), + (Key::Apostrophe, VK_OEM_7), + (Key::Comma, VK_OEM_COMMA), + (Key::Minus, VK_OEM_MINUS), + (Key::Period, VK_OEM_PERIOD), + (Key::Slash, VK_OEM_2), + (Key::Semicolon, VK_OEM_1), + (Key::Equal, VK_OEM_PLUS), + (Key::LeftBracket, VK_OEM_4), + (Key::Backslash, VK_OEM_5), + (Key::RightBracket, VK_OEM_6), + (Key::GraveAccent, VK_OEM_3), + (Key::CapsLock, VK_CAPITAL), + (Key::ScrollLock, VK_SCROLL), + (Key::NumLock, VK_NUMLOCK), + (Key::PrintScreen, VK_SNAPSHOT), + (Key::Pause, VK_PAUSE), + (Key::Keypad0, VK_NUMPAD0), + (Key::Keypad1, VK_NUMPAD1), + (Key::Keypad2, VK_NUMPAD2), + (Key::Keypad3, VK_NUMPAD3), + (Key::Keypad4, VK_NUMPAD4), + (Key::Keypad5, VK_NUMPAD5), + (Key::Keypad6, VK_NUMPAD6), + (Key::Keypad7, VK_NUMPAD7), + (Key::Keypad8, VK_NUMPAD8), + (Key::Keypad9, VK_NUMPAD9), + (Key::KeypadDecimal, VK_DECIMAL), + (Key::KeypadDivide, VK_DIVIDE), + (Key::KeypadMultiply, VK_MULTIPLY), + (Key::KeypadSubtract, VK_SUBTRACT), + (Key::KeypadAdd, VK_ADD), + (Key::KeypadEnter, VK_EXECUTE), + (Key::KeypadEqual, VK_EXSEL), + (Key::GamepadStart, VK_GAMEPAD_MENU), + (Key::GamepadBack, VK_GAMEPAD_VIEW), + (Key::GamepadFaceLeft, VK_GAMEPAD_X), + (Key::GamepadFaceRight, VK_GAMEPAD_B), + (Key::GamepadFaceUp, VK_GAMEPAD_Y), + (Key::GamepadFaceDown, VK_GAMEPAD_A), + (Key::GamepadDpadLeft, VK_GAMEPAD_DPAD_LEFT), + (Key::GamepadDpadRight, VK_GAMEPAD_DPAD_RIGHT), + (Key::GamepadDpadUp, VK_GAMEPAD_DPAD_UP), + (Key::GamepadDpadDown, VK_GAMEPAD_DPAD_DOWN), + (Key::GamepadL1, VK_GAMEPAD_LEFT_SHOULDER), + (Key::GamepadR1, VK_GAMEPAD_RIGHT_SHOULDER), + (Key::GamepadL2, VK_GAMEPAD_LEFT_TRIGGER), + (Key::GamepadR2, VK_GAMEPAD_RIGHT_TRIGGER), + (Key::GamepadLStickLeft, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT), + (Key::GamepadLStickRight, VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT), + (Key::GamepadLStickUp, VK_GAMEPAD_LEFT_THUMBSTICK_UP), + (Key::GamepadLStickDown, VK_GAMEPAD_LEFT_THUMBSTICK_DOWN), + (Key::GamepadRStickLeft, VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT), + (Key::GamepadRStickRight, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT), + (Key::GamepadRStickUp, VK_GAMEPAD_RIGHT_THUMBSTICK_UP), + (Key::GamepadRStickDown, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN), + (Key::MouseLeft, VK_LBUTTON), + (Key::MouseRight, VK_RBUTTON), + (Key::MouseMiddle, VK_MBUTTON), + (Key::MouseX1, VK_XBUTTON1), + (Key::MouseX2, VK_XBUTTON2), + // (Key::MouseWheelX), + // (Key::MouseWheelY), + // (Key::ReservedForModCtrl), + // (Key::ReservedForModShift), + // (Key::ReservedForModAlt), + // (Key::ReservedForModSuper), + // (Key::ModCtrl), + // (Key::ModShift), + // (Key::ModAlt), + // (Key::ModSuper), + // (Key::ModShortcut), + // (Key::GamepadL3), + // (Key::GamepadR3), +]; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs new file mode 100644 index 00000000..272e9ede --- /dev/null +++ b/src/renderer/mod.rs @@ -0,0 +1,2 @@ +pub mod dx12; +pub mod keys; diff --git a/src/renderers/imgui_dx11.rs b/src/renderers/imgui_dx11.rs deleted file mode 100644 index b121e80e..00000000 --- a/src/renderers/imgui_dx11.rs +++ /dev/null @@ -1,937 +0,0 @@ -use std::slice; - -use imgui::internal::RawWrapper; -use imgui::{Context, DrawCmd, DrawData, DrawListIterator, DrawVert}; -use tracing::{error, trace}; -use windows::core::{s, Error}; -use windows::Win32::Foundation::{BOOL, HWND, RECT}; -use windows::Win32::Graphics::Direct3D::Fxc::D3DCompile; -use windows::Win32::Graphics::Direct3D::{ - ID3DBlob, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, D3D11_SRV_DIMENSION_TEXTURE2D, - D3D_DRIVER_TYPE_HARDWARE, D3D_PRIMITIVE_TOPOLOGY, -}; -use windows::Win32::Graphics::Direct3D11::{ - D3D11CreateDeviceAndSwapChain, ID3D11BlendState, ID3D11Buffer, ID3D11ClassInstance, - ID3D11DepthStencilState, ID3D11Device, ID3D11DeviceContext, ID3D11InputLayout, - ID3D11PixelShader, ID3D11RasterizerState, ID3D11RenderTargetView, ID3D11Resource, - ID3D11SamplerState, ID3D11ShaderResourceView, ID3D11Texture2D, ID3D11VertexShader, - D3D11_BIND_CONSTANT_BUFFER, D3D11_BIND_INDEX_BUFFER, D3D11_BIND_SHADER_RESOURCE, - D3D11_BIND_VERTEX_BUFFER, D3D11_BLEND_DESC, D3D11_BLEND_INV_SRC_ALPHA, D3D11_BLEND_OP_ADD, - D3D11_BLEND_SRC_ALPHA, D3D11_BLEND_ZERO, D3D11_BUFFER_DESC, D3D11_COLOR_WRITE_ENABLE_ALL, - D3D11_COMPARISON_ALWAYS, D3D11_CPU_ACCESS_WRITE, D3D11_CREATE_DEVICE_DEBUG, - D3D11_CREATE_DEVICE_FLAG, D3D11_CULL_NONE, D3D11_DEPTH_STENCILOP_DESC, - D3D11_DEPTH_STENCIL_DESC, D3D11_DEPTH_WRITE_MASK_ALL, D3D11_FILL_SOLID, - D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_INPUT_ELEMENT_DESC, D3D11_INPUT_PER_VERTEX_DATA, - D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_WRITE_DISCARD, D3D11_RASTERIZER_DESC, - D3D11_RENDER_TARGET_BLEND_DESC, D3D11_SAMPLER_DESC, D3D11_SDK_VERSION, - D3D11_SHADER_RESOURCE_VIEW_DESC, D3D11_SHADER_RESOURCE_VIEW_DESC_0, D3D11_STENCIL_OP_KEEP, - D3D11_SUBRESOURCE_DATA, D3D11_TEX2D_SRV, D3D11_TEXTURE2D_DESC, D3D11_TEXTURE_ADDRESS_WRAP, - D3D11_USAGE_DEFAULT, D3D11_USAGE_DYNAMIC, D3D11_VIEWPORT, -}; -use windows::Win32::Graphics::Dxgi::Common::{ - DXGI_FORMAT, DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_R32_UINT, - DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_DESC, DXGI_MODE_SCALING, DXGI_MODE_SCANLINE_ORDER, - DXGI_RATIONAL, DXGI_SAMPLE_DESC, -}; -use windows::Win32::Graphics::Dxgi::{ - IDXGISwapChain, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_EFFECT, DXGI_USAGE_RENDER_TARGET_OUTPUT, -}; -use windows::Win32::UI::WindowsAndMessaging::GetClientRect; - -use crate::util::try_out_ptr; - -const DEVICE_FLAGS: D3D11_CREATE_DEVICE_FLAG = D3D11_CREATE_DEVICE_DEBUG; - -/// The DirectX11 imgui render engine. -pub struct RenderEngine { - dasc: DeviceAndSwapChain, - shader_program: ShaderProgram, - buffers: Buffers, - texture: Texture, -} - -impl RenderEngine { - /// Initialize render engine from hwnd and imgui context. - pub fn new(hwnd: HWND, ctx: &mut Context) -> Self { - let dasc = DeviceAndSwapChain::new(hwnd); - let shader_program = ShaderProgram::new(&dasc).expect("ShaderProgram"); - let buffers = Buffers::new(&dasc); - let texture = Texture::new(&dasc, ctx.fonts()).expect("Texture"); - - ctx.set_renderer_name(String::from(concat!("imgui-dx11@", env!("CARGO_PKG_VERSION")))); - - RenderEngine { dasc, shader_program, buffers, texture } - } - - /// Initialize render engine from DirectX11 objects and imgui context. - pub fn new_with_ptrs( - dev: ID3D11Device, - dev_ctx: ID3D11DeviceContext, - swap_chain: IDXGISwapChain, - ctx: &mut Context, - ) -> Self { - let dasc = DeviceAndSwapChain::new_with_ptrs(dev, dev_ctx, swap_chain); - let shader_program = ShaderProgram::new(&dasc).expect("ShaderProgram"); - let buffers = Buffers::new(&dasc); - let texture = Texture::new(&dasc, ctx.fonts()).expect("Texture"); - - ctx.set_renderer_name(String::from(concat!("imgui-dx11@", env!("CARGO_PKG_VERSION")))); - - RenderEngine { dasc, shader_program, buffers, texture } - } - - pub fn dev(&self) -> ID3D11Device { - self.dasc.dev() - } - - pub fn dev_ctx(&self) -> ID3D11DeviceContext { - self.dasc.dev_ctx() - } - - pub fn swap_chain(&self) -> IDXGISwapChain { - self.dasc.swap_chain() - } - - pub fn get_client_rect(&self) -> Option { - self.dasc.get_client_rect() - } - - pub fn render_draw_data(&mut self, draw_data: &DrawData) -> Result<(), String> { - trace!("Rendering started"); - let state_backup = StateBackup::backup(self.dasc.dev_ctx()); - - let [x, y] = draw_data.display_pos; - let [width, height] = draw_data.display_size; - - if width <= 0. && height <= 0. { - return Err(format!("Insufficient display size {width} x {height}")); - } - - let rect = RECT { left: 0, right: width as i32, top: 0, bottom: height as i32 }; - self.dasc.set_viewport(rect); - self.dasc.set_render_target(); - unsafe { self.shader_program.set_state(&self.dasc) }; - - unsafe { - let dev_ctx = self.dasc.dev_ctx(); - - trace!("Setting up buffers"); - self.buffers.set_constant_buffer(&self.dasc, [x, y, x + width, y + height]); - self.buffers.set_buffers(&self.dasc, draw_data.draw_lists()); - - dev_ctx.IASetVertexBuffers( - 0, - 1, - Some(&Some(self.buffers.vtx_buffer())), - Some(&(std::mem::size_of::() as u32)), - Some(&0), - ); - dev_ctx.IASetIndexBuffer( - &self.buffers.idx_buffer(), - if std::mem::size_of::() == 2 { - DXGI_FORMAT_R16_UINT - } else { - DXGI_FORMAT_R32_UINT - }, - 0, - ); - dev_ctx.IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - dev_ctx.VSSetConstantBuffers(0, Some(&[Some(self.buffers.mtx_buffer())])); - dev_ctx.PSSetShaderResources(0, Some(&[Some(self.texture.tex_view())])); - - let mut vtx_offset = 0usize; - let mut idx_offset = 0usize; - - trace!("Rendering draw lists"); - for cl in draw_data.draw_lists() { - for cmd in cl.commands() { - match cmd { - DrawCmd::Elements { count, cmd_params } => { - trace!("Rendering {count} elements"); - let [cx, cy, cw, ch] = cmd_params.clip_rect; - dev_ctx.RSSetScissorRects(Some(&[RECT { - left: (cx - x) as i32, - top: (cy - y) as i32, - right: (cw - x) as i32, - bottom: (ch - y) as i32, - }])); - - // let srv = cmd_params.texture_id.id(); - // We only load the font texture. This may not be correct. - self.dasc.set_shader_resources(self.texture.tex_view()); - - trace!("Drawing indexed {count}, {idx_offset}, {vtx_offset}"); - dev_ctx.DrawIndexed( - count as u32, - (cmd_params.idx_offset + idx_offset) as _, - (cmd_params.vtx_offset + vtx_offset) as _, - ); - }, - DrawCmd::ResetRenderState => { - trace!("Resetting render state"); - self.dasc.setup_state(draw_data); - self.shader_program.set_state(&self.dasc); - }, - DrawCmd::RawCallback { callback, raw_cmd } => { - trace!("Executing raw callback"); - callback(cl.raw(), raw_cmd) - }, - } - } - idx_offset += cl.idx_buffer().len(); - vtx_offset += cl.vtx_buffer().len(); - } - } - - trace!("Restoring state backup"); - state_backup.restore(self.dasc.dev_ctx()); - - trace!("Rendering done"); - - Ok(()) - } - - pub fn present(&self) { - if let Err(e) = unsafe { self.dasc.swap_chain().Present(1, 0).ok() } { - error!("Present: {e}"); - } - } -} - -struct DeviceAndSwapChain { - dev: ID3D11Device, - dev_ctx: ID3D11DeviceContext, - swap_chain: IDXGISwapChain, - back_buffer: ID3D11RenderTargetView, -} - -impl DeviceAndSwapChain { - fn new(hwnd: HWND) -> Self { - let mut swap_chain: Option = None; - let mut dev: Option = None; - let mut dev_ctx: Option = None; - - unsafe { - D3D11CreateDeviceAndSwapChain( - None, - D3D_DRIVER_TYPE_HARDWARE, - None, - DEVICE_FLAGS, - Some(&[]), - D3D11_SDK_VERSION, - Some(&DXGI_SWAP_CHAIN_DESC { - BufferCount: 1, - BufferDesc: DXGI_MODE_DESC { - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - Width: 0, - Height: 0, - RefreshRate: DXGI_RATIONAL { Numerator: 0, Denominator: 0 }, - ScanlineOrdering: DXGI_MODE_SCANLINE_ORDER(0), - Scaling: DXGI_MODE_SCALING(0), - }, - BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, - OutputWindow: hwnd, - SampleDesc: DXGI_SAMPLE_DESC { Count: 4, Quality: 0 }, - Windowed: BOOL(1), - SwapEffect: DXGI_SWAP_EFFECT(0), - Flags: 0, - }), - Some(&mut swap_chain), - Some(&mut dev), - None, - Some(&mut dev_ctx), - ) - .unwrap() - }; - - DeviceAndSwapChain::new_with_ptrs( - dev.expect("Null device"), - dev_ctx.expect("Null device context"), - swap_chain.expect("Null swap chain"), - ) - } - - fn new_with_ptrs( - dev: ID3D11Device, - dev_ctx: ID3D11DeviceContext, - swap_chain: IDXGISwapChain, - ) -> Self { - let back_buffer = unsafe { - let p_back_buffer: ID3D11Resource = swap_chain.GetBuffer(0).expect("GetBuffer"); - - let back_buffer: ID3D11RenderTargetView = - try_out_ptr(|v| dev.CreateRenderTargetView(&p_back_buffer, None, Some(v))) - .expect("CreateRenderTargetView"); - - dev_ctx.OMSetRenderTargets(Some(&[Some(back_buffer.clone())]), None); - - back_buffer - }; - - unsafe { - dev_ctx.RSSetViewports(Some(&[D3D11_VIEWPORT { - TopLeftX: 0., - TopLeftY: 0., - Width: 640., - Height: 480., - MinDepth: 0., - MaxDepth: 1., - }])) - }; - - DeviceAndSwapChain { dev, dev_ctx, swap_chain, back_buffer } - } - - fn setup_state(&self, draw_data: &imgui::DrawData) { - let [_x, _y] = draw_data.display_pos; - let [_w, _h] = draw_data.display_size; - - self.set_render_target(); - } - - fn set_shader_resources(&self, srv: ID3D11ShaderResourceView) { - unsafe { self.dev_ctx.PSSetShaderResources(0, Some(&[Some(srv)])) } - } - - fn set_viewport(&self, rect: RECT) { - unsafe { - self.dev_ctx().RSSetViewports(Some(&[D3D11_VIEWPORT { - TopLeftX: 0., - TopLeftY: 0., - Width: (rect.right - rect.left) as f32, - Height: (rect.bottom - rect.top) as f32, - MinDepth: 0., - MaxDepth: 1., - }])) - }; - } - - fn set_render_target(&self) { - unsafe { - self.dev_ctx.OMSetRenderTargets(Some(&[Some(self.back_buffer.clone())]), None); - } - } - - fn get_client_rect(&self) -> Option { - unsafe { - let mut sd = Default::default(); - self.swap_chain.GetDesc(&mut sd).expect("GetDesc"); - let mut rect: RECT = Default::default(); - if GetClientRect(sd.OutputWindow, &mut rect as _).is_ok() { - Some(rect) - } else { - None - } - } - } - - fn with_mapped(&self, ptr: &ID3D11Buffer, f: F) - where - F: FnOnce(&D3D11_MAPPED_SUBRESOURCE), - { - unsafe { - let mut ms = Default::default(); - self.dev_ctx.Map(ptr, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut ms)).expect("Map"); - - f(&ms); - - self.dev_ctx.Unmap(ptr, 0); - } - } - - fn dev(&self) -> ID3D11Device { - self.dev.clone() - } - - fn dev_ctx(&self) -> ID3D11DeviceContext { - self.dev_ctx.clone() - } - - fn swap_chain(&self) -> IDXGISwapChain { - self.swap_chain.clone() - } -} - -struct Buffers { - vtx_buffer: ID3D11Buffer, - idx_buffer: ID3D11Buffer, - mtx_buffer: ID3D11Buffer, - - vtx_count: usize, - idx_count: usize, -} - -#[repr(transparent)] -struct VertexConstantBuffer([[f32; 4]; 4]); - -impl Buffers { - fn new(dasc: &DeviceAndSwapChain) -> Self { - let mtx_buffer: ID3D11Buffer = try_out_ptr(|v| unsafe { - dasc.dev().CreateBuffer( - &D3D11_BUFFER_DESC { - ByteWidth: std::mem::size_of::() as u32, - Usage: D3D11_USAGE_DYNAMIC, - BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32, - CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, - MiscFlags: 0, - StructureByteStride: 0, - }, - None, - Some(v), - ) - }) - .expect("CreateBuffer"); - - let vtx_buffer = Buffers::create_vertex_buffer(dasc, 1); - let idx_buffer = Buffers::create_index_buffer(dasc, 1); - - Buffers { vtx_buffer, idx_buffer, mtx_buffer, vtx_count: 1, idx_count: 1 } - } - - fn set_constant_buffer(&mut self, dasc: &DeviceAndSwapChain, rect: [f32; 4]) { - let [l, t, r, b] = rect; - - dasc.with_mapped(&self.mtx_buffer, |ms| unsafe { - std::ptr::copy_nonoverlapping( - &VertexConstantBuffer([ - [2. / (r - l), 0., 0., 0.], - [0., 2. / (t - b), 0., 0.], - [0., 0., 0.5, 0.], - [(r + l) / (l - r), (t + b) / (b - t), 0.5, 1.0], - ]), - ms.pData as *mut _, - 1, - ); - }) - } - - fn set_buffers(&mut self, dasc: &DeviceAndSwapChain, meshes: DrawListIterator) { - let (vertices, indices): (Vec, Vec) = meshes - .map(|m| (m.vtx_buffer().iter(), m.idx_buffer().iter())) - .fold((Vec::new(), Vec::new()), |(mut ov, mut oi), (v, i)| { - ov.extend(v); - oi.extend(i); - (ov, oi) - }); - - self.resize(dasc, vertices.len(), indices.len()); - - dasc.with_mapped(&self.vtx_buffer, |ms| unsafe { - std::ptr::copy_nonoverlapping(vertices.as_ptr(), ms.pData as _, vertices.len()); - }); - - dasc.with_mapped(&self.idx_buffer, |ms| unsafe { - std::ptr::copy_nonoverlapping(indices.as_ptr(), ms.pData as _, indices.len()); - }); - } - - fn resize(&mut self, dasc: &DeviceAndSwapChain, vtx_count: usize, idx_count: usize) { - if self.vtx_count <= vtx_count || (self.vtx_count == 0 && vtx_count == 0) { - // unsafe { self.vtx_buffer.as_ref().Release() }; - self.vtx_count = vtx_count + 4096; - self.vtx_buffer = Buffers::create_vertex_buffer(dasc, self.vtx_count); - } - - if self.idx_count <= idx_count || (self.idx_count == 0 && idx_count == 0) { - // unsafe { self.idx_buffer.as_ref().Release() }; - self.idx_count = idx_count + 4096; - self.idx_buffer = Buffers::create_index_buffer(dasc, self.idx_count); - } - } - - fn create_vertex_buffer(dasc: &DeviceAndSwapChain, size: usize) -> ID3D11Buffer { - let buf: ID3D11Buffer = try_out_ptr(|v| unsafe { - dasc.dev().CreateBuffer( - &D3D11_BUFFER_DESC { - Usage: D3D11_USAGE_DYNAMIC, - ByteWidth: (size * std::mem::size_of::()) as u32, - BindFlags: D3D11_BIND_VERTEX_BUFFER.0 as u32, - CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, - MiscFlags: 0, - StructureByteStride: 0, - }, - None, - Some(v), - ) - }) - .expect("CreateBuffer"); - buf - } - - fn create_index_buffer(dasc: &DeviceAndSwapChain, size: usize) -> ID3D11Buffer { - let buf: ID3D11Buffer = try_out_ptr(|v| unsafe { - dasc.dev().CreateBuffer( - &D3D11_BUFFER_DESC { - Usage: D3D11_USAGE_DYNAMIC, - ByteWidth: (size * std::mem::size_of::()) as u32, - BindFlags: D3D11_BIND_INDEX_BUFFER.0 as u32, - CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, - MiscFlags: 0, - StructureByteStride: 0, - }, - None, - Some(v), - ) - }) - .expect("CreateBuffer"); - buf - } - - fn vtx_buffer(&self) -> ID3D11Buffer { - self.vtx_buffer.clone() - } - - fn idx_buffer(&self) -> ID3D11Buffer { - self.idx_buffer.clone() - } - - fn mtx_buffer(&self) -> ID3D11Buffer { - self.mtx_buffer.clone() - } -} - -struct Texture { - _tex: ID3D11Texture2D, - tex_view: ID3D11ShaderResourceView, - _font_sampler: ID3D11SamplerState, -} - -impl Texture { - // TODO FontAtlasTexture may be too specific? - fn new(dasc: &DeviceAndSwapChain, fonts: &mut imgui::FontAtlas) -> Result { - let texture = fonts.build_rgba32_texture(); - let data = texture.data.to_vec(); - - let tex: ID3D11Texture2D = try_out_ptr(|v| unsafe { - dasc.dev().CreateTexture2D( - &D3D11_TEXTURE2D_DESC { - Width: texture.width as _, - Height: texture.height as _, - MipLevels: 1, - ArraySize: 1, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, - Usage: D3D11_USAGE_DEFAULT, - BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32, - CPUAccessFlags: 0, // D3D11_CPU_ACCESS_FLAG(0), - MiscFlags: 0, // D3D11_RESOURCE_MISC_FLAG(0), - }, - Some(&D3D11_SUBRESOURCE_DATA { - pSysMem: data.as_ptr() as _, - SysMemPitch: texture.width * 4, - SysMemSlicePitch: 0, - }), - Some(v), - ) - })?; - - let tex_view: ID3D11ShaderResourceView = try_out_ptr(|v| unsafe { - dasc.dev().CreateShaderResourceView( - &tex, - Some(&D3D11_SHADER_RESOURCE_VIEW_DESC { - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - ViewDimension: D3D11_SRV_DIMENSION_TEXTURE2D, - Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 { - Texture2D: D3D11_TEX2D_SRV { MostDetailedMip: 0, MipLevels: 1 }, - }, - }), - Some(v), - ) - })?; - - let _font_sampler: ID3D11SamplerState = try_out_ptr(|v| unsafe { - dasc.dev().CreateSamplerState( - &D3D11_SAMPLER_DESC { - Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, - AddressU: D3D11_TEXTURE_ADDRESS_WRAP, - AddressV: D3D11_TEXTURE_ADDRESS_WRAP, - AddressW: D3D11_TEXTURE_ADDRESS_WRAP, - MipLODBias: 0., - ComparisonFunc: D3D11_COMPARISON_ALWAYS, - MinLOD: 0., - MaxLOD: 0., - BorderColor: [0.; 4], - MaxAnisotropy: 0, - }, - Some(v), - ) - })?; - - fonts.tex_id = imgui::TextureId::from(&tex_view as *const _ as usize); - trace!("Texture view: {:x} id: {:x}", &tex_view as *const _ as usize, fonts.tex_id.id()); - - Ok(Texture { _tex: tex, tex_view, _font_sampler }) - } - - fn tex_view(&self) -> ID3D11ShaderResourceView { - self.tex_view.clone() - } -} - -const D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE: usize = 16; - -#[derive(Default)] -struct StateBackup { - scissor_rects_count: u32, - viewports_count: u32, - scissor_rects: [RECT; D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE], - viewports: [D3D11_VIEWPORT; D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE], - rasterizer_state: Option, - blend_state: Option, - blend_factor: [f32; 4], - sample_mask: u32, - stencil_ref: u32, - depth_stencil_state: Option, - ps_shader_resource: [Option; 1], - _ps_sampler: Option, - pixel_shader: Option, - vertex_shader: Option, - ps_instances_count: u32, - vs_instances_count: u32, - ps_instances: Option, - vs_instances: Option, - primitive_topology: D3D_PRIMITIVE_TOPOLOGY, - index_buffer: Option, - vertex_buffer: Option, - vertex_constant_buffer: [Option; 1], - index_buffer_offset: u32, - vertex_buffer_stride: u32, - vertex_buffer_offset: u32, - index_buffer_format: DXGI_FORMAT, - input_layout: Option, -} - -impl StateBackup { - fn backup(ctx: ID3D11DeviceContext) -> StateBackup { - let mut r: StateBackup = StateBackup { - scissor_rects_count: D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE as _, - viewports_count: D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE as _, - ..Default::default() - }; - unsafe { - ctx.RSGetScissorRects(&mut r.scissor_rects_count, Some(r.scissor_rects.as_mut_ptr())); - ctx.RSGetViewports(&mut r.viewports_count, Some(r.viewports.as_mut_ptr())); - r.rasterizer_state = ctx.RSGetState().ok(); - ctx.OMGetBlendState( - Some(&mut r.blend_state), - Some(&mut r.blend_factor), - Some(&mut r.sample_mask), - ); - ctx.OMGetDepthStencilState(Some(&mut r.depth_stencil_state), Some(&mut r.stencil_ref)); - ctx.PSGetShaderResources(0, Some(&mut r.ps_shader_resource)); - r.ps_instances_count = 256; - r.vs_instances_count = 256; - ctx.PSGetShader( - &mut r.pixel_shader, - Some(&mut r.ps_instances), - Some(&mut r.ps_instances_count), - ); - ctx.VSGetShader( - &mut r.vertex_shader, - Some(&mut r.vs_instances), - Some(&mut r.vs_instances_count), - ); - ctx.VSGetConstantBuffers(0, Some(&mut r.vertex_constant_buffer)); - r.primitive_topology = ctx.IAGetPrimitiveTopology(); - ctx.IAGetIndexBuffer( - Some(&mut r.index_buffer), - Some(&mut r.index_buffer_format), - Some(&mut r.index_buffer_offset), - ); - ctx.IAGetVertexBuffers( - 0, - 1, - Some(&mut r.vertex_buffer), - Some(&mut r.vertex_buffer_stride), - Some(&mut r.vertex_buffer_offset), - ); - r.input_layout = ctx.IAGetInputLayout().ok(); - } - - r - } - - fn restore(self, ctx: ID3D11DeviceContext) { - unsafe { - ctx.RSSetScissorRects(Some(&self.scissor_rects)); - ctx.RSSetViewports(Some(&self.viewports)); - ctx.RSSetState(self.rasterizer_state.as_ref()); - ctx.OMSetBlendState( - self.blend_state.as_ref(), - Some(&self.blend_factor), - self.sample_mask, - ); - ctx.OMSetDepthStencilState(self.depth_stencil_state.as_ref(), self.stencil_ref); - ctx.PSSetShaderResources(0, Some(&self.ps_shader_resource)); - // ctx.PSSetSamplers(0, &[self.ps_sampler]); - if self.ps_instances.is_some() { - ctx.PSSetShader(self.pixel_shader.as_ref(), Some(&[self.ps_instances])); - } - if self.vs_instances.is_some() { - ctx.VSSetShader(self.vertex_shader.as_ref(), Some(&[self.vs_instances])); - } - ctx.IASetPrimitiveTopology(self.primitive_topology); - ctx.IASetIndexBuffer( - self.index_buffer.as_ref(), - self.index_buffer_format, - self.index_buffer_offset, - ); - ctx.IASetVertexBuffers( - 0, - 1, - Some(&self.vertex_buffer), - Some(&self.vertex_buffer_stride), - Some(&self.vertex_buffer_offset), - ); - ctx.IASetInputLayout(self.input_layout.as_ref()); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Shaders -//////////////////////////////////////////////////////////////////////////////// - -const VERTEX_SHADER_SRC: &str = r" - cbuffer vertexBuffer : register(b0) { - float4x4 ProjectionMatrix; - }; - struct VS_INPUT { - float2 pos : POSITION; - float4 col : COLOR0; - float2 uv : TEXCOORD0; - }; - struct PS_INPUT { - float4 pos : SV_POSITION; - float4 col : COLOR0; - float2 uv : TEXCOORD0; - }; - PS_INPUT main(VS_INPUT input) { - PS_INPUT output; - output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f)); - output.col = input.col; - output.uv = input.uv; - return output; - } -"; - -const PIXEL_SHADER_SRC: &str = r" - struct PS_INPUT { - float4 pos : SV_POSITION; - float4 col : COLOR0; - float2 uv : TEXCOORD0; - }; - sampler sampler0; - Texture2D texture0; - float4 main(PS_INPUT input) : SV_Target { - float4 out_col = input.col * texture0.Sample(sampler0, input.uv); - return out_col; - }; -"; - -struct ShaderProgram { - vtx_shader: ID3D11VertexShader, - pix_shader: ID3D11PixelShader, - layout: ID3D11InputLayout, - sampler: ID3D11SamplerState, - rasterizer_state: ID3D11RasterizerState, - blend_state: ID3D11BlendState, - depth_stencil_state: ID3D11DepthStencilState, -} - -impl ShaderProgram { - fn new(dasc: &DeviceAndSwapChain) -> Result { - let vs_blob: ID3DBlob = try_out_ptr(|v| unsafe { - D3DCompile( - VERTEX_SHADER_SRC.as_ptr() as _, - VERTEX_SHADER_SRC.len(), - None, - None, - None, - s!("main\0"), - s!("vs_4_0\0"), - 0, - 0, - v, - None, - ) - })?; - - let ps_blob = try_out_ptr(|v| unsafe { - D3DCompile( - PIXEL_SHADER_SRC.as_ptr() as _, - PIXEL_SHADER_SRC.len(), - None, - None, - None, - s!("main\0"), - s!("ps_4_0\0"), - 0, - 0, - v, - None, - ) - })?; - - let vtx_shader = try_out_ptr(|v| unsafe { - let ptr = vs_blob.GetBufferPointer(); - let size = vs_blob.GetBufferSize(); - dasc.dev().CreateVertexShader(slice::from_raw_parts(ptr as _, size), None, Some(v)) - })?; - - let pix_shader = try_out_ptr(|v| unsafe { - let ptr = ps_blob.GetBufferPointer(); - let size = ps_blob.GetBufferSize(); - dasc.dev().CreatePixelShader(slice::from_raw_parts(ptr as _, size), None, Some(v)) - })?; - - let layout = try_out_ptr(|v| unsafe { - let ptr = vs_blob.GetBufferPointer(); - let size = vs_blob.GetBufferSize(); - dasc.dev().CreateInputLayout( - &[ - D3D11_INPUT_ELEMENT_DESC { - SemanticName: s!("POSITION\0"), - SemanticIndex: 0, - Format: DXGI_FORMAT_R32G32_FLOAT, - InputSlot: 0, - AlignedByteOffset: 0, - InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, - InstanceDataStepRate: 0, - }, - D3D11_INPUT_ELEMENT_DESC { - SemanticName: s!("TEXCOORD\0"), - SemanticIndex: 0, - Format: DXGI_FORMAT_R32G32_FLOAT, - InputSlot: 0, - AlignedByteOffset: 8, - InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, - InstanceDataStepRate: 0, - }, - D3D11_INPUT_ELEMENT_DESC { - SemanticName: s!("COLOR\0"), - SemanticIndex: 0, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - InputSlot: 0, - AlignedByteOffset: 16, - InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, - InstanceDataStepRate: 0, - }, - ], - slice::from_raw_parts(ptr as _, size), - Some(v), - ) - })?; - - let sampler = try_out_ptr(|v| unsafe { - dasc.dev().CreateSamplerState( - &D3D11_SAMPLER_DESC { - Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, - AddressU: D3D11_TEXTURE_ADDRESS_WRAP, - AddressV: D3D11_TEXTURE_ADDRESS_WRAP, - AddressW: D3D11_TEXTURE_ADDRESS_WRAP, - MipLODBias: 0., - ComparisonFunc: D3D11_COMPARISON_ALWAYS, - MinLOD: 0., - MaxLOD: 0., - BorderColor: [0.; 4], - MaxAnisotropy: 0, - }, - Some(v), - ) - })?; - - let blend_state = try_out_ptr(|v| unsafe { - dasc.dev().CreateBlendState( - &D3D11_BLEND_DESC { - AlphaToCoverageEnable: BOOL(0), - IndependentBlendEnable: BOOL(0), - RenderTarget: [ - D3D11_RENDER_TARGET_BLEND_DESC { - BlendEnable: BOOL(1), - SrcBlend: D3D11_BLEND_SRC_ALPHA, - DestBlend: D3D11_BLEND_INV_SRC_ALPHA, - BlendOp: D3D11_BLEND_OP_ADD, - SrcBlendAlpha: D3D11_BLEND_INV_SRC_ALPHA, - DestBlendAlpha: D3D11_BLEND_ZERO, - BlendOpAlpha: D3D11_BLEND_OP_ADD, - RenderTargetWriteMask: D3D11_COLOR_WRITE_ENABLE_ALL.0 as _, - }, - std::mem::zeroed(), - std::mem::zeroed(), - std::mem::zeroed(), - std::mem::zeroed(), - std::mem::zeroed(), - std::mem::zeroed(), - std::mem::zeroed(), - ], - }, - Some(v), - ) - })?; - - let rasterizer_state = try_out_ptr(|v| unsafe { - dasc.dev().CreateRasterizerState( - &D3D11_RASTERIZER_DESC { - FillMode: D3D11_FILL_SOLID, - CullMode: D3D11_CULL_NONE, - ScissorEnable: BOOL(1), - DepthClipEnable: BOOL(1), - DepthBias: 0, - DepthBiasClamp: 0., - SlopeScaledDepthBias: 0., - MultisampleEnable: BOOL(0), - AntialiasedLineEnable: BOOL(0), - FrontCounterClockwise: BOOL(0), - }, - Some(v), - ) - })?; - - let depth_stencil_state = try_out_ptr(|v| unsafe { - dasc.dev().CreateDepthStencilState( - &D3D11_DEPTH_STENCIL_DESC { - DepthEnable: BOOL(0), - DepthFunc: D3D11_COMPARISON_ALWAYS, - DepthWriteMask: D3D11_DEPTH_WRITE_MASK_ALL, - StencilEnable: BOOL(0), - StencilReadMask: 0, - StencilWriteMask: 0, - FrontFace: D3D11_DEPTH_STENCILOP_DESC { - StencilFailOp: D3D11_STENCIL_OP_KEEP, - StencilDepthFailOp: D3D11_STENCIL_OP_KEEP, - StencilPassOp: D3D11_STENCIL_OP_KEEP, - StencilFunc: D3D11_COMPARISON_ALWAYS, - }, - BackFace: D3D11_DEPTH_STENCILOP_DESC { - StencilFailOp: D3D11_STENCIL_OP_KEEP, - StencilDepthFailOp: D3D11_STENCIL_OP_KEEP, - StencilPassOp: D3D11_STENCIL_OP_KEEP, - StencilFunc: D3D11_COMPARISON_ALWAYS, - }, - }, - Some(v), - ) - })?; - - Ok(ShaderProgram { - vtx_shader, - pix_shader, - layout, - sampler, - blend_state, - depth_stencil_state, - rasterizer_state, - }) - } - - unsafe fn set_state(&self, dasc: &DeviceAndSwapChain) { - dasc.dev_ctx().VSSetShader(&self.vtx_shader, Some(&[])); - dasc.dev_ctx().PSSetShader(&self.pix_shader, Some(&[])); - dasc.dev_ctx().IASetInputLayout(&self.layout); - dasc.dev_ctx().PSSetSamplers(0, Some(&[Some(self.sampler.clone())])); - dasc.dev_ctx().OMSetBlendState(&self.blend_state, Some(&[0f32; 4]), 0xFFFFFFFF); - dasc.dev_ctx().OMSetDepthStencilState(&self.depth_stencil_state, 0); - dasc.dev_ctx().RSSetState(&self.rasterizer_state); - } -} diff --git a/src/renderers/imgui_dx9.rs b/src/renderers/imgui_dx9.rs deleted file mode 100644 index 14e42d64..00000000 --- a/src/renderers/imgui_dx9.rs +++ /dev/null @@ -1,517 +0,0 @@ -// Based on https://github.com/Veykril/imgui-dx9-renderer -// -// Copyright (c) 2019 Lukas Wirth -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#![cfg(windows)] -#![deny(missing_docs)] -//! This crate offers a DirectX 9 renderer for the [imgui-rs](https://docs.rs/imgui/) rust bindings. - -use std::{mem, ptr, slice}; - -use imgui::internal::RawWrapper; -use imgui::{ - BackendFlags, Context, DrawCmd, DrawCmdParams, DrawData, DrawIdx, TextureId, Textures, -}; -use windows::core::ComInterface; -use windows::Foundation::Numerics::Matrix4x4; -use windows::Win32::Foundation::{HWND, RECT}; -use windows::Win32::Graphics::Direct3D9::{ - IDirect3DBaseTexture9, IDirect3DDevice9, IDirect3DIndexBuffer9, IDirect3DStateBlock9, - IDirect3DSurface9, IDirect3DTexture9, IDirect3DVertexBuffer9, D3DBACKBUFFER_TYPE_MONO, - D3DBLENDOP_ADD, D3DBLEND_INVSRCALPHA, D3DBLEND_SRCALPHA, D3DCULL_NONE, - D3DDEVICE_CREATION_PARAMETERS, D3DFMT_A8R8G8B8, D3DFMT_INDEX16, D3DFMT_INDEX32, D3DFVF_DIFFUSE, - D3DFVF_TEX1, D3DFVF_XYZ, D3DLOCKED_RECT, D3DLOCK_DISCARD, D3DPOOL_DEFAULT, D3DPT_TRIANGLELIST, - D3DRS_ALPHABLENDENABLE, D3DRS_ALPHATESTENABLE, D3DRS_BLENDOP, D3DRS_CULLMODE, D3DRS_DESTBLEND, - D3DRS_FOGENABLE, D3DRS_LIGHTING, D3DRS_SCISSORTESTENABLE, D3DRS_SHADEMODE, D3DRS_SRCBLEND, - D3DRS_ZENABLE, D3DSAMP_MAGFILTER, D3DSAMP_MINFILTER, D3DSBT_ALL, D3DSHADE_GOURAUD, - D3DTA_DIFFUSE, D3DTA_TEXTURE, D3DTEXF_LINEAR, D3DTOP_MODULATE, D3DTRANSFORMSTATETYPE, - D3DTSS_ALPHAARG1, D3DTSS_ALPHAARG2, D3DTSS_ALPHAOP, D3DTSS_COLORARG1, D3DTSS_COLORARG2, - D3DTSS_COLOROP, D3DTS_PROJECTION, D3DTS_VIEW, D3DUSAGE_DYNAMIC, D3DUSAGE_WRITEONLY, - D3DVIEWPORT9, -}; -use windows::Win32::Graphics::Dxgi::DXGI_ERROR_INVALID_CALL; -use windows::Win32::UI::WindowsAndMessaging::GetClientRect; - -const FONT_TEX_ID: usize = !0; -const D3DFVF_CUSTOMVERTEX: u32 = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1; - -const FALSE: u32 = 0; -const TRUE: u32 = 1; - -const VERTEX_BUF_ADD_CAPACITY: usize = 5000; -const INDEX_BUF_ADD_CAPACITY: usize = 10000; - -const D3DTS_WORLDMATRIX: D3DTRANSFORMSTATETYPE = D3DTRANSFORMSTATETYPE(256); - -/// Reexport of [`windows::core::Result`] -pub type Result = windows::core::Result; - -static MAT_IDENTITY: Matrix4x4 = Matrix4x4 { - M11: 1.0, - M22: 1.0, - M33: 1.0, - M44: 1.0, - M12: 0.0, - M13: 0.0, - M14: 0.0, - M21: 0.0, - M23: 0.0, - M24: 0.0, - M31: 0.0, - M32: 0.0, - M34: 0.0, - M41: 0.0, - M42: 0.0, - M43: 0.0, -}; - -#[repr(C)] -struct CustomVertex { - pos: [f32; 3], - col: [u8; 4], - uv: [f32; 2], -} - -/// A DirectX 9 renderer for . -pub struct Renderer { - device_creation_parameters: D3DDEVICE_CREATION_PARAMETERS, - device: IDirect3DDevice9, - font_tex: IDirect3DBaseTexture9, - vertex_buffer: (IDirect3DVertexBuffer9, usize), - index_buffer: (IDirect3DIndexBuffer9, usize), - textures: Textures, - surface: IDirect3DSurface9, -} - -impl Renderer { - /// Creates a new renderer for the given [`IDirect3DDevice9`]. - /// - /// # Safety - /// - /// `device` must be a valid [`IDirect3DDevice9`] pointer. - /// - /// [`IDirect3DDevice9`]: https://docs.rs/winapi/0.3/x86_64-pc-windows-msvc/winapi/shared/d3d9/struct.IDirect3DDevice9.html - pub unsafe fn new(ctx: &mut Context, device: IDirect3DDevice9) -> Result { - let font_tex: IDirect3DBaseTexture9 = - Self::create_font_texture(ctx.fonts(), &device)?.cast()?; - - ctx.io_mut().backend_flags |= BackendFlags::RENDERER_HAS_VTX_OFFSET; - ctx.set_renderer_name(String::from(concat!("imgui-dx9@", env!("CARGO_PKG_VERSION")))); - - let mut device_creation_parameters = core::mem::zeroed(); - device.GetCreationParameters(&mut device_creation_parameters).unwrap(); - - let surface = device.GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO).unwrap(); - - Ok(Renderer { - vertex_buffer: Self::create_vertex_buffer(&device, 0)?, - index_buffer: Self::create_index_buffer(&device, 0)?, - device_creation_parameters, - device, - font_tex, - textures: Textures::new(), - surface, - }) - } - - /// Gets the window width & height via the backbuffer - pub fn get_client_rect(&self) -> Option { - unsafe { - let mut rect: RECT = core::mem::zeroed(); - if GetClientRect(self.device_creation_parameters.hFocusWindow, &mut rect).is_ok() { - Some(rect) - } else { - None - } - } - } - - /// Returns the hFocuswindow HWND from the device creation parameters - pub fn get_hwnd(&self) -> HWND { - self.device_creation_parameters.hFocusWindow - } - - /// Creates a new renderer for the given [`IDirect3DDevice9`]. - /// - /// # Safety - /// - /// `device` must be a valid [`IDirect3DDevice9`] pointer. - /// - /// [`IDirect3DDevice9`]: https://docs.rs/winapi/0.3/x86_64-pc-windows-msvc/winapi/shared/d3d9/struct.IDirect3DDevice9.html - pub unsafe fn new_raw(im_ctx: &mut imgui::Context, device: IDirect3DDevice9) -> Result { - Self::new(im_ctx, device) - } - - /// The textures registry of this renderer. - /// - /// The texture slot at !0 is reserved for the font texture, therefore the - /// renderer will ignore any texture inserted into said slot. - #[inline] - pub fn textures_mut(&mut self) -> &mut Textures { - &mut self.textures - } - - /// The textures registry of this renderer. - #[inline] - pub fn textures(&self) -> &Textures { - &self.textures - } - - /// Renders the given [`Ui`] with this renderer. - /// - /// Should the [`DrawData`] contain an invalid texture index the renderer - /// will return `DXGI_ERROR_INVALID_CALL` and immediately stop rendering. - /// - /// [`Ui`]: https://docs.rs/imgui/*/imgui/struct.Ui.html - pub fn render(&mut self, draw_data: &DrawData) -> Result<()> { - if draw_data.display_size[0] < 0.0 || draw_data.display_size[1] < 0.0 { - return Ok(()); - } - unsafe { - if self.vertex_buffer.1 < draw_data.total_vtx_count as usize { - self.vertex_buffer = - Self::create_vertex_buffer(&self.device, draw_data.total_vtx_count as usize)?; - } - if self.index_buffer.1 < draw_data.total_idx_count as usize { - self.index_buffer = - Self::create_index_buffer(&self.device, draw_data.total_idx_count as usize)?; - } - - let state = StateBackup::backup(&self.device)?; - - self.set_render_state(draw_data); - self.write_buffers(draw_data)?; - self.render_impl(draw_data)?; - - state.restore(&self.device)?; - Ok(()) - } - } - - unsafe fn render_impl(&mut self, draw_data: &DrawData) -> Result<()> { - let clip_off = draw_data.display_pos; - let clip_scale = draw_data.framebuffer_scale; - let mut vertex_offset = 0; - let mut index_offset = 0; - let mut last_tex = TextureId::from(FONT_TEX_ID); - self.device.SetTexture(0, &self.font_tex).unwrap(); - for draw_list in draw_data.draw_lists() { - for cmd in draw_list.commands() { - match cmd { - DrawCmd::Elements { - count, - cmd_params: DrawCmdParams { clip_rect, texture_id, .. }, - } => { - if texture_id != last_tex { - let texture = if texture_id.id() == FONT_TEX_ID { - &self.font_tex - } else { - self.textures.get(texture_id).ok_or(DXGI_ERROR_INVALID_CALL)? - }; - self.device.SetTexture(0, texture).unwrap(); - last_tex = texture_id; - } - - let r: RECT = RECT { - left: ((clip_rect[0] - clip_off[0]) * clip_scale[0]) as i32, - top: ((clip_rect[1] - clip_off[1]) * clip_scale[1]) as i32, - right: ((clip_rect[2] - clip_off[0]) * clip_scale[0]) as i32, - bottom: ((clip_rect[3] - clip_off[1]) * clip_scale[1]) as i32, - }; - self.device.SetScissorRect(&r).unwrap(); - self.device - .DrawIndexedPrimitive( - D3DPT_TRIANGLELIST, - vertex_offset as i32, - 0, - draw_list.vtx_buffer().len() as u32, - index_offset as u32, - count as u32 / 3, - ) - .unwrap(); - index_offset += count; - }, - DrawCmd::ResetRenderState => self.set_render_state(draw_data), - DrawCmd::RawCallback { callback, raw_cmd } => { - callback(draw_list.raw(), raw_cmd) - }, - } - } - vertex_offset += draw_list.vtx_buffer().len(); - } - Ok(()) - } - - unsafe fn set_render_state(&mut self, draw_data: &DrawData) { - let vp = D3DVIEWPORT9 { - X: 0, - Y: 0, - Width: draw_data.display_size[0] as u32, - Height: draw_data.display_size[1] as u32, - MinZ: 0.0, - MaxZ: 1.0, - }; - - let device = &self.device; - device.SetRenderTarget(0, &self.surface).unwrap(); - device.SetViewport(&vp).unwrap(); - device.SetPixelShader(None).unwrap(); - device.SetVertexShader(None).unwrap(); - device.SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE.0).unwrap(); - device.SetRenderState(D3DRS_LIGHTING, FALSE).unwrap(); - device.SetRenderState(D3DRS_ZENABLE, FALSE).unwrap(); - device.SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE).unwrap(); - device.SetRenderState(D3DRS_ALPHATESTENABLE, FALSE).unwrap(); - device.SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD.0).unwrap(); - device.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA.0).unwrap(); - device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA.0).unwrap(); - device.SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE).unwrap(); - device.SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD.0 as u32).unwrap(); - device.SetRenderState(D3DRS_FOGENABLE, FALSE).unwrap(); - device.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE.0 as u32).unwrap(); - device.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE).unwrap(); - device.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE).unwrap(); - device.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE.0 as u32).unwrap(); - device.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE).unwrap(); - device.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE).unwrap(); - device.SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR.0 as u32).unwrap(); - device.SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR.0 as u32).unwrap(); - - let l = draw_data.display_pos[0] + 0.5; - let r = draw_data.display_pos[0] + draw_data.display_size[0] + 0.5; - let t = draw_data.display_pos[1] + 0.5; - let b = draw_data.display_pos[1] + draw_data.display_size[1] + 0.5; - - let mat_projection = Matrix4x4 { - M11: 2.0 / (r - l), - M12: 0.0, - M13: 0.0, - M14: 0.0, - M21: 0.0, - M22: 2.0 / (t - b), - M23: 0.0, - M24: 0.0, - M31: 0.0, - M32: 0.0, - M33: 0.5, - M34: 0.0, - M41: (l + r) / (l - r), - M42: (t + b) / (b - t), - M43: 0.5, - M44: 1.0, - }; - - device.SetTransform(D3DTS_WORLDMATRIX, &MAT_IDENTITY).unwrap(); - device.SetTransform(D3DTS_VIEW, &MAT_IDENTITY).unwrap(); - device.SetTransform(D3DTS_PROJECTION, &mat_projection).unwrap(); - } - - unsafe fn lock_buffers<'v, 'i>( - vb: &'v IDirect3DVertexBuffer9, - ib: &'i IDirect3DIndexBuffer9, - vtx_count: usize, - idx_count: usize, - ) -> Result<(&'v mut [CustomVertex], &'i mut [DrawIdx])> { - let mut vtx_dst: *mut CustomVertex = ptr::null_mut(); - let mut idx_dst: *mut DrawIdx = ptr::null_mut(); - - vb.Lock( - 0, - (vtx_count * mem::size_of::()) as u32, - &mut vtx_dst as *mut _ as _, - D3DLOCK_DISCARD as u32, - )?; - - match ib.Lock( - 0, - (idx_count * mem::size_of::()) as u32, - &mut idx_dst as *mut _ as _, - D3DLOCK_DISCARD as u32, - ) { - Ok(_) => Ok(( - slice::from_raw_parts_mut(vtx_dst, vtx_count), - slice::from_raw_parts_mut(idx_dst, idx_count), - )), - Err(e) => { - vb.Unlock().unwrap(); - Err(e) - }, - } - } - - unsafe fn write_buffers(&mut self, draw_data: &DrawData) -> Result<()> { - let (mut vtx_dst, mut idx_dst) = Self::lock_buffers( - &self.vertex_buffer.0, - &self.index_buffer.0, - draw_data.total_vtx_count as usize, - draw_data.total_idx_count as usize, - )?; - - for (vbuf, ibuf) in - draw_data.draw_lists().map(|draw_list| (draw_list.vtx_buffer(), draw_list.idx_buffer())) - { - for (vertex, vtx_dst) in vbuf.iter().zip(vtx_dst.iter_mut()) { - *vtx_dst = CustomVertex { - pos: [vertex.pos[0], vertex.pos[1], 0.0], - col: [vertex.col[2], vertex.col[1], vertex.col[0], vertex.col[3]], - uv: [vertex.uv[0], vertex.uv[1]], - }; - } - idx_dst[..ibuf.len()].copy_from_slice(ibuf); - vtx_dst = &mut vtx_dst[vbuf.len()..]; - idx_dst = &mut idx_dst[ibuf.len()..]; - } - self.vertex_buffer.0.Unlock().unwrap(); - self.index_buffer.0.Unlock().unwrap(); - self.device - .SetStreamSource(0, &self.vertex_buffer.0, 0, mem::size_of::() as u32) - .unwrap(); - self.device.SetIndices(&self.index_buffer.0).unwrap(); - self.device.SetFVF(D3DFVF_CUSTOMVERTEX).unwrap(); - Ok(()) - } - - unsafe fn create_vertex_buffer( - device: &IDirect3DDevice9, - vtx_count: usize, - ) -> Result<(IDirect3DVertexBuffer9, usize)> { - let len = vtx_count + VERTEX_BUF_ADD_CAPACITY; - let mut vertex_buffer: Option = None; - - device.CreateVertexBuffer( - (len * mem::size_of::()) as u32, - (D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY) as u32, - D3DFVF_CUSTOMVERTEX, - D3DPOOL_DEFAULT, - &mut vertex_buffer, - ptr::null_mut(), - )?; - Ok((vertex_buffer.unwrap(), len)) - } - - unsafe fn create_index_buffer( - device: &IDirect3DDevice9, - idx_count: usize, - ) -> Result<(IDirect3DIndexBuffer9, usize)> { - let len = idx_count + INDEX_BUF_ADD_CAPACITY; - let mut index_buffer: Option = None; - - device.CreateIndexBuffer( - (len * mem::size_of::()) as u32, - (D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY) as u32, - if mem::size_of::() == 2 { D3DFMT_INDEX16 } else { D3DFMT_INDEX32 }, - D3DPOOL_DEFAULT, - &mut index_buffer, - ptr::null_mut(), - )?; - Ok((index_buffer.unwrap(), len)) - } - - // FIXME, imgui hands us an rgba texture while we make dx9 think it receives an - // argb texture - unsafe fn create_font_texture( - fonts: &mut imgui::FontAtlas, - device: &IDirect3DDevice9, - ) -> Result { - let texture = fonts.build_rgba32_texture(); - let mut texture_handle: Option = None; - - device.CreateTexture( - texture.width, - texture.height, - 1, - D3DUSAGE_DYNAMIC as u32, - D3DFMT_A8R8G8B8, - D3DPOOL_DEFAULT, - &mut texture_handle, - ptr::null_mut(), - )?; - - let mut locked_rect: D3DLOCKED_RECT = D3DLOCKED_RECT { Pitch: 0, pBits: ptr::null_mut() }; - let result_texture = texture_handle.unwrap(); - - result_texture.LockRect(0, &mut locked_rect, ptr::null_mut(), 0)?; - - let bits = locked_rect.pBits as *mut u8; - let pitch = locked_rect.Pitch as usize; - let height = texture.height as usize; - let width = texture.width as usize; - - for y in 0..height { - let d3d9_memory = bits.add(pitch * y); - let pixels = texture.data.as_ptr(); - let pixels = pixels.add((width * 4) * y); - std::ptr::copy(pixels, d3d9_memory, width * 4); - } - - result_texture.UnlockRect(0).unwrap(); - fonts.tex_id = TextureId::from(FONT_TEX_ID); - Ok(result_texture) - } -} - -struct StateBackup { - state_block: IDirect3DStateBlock9, - mat_world: Matrix4x4, - mat_view: Matrix4x4, - mat_projection: Matrix4x4, - viewport: D3DVIEWPORT9, - surface: IDirect3DSurface9, -} - -impl StateBackup { - unsafe fn backup(device: &IDirect3DDevice9) -> Result { - match device.CreateStateBlock(D3DSBT_ALL) { - Ok(state_block) => { - let mut mat_world: Matrix4x4 = Default::default(); - let mut mat_view: Matrix4x4 = Default::default(); - let mut mat_projection: Matrix4x4 = Default::default(); - let mut viewport: D3DVIEWPORT9 = core::mem::zeroed(); - - device.GetTransform(D3DTS_WORLDMATRIX, &mut mat_world)?; - device.GetTransform(D3DTS_VIEW, &mut mat_view)?; - device.GetTransform(D3DTS_PROJECTION, &mut mat_projection)?; - device.GetViewport(&mut viewport)?; - let surface = device.GetRenderTarget(0).unwrap(); - - Ok(StateBackup { - state_block, - mat_world, - mat_view, - mat_projection, - viewport, - surface, - }) - }, - Err(e) => Err(e), - } - } - - unsafe fn restore(&self, device: &IDirect3DDevice9) -> Result<()> { - self.state_block.Apply().unwrap(); - device.SetTransform(D3DTS_WORLDMATRIX, &self.mat_world)?; - device.SetTransform(D3DTS_VIEW, &self.mat_view)?; - device.SetTransform(D3DTS_PROJECTION, &self.mat_projection)?; - device.SetViewport(&self.viewport).unwrap(); - device.SetRenderTarget(0, &self.surface).unwrap(); - Ok(()) - } -} diff --git a/src/renderers/imgui_opengl3.rs b/src/renderers/imgui_opengl3.rs deleted file mode 100644 index db6e7285..00000000 --- a/src/renderers/imgui_opengl3.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::ffi::{c_void, CString}; -use std::sync::OnceLock; - -use windows::core::{s, PCSTR}; -use windows::Win32::Foundation::{FARPROC, HINSTANCE}; -use windows::Win32::Graphics::OpenGL::wglGetProcAddress; -use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; - -static OPENGL3_LIB: OnceLock = OnceLock::new(); - -unsafe fn get_opengl3_lib() -> HINSTANCE { - LoadLibraryA(s!("opengl32.dll\0")).expect("LoadLibraryA").into() -} - -/// # Safety -/// -/// Undefined behaviour -pub unsafe fn get_proc_address(function_string: CString) -> *const c_void { - let module = OPENGL3_LIB.get_or_init(|| get_opengl3_lib()); - - if let Some(wgl_proc_address) = wglGetProcAddress(PCSTR(function_string.as_ptr() as _)) { - wgl_proc_address as _ - } else { - let proc_address: FARPROC = GetProcAddress(*module, PCSTR(function_string.as_ptr() as _)); - proc_address.unwrap() as _ - } -} diff --git a/src/renderers/mod.rs b/src/renderers/mod.rs deleted file mode 100644 index 45149e18..00000000 --- a/src/renderers/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Engine-specific renderers. - -#[cfg(feature = "dx11")] -pub mod imgui_dx11; -#[cfg(feature = "dx12")] -pub mod imgui_dx12; -#[cfg(feature = "dx9")] -pub mod imgui_dx9; -#[cfg(feature = "opengl3")] -pub mod imgui_opengl3; diff --git a/src/util.rs b/src/util.rs index aeb33f3f..17ee0519 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,6 @@ +use windows::Win32::Foundation::{HWND, RECT}; +use windows::Win32::UI::WindowsAndMessaging::GetClientRect; + pub fn try_out_param(mut f: F) -> Result where T: Default, @@ -20,3 +23,9 @@ where Err(e) => Err(e), } } + +pub fn win_size(hwnd: HWND) -> (i32, i32) { + let mut rect = RECT::default(); + unsafe { GetClientRect(hwnd, &mut rect).unwrap() }; + (rect.right - rect.left, rect.bottom - rect.top) +} diff --git a/tests/dx11.rs b/tests/dx11.rs index 57784fcc..1bdeb0d0 100644 --- a/tests/dx11.rs +++ b/tests/dx11.rs @@ -8,27 +8,18 @@ use harness::dx11::Dx11Harness; use hook::HookExample; use hudhook::hooks::dx11::ImguiDx11Hooks; use hudhook::*; -use tracing::metadata::LevelFilter; #[test] fn test_imgui_dx11() { - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true) - .with_thread_names(true) - .init(); + hook::setup_tracing(); let dx11_harness = Dx11Harness::new("DX11 hook example"); thread::sleep(Duration::from_millis(500)); - if let Err(e) = - Hudhook::builder().with(HookExample::new().into_hook::()).build().apply() - { + if let Err(e) = Hudhook::builder().with::(HookExample::new()).build().apply() { eprintln!("Couldn't apply hooks: {e:?}"); } - thread::sleep(Duration::from_millis(5000)); + thread::sleep(Duration::from_millis(25000)); drop(dx11_harness); } diff --git a/tests/dx12.rs b/tests/dx12.rs index e0c6d30f..a6cdf843 100644 --- a/tests/dx12.rs +++ b/tests/dx12.rs @@ -8,27 +8,18 @@ use harness::dx12::Dx12Harness; use hook::HookExample; use hudhook::hooks::dx12::ImguiDx12Hooks; use hudhook::*; -use tracing::metadata::LevelFilter; #[test] fn test_imgui_dx12() { - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true) - .with_thread_names(true) - .init(); + hook::setup_tracing(); let dx12_harness = Dx12Harness::new("DX12 hook example"); thread::sleep(Duration::from_millis(500)); - if let Err(e) = - Hudhook::builder().with(HookExample::new().into_hook::()).build().apply() - { + if let Err(e) = Hudhook::builder().with::(HookExample::new()).build().apply() { eprintln!("Couldn't apply hooks: {e:?}"); } - thread::sleep(Duration::from_millis(5000)); + thread::sleep(Duration::from_millis(25000)); drop(dx12_harness); } diff --git a/tests/dx9.rs b/tests/dx9.rs index e3c00ea6..50262238 100644 --- a/tests/dx9.rs +++ b/tests/dx9.rs @@ -7,26 +7,16 @@ use std::time::Duration; use harness::dx9::Dx9Harness; use hook::HookExample; use hudhook::hooks::dx9::ImguiDx9Hooks; -use hudhook::hooks::ImguiRenderLoop; -use hudhook::Hudhook; -use tracing::metadata::LevelFilter; +use hudhook::*; #[test] fn test_imgui_dx9() { - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true) - .with_thread_names(true) - .init(); + hook::setup_tracing(); let dx9_harness = Dx9Harness::new("DX9 hook example"); thread::sleep(Duration::from_millis(500)); - if let Err(e) = - Hudhook::builder().with(HookExample::new().into_hook::()).build().apply() - { + if let Err(e) = Hudhook::builder().with::(HookExample::new()).build().apply() { eprintln!("Couldn't apply hooks: {e:?}"); } diff --git a/tests/harness/dx11.rs b/tests/harness/dx11.rs index fcebb2ac..56304297 100644 --- a/tests/harness/dx11.rs +++ b/tests/harness/dx11.rs @@ -23,10 +23,10 @@ use windows::Win32::Graphics::Dxgi::{ use windows::Win32::Graphics::Gdi::HBRUSH; use windows::Win32::System::LibraryLoader::GetModuleHandleA; use windows::Win32::UI::WindowsAndMessaging::{ - AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, + AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, PeekMessageA, PostQuitMessage, RegisterClassA, SetTimer, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, - HCURSOR, HICON, HMENU, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, WS_OVERLAPPEDWINDOW, - WS_VISIBLE, + HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, + WS_OVERLAPPEDWINDOW, WS_VISIBLE, }; pub struct Dx11Harness { @@ -173,12 +173,12 @@ impl Drop for Dx11Harness { fn handle_message(window: HWND) -> bool { unsafe { let mut msg = MaybeUninit::uninit(); - if GetMessageA(msg.as_mut_ptr(), window, 0, 0).0 > 0 { + if PeekMessageA(msg.as_mut_ptr(), window, 0, 0, PM_REMOVE).0 > 0 { TranslateMessage(msg.as_ptr()); DispatchMessageA(msg.as_ptr()); msg.as_ptr().as_ref().map(|m| m.message != WM_QUIT).unwrap_or(true) } else { - false + true } } } diff --git a/tests/harness/dx12.rs b/tests/harness/dx12.rs index bd28097b..69a2a28d 100644 --- a/tests/harness/dx12.rs +++ b/tests/harness/dx12.rs @@ -30,10 +30,10 @@ use windows::Win32::Graphics::Dxgi::{ use windows::Win32::Graphics::Gdi::HBRUSH; use windows::Win32::System::LibraryLoader::GetModuleHandleA; use windows::Win32::UI::WindowsAndMessaging::{ - AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, + AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, PeekMessageA, PostQuitMessage, RegisterClassA, SetTimer, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, - HCURSOR, HICON, HMENU, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, WS_OVERLAPPEDWINDOW, - WS_VISIBLE, + HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, + WS_OVERLAPPEDWINDOW, WS_VISIBLE, }; pub struct Dx12Harness { @@ -245,12 +245,12 @@ impl Drop for Dx12Harness { fn handle_message(window: HWND) -> bool { unsafe { let mut msg = MaybeUninit::uninit(); - if GetMessageA(msg.as_mut_ptr(), window, 0, 0).as_bool() { + if PeekMessageA(msg.as_mut_ptr(), window, 0, 0, PM_REMOVE).as_bool() { TranslateMessage(msg.as_ptr()); DispatchMessageA(msg.as_ptr()); msg.as_ptr().as_ref().map(|m| m.message != WM_QUIT).unwrap_or(true) } else { - false + true } } } diff --git a/tests/harness/dx9.rs b/tests/harness/dx9.rs index d2764d2c..86b17cc0 100644 --- a/tests/harness/dx9.rs +++ b/tests/harness/dx9.rs @@ -14,10 +14,10 @@ use windows::Win32::Graphics::Direct3D9::{ use windows::Win32::Graphics::Gdi::HBRUSH; use windows::Win32::System::LibraryLoader::GetModuleHandleA; use windows::Win32::UI::WindowsAndMessaging::{ - AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, + AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, PeekMessageA, PostQuitMessage, RegisterClassA, SetTimer, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, - HCURSOR, HICON, HMENU, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, WS_OVERLAPPEDWINDOW, - WS_VISIBLE, + HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, + WS_OVERLAPPEDWINDOW, WS_VISIBLE, }; pub struct Dx9Harness { @@ -126,12 +126,12 @@ impl Drop for Dx9Harness { fn handle_message(window: HWND) -> bool { unsafe { let mut msg = MaybeUninit::uninit(); - if GetMessageA(msg.as_mut_ptr(), window, 0, 0).0 > 0 { + if PeekMessageA(msg.as_mut_ptr(), window, 0, 0, PM_REMOVE).0 > 0 { TranslateMessage(msg.as_ptr()); DispatchMessageA(msg.as_ptr()); msg.as_ptr().as_ref().map(|m| m.message != WM_QUIT).unwrap_or(true) } else { - false + true } } } diff --git a/tests/harness/opengl3.rs b/tests/harness/opengl3.rs index 85dd07cd..c6532100 100644 --- a/tests/harness/opengl3.rs +++ b/tests/harness/opengl3.rs @@ -16,10 +16,10 @@ use windows::Win32::Graphics::OpenGL::{ }; use windows::Win32::System::LibraryLoader::GetModuleHandleA; use windows::Win32::UI::WindowsAndMessaging::{ - AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, + AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, PeekMessageA, PostQuitMessage, RegisterClassA, SetTimer, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, - HCURSOR, HICON, HMENU, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, WS_OVERLAPPEDWINDOW, - WS_VISIBLE, + HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, + WS_OVERLAPPEDWINDOW, WS_VISIBLE, }; pub struct Opengl3Harness { @@ -147,12 +147,12 @@ impl Drop for Opengl3Harness { fn handle_message(window: HWND) -> bool { unsafe { let mut msg = MaybeUninit::uninit(); - if GetMessageA(msg.as_mut_ptr(), window, 0, 0).as_bool() { + if PeekMessageA(msg.as_mut_ptr(), window, 0, 0, PM_REMOVE).as_bool() { TranslateMessage(msg.as_ptr()); DispatchMessageA(msg.as_ptr()); msg.as_ptr().as_ref().map(|m| m.message != WM_QUIT).unwrap_or(true) } else { - false + true } } } diff --git a/tests/hook.rs b/tests/hook.rs index 3e0b606a..f960a258 100644 --- a/tests/hook.rs +++ b/tests/hook.rs @@ -1,14 +1,37 @@ -use hudhook::hooks::ImguiRenderLoop; +use std::time::{Duration, Instant}; + +use hudhook::ImguiRenderLoop; use imgui::{Condition, StyleColor}; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{fmt, EnvFilter}; + +pub fn setup_tracing() { + tracing_subscriber::registry() + .with( + fmt::layer().event_format( + fmt::format() + .with_level(true) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true) + .with_thread_names(true), + ), + ) + .with(EnvFilter::from_default_env()) + .init(); +} -pub struct HookExample; +pub struct HookExample { + frame_times: Vec, + last_time: Instant, +} impl HookExample { pub fn new() -> Self { println!("Initializing"); hudhook::alloc_console().ok(); - HookExample + HookExample { frame_times: Vec::new(), last_time: Instant::now() } } } @@ -20,6 +43,14 @@ impl Default for HookExample { impl ImguiRenderLoop for HookExample { fn render(&mut self, ui: &mut imgui::Ui) { + let duration = self.last_time.elapsed(); + self.frame_times.push(duration); + self.last_time = Instant::now(); + + let avg: Duration = + self.frame_times.iter().sum::() / self.frame_times.len() as u32; + let last = self.frame_times.last().unwrap(); + ui.window("Hello world").size([400.0, 500.0], Condition::FirstUseEver).build(|| { ui.text("Hello world!"); ui.text("こんにちは世界!"); @@ -46,6 +77,10 @@ impl ImguiRenderLoop for HookExample { ui.separator(); let mouse_pos = ui.io().mouse_pos; ui.text(format!("Mouse Position: ({:.1},{:.1})", mouse_pos[0], mouse_pos[1])); + ui.text(format!("Frame time: {:8.2}", last.as_secs_f64() * 1000.)); + ui.text(format!("Avg: {:8.2}", avg.as_secs_f64() * 1000.)); + ui.text(format!("FPS: {:8.2}", 1. / last.as_secs_f64())); + ui.text(format!("Avg: {:8.2}", 1. / avg.as_secs_f64())); }); } } diff --git a/tests/opengl3.rs b/tests/opengl3.rs index 1ca30835..b3ac363d 100644 --- a/tests/opengl3.rs +++ b/tests/opengl3.rs @@ -8,23 +8,15 @@ use harness::opengl3::Opengl3Harness; use hook::HookExample; use hudhook::hooks::opengl3::ImguiOpenGl3Hooks; use hudhook::*; -use tracing::metadata::LevelFilter; #[test] fn test_imgui_opengl3() { - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true) - .with_thread_names(true) - .init(); + hook::setup_tracing(); let opengl3_harness = Opengl3Harness::new("OpenGL3 hook example"); thread::sleep(Duration::from_millis(500)); - if let Err(e) = - Hudhook::builder().with(HookExample::new().into_hook::()).build().apply() + if let Err(e) = Hudhook::builder().with::(HookExample::new()).build().apply() { eprintln!("Couldn't apply hooks: {e:?}"); }