diff --git a/.gitignore b/.gitignore index 525047ac..7378b586 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env Cargo.lock /target **/*.rs.bk @@ -8,3 +9,4 @@ Cargo.lock /tests/test_sample.exe .idea +.cargo diff --git a/Cargo.toml b/Cargo.toml index 38a70951..03c11597 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ default = ["dx9", "dx11", "dx12", "opengl3", "inject"] dx9 = [] dx11 = [] dx12 = [] -opengl3 = [] +opengl3 = ["dep:gl_generator"] inject = [] [[example]] @@ -29,6 +29,26 @@ crate-type = ["cdylib"] name = "hook_with_image" crate-type = ["cdylib"] +[[example]] +name = "injector" +crate-type = ["bin"] + +[[example]] +name = "demo_hook_dx12" +crate-type = ["cdylib"] + +[[example]] +name = "demo_hook_dx11" +crate-type = ["cdylib"] + +[[example]] +name = "demo_hook_dx9" +crate-type = ["cdylib"] + +[[example]] +name = "demo_hook_opengl3" +crate-type = ["cdylib"] + [dependencies] imgui = "0.11" imgui-opengl = "0.1" @@ -49,14 +69,19 @@ windows = { version = "0.51.0", features = [ "Win32_System_WindowsProgramming", "Win32_Graphics_Dxgi", "Win32_Graphics_Dxgi_Common", + "Win32_Graphics_Direct2D", + "Win32_Graphics_Direct2D_Common", "Win32_Graphics_Direct3D9", + "Win32_Graphics_Direct3D11on12", "Win32_Graphics_Direct3D11", "Win32_Graphics_Direct3D12", "Win32_Graphics_Direct3D_Fxc", "Win32_Graphics_Direct3D", "Win32_Graphics_DirectComposition", + "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_Graphics_OpenGL", + "Win32_UI_Controls", "Win32_UI_Input", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", @@ -67,11 +92,13 @@ once_cell = "1.18.0" tracing-subscriber = { version = "0.3", features = ["env-filter"] } [dev-dependencies] +dotenv = "0.15.0" image = "0.24.8" tracing-subscriber = "0.3" [build-dependencies] cc = "1.0.72" +gl_generator = { version = "0.14.0", optional = true } [profile.test] opt-level = 3 diff --git a/build.rs b/build.rs index 29df7e93..754912ab 100644 --- a/build.rs +++ b/build.rs @@ -31,4 +31,18 @@ fn main() { println!("cargo:rerun-if-changed=vendor/minhook/src"); println!("cargo:rustc-link-search=native={}", env::var("OUT_DIR").unwrap()); + + #[cfg(feature = "opengl3")] + { + use std::fs::File; + + use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator}; + + let dest = env::var("OUT_DIR").unwrap(); + let mut file = File::create(Path::new(&dest).join("gl_bindings.rs")).unwrap(); + + Registry::new(Api::Gl, (3, 3), Profile::Core, Fallbacks::All, []) + .write_bindings(StructGenerator, &mut file) + .unwrap(); + } } diff --git a/examples/demo_hook_dx11.rs b/examples/demo_hook_dx11.rs new file mode 100644 index 00000000..2333baa7 --- /dev/null +++ b/examples/demo_hook_dx11.rs @@ -0,0 +1,56 @@ +use hudhook::*; +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(bool); + +impl ImguiRenderLoop for HookExample { + fn render(&mut self, ui: &mut imgui::Ui) { + ui.show_demo_window(&mut self.0); + } +} + +/// Entry point created by the `hudhook` library. +/// +/// # Safety +/// +/// haha +#[no_mangle] +pub unsafe extern "stdcall" fn DllMain( + hmodule: ::hudhook::windows::Win32::Foundation::HINSTANCE, + reason: u32, + _: *mut ::std::ffi::c_void, +) { + if reason == ::hudhook::windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH { + setup_tracing(); + alloc_console().unwrap(); + ::hudhook::tracing::trace!("DllMain()"); + ::std::thread::spawn(move || { + if let Err(e) = ::hudhook::Hudhook::builder() + .with::(HookExample(true)) + .with_hmodule(hmodule) + .build() + .apply() + { + ::hudhook::tracing::error!("Couldn't apply hooks: {e:?}"); + ::hudhook::eject(); + } + }); + } +} diff --git a/examples/demo_hook_dx12.rs b/examples/demo_hook_dx12.rs new file mode 100644 index 00000000..2c39d40a --- /dev/null +++ b/examples/demo_hook_dx12.rs @@ -0,0 +1,56 @@ +use hudhook::*; +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(bool); + +impl ImguiRenderLoop for HookExample { + fn render(&mut self, ui: &mut imgui::Ui) { + ui.show_demo_window(&mut self.0); + } +} + +/// Entry point created by the `hudhook` library. +/// +/// # Safety +/// +/// haha +#[no_mangle] +pub unsafe extern "stdcall" fn DllMain( + hmodule: ::hudhook::windows::Win32::Foundation::HINSTANCE, + reason: u32, + _: *mut ::std::ffi::c_void, +) { + if reason == ::hudhook::windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH { + setup_tracing(); + alloc_console().unwrap(); + ::hudhook::tracing::trace!("DllMain()"); + ::std::thread::spawn(move || { + if let Err(e) = ::hudhook::Hudhook::builder() + .with::(HookExample(true)) + .with_hmodule(hmodule) + .build() + .apply() + { + ::hudhook::tracing::error!("Couldn't apply hooks: {e:?}"); + ::hudhook::eject(); + } + }); + } +} diff --git a/examples/demo_hook_dx9.rs b/examples/demo_hook_dx9.rs new file mode 100644 index 00000000..4501067a --- /dev/null +++ b/examples/demo_hook_dx9.rs @@ -0,0 +1,56 @@ +use hudhook::*; +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(bool); + +impl ImguiRenderLoop for HookExample { + fn render(&mut self, ui: &mut imgui::Ui) { + ui.show_demo_window(&mut self.0); + } +} + +/// Entry point created by the `hudhook` library. +/// +/// # Safety +/// +/// haha +#[no_mangle] +pub unsafe extern "stdcall" fn DllMain( + hmodule: ::hudhook::windows::Win32::Foundation::HINSTANCE, + reason: u32, + _: *mut ::std::ffi::c_void, +) { + if reason == ::hudhook::windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH { + setup_tracing(); + alloc_console().unwrap(); + ::hudhook::tracing::trace!("DllMain()"); + ::std::thread::spawn(move || { + if let Err(e) = ::hudhook::Hudhook::builder() + .with::(HookExample(true)) + .with_hmodule(hmodule) + .build() + .apply() + { + ::hudhook::tracing::error!("Couldn't apply hooks: {e:?}"); + ::hudhook::eject(); + } + }); + } +} diff --git a/examples/demo_hook_opengl3.rs b/examples/demo_hook_opengl3.rs new file mode 100644 index 00000000..21f0b104 --- /dev/null +++ b/examples/demo_hook_opengl3.rs @@ -0,0 +1,56 @@ +use hudhook::*; +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(bool); + +impl ImguiRenderLoop for HookExample { + fn render(&mut self, ui: &mut imgui::Ui) { + ui.show_demo_window(&mut self.0); + } +} + +/// Entry point created by the `hudhook` library. +/// +/// # Safety +/// +/// haha +#[no_mangle] +pub unsafe extern "stdcall" fn DllMain( + hmodule: ::hudhook::windows::Win32::Foundation::HINSTANCE, + reason: u32, + _: *mut ::std::ffi::c_void, +) { + if reason == ::hudhook::windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH { + setup_tracing(); + alloc_console().unwrap(); + ::hudhook::tracing::trace!("DllMain()"); + ::std::thread::spawn(move || { + if let Err(e) = ::hudhook::Hudhook::builder() + .with::(HookExample(true)) + .with_hmodule(hmodule) + .build() + .apply() + { + ::hudhook::tracing::error!("Couldn't apply hooks: {e:?}"); + ::hudhook::eject(); + } + }); + } +} diff --git a/examples/hook_with_image.rs b/examples/hook_with_image.rs index 3602b0e6..f460d54f 100644 --- a/examples/hook_with_image.rs +++ b/examples/hook_with_image.rs @@ -4,7 +4,7 @@ use hudhook::renderer::RenderEngine; use hudhook::ImguiRenderLoop; use image::io::Reader as ImageReader; use image::{EncodableLayout, RgbaImage}; -use imgui::{Condition, Image, TextureId}; +use imgui::{Condition, Context, Image, TextureId}; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; @@ -49,7 +49,7 @@ impl Default for HookExample { } impl ImguiRenderLoop for HookExample { - fn initialize(&mut self, render_engine: &mut RenderEngine) { + fn initialize(&mut self, _ctx: &mut Context, render_engine: &mut RenderEngine) { self.image_id = render_engine .load_image(self.image.as_bytes(), self.image.width() as _, self.image.height() as _) .ok(); diff --git a/examples/injector.rs b/examples/injector.rs new file mode 100644 index 00000000..da86c675 --- /dev/null +++ b/examples/injector.rs @@ -0,0 +1,12 @@ +use std::path::PathBuf; + +use hudhook::inject::Process; + +fn main() { + let mut args = std::env::args(); + args.next().unwrap(); + let name = args.next().unwrap(); + let dll: PathBuf = args.next().unwrap().into(); + let process = Process::by_name(&name).expect("Process by name"); + process.inject(dll).expect("Inject"); +} diff --git a/src/compositor/dx11.rs b/src/compositor/dx11.rs new file mode 100644 index 00000000..821de20a --- /dev/null +++ b/src/compositor/dx11.rs @@ -0,0 +1,410 @@ +use std::{mem, ptr, slice}; + +use once_cell::sync::OnceCell; +use windows::core::{s, ComInterface, Result}; +use windows::Win32::Foundation::{CloseHandle, BOOL, HANDLE, RECT}; +use windows::Win32::Graphics::Direct3D::Fxc::D3DCompile; +use windows::Win32::Graphics::Direct3D::*; +use windows::Win32::Graphics::Direct3D11::*; +use windows::Win32::Graphics::Dxgi::Common::*; +use windows::Win32::Graphics::Dxgi::*; + +use crate::util; + +pub struct Compositor { + device: ID3D11Device1, + device_ctx: ID3D11DeviceContext, + quad_renderer: OnceCell, +} + +impl Compositor { + pub fn new(device: &ID3D11Device1) -> Result { + Ok(Self { + device: device.clone(), + device_ctx: unsafe { device.GetImmediateContext()? }, + quad_renderer: OnceCell::new(), + }) + } + + pub fn composite(&mut self, handle: HANDLE, target: &IDXGISwapChain) -> Result<()> { + let resource: ID3D11Texture2D = unsafe { self.device.OpenSharedResource1(handle) }?; + unsafe { self.render_quad(resource, target) }?; + + unsafe { CloseHandle(handle)? }; + + Ok(()) + } + + unsafe fn quad_renderer(&mut self) -> Result<&QuadRenderer> { + self.quad_renderer.get_or_try_init(|| QuadRenderer::new(&self.device, &self.device_ctx)) + } + + unsafe fn render_quad( + &mut self, + texture: ID3D11Texture2D, + target: &IDXGISwapChain, + ) -> Result<()> { + let device = self.device.clone(); + let ctx = self.device_ctx.clone(); + let quad_renderer = self.quad_renderer()?; + + quad_renderer.setup_state(&ctx); + quad_renderer.render(&device, &ctx, texture, target)?; + + Ok(()) + } +} + +#[repr(C)] +struct Vertex { + pos: [f32; 2], + uv: [f32; 2], +} + +struct QuadRenderer { + vtx_shader: ID3D11VertexShader, + pix_shader: ID3D11PixelShader, + layout: ID3D11InputLayout, + sampler: ID3D11SamplerState, + rasterizer_state: ID3D11RasterizerState, + blend_state: ID3D11BlendState, + depth_stencil_state: ID3D11DepthStencilState, + vertex_buffer: Option, + index_buffer: ID3D11Buffer, +} + +impl QuadRenderer { + unsafe fn new(d3d11: &ID3D11Device1, d3d11_ctx: &ID3D11DeviceContext) -> Result { + const VERTICES: [Vertex; 4] = [ + Vertex { pos: [-1., 1.], uv: [0., 0.] }, + Vertex { pos: [1., 1.], uv: [1., 0.] }, + Vertex { pos: [-1., -1.], uv: [0., 1.] }, + Vertex { pos: [1., -1.], uv: [1., 1.] }, + ]; + + const INDICES: [u16; 6] = [0, 1, 2, 1, 3, 2]; + + const VERTEX_SHADER_SRC: &str = r" + struct VS_INPUT + { + float2 pos : POSITION; + float2 uv : TEXCOORD0; + }; + + struct PS_INPUT + { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + PS_INPUT main(VS_INPUT input) + { + PS_INPUT output; + output.pos = float4(input.pos.xy, 0.0f, 1.0f); + output.uv = input.uv.xy; + return output; + } + "; + + const PIXEL_SHADER_SRC: &str = r" + struct PS_INPUT + { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + }; + Texture2D texture0 : register(t0); + SamplerState sampler0 : register(s0); + + float4 main(PS_INPUT input) : SV_Target + { + float4 overlay_color = texture0.Sample(sampler0, input.uv); + return overlay_color; + } + "; + + let vs_blob: ID3DBlob = util::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 = util::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 = util::try_out_ptr(|v| unsafe { + let ptr = vs_blob.GetBufferPointer(); + let size = vs_blob.GetBufferSize(); + d3d11.CreateVertexShader(slice::from_raw_parts(ptr as _, size), None, Some(v)) + })?; + + let pix_shader = util::try_out_ptr(|v| unsafe { + let ptr = ps_blob.GetBufferPointer(); + let size = ps_blob.GetBufferSize(); + d3d11.CreatePixelShader(slice::from_raw_parts(ptr as _, size), None, Some(v)) + })?; + + let layout = util::try_out_ptr(|v| unsafe { + let ptr = vs_blob.GetBufferPointer(); + let size = vs_blob.GetBufferSize(); + d3d11.CreateInputLayout( + &[ + D3D11_INPUT_ELEMENT_DESC { + SemanticName: s!("POSITION"), + 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"), + SemanticIndex: 0, + Format: DXGI_FORMAT_R32G32_FLOAT, + InputSlot: 0, + AlignedByteOffset: D3D11_APPEND_ALIGNED_ELEMENT, + InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, + InstanceDataStepRate: 0, + }, + ], + slice::from_raw_parts(ptr as _, size), + Some(v), + ) + })?; + + let sampler = util::try_out_ptr(|v| unsafe { + d3d11.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 = util::try_out_ptr(|v| unsafe { + d3d11.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 = util::try_out_ptr(|v| unsafe { + d3d11.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 = util::try_out_ptr(|v| unsafe { + d3d11.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), + ) + })?; + + let vertex_buffer: ID3D11Buffer = util::try_out_ptr(|v| { + d3d11.CreateBuffer( + &D3D11_BUFFER_DESC { + ByteWidth: mem::size_of_val(&VERTICES) as _, + Usage: D3D11_USAGE_DYNAMIC, + BindFlags: D3D11_BIND_VERTEX_BUFFER.0 as _, + CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as _, + MiscFlags: 0, + StructureByteStride: 0, + }, + Some(&D3D11_SUBRESOURCE_DATA { + pSysMem: VERTICES.as_ptr() as *const _, + SysMemPitch: 0, + SysMemSlicePitch: 0, + }), + Some(v), + ) + })?; + + let index_buffer: ID3D11Buffer = util::try_out_ptr(|v| { + d3d11.CreateBuffer( + &D3D11_BUFFER_DESC { + ByteWidth: mem::size_of_val(&INDICES) as _, + Usage: D3D11_USAGE_DYNAMIC, + BindFlags: D3D11_BIND_INDEX_BUFFER.0 as _, + CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as _, + MiscFlags: 0, + StructureByteStride: 0, + }, + Some(&D3D11_SUBRESOURCE_DATA { + pSysMem: INDICES.as_ptr() as *const _, + SysMemPitch: 0, + SysMemSlicePitch: 0, + }), + Some(v), + ) + })?; + + let mut ms = Default::default(); + d3d11_ctx.Map(&vertex_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut ms))?; + ptr::copy_nonoverlapping(VERTICES.as_ptr(), ms.pData as _, VERTICES.len()); + d3d11_ctx.Unmap(&vertex_buffer, 0); + + let mut ms = Default::default(); + d3d11_ctx.Map(&index_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut ms))?; + ptr::copy_nonoverlapping(INDICES.as_ptr(), ms.pData as _, INDICES.len()); + d3d11_ctx.Unmap(&index_buffer, 0); + + Ok(Self { + vtx_shader, + pix_shader, + layout, + sampler, + blend_state, + depth_stencil_state, + rasterizer_state, + vertex_buffer: Some(vertex_buffer), + index_buffer, + }) + } + + unsafe fn setup_state(&self, d3d11_ctx: &ID3D11DeviceContext) { + d3d11_ctx.VSSetShader(&self.vtx_shader, Some(&[])); + d3d11_ctx.PSSetShader(&self.pix_shader, Some(&[])); + d3d11_ctx.IASetInputLayout(&self.layout); + d3d11_ctx.PSSetSamplers(0, Some(&[Some(self.sampler.clone())])); + d3d11_ctx.OMSetBlendState(&self.blend_state, Some(&[0f32; 4]), 0xFFFFFFFF); + d3d11_ctx.OMSetDepthStencilState(&self.depth_stencil_state, 0); + d3d11_ctx.RSSetState(&self.rasterizer_state); + d3d11_ctx.IASetVertexBuffers(0, 1, Some(&self.vertex_buffer), Some(&16), Some(&0)); + d3d11_ctx.IASetIndexBuffer(&self.index_buffer, DXGI_FORMAT_R16_UINT, 0); + d3d11_ctx.IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + } + + unsafe fn render( + &self, + d3d11: &ID3D11Device1, + d3d11_ctx: &ID3D11DeviceContext, + texture: ID3D11Texture2D, + target: &IDXGISwapChain, + ) -> Result<()> { + let back_buffer: ID3D11Resource = target.GetBuffer(0)?; + let back_buffer: ID3D11RenderTargetView = + util::try_out_ptr(|v| d3d11.CreateRenderTargetView(&back_buffer, None, Some(v)))?; + + let texture = texture.cast::()?; + let srv: ID3D11ShaderResourceView = util::try_out_ptr(|v| { + d3d11.CreateShaderResourceView( + &texture, + Some(&D3D11_SHADER_RESOURCE_VIEW_DESC { + Format: DXGI_FORMAT_UNKNOWN, + ViewDimension: D3D11_SRV_DIMENSION_TEXTURE2D, + Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 { + Texture2D: D3D11_TEX2D_SRV { MostDetailedMip: 0, MipLevels: 1 }, + }, + }), + Some(v), + ) + })?; + + let desc: DXGI_SWAP_CHAIN_DESC = util::try_out_param(|v| target.GetDesc(v))?; + + d3d11_ctx.RSSetViewports(Some(&[D3D11_VIEWPORT { + TopLeftX: 0., + TopLeftY: 0., + Width: desc.BufferDesc.Width as f32, + Height: desc.BufferDesc.Height as f32, + MinDepth: 0., + MaxDepth: 1., + }])); + d3d11_ctx.RSSetScissorRects(Some(&[RECT { + left: 0, + top: 0, + right: desc.BufferDesc.Width as _, + bottom: desc.BufferDesc.Height as _, + }])); + d3d11_ctx.PSSetShaderResources(0, Some(&[Some(srv)])); + d3d11_ctx.OMSetRenderTargets(Some(&[Some(back_buffer)]), None); + d3d11_ctx.DrawIndexed(6, 0, 0); + + Ok(()) + } +} diff --git a/src/compositor/dx12.rs b/src/compositor/dx12.rs new file mode 100644 index 00000000..d8453023 --- /dev/null +++ b/src/compositor/dx12.rs @@ -0,0 +1,527 @@ +use std::mem::ManuallyDrop; +use std::{mem, ptr, slice}; + +use memoffset::offset_of; +use windows::core::{s, w, ComInterface, Result}; +use windows::Win32::Foundation::*; +use windows::Win32::Graphics::Direct3D::Fxc::*; +use windows::Win32::Graphics::Direct3D::*; +use windows::Win32::Graphics::Direct3D12::*; +use windows::Win32::Graphics::Dxgi::Common::*; + +use crate::util::{self, Fence}; + +#[repr(C)] +struct Vertex { + pos: [f32; 2], + uv: [f32; 2], +} + +pub struct Compositor { + device: ID3D12Device, + + command_queue: ID3D12CommandQueue, + command_allocator: ID3D12CommandAllocator, + command_list: ID3D12GraphicsCommandList, + + rtv_heap: ID3D12DescriptorHeap, + srv_heap: ID3D12DescriptorHeap, + + vertex_buffer: ID3D12Resource, + index_buffer: ID3D12Resource, + + root_signature: ID3D12RootSignature, + pipeline_state: ID3D12PipelineState, + + fence: Fence, +} + +impl Compositor { + pub fn new(command_queue: &ID3D12CommandQueue) -> Result { + let (device, command_queue, command_allocator, command_list) = + unsafe { create_command_objects(command_queue) }?; + + let (rtv_heap, srv_heap) = unsafe { create_heaps(&device) }?; + let (vertex_buffer, index_buffer) = unsafe { create_buffers(&device) }?; + let (root_signature, pipeline_state) = unsafe { create_shader_program(&device) }?; + let fence = Fence::new(&device)?; + + Ok(Self { + device, + command_queue, + command_allocator, + command_list, + rtv_heap, + srv_heap, + vertex_buffer, + index_buffer, + root_signature, + pipeline_state, + fence, + }) + } + + pub fn composite(&self, source: ID3D12Resource, target: ID3D12Resource) -> Result<()> { + let desc = unsafe { target.GetDesc() }; + + self.fence.wait()?; + self.fence.incr(); + + unsafe { + self.device.CreateRenderTargetView( + &target, + None, + self.rtv_heap.GetCPUDescriptorHandleForHeapStart(), + ) + }; + + unsafe { + self.device.CreateShaderResourceView( + &source, + Some(&D3D12_SHADER_RESOURCE_VIEW_DESC { + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D, + Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 { + Texture2D: D3D12_TEX2D_SRV { + MostDetailedMip: 0, + MipLevels: 1, + PlaneSlice: Default::default(), + ResourceMinLODClamp: Default::default(), + }, + }, + }), + self.srv_heap.GetCPUDescriptorHandleForHeapStart(), + ) + }; + + let target_rt_barriers = [util::create_barrier( + &target, + D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATE_RENDER_TARGET, + )]; + + let target_present_barriers = [util::create_barrier( + &target, + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT, + )]; + + unsafe { + self.command_allocator.Reset()?; + self.command_list.Reset(&self.command_allocator, None)?; + self.command_list.ResourceBarrier(&target_rt_barriers); + self.command_list.RSSetViewports(&[D3D12_VIEWPORT { + TopLeftX: 0f32, + TopLeftY: 0f32, + Width: desc.Width as f32, + Height: desc.Height as f32, + MinDepth: 0f32, + MaxDepth: 1f32, + }]); + self.command_list.RSSetScissorRects(&[RECT { + left: 0, + top: 0, + right: desc.Width as _, + bottom: desc.Height as _, + }]); + self.command_list.IASetVertexBuffers( + 0, + Some(&[D3D12_VERTEX_BUFFER_VIEW { + BufferLocation: self.vertex_buffer.GetGPUVirtualAddress(), + SizeInBytes: (4 * mem::size_of::()) as _, + StrideInBytes: mem::size_of::() as _, + }]), + ); + self.command_list.IASetIndexBuffer(Some(&D3D12_INDEX_BUFFER_VIEW { + BufferLocation: self.index_buffer.GetGPUVirtualAddress(), + SizeInBytes: (6 * mem::size_of::()) as _, + Format: DXGI_FORMAT_R16_UINT, + })); + self.command_list.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + self.command_list.OMSetBlendFactor(Some(&[0f32; 4])); + self.command_list.OMSetRenderTargets( + 1, + Some(&self.rtv_heap.GetCPUDescriptorHandleForHeapStart()), + false, + None, + ); + + self.command_list.SetPipelineState(&self.pipeline_state); + self.command_list.SetGraphicsRootSignature(&self.root_signature); + self.command_list.SetDescriptorHeaps(&[Some(self.srv_heap.clone())]); + + self.command_list.SetGraphicsRootDescriptorTable( + 0, + self.srv_heap.GetGPUDescriptorHandleForHeapStart(), + ); + self.command_list.DrawIndexedInstanced(6, 1, 0, 0, 0); + self.command_list.ResourceBarrier(&target_present_barriers); + self.command_list.Close()?; + + self.command_queue.ExecuteCommandLists(&[Some(self.command_list.clone().cast()?)]); + self.command_queue.Signal(self.fence.fence(), self.fence.value())?; + } + + target_rt_barriers.into_iter().for_each(util::drop_barrier); + target_present_barriers.into_iter().for_each(util::drop_barrier); + + Ok(()) + } +} + +unsafe fn create_command_objects( + command_queue: &ID3D12CommandQueue, +) -> Result<(ID3D12Device, ID3D12CommandQueue, ID3D12CommandAllocator, ID3D12GraphicsCommandList)> { + let device: ID3D12Device = util::try_out_ptr(|v| unsafe { command_queue.GetDevice(v) })?; + let command_queue = command_queue.clone(); + 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) + }?; + + command_list.Close()?; + command_allocator.SetName(w!("hudhook Compositor Command List"))?; + command_list.SetName(w!("hudhook Compositor Command List"))?; + + Ok((device, command_queue, command_allocator, command_list)) +} + +unsafe fn create_heaps( + device: &ID3D12Device, +) -> Result<(ID3D12DescriptorHeap, ID3D12DescriptorHeap)> { + let rtv_heap: ID3D12DescriptorHeap = + device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { + Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + NumDescriptors: 1, + Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + NodeMask: 0, + })?; + rtv_heap.SetName(w!("hudhook Compositor RTV Heap"))?; + + let srv_heap: ID3D12DescriptorHeap = + device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { + Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + NumDescriptors: 1, + Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + NodeMask: 0, + })?; + srv_heap.SetName(w!("hudhook Compositor SRV Heap"))?; + Ok((rtv_heap, srv_heap)) +} + +unsafe fn create_buffers(device: &ID3D12Device) -> Result<(ID3D12Resource, ID3D12Resource)> { + let vertex_buffer: ID3D12Resource = util::try_out_ptr(|v| { + device.CreateCommittedResource( + &D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_UPLOAD, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }, + D3D12_HEAP_FLAG_NONE, + &D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 65536, + Width: (64 * mem::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, + }, + D3D12_RESOURCE_STATE_GENERIC_READ, + None, + v, + ) + })?; + + let index_buffer: ID3D12Resource = util::try_out_ptr(|v| { + device.CreateCommittedResource( + &D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_UPLOAD, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }, + D3D12_HEAP_FLAG_NONE, + &D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 0, + Width: (64 * mem::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, + }, + D3D12_RESOURCE_STATE_GENERIC_READ, + None, + v, + ) + })?; + + vertex_buffer.SetName(w!("hudhook Compositor Vertex Buffer"))?; + index_buffer.SetName(w!("hudhook Compositor Index Buffer"))?; + + const VERTICES: [Vertex; 4] = [ + Vertex { pos: [-1., -1.], uv: [0., 1.] }, + Vertex { pos: [1., -1.], uv: [1., 1.] }, + Vertex { pos: [-1., 1.], uv: [0., 0.] }, + Vertex { pos: [1., 1.], uv: [1., 0.] }, + ]; + const INDICES: [u16; 6] = [0, 1, 2, 1, 3, 2]; + + let mut resource = ptr::null_mut(); + vertex_buffer.Map(0, None, Some(&mut resource))?; + ptr::copy_nonoverlapping(VERTICES.as_ptr(), resource as *mut Vertex, VERTICES.len()); + vertex_buffer.Unmap(0, None); + + let mut resource = ptr::null_mut(); + index_buffer.Map(0, None, Some(&mut resource))?; + ptr::copy_nonoverlapping(INDICES.as_ptr(), resource as *mut u16, INDICES.len()); + index_buffer.Unmap(0, None); + + Ok((vertex_buffer, index_buffer)) +} + +unsafe fn create_shader_program( + device: &ID3D12Device, +) -> Result<(ID3D12RootSignature, ID3D12PipelineState)> { + let desc_range = [D3D12_DESCRIPTOR_RANGE { + RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + NumDescriptors: 1, + BaseShaderRegister: 0, + RegisterSpace: 0, + OffsetInDescriptorsFromTableStart: 0, + }]; + + let parameters = [D3D12_ROOT_PARAMETER { + ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, + Anonymous: D3D12_ROOT_PARAMETER_0 { + DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE { + NumDescriptorRanges: 1, + pDescriptorRanges: desc_range.as_ptr(), + }, + }, + ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL, + }]; + + let samplers = [D3D12_STATIC_SAMPLER_DESC { + Filter: D3D12_FILTER_MIN_MAG_MIP_LINEAR, + AddressU: D3D12_TEXTURE_ADDRESS_MODE_WRAP, + AddressV: D3D12_TEXTURE_ADDRESS_MODE_WRAP, + AddressW: D3D12_TEXTURE_ADDRESS_MODE_WRAP, + MipLODBias: 0f32, + MaxAnisotropy: 0, + ComparisonFunc: D3D12_COMPARISON_FUNC_ALWAYS, + BorderColor: D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK, + MinLOD: 0f32, + MaxLOD: 0f32, + ShaderRegister: 0, + RegisterSpace: 0, + ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL, + }]; + + let root_signature_desc = D3D12_ROOT_SIGNATURE_DESC { + NumParameters: 1, + pParameters: parameters.as_ptr(), + NumStaticSamplers: 1, + pStaticSamplers: samplers.as_ptr(), + Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT + | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS + | D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS + | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS, + }; + + let blob = util::try_out_err_blob(|v, err_blob| { + D3D12SerializeRootSignature( + &root_signature_desc, + D3D_ROOT_SIGNATURE_VERSION_1_0, + v, + Some(err_blob), + ) + }) + .map_err(util::print_error_blob("Serializing root signature")) + .expect("D3D12SerializeRootSignature"); + + let root_signature: ID3D12RootSignature = device.CreateRootSignature( + 0, + slice::from_raw_parts(blob.GetBufferPointer() as *const u8, blob.GetBufferSize()), + )?; + root_signature.SetName(w!("hudhook Compositor Root Signature"))?; + + const VS: &str = r#" + struct VS_INPUT { + float2 pos: POSITION; + float2 uv: TEXCOORD0; + }; + + struct PS_INPUT { + float4 pos: SV_POSITION; + float2 uv: TEXCOORD0; + }; + + PS_INPUT main(VS_INPUT input) { + PS_INPUT output; + output.pos = float4(input.pos.xy, 0.f, 1.f); + output.uv = input.uv; + return output; + }"#; + + const PS: &str = r#" + struct PS_INPUT { + float4 pos: SV_POSITION; + float2 uv: TEXCOORD0; + }; + + SamplerState sampler0: register(s0); + Texture2D texture0: register(t0); + + float4 main(PS_INPUT input): SV_Target { + float4 out_col = texture0.Sample(sampler0, input.uv); + return out_col; + }"#; + + let vtx_shader: ID3DBlob = util::try_out_err_blob(|v, err_blob| { + D3DCompile( + VS.as_ptr() as _, + VS.len(), + None, + None, + None::<&ID3DInclude>, + s!("main\0"), + s!("vs_5_0\0"), + 0, + 0, + v, + Some(err_blob), + ) + }) + .map_err(util::print_error_blob("Compiling vertex shader")) + .expect("D3DCompile"); + + let pix_shader = util::try_out_err_blob(|v, err_blob| { + D3DCompile( + PS.as_ptr() as _, + PS.len(), + None, + None, + None::<&ID3DInclude>, + s!("main\0"), + s!("ps_5_0\0"), + 0, + 0, + v, + Some(err_blob), + ) + }) + .map_err(util::print_error_blob("Compiling pixel shader")) + .expect("D3DCompile"); + + let input_element_desc = [ + D3D12_INPUT_ELEMENT_DESC { + SemanticName: s!("POSITION"), + SemanticIndex: 0, + Format: DXGI_FORMAT_R32G32_FLOAT, + InputSlot: 0, + AlignedByteOffset: offset_of!(Vertex, pos) as u32, + InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + InstanceDataStepRate: 0, + }, + D3D12_INPUT_ELEMENT_DESC { + SemanticName: s!("TEXCOORD"), + SemanticIndex: 0, + Format: DXGI_FORMAT_R32G32_FLOAT, + InputSlot: 0, + AlignedByteOffset: offset_of!(Vertex, uv) as u32, + InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + InstanceDataStepRate: 0, + }, + ]; + + let pso_desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC { + pRootSignature: ManuallyDrop::new(Some(root_signature.clone())), + NodeMask: 0, + PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, + SampleMask: u32::MAX, + NumRenderTargets: 1, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + Flags: D3D12_PIPELINE_STATE_FLAG_NONE, + RTVFormats: [ + DXGI_FORMAT_R8G8B8A8_UNORM, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ], + DSVFormat: DXGI_FORMAT_D32_FLOAT, + VS: D3D12_SHADER_BYTECODE { + pShaderBytecode: unsafe { vtx_shader.GetBufferPointer() }, + BytecodeLength: unsafe { vtx_shader.GetBufferSize() }, + }, + PS: D3D12_SHADER_BYTECODE { + pShaderBytecode: unsafe { pix_shader.GetBufferPointer() }, + BytecodeLength: unsafe { pix_shader.GetBufferSize() }, + }, + InputLayout: D3D12_INPUT_LAYOUT_DESC { + pInputElementDescs: input_element_desc.as_ptr(), + NumElements: 2, + }, + BlendState: D3D12_BLEND_DESC { + AlphaToCoverageEnable: false.into(), + IndependentBlendEnable: false.into(), + RenderTarget: [ + D3D12_RENDER_TARGET_BLEND_DESC { + BlendEnable: true.into(), + LogicOpEnable: false.into(), + SrcBlend: D3D12_BLEND_SRC_ALPHA, + DestBlend: D3D12_BLEND_INV_SRC_ALPHA, + BlendOp: D3D12_BLEND_OP_ADD, + SrcBlendAlpha: D3D12_BLEND_ONE, + DestBlendAlpha: D3D12_BLEND_INV_SRC_ALPHA, + BlendOpAlpha: D3D12_BLEND_OP_ADD, + LogicOp: Default::default(), + RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as _, + }, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ], + }, + RasterizerState: D3D12_RASTERIZER_DESC { + FillMode: D3D12_FILL_MODE_SOLID, + CullMode: D3D12_CULL_MODE_NONE, + FrontCounterClockwise: false.into(), + DepthBias: D3D12_DEFAULT_DEPTH_BIAS, + DepthBiasClamp: D3D12_DEFAULT_DEPTH_BIAS_CLAMP, + SlopeScaledDepthBias: D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS, + DepthClipEnable: true.into(), + MultisampleEnable: false.into(), + AntialiasedLineEnable: false.into(), + ForcedSampleCount: 0, + ConservativeRaster: D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF, + }, + ..Default::default() + }; + + let pipeline_state: ID3D12PipelineState = device.CreateGraphicsPipelineState(&pso_desc)?; + pipeline_state.SetName(w!("hudhook Compositor Pipeline State"))?; + let _ = ManuallyDrop::into_inner(pso_desc.pRootSignature); + + Ok((root_signature, pipeline_state)) +} diff --git a/src/compositor/dx9.rs b/src/compositor/dx9.rs new file mode 100644 index 00000000..97977b1d --- /dev/null +++ b/src/compositor/dx9.rs @@ -0,0 +1,137 @@ +use std::mem; +use std::ptr::{self, null_mut}; + +use windows::core::Result; +use windows::Foundation::Numerics::Matrix4x4; +use windows::Win32::Foundation::HWND; +use windows::Win32::Graphics::Direct3D12::*; +use windows::Win32::Graphics::Direct3D9::*; + +use crate::renderer::RenderEngine; +use crate::util::{self, try_out_param}; + +const D3DTS_WORLDMATRIX: D3DTRANSFORMSTATETYPE = D3DTRANSFORMSTATETYPE(256); +const 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, +}; + +const D3DFVF_CUSTOMVERTEX: u32 = D3DFVF_XYZ | D3DFVF_TEX1; + +#[repr(C)] +struct Vertex { + pos: [f32; 3], + uv: [f32; 2], +} + +const VERTICES: [Vertex; 6] = [ + Vertex { pos: [-1.0, 1.0, 0.0], uv: [0.0, 0.0] }, + Vertex { pos: [1.0, 1.0, 0.0], uv: [1.0, 0.0] }, + Vertex { pos: [-1.0, -1.0, 0.0], uv: [0.0, 1.0] }, + Vertex { pos: [1.0, 1.0, 0.0], uv: [1.0, 0.0] }, + Vertex { pos: [1.0, -1.0, 0.0], uv: [1.0, 1.0] }, + Vertex { pos: [-1.0, -1.0, 0.0], uv: [0.0, 1.0] }, +]; + +pub struct Compositor { + device: IDirect3DDevice9, + vertex_buffer: IDirect3DVertexBuffer9, + texture: IDirect3DTexture9, +} + +impl Compositor { + pub fn new(device: &IDirect3DDevice9, target_hwnd: HWND) -> Result { + let (width, height) = util::win_size(target_hwnd); + + let vertex_buffer = try_out_param(|v| unsafe { + device.CreateVertexBuffer( + mem::size_of_val(&VERTICES) as u32, + 0, + D3DFVF_CUSTOMVERTEX, + D3DPOOL_DEFAULT, + v, + null_mut(), + ) + })?; + let vertex_buffer = vertex_buffer.unwrap(); + + unsafe { + let mut p_vertices: *mut u8 = null_mut(); + vertex_buffer.Lock(0, 0, &mut p_vertices as *mut *mut u8 as _, 0)?; + ptr::copy_nonoverlapping( + VERTICES.as_ptr() as *const u8, + p_vertices, + mem::size_of_val(&VERTICES), + ); + vertex_buffer.Unlock()?; + } + + let texture = util::try_out_ptr(|v| unsafe { + device.CreateTexture( + width as u32, + height as u32, + 1, + D3DUSAGE_DYNAMIC as _, + D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, + v, + ptr::null_mut(), + ) + })?; + + Ok(Self { device: device.clone(), vertex_buffer, texture }) + } + + pub fn composite(&self, engine: &RenderEngine, resource: ID3D12Resource) -> Result<()> { + unsafe { + self.device.BeginScene()?; + let rect = util::try_out_param(|v| self.texture.LockRect(0, v, ptr::null_mut(), 0))?; + + engine.copy_texture(resource, rect.pBits as *mut u8)?; + + self.texture.UnlockRect(0)?; + + self.device + .SetRenderTarget(0, &self.device.GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO)?)?; + self.device.SetPixelShader(None)?; + self.device.SetVertexShader(None)?; + self.device.SetTexture(0, &self.texture)?; + self.device.SetRenderState(D3DRS_ALPHABLENDENABLE, true.into())?; + self.device.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA.0)?; + self.device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA.0)?; + self.device.SetRenderState(D3DRS_LIGHTING, false.into())?; + self.device.SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_NONE.0 as u32)?; + self.device.SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_NONE.0 as u32)?; + + self.device.SetTransform(D3DTS_WORLDMATRIX, &MAT_IDENTITY).unwrap(); + self.device.SetTransform(D3DTS_VIEW, &MAT_IDENTITY).unwrap(); + self.device.SetTransform(D3DTS_PROJECTION, &MAT_IDENTITY).unwrap(); + + self.device.SetFVF(D3DFVF_CUSTOMVERTEX)?; + self.device.SetStreamSource( + 0, + &self.vertex_buffer, + 0, + mem::size_of::() as u32, + )?; + self.device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2)?; + self.device.EndScene()?; + } + + Ok(()) + } +} diff --git a/src/compositor/mod.rs b/src/compositor/mod.rs new file mode 100644 index 00000000..b7c235de --- /dev/null +++ b/src/compositor/mod.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "dx11")] +pub mod dx11; +#[cfg(feature = "dx12")] +pub mod dx12; +#[cfg(feature = "dx9")] +pub mod dx9; +#[cfg(feature = "opengl3")] +pub mod opengl3; diff --git a/src/compositor/opengl3.rs b/src/compositor/opengl3.rs new file mode 100644 index 00000000..bfbae595 --- /dev/null +++ b/src/compositor/opengl3.rs @@ -0,0 +1,368 @@ +use std::cell::RefCell; +use std::ffi::CString; +use std::os::raw::c_void; +use std::{mem, ptr}; + +use gl::types::{GLchar, GLint, GLuint}; +use once_cell::sync::OnceCell; +use windows::core::{s, Result, PCSTR}; +use windows::Win32::Foundation::{FARPROC, HINSTANCE}; +use windows::Win32::Graphics::Direct3D12::ID3D12Resource; +use windows::Win32::Graphics::OpenGL::wglGetProcAddress; +use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; + +use crate::renderer::RenderEngine; + +mod gl { + #![cfg_attr( + feature = "cargo-clippy", + allow( + clippy::unreadable_literal, + clippy::too_many_arguments, + clippy::unused_unit, + clippy::upper_case_acronyms, + clippy::manual_non_exhaustive + ) + )] + + include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); +} + +unsafe fn load_func(function_string: CString) -> *const c_void { + static OPENGL3_LIB: OnceCell = OnceCell::new(); + let module = OPENGL3_LIB + .get_or_init(|| LoadLibraryA(s!("opengl32.dll\0")).expect("LoadLibraryA").into()); + + 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 _ + } +} + +pub struct Compositor { + gl: gl::Gl, + program: GLuint, + texture_loc: GLuint, + ebo: GLuint, + texture: GLuint, + texture_data: RefCell>, +} + +impl Compositor { + pub fn new() -> Result { + const VS: &[u8] = b" + #version 130 + + in vec2 Position; + in vec2 UV; + out vec2 Frag_UV; + void main() + { + const vec2 positions[4] = vec2[]( + vec2(-1, 1), + vec2(+1, 1), + vec2(-1, -1), + vec2(+1, -1) + ); + const vec2 coords[4] = vec2[]( + vec2(0, 0), + vec2(1, 0), + vec2(0, 1), + vec2(1, 1) + ); + Frag_UV = coords[gl_VertexID]; + gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); + } + \0"; + + const FS: &[u8] = b" + #version 130 + + uniform sampler2D Texture; + in vec2 Frag_UV; + out vec4 Out_Color; + void main() + { + Out_Color = texture(Texture, Frag_UV.st); + } + \0"; + + const ELEMENTS: [u16; 6] = [0, 1, 2, 1, 3, 2]; + + tracing::trace!("Loading"); + let gl = gl::Gl::load_with(|s| unsafe { load_func(CString::new(s).unwrap()) }); + tracing::trace!("Loaded"); + + unsafe { + let program = gl.CreateProgram(); + let vertex_shader = gl.CreateShader(gl::VERTEX_SHADER); + let fragment_shader = gl.CreateShader(gl::FRAGMENT_SHADER); + let vertex_source = [VS.as_ptr() as *const GLchar]; + let fragment_source = [FS.as_ptr() as *const GLchar]; + let vertex_source_len = [VS.len() as i32]; + let fragment_source_len = [FS.len() as i32]; + gl.ShaderSource(vertex_shader, 1, vertex_source.as_ptr(), vertex_source_len.as_ptr()); + gl.ShaderSource( + fragment_shader, + 1, + fragment_source.as_ptr(), + fragment_source_len.as_ptr(), + ); + gl.CompileShader(vertex_shader); + gl.CompileShader(fragment_shader); + gl.AttachShader(program, vertex_shader); + gl.AttachShader(program, fragment_shader); + gl.LinkProgram(program); + gl.DeleteShader(vertex_shader); + gl.DeleteShader(fragment_shader); + + let texture_loc = gl.GetUniformLocation(program, b"Texture\0".as_ptr() as _) as GLuint; + + let ebo = out_param(|x| gl.GenBuffers(1, x)); + let texture = out_param(|x| gl.GenTextures(1, x)); + + gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + gl.BufferData( + gl::ELEMENT_ARRAY_BUFFER, + mem::size_of_val(&ELEMENTS) as _, + ELEMENTS.as_ptr() as _, + gl::STREAM_DRAW, + ); + + let mut bound_texture = 0; + gl.GetIntegerv(gl::TEXTURE_BINDING_2D, &mut bound_texture); + + gl.ActiveTexture(gl::TEXTURE0); + gl.BindTexture(gl::TEXTURE_2D, texture); + gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _); + gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); + gl.BindTexture(gl::TEXTURE_2D, bound_texture as _); + + Ok(Compositor { + gl, + program, + ebo, + texture, + texture_loc, + texture_data: RefCell::new(Vec::new()), + }) + } + } + + pub fn composite(&self, engine: &RenderEngine, source: ID3D12Resource) -> Result<()> { + let gl = &self.gl; + + unsafe { + let desc = source.GetDesc(); + + let backup = backup(gl); + + gl.Enable(gl::BLEND); + gl.BlendEquation(gl::FUNC_ADD); + gl.BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + gl.Disable(gl::CULL_FACE); + gl.Disable(gl::DEPTH_TEST); + gl.Enable(gl::SCISSOR_TEST); + gl.PolygonMode(gl::FRONT_AND_BACK, gl::FILL); + + gl.Viewport(0, 0, desc.Width as _, desc.Height as _); + gl.UseProgram(self.program); + if gl.BindSampler.is_loaded() { + gl.BindSampler(0, 0); + } + gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); + + gl.ActiveTexture(gl::TEXTURE0); + gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _); + gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); + gl.BindTexture(gl::TEXTURE_2D, self.texture); + gl.Uniform1i(self.texture_loc as _, 0); + + let mut texture_data = self.texture_data.borrow_mut(); + texture_data.resize(desc.Width as usize * desc.Height as usize * 4, 0); + engine.copy_texture(source, texture_data.as_mut_ptr())?; + + gl.TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA as _, + desc.Width as _, + desc.Height as _, + 0, + gl::BGRA, + gl::UNSIGNED_BYTE, + texture_data.as_ptr() as _, + ); + + gl.Scissor(0, 0, desc.Width as _, desc.Height as _); + gl.DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_SHORT, ptr::null()); + + restore(gl, backup); + } + + Ok(()) + } +} + +struct Backup { + last_active_texture: i32, + last_program: i32, + last_texture: i32, + last_sampler: i32, + last_array_buffer: i32, + last_element_array_buffer: i32, + last_vertex_array: i32, + last_polygon_mode: [i32; 2], + last_viewport: [i32; 4], + last_scissor_box: [i32; 4], + last_blend_src_rgb: i32, + last_blend_dst_rgb: i32, + last_blend_src_alpha: i32, + last_blend_dst_alpha: i32, + last_blend_equation_rgb: i32, + last_blend_equation_alpha: i32, + last_enable_blend: bool, + last_enable_cull_face: bool, + last_enable_depth_test: bool, + last_enable_scissor_test: bool, +} + +unsafe fn backup(gl: &gl::Gl) -> Backup { + let last_active_texture = out_param(|x| gl.GetIntegerv(gl::ACTIVE_TEXTURE, x)); + gl.ActiveTexture(gl::TEXTURE0); + let last_program = out_param(|x| gl.GetIntegerv(gl::CURRENT_PROGRAM, x)); + let last_texture = out_param(|x| gl.GetIntegerv(gl::TEXTURE_BINDING_2D, x)); + let last_sampler = if gl.BindSampler.is_loaded() { + out_param(|x| gl.GetIntegerv(gl::SAMPLER_BINDING, x)) + } else { + 0 + }; + let last_array_buffer = out_param(|x| gl.GetIntegerv(gl::ARRAY_BUFFER_BINDING, x)); + let last_element_array_buffer = + out_param(|x| gl.GetIntegerv(gl::ELEMENT_ARRAY_BUFFER_BINDING, x)); + let last_vertex_array = out_param(|x| gl.GetIntegerv(gl::VERTEX_ARRAY_BINDING, x)); + let last_polygon_mode = + out_param(|x: &mut [GLint; 2]| gl.GetIntegerv(gl::POLYGON_MODE, x.as_mut_ptr())); + let last_viewport = + out_param(|x: &mut [GLint; 4]| gl.GetIntegerv(gl::VIEWPORT, x.as_mut_ptr())); + let last_scissor_box = + out_param(|x: &mut [GLint; 4]| gl.GetIntegerv(gl::SCISSOR_BOX, x.as_mut_ptr())); + let last_blend_src_rgb = out_param(|x| gl.GetIntegerv(gl::BLEND_SRC_RGB, x)); + let last_blend_dst_rgb = out_param(|x| gl.GetIntegerv(gl::BLEND_DST_RGB, x)); + let last_blend_src_alpha = out_param(|x| gl.GetIntegerv(gl::BLEND_SRC_ALPHA, x)); + let last_blend_dst_alpha = out_param(|x| gl.GetIntegerv(gl::BLEND_DST_ALPHA, x)); + let last_blend_equation_rgb = out_param(|x| gl.GetIntegerv(gl::BLEND_EQUATION_RGB, x)); + let last_blend_equation_alpha = out_param(|x| gl.GetIntegerv(gl::BLEND_EQUATION_ALPHA, x)); + let last_enable_blend = gl.IsEnabled(gl::BLEND) == gl::TRUE; + let last_enable_cull_face = gl.IsEnabled(gl::CULL_FACE) == gl::TRUE; + let last_enable_depth_test = gl.IsEnabled(gl::DEPTH_TEST) == gl::TRUE; + let last_enable_scissor_test = gl.IsEnabled(gl::SCISSOR_TEST) == gl::TRUE; + + Backup { + last_active_texture, + last_program, + last_texture, + last_sampler, + last_array_buffer, + last_element_array_buffer, + last_vertex_array, + last_polygon_mode, + last_viewport, + last_scissor_box, + last_blend_src_rgb, + last_blend_dst_rgb, + last_blend_src_alpha, + last_blend_dst_alpha, + last_blend_equation_rgb, + last_blend_equation_alpha, + last_enable_blend, + last_enable_cull_face, + last_enable_depth_test, + last_enable_scissor_test, + } +} + +unsafe fn restore(gl: &gl::Gl, backup: Backup) { + let Backup { + last_active_texture, + last_program, + last_texture, + last_sampler, + last_array_buffer, + last_element_array_buffer, + last_vertex_array, + last_polygon_mode, + last_viewport, + last_scissor_box, + last_blend_src_rgb, + last_blend_dst_rgb, + last_blend_src_alpha, + last_blend_dst_alpha, + last_blend_equation_rgb, + last_blend_equation_alpha, + last_enable_blend, + last_enable_cull_face, + last_enable_depth_test, + last_enable_scissor_test, + } = backup; + gl.UseProgram(last_program as _); + gl.BindTexture(gl::TEXTURE_2D, last_texture as _); + if gl.BindSampler.is_loaded() { + gl.BindSampler(0, last_sampler as _); + } + gl.ActiveTexture(last_active_texture as _); + gl.BindVertexArray(last_vertex_array as _); + gl.BindBuffer(gl::ARRAY_BUFFER, last_array_buffer as _); + gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, last_element_array_buffer as _); + gl.BlendEquationSeparate(last_blend_equation_rgb as _, last_blend_equation_alpha as _); + gl.BlendFuncSeparate( + last_blend_src_rgb as _, + last_blend_dst_rgb as _, + last_blend_src_alpha as _, + last_blend_dst_alpha as _, + ); + if last_enable_blend { + gl.Enable(gl::BLEND) + } else { + gl.Disable(gl::BLEND) + }; + if last_enable_cull_face { + gl.Enable(gl::CULL_FACE) + } else { + gl.Disable(gl::CULL_FACE) + }; + if last_enable_depth_test { + gl.Enable(gl::DEPTH_TEST) + } else { + gl.Disable(gl::DEPTH_TEST) + }; + if last_enable_scissor_test { + gl.Enable(gl::SCISSOR_TEST) + } else { + gl.Disable(gl::SCISSOR_TEST) + }; + gl.PolygonMode(gl::FRONT_AND_BACK, last_polygon_mode[0] as _); + gl.Viewport( + last_viewport[0] as _, + last_viewport[1] as _, + last_viewport[2] as _, + last_viewport[3] as _, + ); + gl.Scissor( + last_scissor_box[0] as _, + last_scissor_box[1] as _, + last_scissor_box[2] as _, + last_scissor_box[3] as _, + ); +} + +fn out_param(f: F) -> T +where + F: FnOnce(&mut T), +{ + let mut val = Default::default(); + f(&mut val); + val +} diff --git a/src/hooks/dx11.rs b/src/hooks/dx11.rs index 6aa1c7b7..5a559395 100644 --- a/src/hooks/dx11.rs +++ b/src/hooks/dx11.rs @@ -1,10 +1,13 @@ use std::ffi::c_void; use std::mem; -use std::sync::OnceLock; - -use tracing::{info, trace}; -use windows::core::{Interface, HRESULT}; -use windows::Win32::Foundation::BOOL; +use std::sync::atomic::Ordering; +use std::sync::{Arc, OnceLock}; + +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use tracing::{error, trace}; +use windows::core::{Error, Interface, Result, HRESULT}; +use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, WPARAM}; use windows::Win32::Graphics::Direct3D::{ D3D_DRIVER_TYPE_NULL, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_11_0, }; @@ -19,11 +22,13 @@ use windows::Win32::Graphics::Dxgi::Common::{ use windows::Win32::Graphics::Dxgi::{ IDXGISwapChain, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_EFFECT_DISCARD, DXGI_USAGE_RENDER_TARGET_OUTPUT, }; +use windows::Win32::UI::WindowsAndMessaging::{CallWindowProcW, DefWindowProcW}; use super::DummyHwnd; +use crate::compositor::dx11::Compositor; use crate::mh::MhHook; -use crate::renderer::RenderState; -use crate::{Hooks, ImguiRenderLoop}; +use crate::pipeline::{Pipeline, PipelineMessage, PipelineSharedState}; +use crate::{util, Hooks, ImguiRenderLoop}; type DXGISwapChainPresentType = unsafe extern "system" fn(This: IDXGISwapChain, SyncInterval: u32, Flags: u32) -> HRESULT; @@ -33,33 +38,82 @@ struct Trampolines { } static mut TRAMPOLINES: OnceLock = OnceLock::new(); +static mut PIPELINE: OnceCell<(Mutex>, Arc)> = + OnceCell::new(); +static mut RENDER_LOOP: OnceCell> = OnceCell::new(); + +unsafe fn init_pipeline( + swap_chain: &IDXGISwapChain, +) -> Result<(Mutex>, Arc)> { + let hwnd = util::try_out_param(|v| swap_chain.GetDesc(v)).map(|desc| desc.OutputWindow)?; + let compositor = Compositor::new(&swap_chain.GetDevice()?)?; + + let Some(render_loop) = RENDER_LOOP.take() else { + return Err(Error::new(HRESULT(-1), "Render loop not yet initialized".into())); + }; + + let (pipeline, shared_state) = Pipeline::new(hwnd, imgui_wnd_proc, compositor, render_loop) + .map_err(|(e, render_loop)| { + RENDER_LOOP.get_or_init(move || render_loop); + e + })?; + + Ok((Mutex::new(pipeline), shared_state)) +} + +fn render(swap_chain: &IDXGISwapChain) -> Result<()> { + let (pipeline, _) = unsafe { PIPELINE.get_or_try_init(|| init_pipeline(swap_chain)) }?; + + let Some(mut pipeline) = pipeline.try_lock() else { + return Err(Error::new(HRESULT(-1), "Could not lock pipeline".into())); + }; + + let source = pipeline.render()?; + let handle = pipeline.engine_mut().create_shared_handle(source)?; + + pipeline.compositor_mut().composite(handle, swap_chain)?; + + Ok(()) +} + +unsafe extern "system" fn imgui_wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + let Some(shared_state) = PIPELINE.get().map(|(_, shared_state)| shared_state) else { + return DefWindowProcW(hwnd, msg, wparam, lparam); + }; + + let _ = shared_state.tx.send(PipelineMessage(hwnd, msg, wparam, lparam)); + + // CONCURRENCY: as the message interpretation now happens out of band, this + // expresses the intent as of *before* the current message was received. + let should_block_messages = shared_state.should_block_events.load(Ordering::SeqCst); + + if should_block_messages { + LRESULT(1) + } else { + CallWindowProcW(Some(shared_state.wnd_proc), hwnd, msg, wparam, lparam) + } +} unsafe extern "system" fn dxgi_swap_chain_present_impl( - p_this: IDXGISwapChain, + swap_chain: IDXGISwapChain, sync_interval: u32, flags: u32, ) -> HRESULT { let Trampolines { dxgi_swap_chain_present } = TRAMPOLINES.get().expect("DirectX 11 trampolines 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 dxgi_swap_chain_present(p_this, sync_interval, flags); + if let Err(e) = render(&swap_chain) { + util::print_dxgi_debug_messages(); + error!("Render error: {e:?}"); } - 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 - }); - - RenderState::render(hwnd); - trace!("Call IDXGISwapChain::Present trampoline"); - dxgi_swap_chain_present(p_this, sync_interval, flags) + dxgi_swap_chain_present(swap_chain, sync_interval, flags) } fn get_target_addrs() -> DXGISwapChainPresentType { @@ -132,7 +186,7 @@ impl ImguiDx11Hooks { ) .expect("couldn't create IDXGISwapChain::Present hook"); - RenderState::set_render_loop(t); + RENDER_LOOP.get_or_init(|| Box::new(t)); TRAMPOLINES.get_or_init(|| Trampolines { dxgi_swap_chain_present: mem::transmute(hook_present.trampoline()), }); @@ -155,7 +209,8 @@ impl Hooks for ImguiDx11Hooks { } unsafe fn unhook(&mut self) { - RenderState::cleanup(); TRAMPOLINES.take(); + PIPELINE.take(); + RENDER_LOOP.take(); // should already be null } } diff --git a/src/hooks/dx12.rs b/src/hooks/dx12.rs index 0ce517f8..bb129fb0 100644 --- a/src/hooks/dx12.rs +++ b/src/hooks/dx12.rs @@ -1,31 +1,34 @@ use std::ffi::c_void; use std::mem; -use std::sync::OnceLock; +use std::sync::atomic::Ordering; +use std::sync::{Arc, OnceLock}; -use tracing::{debug, info, trace}; -use windows::core::{Interface, HRESULT}; -use windows::Win32::Foundation::BOOL; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use tracing::{error, trace}; +use windows::core::{Error, Interface, Result, HRESULT}; +use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, WPARAM}; use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0; use windows::Win32::Graphics::Direct3D12::{ - D3D12CreateDevice, ID3D12CommandQueue, ID3D12Device, D3D12_COMMAND_LIST_TYPE_DIRECT, - D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, + D3D12CreateDevice, ID3D12CommandList, ID3D12CommandQueue, ID3D12Device, ID3D12Resource, + 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, + CreateDXGIFactory1, IDXGIFactory1, IDXGISwapChain, IDXGISwapChain3, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH, DXGI_SWAP_EFFECT_FLIP_DISCARD, DXGI_USAGE_RENDER_TARGET_OUTPUT, }; +use windows::Win32::UI::WindowsAndMessaging::{CallWindowProcW, DefWindowProcW}; use super::DummyHwnd; +use crate::compositor::dx12::Compositor; use crate::mh::MhHook; -use crate::renderer::RenderState; -use crate::util::try_out_ptr; -use crate::{Hooks, ImguiRenderLoop}; +use crate::pipeline::{Pipeline, PipelineMessage, PipelineSharedState}; +use crate::{util, Hooks, ImguiRenderLoop}; type DXGISwapChainPresentType = unsafe extern "system" fn(This: IDXGISwapChain3, SyncInterval: u32, Flags: u32) -> HRESULT; @@ -39,39 +42,103 @@ type DXGISwapChainResizeBuffersType = unsafe extern "system" fn( flags: u32, ) -> HRESULT; +type D3D12CommandQueueExecuteCommandListsType = unsafe extern "system" fn( + This: ID3D12CommandQueue, + num_command_lists: u32, + command_lists: *mut ID3D12CommandList, +); + struct Trampolines { dxgi_swap_chain_present: DXGISwapChainPresentType, dxgi_swap_chain_resize_buffers: DXGISwapChainResizeBuffersType, + d3d12_command_queue_execute_command_lists: D3D12CommandQueueExecuteCommandListsType, } static mut TRAMPOLINES: OnceLock = OnceLock::new(); +static mut PIPELINE: OnceCell<(Mutex>, Arc)> = + OnceCell::new(); +static mut COMMAND_QUEUE: OnceCell = OnceCell::new(); +static mut RENDER_LOOP: OnceCell> = OnceCell::new(); + +unsafe fn init_pipeline( + swap_chain: &IDXGISwapChain3, +) -> Result<(Mutex>, Arc)> { + let Some(command_queue) = COMMAND_QUEUE.get() else { + return Err(Error::new(HRESULT(-1), "Command queue not yet initialized".into())); + }; + + let compositor = Compositor::new(command_queue)?; + + let hwnd = util::try_out_param(|v| swap_chain.GetDesc(v)).map(|desc| desc.OutputWindow)?; + + let Some(render_loop) = RENDER_LOOP.take() else { + return Err(Error::new(HRESULT(-1), "Render loop not yet initialized".into())); + }; + + let (pipeline, shared_state) = Pipeline::new(hwnd, imgui_wnd_proc, compositor, render_loop) + .map_err(|(e, render_loop)| { + RENDER_LOOP.get_or_init(move || render_loop); + e + })?; + + Ok((Mutex::new(pipeline), shared_state)) +} + +fn render(swap_chain: &IDXGISwapChain3) -> Result<()> { + let (pipeline, _) = unsafe { PIPELINE.get_or_try_init(|| init_pipeline(swap_chain)) }?; + + let Some(mut pipeline) = pipeline.try_lock() else { + return Err(Error::new(HRESULT(-1), "Could not lock pipeline".into())); + }; + + let source = pipeline.render()?; + let target: ID3D12Resource = + unsafe { swap_chain.GetBuffer(swap_chain.GetCurrentBackBufferIndex())? }; + + pipeline.compositor_mut().composite(source, target)?; + + Ok(()) +} + +unsafe extern "system" fn imgui_wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + let Some((_, shared_state)) = PIPELINE.get() else { + return DefWindowProcW(hwnd, msg, wparam, lparam); + }; + + let _ = shared_state.tx.send(PipelineMessage(hwnd, msg, wparam, lparam)); + + // CONCURRENCY: as the message interpretation now happens out of band, this + // expresses the intent as of *before* the current message was received. + let should_block_messages = shared_state.should_block_events.load(Ordering::SeqCst); + + if should_block_messages { + LRESULT(1) + } else { + CallWindowProcW(Some(shared_state.wnd_proc), hwnd, msg, wparam, lparam) + } +} + unsafe extern "system" fn dxgi_swap_chain_present_impl( - p_this: IDXGISwapChain3, + swap_chain: IDXGISwapChain3, sync_interval: u32, flags: u32, ) -> HRESULT { let Trampolines { dxgi_swap_chain_present, .. } = TRAMPOLINES.get().expect("DirectX 12 trampolines 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 dxgi_swap_chain_present(p_this, sync_interval, flags); + if let Err(e) = render(&swap_chain) { + util::print_dxgi_debug_messages(); + error!("Render error: {e:?}"); } - 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 - }); - - RenderState::render(hwnd); - trace!("Call IDXGISwapChain::Present trampoline"); - dxgi_swap_chain_present(p_this, sync_interval, flags) + dxgi_swap_chain_present(swap_chain, sync_interval, flags) } unsafe extern "system" fn dxgi_swap_chain_resize_buffers_impl( @@ -89,35 +156,37 @@ unsafe extern "system" fn dxgi_swap_chain_resize_buffers_impl( dxgi_swap_chain_resize_buffers(p_this, buffer_count, width, height, new_format, flags) } -unsafe fn print_dxgi_debug_messages() { - let diq: IDXGIInfoQueue = DXGIGetDebugInterface1(0).unwrap(); - - 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(); - debug!( - "[DIQ] {}", - String::from_utf8_lossy(std::slice::from_raw_parts( - diqm.pDescription, - diqm.DescriptionByteLength - 1 - )) - ); - } - diq.ClearStoredMessages(DXGI_DEBUG_ALL); +unsafe extern "system" fn d3d12_command_queue_execute_command_lists_impl( + command_queue: ID3D12CommandQueue, + num_command_lists: u32, + command_lists: *mut ID3D12CommandList, +) { + trace!( + "ID3D12CommandQueue::ExecuteCommandLists({command_queue:?}, {num_command_lists}, \ + {command_lists:p}) invoked", + ); + + let Trampolines { d3d12_command_queue_execute_command_lists, .. } = + TRAMPOLINES.get().expect("DirectX 12 trampolines uninitialized"); + + // TODO check command queue type + COMMAND_QUEUE.get_or_init(|| command_queue.clone()); + + d3d12_command_queue_execute_command_lists(command_queue, num_command_lists, command_lists); } -fn get_target_addrs() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersType) { +fn get_target_addrs() -> ( + DXGISwapChainPresentType, + DXGISwapChainResizeBuffersType, + D3D12CommandQueueExecuteCommandListsType, +) { let dummy_hwnd = DummyHwnd::new(); 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) }) + util::try_out_ptr(|v| unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_11_0, v) }) .expect("D3D12CreateDevice failed"); let command_queue: ID3D12CommandQueue = unsafe { @@ -130,7 +199,7 @@ fn get_target_addrs() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersTy } .unwrap(); - let swap_chain: IDXGISwapChain = match try_out_ptr(|v| unsafe { + let swap_chain: IDXGISwapChain = match util::try_out_ptr(|v| unsafe { factory .CreateSwapChain( &command_queue, @@ -157,7 +226,7 @@ fn get_target_addrs() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersTy }) { Ok(swap_chain) => swap_chain, Err(e) => { - unsafe { print_dxgi_debug_messages() }; + util::print_dxgi_debug_messages(); panic!("{e:?}"); }, }; @@ -166,11 +235,13 @@ fn get_target_addrs() -> (DXGISwapChainPresentType, DXGISwapChainResizeBuffersTy unsafe { mem::transmute(swap_chain.vtable().Present) }; let resize_buffers_ptr: DXGISwapChainResizeBuffersType = unsafe { mem::transmute(swap_chain.vtable().ResizeBuffers) }; + let cqecl_ptr: D3D12CommandQueueExecuteCommandListsType = + unsafe { mem::transmute(command_queue.vtable().ExecuteCommandLists) }; - (present_ptr, resize_buffers_ptr) + (present_ptr, resize_buffers_ptr, cqecl_ptr) } -pub struct ImguiDx12Hooks([MhHook; 2]); +pub struct ImguiDx12Hooks([MhHook; 3]); impl ImguiDx12Hooks { /// Construct a set of [`MhHook`]s that will render UI via the @@ -179,6 +250,7 @@ impl ImguiDx12Hooks { /// The following functions are hooked: /// - `IDXGISwapChain3::Present` /// - `IDXGISwapChain3::ResizeBuffers` + /// - `ID3D12CommandQueue::ExecuteCommandLists` /// /// # Safety /// @@ -187,8 +259,11 @@ impl ImguiDx12Hooks { where T: ImguiRenderLoop + Send + Sync, { - let (dxgi_swap_chain_present_addr, dxgi_swap_chain_resize_buffers_addr) = - get_target_addrs(); + let ( + dxgi_swap_chain_present_addr, + dxgi_swap_chain_resize_buffers_addr, + d3d12_command_queue_execute_command_lists_addr, + ) = get_target_addrs(); trace!("IDXGISwapChain::Present = {:p}", dxgi_swap_chain_present_addr as *const c_void); let hook_present = MhHook::new( @@ -201,14 +276,21 @@ impl ImguiDx12Hooks { dxgi_swap_chain_resize_buffers_impl as *mut _, ) .expect("couldn't create IDXGISwapChain::ResizeBuffers hook"); + let hook_cqecl = MhHook::new( + d3d12_command_queue_execute_command_lists_addr as *mut _, + d3d12_command_queue_execute_command_lists_impl as *mut _, + ) + .expect("couldn't create ID3D12CommandQueue::ExecuteCommandLists hook"); + + RENDER_LOOP.get_or_init(|| Box::new(t)); - 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()), + d3d12_command_queue_execute_command_lists: mem::transmute(hook_cqecl.trampoline()), }); - Self([hook_present, hook_resize_buffers]) + Self([hook_present, hook_resize_buffers, hook_cqecl]) } } @@ -226,7 +308,9 @@ impl Hooks for ImguiDx12Hooks { } unsafe fn unhook(&mut self) { - RenderState::cleanup(); TRAMPOLINES.take(); + PIPELINE.take(); + COMMAND_QUEUE.take(); + RENDER_LOOP.take(); // should already be null } } diff --git a/src/hooks/dx9.rs b/src/hooks/dx9.rs index 3dc7e13d..d3f478f7 100644 --- a/src/hooks/dx9.rs +++ b/src/hooks/dx9.rs @@ -1,22 +1,26 @@ use std::ffi::c_void; use std::mem; -use std::sync::OnceLock; - -use tracing::trace; -use windows::core::{Interface, HRESULT}; -use windows::Win32::Foundation::{BOOL, HWND, RECT}; +use std::sync::atomic::Ordering; +use std::sync::{Arc, OnceLock}; + +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use tracing::{error, trace}; +use windows::core::{Error, Interface, Result, HRESULT}; +use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, RECT, WPARAM}; use windows::Win32::Graphics::Direct3D9::{ 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 windows::Win32::UI::WindowsAndMessaging::{CallWindowProcW, DefWindowProcW}; use super::DummyHwnd; +use crate::compositor::dx9::Compositor; use crate::mh::MhHook; -use crate::renderer::RenderState; -use crate::util::try_out_ptr; -use crate::{Hooks, ImguiRenderLoop}; +use crate::pipeline::{Pipeline, PipelineMessage, PipelineSharedState}; +use crate::{util, Hooks, ImguiRenderLoop}; type Dx9PresentType = unsafe extern "system" fn( this: IDirect3DDevice9, @@ -31,9 +35,71 @@ struct Trampolines { } static mut TRAMPOLINES: OnceLock = OnceLock::new(); +static mut PIPELINE: OnceCell<(Mutex>, Arc)> = + OnceCell::new(); +static mut RENDER_LOOP: OnceCell> = OnceCell::new(); + +unsafe fn init_pipeline( + device: &IDirect3DDevice9, +) -> Result<(Mutex>, Arc)> { + let mut creation_parameters = Default::default(); + let _ = device.GetCreationParameters(&mut creation_parameters); + let hwnd = creation_parameters.hFocusWindow; + + let compositor = Compositor::new(device, hwnd)?; + + let Some(render_loop) = RENDER_LOOP.take() else { + return Err(Error::new(HRESULT(-1), "Render loop not yet initialized".into())); + }; + + let (pipeline, shared_state) = Pipeline::new(hwnd, imgui_wnd_proc, compositor, render_loop) + .map_err(|(e, render_loop)| { + RENDER_LOOP.get_or_init(move || render_loop); + e + })?; + + Ok((Mutex::new(pipeline), shared_state)) +} + +fn render(device: &IDirect3DDevice9) -> Result<()> { + let (pipeline, _) = unsafe { PIPELINE.get_or_try_init(|| init_pipeline(device)) }?; + + let Some(mut pipeline) = pipeline.try_lock() else { + return Err(Error::new(HRESULT(-1), "Could not lock pipeline".into())); + }; + + let source = pipeline.render()?; + + pipeline.compositor().composite(pipeline.engine(), source)?; + + Ok(()) +} + +unsafe extern "system" fn imgui_wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + let Some(shared_state) = PIPELINE.get().map(|(_, shared_state)| shared_state) else { + return DefWindowProcW(hwnd, msg, wparam, lparam); + }; + + let _ = shared_state.tx.send(PipelineMessage(hwnd, msg, wparam, lparam)); + + // CONCURRENCY: as the message interpretation now happens out of band, this + // expresses the intent as of *before* the current message was received. + let should_block_messages = shared_state.should_block_events.load(Ordering::SeqCst); + + if should_block_messages { + LRESULT(1) + } else { + CallWindowProcW(Some(shared_state.wnd_proc), hwnd, msg, wparam, lparam) + } +} unsafe extern "system" fn dx9_present_impl( - p_this: IDirect3DDevice9, + device: IDirect3DDevice9, psourcerect: *const RECT, pdestrect: *const RECT, hdestwindowoverride: HWND, @@ -42,22 +108,13 @@ unsafe extern "system" fn dx9_present_impl( let Trampolines { dx9_present } = TRAMPOLINES.get().expect("DirectX 12 trampolines 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); + if let Err(e) = render(&device) { + util::print_dxgi_debug_messages(); + error!("Render error: {e:?}"); } - let hwnd = RenderState::setup(|| { - let mut creation_parameters = Default::default(); - let _ = p_this.GetCreationParameters(&mut creation_parameters); - creation_parameters.hFocusWindow - }); - - RenderState::render(hwnd); - trace!("Call IDirect3DDevice9::Present trampoline"); - dx9_present(p_this, psourcerect, pdestrect, hdestwindowoverride, pdirtyregion) + dx9_present(device, psourcerect, pdestrect, hdestwindowoverride, pdirtyregion) } fn get_target_addrs() -> Dx9PresentType { @@ -75,7 +132,7 @@ fn get_target_addrs() -> Dx9PresentType { }; let dummy_hwnd = DummyHwnd::new(); - let device: IDirect3DDevice9 = try_out_ptr(|v| { + let device: IDirect3DDevice9 = util::try_out_ptr(|v| { unsafe { d9.CreateDevice( D3DADAPTER_DEFAULT, @@ -116,7 +173,7 @@ impl ImguiDx9Hooks { let hook_present = MhHook::new(dx9_present_addr as *mut _, dx9_present_impl as *mut _) .expect("couldn't create IDirect3DDevice9::Present hook"); - RenderState::set_render_loop(t); + RENDER_LOOP.get_or_init(|| Box::new(t)); TRAMPOLINES .get_or_init(|| Trampolines { dx9_present: mem::transmute(hook_present.trampoline()) }); @@ -138,7 +195,8 @@ impl Hooks for ImguiDx9Hooks { } unsafe fn unhook(&mut self) { - RenderState::cleanup(); TRAMPOLINES.take(); + PIPELINE.take(); + RENDER_LOOP.take(); } } diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 443b710b..0b9e901c 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -14,9 +14,13 @@ use windows::Win32::UI::WindowsAndMessaging::{ WS_EX_OVERLAPPEDWINDOW, WS_OVERLAPPEDWINDOW, }; +#[cfg(feature = "dx11")] pub mod dx11; +#[cfg(feature = "dx12")] pub mod dx12; +#[cfg(feature = "dx9")] pub mod dx9; +#[cfg(feature = "opengl3")] pub mod opengl3; /// A utility function to retrieve the top level [`HWND`] belonging to this diff --git a/src/hooks/opengl3.rs b/src/hooks/opengl3.rs index 058179aa..6e1563c5 100644 --- a/src/hooks/opengl3.rs +++ b/src/hooks/opengl3.rs @@ -1,15 +1,21 @@ use std::ffi::CString; use std::mem; -use std::sync::OnceLock; - -use tracing::trace; -use windows::core::PCSTR; +use std::sync::atomic::Ordering; +use std::sync::{Arc, OnceLock}; + +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use tracing::{error, trace}; +use windows::core::{Error, Result, HRESULT, PCSTR}; +use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; use windows::Win32::Graphics::Gdi::{WindowFromDC, HDC}; use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; +use windows::Win32::UI::WindowsAndMessaging::{CallWindowProcW, DefWindowProcW}; +use crate::compositor::opengl3::Compositor; use crate::mh::MhHook; -use crate::renderer::RenderState; -use crate::{Hooks, ImguiRenderLoop}; +use crate::pipeline::{Pipeline, PipelineMessage, PipelineSharedState}; +use crate::{util, Hooks, ImguiRenderLoop}; type OpenGl32wglSwapBuffersType = unsafe extern "system" fn(HDC) -> (); @@ -18,21 +24,74 @@ struct Trampolines { } static mut TRAMPOLINES: OnceLock = OnceLock::new(); +static mut PIPELINE: OnceCell<(Mutex>, Arc)> = + OnceCell::new(); +static mut RENDER_LOOP: OnceCell> = OnceCell::new(); + +unsafe fn init_pipeline( + dc: HDC, +) -> Result<(Mutex>, Arc)> { + let hwnd = WindowFromDC(dc); + let compositor = Compositor::new()?; + + let Some(render_loop) = RENDER_LOOP.take() else { + return Err(Error::new(HRESULT(-1), "Render loop not yet initialized".into())); + }; + + let (pipeline, shared_state) = Pipeline::new(hwnd, imgui_wnd_proc, compositor, render_loop) + .map_err(|(e, render_loop)| { + RENDER_LOOP.get_or_init(move || render_loop); + e + })?; + + Ok((Mutex::new(pipeline), shared_state)) +} + +fn render(dc: HDC) -> Result<()> { + let (pipeline, _) = unsafe { PIPELINE.get_or_try_init(|| init_pipeline(dc)) }?; + + let Some(mut pipeline) = pipeline.try_lock() else { + return Err(Error::new(HRESULT(-1), "Could not lock pipeline".into())); + }; + + let source = pipeline.render()?; + pipeline.compositor().composite(pipeline.engine(), source)?; + + Ok(()) +} + +unsafe extern "system" fn imgui_wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + let Some(shared_state) = PIPELINE.get().map(|(_, shared_state)| shared_state) else { + return DefWindowProcW(hwnd, msg, wparam, lparam); + }; + + let _ = shared_state.tx.send(PipelineMessage(hwnd, msg, wparam, lparam)); + + // CONCURRENCY: as the message interpretation now happens out of band, this + // expresses the intent as of *before* the current message was received. + let should_block_messages = shared_state.should_block_events.load(Ordering::SeqCst); + + if should_block_messages { + LRESULT(1) + } else { + CallWindowProcW(Some(shared_state.wnd_proc), hwnd, msg, wparam, lparam) + } +} unsafe extern "system" fn opengl32_wgl_swap_buffers_impl(dc: HDC) { let Trampolines { opengl32_wgl_swap_buffers } = TRAMPOLINES.get().expect("OpenGL3 trampolines 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 opengl32_wgl_swap_buffers(dc); + if let Err(e) = render(dc) { + util::print_dxgi_debug_messages(); + error!("Render error: {e:?}"); } - let hwnd = RenderState::setup(|| WindowFromDC(dc)); - - RenderState::render(hwnd); - trace!("Call OpenGL3 wglSwapBuffers trampoline"); opengl32_wgl_swap_buffers(dc); } @@ -80,7 +139,7 @@ impl ImguiOpenGl3Hooks { .expect("couldn't create opengl32.wglSwapBuffers hook"); // Initialize the render loop and store detours - RenderState::set_render_loop(t); + RENDER_LOOP.get_or_init(move || Box::new(t)); TRAMPOLINES.get_or_init(|| Trampolines { opengl32_wgl_swap_buffers: std::mem::transmute( hook_opengl_wgl_swap_buffers.trampoline(), @@ -105,7 +164,8 @@ impl Hooks for ImguiOpenGl3Hooks { } unsafe fn unhook(&mut self) { - RenderState::cleanup(); TRAMPOLINES.take(); + PIPELINE.take(); + RENDER_LOOP.take(); } } diff --git a/src/inject.rs b/src/inject.rs index d6e09a37..6834d236 100644 --- a/src/inject.rs +++ b/src/inject.rs @@ -228,6 +228,7 @@ unsafe fn get_process_by_name64(name_str: &str) -> Result { let pid = loop { let zero_idx = pe32.szExeFile.iter().position(|&x| x == 0).unwrap_or(pe32.szExeFile.len()); let proc_name = HSTRING::from_wide(&pe32.szExeFile[..zero_idx])?; + println!("{proc_name:?}"); if name == proc_name { break Ok(pe32.th32ProcessID); } diff --git a/src/lib.rs b/src/lib.rs index aeda8f79..a61778e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,7 +113,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; -use imgui::{Io, Ui}; +use imgui::{Context, Io, Ui}; use once_cell::sync::OnceCell; use renderer::RenderEngine; use tracing::error; @@ -128,13 +128,15 @@ pub use {imgui, tracing, windows}; use crate::mh::{MH_ApplyQueued, MH_Initialize, MH_Uninitialize, MhHook, MH_STATUS}; +pub mod compositor; pub mod hooks; #[cfg(feature = "inject")] pub mod inject; pub mod mh; +pub mod pipeline; pub mod renderer; -mod util; +pub mod util; // Global state objects. static mut MODULE: OnceCell = OnceCell::new(); @@ -213,7 +215,7 @@ pub fn eject() { pub trait ImguiRenderLoop { /// Called once at the first occurrence of the hook. Implement this to /// initialize your data. - fn initialize(&mut self, _render_engine: &mut RenderEngine) {} + fn initialize(&mut self, _ctx: &mut Context, _render_engine: &mut RenderEngine) {} /// Called every frame. Use the provided `ui` object to build your UI. fn render(&mut self, ui: &mut Ui); diff --git a/src/pipeline.rs b/src/pipeline.rs new file mode 100644 index 00000000..65ce0524 --- /dev/null +++ b/src/pipeline.rs @@ -0,0 +1,174 @@ +use std::mem; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::Arc; + +use imgui::Context; +use tracing::error; +use windows::core::{Error, Result, HRESULT}; +use windows::Win32::Foundation::{HWND, LPARAM, WPARAM}; +use windows::Win32::Graphics::Direct3D12::ID3D12Resource; +#[cfg(target_arch = "x86")] +use windows::Win32::UI::WindowsAndMessaging::SetWindowLongA; +use windows::Win32::UI::WindowsAndMessaging::{SetWindowLongPtrA, GWLP_WNDPROC}; + +use crate::renderer::input::{imgui_wnd_proc_impl, WndProcType}; +use crate::renderer::RenderEngine; +use crate::{util, ImguiRenderLoop}; + +type RenderLoop = Box; + +pub struct PipelineMessage(pub HWND, pub u32, pub WPARAM, pub LPARAM); + +pub struct PipelineSharedState { + pub should_block_events: AtomicBool, + pub wnd_proc: WndProcType, + pub tx: Sender, +} + +pub struct Pipeline { + hwnd: HWND, + compositor: T, + ctx: Context, + engine: RenderEngine, + render_loop: RenderLoop, + rx: Receiver, + shared_state: Arc, +} + +impl Pipeline { + pub fn new( + hwnd: HWND, + wnd_proc: WndProcType, + compositor: T, + mut render_loop: RenderLoop, + ) -> std::result::Result<(Self, Arc), (Error, RenderLoop)> { + let (width, height) = util::win_size(hwnd); + + let mut ctx = Context::create(); + ctx.io_mut().display_size = [width as f32, height as f32]; + + let mut engine = match RenderEngine::new(&mut ctx, width as u32, height as u32) { + Ok(engine) => engine, + Err(e) => return Err((e, render_loop)), + }; + + render_loop.initialize(&mut ctx, &mut engine); + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + let wnd_proc = unsafe { + mem::transmute(SetWindowLongPtrA(hwnd, GWLP_WNDPROC, wnd_proc as usize as isize)) + }; + + // TODO is this necessary? SetWindowLongPtrA should already decay to + // SetWindowLongA + #[cfg(target_arch = "x86")] + let wnd_proc = + unsafe { mem::transmute(SetWindowLongA(hwnd, GWLP_WNDPROC, wnd_proc as usize as i32)) }; + + let (tx, rx) = mpsc::channel(); + let shared_state = Arc::new(PipelineSharedState { + should_block_events: AtomicBool::new(false), + wnd_proc, + tx, + }); + + Ok(( + Self { + hwnd, + compositor, + ctx, + engine, + render_loop, + rx, + shared_state: Arc::clone(&shared_state), + }, + shared_state, + )) + } + + pub fn render(&mut self) -> Result { + // TODO find a better alternative than allocating each frame + let message_queue = self.rx.try_iter().collect::>(); + + message_queue.into_iter().for_each(|PipelineMessage(hwnd, umsg, wparam, lparam)| { + imgui_wnd_proc_impl(hwnd, umsg, wparam, lparam, self); + }); + + let should_block_events = self.render_loop.should_block_messages(self.ctx.io_mut()); + + self.shared_state.should_block_events.store(should_block_events, Ordering::SeqCst); + + self.engine.render_setup(self.hwnd, &mut self.ctx)?; + self.render_loop.before_render(&mut self.engine); + + let [w, h] = self.ctx.io().display_size; + if w <= 0.0 || h <= 0.0 { + error!("Insufficient display size: {w}x{h}"); + return Err(Error::new(HRESULT(-1), "Insufficient display size".into())); + } + + let ui = self.ctx.frame(); + self.render_loop.render(ui); + let draw_data = self.ctx.render(); + + self.engine.render(draw_data) + } + + pub fn engine(&self) -> &RenderEngine { + &self.engine + } + + pub fn engine_mut(&mut self) -> &mut RenderEngine { + &mut self.engine + } + + pub fn context(&mut self) -> &mut Context { + &mut self.ctx + } + + pub fn compositor(&self) -> &T { + &self.compositor + } + + pub fn compositor_mut(&mut self) -> &mut T { + &mut self.compositor + } + + pub fn render_loop(&mut self) -> &mut RenderLoop { + &mut self.render_loop + } + + pub fn resize(&mut self) { + let (width, height) = util::win_size(self.hwnd); + let (width, height) = (width as u32, height as u32); + + let io = self.ctx.io_mut(); + + if let Err(e) = self.engine.resize(width, height) { + error!("Couldn't resize engine: {e:?}"); + } + + io.display_size = [width as f32, height as f32]; + } + + pub fn cleanup(&mut self) { + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + unsafe { + SetWindowLongPtrA(self.hwnd, GWLP_WNDPROC, self.shared_state.wnd_proc as usize as isize) + }; + + // TODO is this necessary? SetWindowLongPtrA should already decay to + // SetWindowLongA + #[cfg(target_arch = "x86")] + unsafe { + SetWindowLongA(self.hwnd, GWLP_WNDPROC, self.shared_state.wnd_proc as usize as i32) + }; + } +} + +impl Drop for Pipeline { + fn drop(&mut self) { + self.cleanup(); + } +} diff --git a/src/renderer/engine.rs b/src/renderer/engine.rs index 9e82941b..9a1db2ee 100644 --- a/src/renderer/engine.rs +++ b/src/renderer/engine.rs @@ -1,679 +1,577 @@ -use std::cell::RefCell; +// NOTE: see this for ManuallyDrop instanceshttps://github.com/microsoft/windows-rs/issues/2386 + 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}; +use std::mem::ManuallyDrop; +use std::{mem, ptr, slice}; -pub use imgui; use imgui::internal::RawWrapper; -use imgui::{BackendFlags, Context, DrawCmd, DrawData, DrawIdx, DrawVert, TextureId, Ui}; +use imgui::{BackendFlags, Context, DrawCmd, DrawData, DrawIdx, DrawVert, TextureId}; use memoffset::offset_of; -use tracing::{error, trace}; -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_FEATURE_LEVEL_11_1, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, -}; +use tracing::trace; +use windows::core::{s, w, ComInterface, Result}; +use windows::Win32::Foundation::*; +use windows::Win32::Graphics::Direct3D::Fxc::*; +use windows::Win32::Graphics::Direct3D::*; use windows::Win32::Graphics::Direct3D12::*; -use windows::Win32::Graphics::DirectComposition::{ - DCompositionCreateDevice, IDCompositionDevice, IDCompositionTarget, IDCompositionVisual, -}; use windows::Win32::Graphics::Dxgi::Common::*; -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::Dxgi::*; 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 windows::Win32::UI::WindowsAndMessaging::*; 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"), -]; - -// Holds D3D12 resources for a back buffer. -#[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); - } - } - } -} - -// Holds D3D12 buffers for a frame. -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(), - } - } -} - -// RAII wrapper around a [`std::mem::ManuallyDrop`] for a D3D12 resource -// barrier. -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); - } - } -} - -// Holds and manages the lifetimes for the DirectComposition data structures. -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()?; +use crate::util::{self, Fence}; - Ok(()) - } -} - -/// The [`hudhook`](crate) render engine. -/// -/// Most of the operations of this structures are managed by the library itself -/// and are not available to the clients. For this reason, it can't be -/// instantiated directly but only by [`Hooks`](crate::Hooks) implementations -/// via [`RenderState`](crate::renderer::RenderState). pub struct RenderEngine { - target_hwnd: HWND, - - _dxgi_factory: IDXGIFactory2, - _dxgi_adapter: IDXGIAdapter, - device: ID3D12Device, - swap_chain: IDXGISwapChain3, command_queue: ID3D12CommandQueue, + command_allocator: ID3D12CommandAllocator, command_list: ID3D12GraphicsCommandList, - textures_heap: ID3D12DescriptorHeap, + #[allow(unused)] rtv_heap: ID3D12DescriptorHeap, + rtv_heap_start: D3D12_CPU_DESCRIPTOR_HANDLE, + rtv_target: ID3D12Resource, - 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>, + texture_heap: TextureHeap, - font_texture_resource: Option, - root_signature: Option, - pipeline_state: Option, + root_signature: ID3D12RootSignature, + pipeline_state: ID3D12PipelineState, - textures: Vec<(ID3D12Resource, TextureId)>, + vertex_buffer: Buffer, + index_buffer: Buffer, + projection_buffer: [[f32; 4]; 4], - compositor: Compositor, + fence: Fence, } impl RenderEngine { - pub(crate) 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, - }; + pub fn new(ctx: &mut Context, width: u32, height: u32) -> Result { + let device = unsafe { create_device() }?; - 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 (command_queue, command_allocator, command_list) = + unsafe { create_command_objects(&device) }?; - let swap_chain = - unsafe { dxgi_factory.CreateSwapChainForComposition(&command_queue, &sd, None) }? - .cast::()?; - - // Descriptor heap for textures (font + user-defined) - let textures_heap: ID3D12DescriptorHeap = unsafe { - device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { - Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - NumDescriptors: 8, - Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, - NodeMask: 0, - }) - }?; + let (rtv_heap, mut texture_heap) = unsafe { create_heaps(&device) }?; + let rtv_heap_start = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() }; + let rtv_target = unsafe { create_render_target(&device, width, height, rtv_heap_start) }?; + unsafe { rtv_target.SetName(w!("hudhook Render Target")) }?; - 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 (root_signature, pipeline_state) = unsafe { create_shader_program(&device) }?; - let rtv_heap_inc_size = - unsafe { device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) }; + let vertex_buffer = Buffer::new(&device, 5000)?; + let index_buffer = Buffer::new(&device, 10000)?; - let rtv_heap_handle_start = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() }; + let fence = Fence::new(&device)?; - // Build frame contexts. - let frame_contexts: Vec = (0..sd.BufferCount) - .map(|i| unsafe { - FrameContext::new(&device, &swap_chain, rtv_heap_handle_start, rtv_heap_inc_size, i) - }) - .collect::>>()?; - - let frame_resources = - (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 { textures_heap.GetCPUDescriptorHandleForHeapStart() }; - let gpu_desc = unsafe { textures_heap.GetGPUDescriptorHandleForHeapStart() }; - - 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) }?; + let fonts = ctx.fonts(); + let fonts_texture = fonts.build_rgba32_texture(); + fonts.tex_id = unsafe { + texture_heap.create_texture( + fonts_texture.data, + fonts_texture.width, + fonts_texture.height, + ) + }?; Ok(Self { - target_hwnd, - _dxgi_factory: dxgi_factory, - _dxgi_adapter: dxgi_adapter, device, - swap_chain, command_queue, + command_allocator, command_list, - textures_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, - textures: Vec::new(), + rtv_heap_start, + rtv_target, + texture_heap, + root_signature, + pipeline_state, + vertex_buffer, + index_buffer, + projection_buffer: Default::default(), + fence, }) } - pub(crate) fn resize(&mut self) -> Result<()> { - let (width, height) = crate::util::win_size(self.target_hwnd); - - self.frame_contexts.drain(..).for_each(drop); - self.frame_resources.drain(..).for_each(drop); + pub fn load_image(&mut self, data: &[u8], width: u32, height: u32) -> Result { + unsafe { self.texture_heap.create_texture(data, width, height) } + } - let mut sd = Default::default(); - unsafe { self.swap_chain.GetDesc(&mut sd)? }; + pub fn create_shared_handle(&self, resource: ID3D12Resource) -> Result { unsafe { - self.swap_chain.ResizeBuffers( - sd.BufferCount, - width as _, - height as _, - sd.BufferDesc.Format, - sd.Flags, - )? - }; + self.device.CreateSharedHandle( + &resource, + None, + GENERIC_ALL.0, + w!("hudhook Shared Resource"), + ) + } + } - let rtv_heap_inc_size = - unsafe { self.device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) }; - - let rtv_heap_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, - rtv_heap_handle_start, - rtv_heap_inc_size, - i, - ) - }) - .collect::>>()?; + /// # Safety + /// + /// This method copies bytes from the target resource, assumed BGRA, to the + /// address `data` points to. The resource should always be the one + /// emitted by this renderer's `render` method. + /// + /// `data` must be a valid pointer to a region of memory that has at least + /// `width` * `height` * 4 bytes available. + pub unsafe fn copy_texture(&self, resource: ID3D12Resource, data: *mut u8) -> Result<()> { + let desc = resource.GetDesc(); + + let mut placed_footprint: D3D12_PLACED_SUBRESOURCE_FOOTPRINT = Default::default(); + self.device.GetCopyableFootprints( + &desc, + 0, + 1, + 0, + Some(&mut placed_footprint), + None, + None, + None, + ); - self.frame_resources = (0..sd.BufferCount).map(|_| FrameResources::default()).collect(); + let size_aligned = placed_footprint.Footprint.RowPitch * placed_footprint.Footprint.Height; - Ok(()) - } + let staging_resource: ID3D12Resource = util::try_out_ptr(|v| { + self.device.CreateCommittedResource( + &D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_READBACK, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }, + D3D12_HEAP_FLAG_NONE, + &D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 65536, + Width: size_aligned 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, + }, + D3D12_RESOURCE_STATE_PRESENT, + None, + v, + ) + })?; - pub(crate) fn render(&mut self, mut render_loop: F) -> Result<()> { - unsafe { self.setup_io()? }; + let staging_location = D3D12_TEXTURE_COPY_LOCATION { + pResource: ManuallyDrop::new(Some(staging_resource.clone())), + Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, + Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { PlacedFootprint: placed_footprint }, + }; - // Create device objects if necessary. - if self.pipeline_state.is_none() { - unsafe { self.create_device_objects() }?; - } + let source_location = D3D12_TEXTURE_COPY_LOCATION { + pResource: ManuallyDrop::new(Some(resource.clone())), + Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { SubresourceIndex: 0 }, + }; - let idx = unsafe { self.swap_chain.GetCurrentBackBufferIndex() } as usize; - self.frame_contexts[idx].wait_fence(); - self.frame_contexts[idx].incr(); + self.command_allocator.Reset()?; + self.command_list.Reset(&self.command_allocator, None)?; + + self.command_list.CopyTextureRegion(&staging_location, 0, 0, 0, &source_location, None); + + self.command_list.Close()?; + self.command_queue.ExecuteCommandLists(&[Some(self.command_list.cast()?)]); + self.command_queue.Signal(self.fence.fence(), self.fence.value())?; + self.fence.wait()?; + self.fence.incr(); + + let mut resource_ptr = ptr::null_mut(); + staging_resource + .Map(0, None, Some(&mut resource_ptr)) + .inspect_err(|e| tracing::error!("Map: {e:?}"))?; + + // The row pitch has a larger alignment than the width. E.g. a width of 800 + // would occupy 800 * 4 = 3200 bytes but the pitch will be 3328 which is + // aligned to 256. + // + // We must then copy each row individually. This is most likely slower than + // copying the entire thing in one go, but the whole operation + // surprisingly doesn't let the frame rate ever dip below 100fps on my + // machine. Considering that D3D9 applications expect much weaker + // hardware than is available today, this is unlikely to be a bottleneck + // for users. + // + // Please open an issue with benchmarks if this hampers your application in any + // way. + let resource_ptr = resource_ptr as *mut u8; + let height = desc.Height as isize; + let width = 4 * placed_footprint.Footprint.Width as isize; + let pitch = placed_footprint.Footprint.RowPitch as isize; + for y in 0..height { + ptr::copy_nonoverlapping( + resource_ptr.offset(y * pitch), + data.offset(y * width), + width as usize, + ); + } - let command_allocator = &self.frame_contexts[idx].command_allocator; + staging_resource.Unmap(0, None); - // Reset command allocator and list state. - unsafe { - command_allocator.Reset().unwrap(); - self.command_list.Reset(command_allocator, None).unwrap(); - } + ManuallyDrop::into_inner(staging_location.pResource); + ManuallyDrop::into_inner(source_location.pResource); - // 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, - ); + Ok(()) + } + pub fn render(&mut self, draw_data: &DrawData) -> Result { unsafe { - 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.textures_heap.clone())]); + self.command_allocator.Reset()?; + self.command_list.Reset(&self.command_allocator, None)?; + + let present_to_rtv_barriers = [util::create_barrier( + &self.rtv_target, + D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATE_RENDER_TARGET, + )]; + + let rtv_to_present_barriers = [util::create_barrier( + &self.rtv_target, + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_COMMON, + )]; + + self.command_list.ResourceBarrier(&present_to_rtv_barriers); + self.command_list.OMSetRenderTargets(1, Some(&self.rtv_heap_start), false, None); + self.command_list.SetDescriptorHeaps(&[Some(self.texture_heap.srv_heap.clone())]); self.command_list.ClearRenderTargetView( - self.frame_contexts[idx].desc_handle, + self.rtv_heap_start, &[0.0, 0.0, 0.0, 0.0], None, ); - } - // 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(); + self.render_draw_data(draw_data)?; - if let Err(e) = unsafe { self.render_draw_data(draw_data, idx) } { - eprintln!("{}", e); - }; - - // 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.ResourceBarrier(&rtv_to_present_barriers); 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.command_queue.Signal(self.fence.fence(), self.fence.value())?; + self.fence.wait()?; + self.fence.incr(); + + present_to_rtv_barriers.into_iter().for_each(util::drop_barrier); + rtv_to_present_barriers.into_iter().for_each(util::drop_barrier); } + Ok(self.rtv_target.clone()) + } + + pub fn render_setup(&mut self, hwnd: HWND, ctx: &mut Context) -> Result<()> { unsafe { - // Present the content. - self.swap_chain.Present(1, 0).ok()?; - }; + let io = ctx.io_mut(); - // Drop the barriers. - Barrier::drop(back_buffer_to_rt_barrier); - Barrier::drop(back_buffer_to_present_barrier); + let active_window = GetForegroundWindow(); + if active_window == hwnd + || (!HANDLE(active_window.0).is_invalid() && IsChild(active_window, hwnd).as_bool()) + { + let mut pos = util::try_out_param(|v| GetCursorPos(v))?; + if ScreenToClient(hwnd, &mut pos).as_bool() { + io.mouse_pos = [pos.x as f32, pos.y as f32]; + } + } - // Composite the frame over the hwnd. - unsafe { self.compositor.render(&self.swap_chain) }?; + io.nav_active = true; + io.nav_visible = true; + + for (key, virtual_key) in keys::KEYS { + io[key] = virtual_key.0 as u32; + } + } Ok(()) } -} -impl RenderEngine { - /// Returns the [`HWND`] the UI is composited on top of. - pub fn hwnd(&self) -> HWND { - self.target_hwnd - } + unsafe fn render_draw_data(&mut self, draw_data: &DrawData) -> Result<()> { + 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(()); + } + + self.vertex_buffer.clear(); + self.index_buffer.clear(); + + draw_data + .draw_lists() + .map(|draw_list| { + (draw_list.vtx_buffer().iter().copied(), draw_list.idx_buffer().iter().copied()) + }) + .for_each(|(vertices, indices)| { + self.vertex_buffer.extend(vertices); + self.index_buffer.extend(indices); + }); + + self.vertex_buffer.upload(&self.device)?; + self.index_buffer.upload(&self.device)?; + self.projection_buffer = { + let [l, t, r, b] = [ + draw_data.display_pos[0], + draw_data.display_pos[1], + draw_data.display_pos[0] + draw_data.display_size[0], + draw_data.display_pos[1] + draw_data.display_size[1], + ]; + + [[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, + ]] + }; + + self.setup_render_state(draw_data); + + 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 u64, + }; + 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 => { + // Q: looking at the commands recorded in here, it + // doesn't seem like this should have any effect + // whatsoever. What am I doing wrong? + self.setup_render_state(draw_data); + }, + DrawCmd::RawCallback { callback, raw_cmd } => unsafe { + callback(cl.raw(), raw_cmd) + }, + } + } + idx_offset += cl.idx_buffer().len(); + vtx_offset += cl.vtx_buffer().len(); + } - /// Returns an internally mutable reference to the active - /// [`imgui::Context`]. - pub fn ctx(&mut self) -> Rc> { - Rc::clone(&self.ctx) + Ok(()) } - /// Upload an image into a texture and return a [`imgui::TextureId`]. - /// - /// Should be used ideally ahead-of-time in - /// [`ImguiRenderLoop::initialize`](crate::ImguiRenderLoop::initialize), but - /// can be used in - /// [`ImguiRenderLoop::before_render`](crate::ImguiRenderLoop::before_render) - /// provided the performance cost is acceptable. - pub fn load_image(&mut self, data: &[u8], width: u32, height: u32) -> Result { - unsafe { self.resize_texture_heap()? }; + unsafe fn setup_render_state(&self, draw_data: &DrawData) { + self.command_list.RSSetViewports(&[D3D12_VIEWPORT { + TopLeftX: 0f32, + TopLeftY: 0f32, + Width: draw_data.display_size[0], + Height: draw_data.display_size[1], + MinDepth: 0f32, + MaxDepth: 1f32, + }]); - let (p_texture, tex_id) = - self.create_texture_inner(data, width, height, self.textures.len() as u32 + 1)?; + self.command_list.IASetVertexBuffers( + 0, + Some(&[D3D12_VERTEX_BUFFER_VIEW { + BufferLocation: self.vertex_buffer.resource.GetGPUVirtualAddress(), + SizeInBytes: (self.vertex_buffer.data.len() * mem::size_of::()) as _, + StrideInBytes: mem::size_of::() as _, + }]), + ); - self.textures.push((p_texture, tex_id)); + self.command_list.IASetIndexBuffer(Some(&D3D12_INDEX_BUFFER_VIEW { + BufferLocation: self.index_buffer.resource.GetGPUVirtualAddress(), + SizeInBytes: (self.index_buffer.data.len() * mem::size_of::()) as _, + Format: if mem::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); + self.command_list.SetGraphicsRootSignature(&self.root_signature); + self.command_list.SetGraphicsRoot32BitConstants( + 0, + 16, + self.projection_buffer.as_ptr() as *const c_void, + 0, + ); + self.command_list.OMSetBlendFactor(Some(&[0f32; 4])); + } - Ok(tex_id) + pub fn resize(&mut self, width: u32, height: u32) -> Result<()> { + drop(mem::replace(&mut self.rtv_target, unsafe { + create_render_target(&self.device, width, height, self.rtv_heap_start) + }?)); + + Ok(()) } } -impl RenderEngine { - unsafe fn setup_io(&mut self) -> Result<()> { - let sd = try_out_param(|sd| unsafe { self.swap_chain.GetDesc1(sd) })?; +unsafe fn create_device() -> Result { + let dxgi_factory: IDXGIFactory2 = unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG) }?; + let dxgi_adapter = unsafe { dxgi_factory.EnumAdapters(0) }?; - let mut ctx = self.ctx.borrow_mut(); + let device: ID3D12Device = util::try_out_ptr(|v| unsafe { + D3D12CreateDevice(&dxgi_adapter, D3D_FEATURE_LEVEL_11_1, v) + })?; - // Setup display size and cursor position. - let io = ctx.io_mut(); + Ok(device) +} - io.display_size = [sd.Width as f32, sd.Height as f32]; +unsafe fn create_command_objects( + device: &ID3D12Device, +) -> Result<(ID3D12CommandQueue, ID3D12CommandAllocator, ID3D12GraphicsCommandList)> { + let command_queue: ID3D12CommandQueue = + device.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC { + Type: D3D12_COMMAND_LIST_TYPE_DIRECT, + Priority: 0, + Flags: D3D12_COMMAND_QUEUE_FLAG_NONE, + NodeMask: 0, + })?; - 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 _; - } - } + let command_allocator: ID3D12CommandAllocator = + device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?; - io.nav_active = true; - io.nav_visible = true; + let command_list: ID3D12GraphicsCommandList = + device.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &command_allocator, None)?; + command_list.Close()?; - // Map key indices to the virtual key codes - for i in keys::KEYS { - io[i.0] = i.1 .0 as _; - } + command_queue.SetName(w!("hudhook Render Engine Command Queue"))?; + command_allocator.SetName(w!("hudhook Render Engine Command Allocator"))?; + command_list.SetName(w!("hudhook Render Engine Command List"))?; - Ok(()) - } + Ok((command_queue, command_allocator, command_list)) +} - unsafe fn create_device_objects(&mut self) -> Result<()> { - if self.pipeline_state.is_some() { - self.invalidate_device_objects(); - } +unsafe fn create_render_target( + device: &ID3D12Device, + width: u32, + height: u32, + heap_descriptor: D3D12_CPU_DESCRIPTOR_HANDLE, +) -> Result { + let resource = util::try_out_ptr(|v| { + device.CreateCommittedResource( + &D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_DEFAULT, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }, + D3D12_HEAP_FLAG_SHARED, + &D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D, + Alignment: 65536, + Width: width as u64, + Height: height, + DepthOrArraySize: 1, + MipLevels: 1, + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN, + Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, + }, + D3D12_RESOURCE_STATE_PRESENT, + Some(&D3D12_CLEAR_VALUE { + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + Anonymous: D3D12_CLEAR_VALUE_0 { Color: [0.0, 0.0, 0.0, 0.0] }, + }), + v, + ) + })?; + + device.CreateRenderTargetView(&resource, None, heap_descriptor); - let desc_range = D3D12_DESCRIPTOR_RANGE { - RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + Ok(resource) +} + +unsafe fn create_heaps(device: &ID3D12Device) -> Result<(ID3D12DescriptorHeap, TextureHeap)> { + let rtv_heap: ID3D12DescriptorHeap = + device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { + Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV, NumDescriptors: 1, - BaseShaderRegister: 0, - RegisterSpace: 0, - OffsetInDescriptorsFromTableStart: 0, - }; + Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + NodeMask: 1, + })?; - let params = [ - D3D12_ROOT_PARAMETER { - ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, - Anonymous: D3D12_ROOT_PARAMETER_0 { - Constants: D3D12_ROOT_CONSTANTS { - ShaderRegister: 0, - RegisterSpace: 0, - Num32BitValues: 16, - }, + let srv_heap: ID3D12DescriptorHeap = + device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { + Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + NumDescriptors: 8, + Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + NodeMask: 0, + })?; + + let texture_heap = TextureHeap::new(device, srv_heap)?; + + Ok((rtv_heap, texture_heap)) +} + +unsafe fn create_shader_program( + device: &ID3D12Device, +) -> Result<(ID3D12RootSignature, ID3D12PipelineState)> { + let parameters = [ + D3D12_ROOT_PARAMETER { + ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, + Anonymous: D3D12_ROOT_PARAMETER_0 { + Constants: D3D12_ROOT_CONSTANTS { + ShaderRegister: 0, + RegisterSpace: 0, + Num32BitValues: 16, }, - ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX, }, - D3D12_ROOT_PARAMETER { - ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, - Anonymous: D3D12_ROOT_PARAMETER_0 { - DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE { - NumDescriptorRanges: 1, - pDescriptorRanges: &desc_range, + ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX, + }, + D3D12_ROOT_PARAMETER { + ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, + Anonymous: D3D12_ROOT_PARAMETER_0 { + DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE { + NumDescriptorRanges: 1, + pDescriptorRanges: &D3D12_DESCRIPTOR_RANGE { + RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + NumDescriptors: 1, + BaseShaderRegister: 0, + RegisterSpace: 0, + OffsetInDescriptorsFromTableStart: 0, }, }, - ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL, }, - ]; - - let sampler = D3D12_STATIC_SAMPLER_DESC { + ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL, + }, + ]; + + let root_signature_desc = D3D12_ROOT_SIGNATURE_DESC { + NumParameters: 2, + pParameters: parameters.as_ptr(), + NumStaticSamplers: 1, + pStaticSamplers: &D3D12_STATIC_SAMPLER_DESC { Filter: D3D12_FILTER_MIN_MAG_MIP_LINEAR, AddressU: D3D12_TEXTURE_ADDRESS_MODE_WRAP, AddressV: D3D12_TEXTURE_ADDRESS_MODE_WRAP, @@ -687,190 +585,192 @@ impl RenderEngine { ShaderRegister: 0, RegisterSpace: 0, ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL, - }; - - let root_signature_desc = D3D12_ROOT_SIGNATURE_DESC { - NumParameters: 2, - pParameters: params.as_ptr(), - NumStaticSamplers: 1, - pStaticSamplers: &sampler, - Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT - | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS - | D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS - | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS, - }; - let mut blob: Option = None; - let mut err_blob: Option = None; - if let Err(e) = D3D12SerializeRootSignature( + }, + Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT + | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS + | D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS + | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS, + }; + + let blob: ID3DBlob = util::try_out_err_blob(|v, err_blob| { + 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(self.device.CreateRootSignature( - 0, - std::slice::from_raw_parts(blob.GetBufferPointer() as *const u8, blob.GetBufferSize()), - )?); - - let vs = 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; - }"#; - - let vtx_shader: ID3DBlob = try_out_ptr(|v| unsafe { - D3DCompile( - vs.as_ptr() as _, - vs.len(), - None, - None, - None::<&ID3DInclude>, - s!("main\0"), - s!("vs_5_0\0"), - 0, - 0, - v, - None, - ) - }) - .expect("D3DCompile vertex shader"); - - let ps = r#" - struct PS_INPUT - { - float4 pos : SV_POSITION; - float4 col : COLOR0; - float2 uv : TEXCOORD0; - }; - SamplerState sampler0 : register(s0); - Texture2D texture0 : register(t0); - - float4 main(PS_INPUT input) : SV_Target - { - float4 out_col = input.col * texture0.Sample(sampler0, input.uv); - return out_col; - }"#; - - let pix_shader = try_out_ptr(|v| unsafe { - D3DCompile( - ps.as_ptr() as _, - ps.len(), - None, - None, - None::<&ID3DInclude>, - s!("main\0"), - s!("ps_5_0\0"), - 0, - 0, - v, - None, - ) - }) - .expect("D3DCompile pixel shader"); - - let root_signature = ManuallyDrop::new(self.root_signature.clone()); - let mut pso_desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC { - pRootSignature: root_signature, - NodeMask: 1, - PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, - SampleMask: u32::MAX, - NumRenderTargets: 1, - SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, - Flags: D3D12_PIPELINE_STATE_FLAG_NONE, - ..Default::default() + v, + Some(err_blob), + ) + }) + .map_err(util::print_error_blob("Serializing root signature")) + .expect("D3D12SerializeRootSignature"); + + let root_signature: ID3D12RootSignature = device.CreateRootSignature( + 0, + slice::from_raw_parts(blob.GetBufferPointer() as *const u8, blob.GetBufferSize()), + )?; + + const VS: &str = r#" + cbuffer vertexBuffer : register(b0) { + float4x4 ProjectionMatrix; }; - pso_desc.RTVFormats[0] = DXGI_FORMAT_B8G8R8A8_UNORM; - pso_desc.DSVFormat = DXGI_FORMAT_D32_FLOAT; - pso_desc.VS = D3D12_SHADER_BYTECODE { - pShaderBytecode: unsafe { vtx_shader.GetBufferPointer() }, - BytecodeLength: unsafe { vtx_shader.GetBufferSize() }, + struct VS_INPUT { + float2 pos: POSITION; + float4 col: COLOR0; + float2 uv: TEXCOORD0; }; - pso_desc.PS = D3D12_SHADER_BYTECODE { - pShaderBytecode: unsafe { pix_shader.GetBufferPointer() }, - BytecodeLength: unsafe { pix_shader.GetBufferSize() }, + struct PS_INPUT { + float4 pos: SV_POSITION; + float4 col: COLOR0; + float2 uv: TEXCOORD0; }; - let elem_descs = [ - D3D12_INPUT_ELEMENT_DESC { - SemanticName: PCSTR("POSITION\0".as_ptr()), - SemanticIndex: 0, - Format: DXGI_FORMAT_R32G32_FLOAT, - InputSlot: 0, - AlignedByteOffset: offset_of!(DrawVert, pos) as u32, - InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, - InstanceDataStepRate: 0, - }, - D3D12_INPUT_ELEMENT_DESC { - SemanticName: PCSTR("TEXCOORD\0".as_ptr()), - SemanticIndex: 0, - Format: DXGI_FORMAT_R32G32_FLOAT, - InputSlot: 0, - AlignedByteOffset: offset_of!(DrawVert, uv) as u32, - InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, - InstanceDataStepRate: 0, - }, - D3D12_INPUT_ELEMENT_DESC { - SemanticName: PCSTR("COLOR\0".as_ptr()), - SemanticIndex: 0, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - InputSlot: 0, - AlignedByteOffset: offset_of!(DrawVert, col) as u32, - InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, - InstanceDataStepRate: 0, - }, - ]; - - pso_desc.InputLayout = - D3D12_INPUT_LAYOUT_DESC { pInputElementDescs: elem_descs.as_ptr(), NumElements: 3 }; - - pso_desc.BlendState.AlphaToCoverageEnable = BOOL::from(false); - pso_desc.BlendState.RenderTarget[0] = D3D12_RENDER_TARGET_BLEND_DESC { - BlendEnable: true.into(), - LogicOpEnable: false.into(), - SrcBlend: D3D12_BLEND_SRC_ALPHA, - DestBlend: D3D12_BLEND_INV_SRC_ALPHA, - BlendOp: D3D12_BLEND_OP_ADD, - SrcBlendAlpha: D3D12_BLEND_ONE, - DestBlendAlpha: D3D12_BLEND_INV_SRC_ALPHA, - BlendOpAlpha: D3D12_BLEND_OP_ADD, - LogicOp: Default::default(), - RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as _, + 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 PS: &str = r#" + struct PS_INPUT { + float4 pos: SV_POSITION; + float4 col: COLOR0; + float2 uv: TEXCOORD0; }; - pso_desc.RasterizerState = D3D12_RASTERIZER_DESC { + + SamplerState sampler0: register(s0); + Texture2D texture0: register(t0); + + float4 main(PS_INPUT input): SV_Target { + float4 out_col = input.col * texture0.Sample(sampler0, input.uv); + return out_col; + }"#; + + let vtx_shader: ID3DBlob = util::try_out_err_blob(|v, err_blob| unsafe { + D3DCompile( + VS.as_ptr() as _, + VS.len(), + None, + None, + None::<&ID3DInclude>, + s!("main\0"), + s!("vs_5_0\0"), + 0, + 0, + v, + Some(err_blob), + ) + }) + .map_err(util::print_error_blob("Compiling vertex shader")) + .expect("D3DCompile"); + + let pix_shader = util::try_out_err_blob(|v, err_blob| unsafe { + D3DCompile( + PS.as_ptr() as _, + PS.len(), + None, + None, + None::<&ID3DInclude>, + s!("main\0"), + s!("ps_5_0\0"), + 0, + 0, + v, + Some(err_blob), + ) + }) + .map_err(util::print_error_blob("Compiling pixel shader")) + .expect("D3DCompile"); + + let input_elements = [ + D3D12_INPUT_ELEMENT_DESC { + SemanticName: s!("POSITION"), + SemanticIndex: 0, + Format: DXGI_FORMAT_R32G32_FLOAT, + InputSlot: 0, + AlignedByteOffset: offset_of!(DrawVert, pos) as u32, + InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + InstanceDataStepRate: 0, + }, + D3D12_INPUT_ELEMENT_DESC { + SemanticName: s!("TEXCOORD"), + SemanticIndex: 0, + Format: DXGI_FORMAT_R32G32_FLOAT, + InputSlot: 0, + AlignedByteOffset: offset_of!(DrawVert, uv) as u32, + InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + InstanceDataStepRate: 0, + }, + D3D12_INPUT_ELEMENT_DESC { + SemanticName: s!("COLOR"), + SemanticIndex: 0, + Format: DXGI_FORMAT_R8G8B8A8_UNORM, + InputSlot: 0, + AlignedByteOffset: offset_of!(DrawVert, col) as u32, + InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + InstanceDataStepRate: 0, + }, + ]; + + let pso_desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC { + pRootSignature: ManuallyDrop::new(Some(root_signature.clone())), + NodeMask: 1, + PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, + SampleMask: u32::MAX, + NumRenderTargets: 1, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + Flags: D3D12_PIPELINE_STATE_FLAG_NONE, + RTVFormats: [ + DXGI_FORMAT_B8G8R8A8_UNORM, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ], + DSVFormat: DXGI_FORMAT_D32_FLOAT, + VS: D3D12_SHADER_BYTECODE { + pShaderBytecode: unsafe { vtx_shader.GetBufferPointer() }, + BytecodeLength: unsafe { vtx_shader.GetBufferSize() }, + }, + PS: D3D12_SHADER_BYTECODE { + pShaderBytecode: unsafe { pix_shader.GetBufferPointer() }, + BytecodeLength: unsafe { pix_shader.GetBufferSize() }, + }, + InputLayout: D3D12_INPUT_LAYOUT_DESC { + pInputElementDescs: input_elements.as_ptr(), + NumElements: 3, + }, + BlendState: D3D12_BLEND_DESC { + AlphaToCoverageEnable: false.into(), + IndependentBlendEnable: false.into(), + RenderTarget: [ + D3D12_RENDER_TARGET_BLEND_DESC { + BlendEnable: true.into(), + LogicOpEnable: false.into(), + SrcBlend: D3D12_BLEND_SRC_ALPHA, + DestBlend: D3D12_BLEND_INV_SRC_ALPHA, + BlendOp: D3D12_BLEND_OP_ADD, + SrcBlendAlpha: D3D12_BLEND_ONE, + DestBlendAlpha: D3D12_BLEND_INV_SRC_ALPHA, + BlendOpAlpha: D3D12_BLEND_OP_ADD, + LogicOp: Default::default(), + RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as _, + }, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ], + }, + RasterizerState: D3D12_RASTERIZER_DESC { FillMode: D3D12_FILL_MODE_SOLID, CullMode: D3D12_CULL_MODE_NONE, FrontCounterClockwise: false.into(), @@ -882,86 +782,189 @@ impl RenderEngine { AntialiasedLineEnable: false.into(), ForcedSampleCount: 0, ConservativeRaster: D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF, - }; + }, + ..Default::default() + }; + + let pipeline_state = unsafe { device.CreateGraphicsPipelineState(&pso_desc)? }; + let _ = ManuallyDrop::into_inner(pso_desc.pRootSignature); + + Ok((root_signature, pipeline_state)) +} + +struct Buffer { + resource: ID3D12Resource, + resource_capacity: usize, + data: Vec, +} + +impl Buffer { + fn new(device: &ID3D12Device, resource_capacity: usize) -> Result { + let resource = Self::create_resource(device, resource_capacity)?; + let data = Vec::with_capacity(resource_capacity); + + Ok(Self { resource, resource_capacity, data }) + } + + fn create_resource(device: &ID3D12Device, resource_capacity: usize) -> Result { + util::try_out_ptr(|v| unsafe { + device.CreateCommittedResource( + &D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_UPLOAD, + CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }, + D3D12_HEAP_FLAG_NONE, + &D3D12_RESOURCE_DESC { + Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 0, + Width: (resource_capacity * mem::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, + }, + D3D12_RESOURCE_STATE_GENERIC_READ, + None, + v, + ) + }) + } + + fn clear(&mut self) { + self.data.clear(); + } + + fn extend>(&mut self, it: I) { + self.data.extend(it) + } - let pipeline_state = unsafe { self.device.CreateGraphicsPipelineState(&pso_desc) }; - self.pipeline_state = Some(pipeline_state.unwrap()); + fn upload(&mut self, device: &ID3D12Device) -> Result<()> { + let capacity = self.data.capacity(); + if capacity > self.resource_capacity { + drop(mem::replace(&mut self.resource, Self::create_resource(device, capacity)?)); + } - self.create_font_texture()?; + unsafe { + let mut resource_ptr = ptr::null_mut(); + self.resource.Map(0, None, Some(&mut resource_ptr))?; + ptr::copy_nonoverlapping(self.data.as_ptr(), resource_ptr as *mut T, self.data.len()); + self.resource.Unmap(0, None); + } Ok(()) } +} + +#[derive(Debug)] +#[allow(unused)] +struct Texture { + resource: ID3D12Resource, + id: TextureId, +} + +struct TextureHeap { + device: ID3D12Device, + srv_heap: ID3D12DescriptorHeap, + textures: Vec, + command_queue: ID3D12CommandQueue, + command_allocator: ID3D12CommandAllocator, + command_list: ID3D12GraphicsCommandList, + fence: Fence, +} + +impl TextureHeap { + fn new(device: &ID3D12Device, srv_heap: ID3D12DescriptorHeap) -> Result { + let (command_queue, command_allocator, command_list) = + unsafe { create_command_objects(device) }?; + + let fence = Fence::new(device)?; + + Ok(Self { + device: device.clone(), + srv_heap, + textures: Vec::new(), + command_queue, + command_allocator, + command_list, + fence, + }) + } - unsafe fn resize_texture_heap(&mut self) -> Result<()> { - let mut desc = self.textures_heap.GetDesc(); + unsafe fn resize_heap(&mut self) -> Result<()> { + let mut desc = self.srv_heap.GetDesc(); let old_num_descriptors = desc.NumDescriptors; - if (old_num_descriptors as usize) < self.textures.len() { + + if old_num_descriptors <= self.textures.len() as _ { desc.NumDescriptors *= 2; - let new_texture_heap: ID3D12DescriptorHeap = self.device.CreateDescriptorHeap(&desc)?; + + let srv_heap: ID3D12DescriptorHeap = self.device.CreateDescriptorHeap(&desc)?; self.device.CopyDescriptorsSimple( old_num_descriptors, - new_texture_heap.GetCPUDescriptorHandleForHeapStart(), - self.textures_heap.GetCPUDescriptorHandleForHeapStart(), + srv_heap.GetCPUDescriptorHandleForHeapStart(), + self.srv_heap.GetCPUDescriptorHandleForHeapStart(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, ); - self.textures_heap = new_texture_heap; + self.srv_heap = srv_heap; } Ok(()) } - fn create_texture_inner( - &mut self, - data: &[u8], - width: u32, - height: u32, - tex_index: u32, - ) -> Result<(ID3D12Resource, TextureId)> { - let heap_inc_size = unsafe { - self.device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) - }; + unsafe fn create_texture(&mut self, data: &[u8], width: u32, height: u32) -> Result { + self.resize_heap()?; + + let cpu_heap_start = self.srv_heap.GetCPUDescriptorHandleForHeapStart(); + let gpu_heap_start = self.srv_heap.GetGPUDescriptorHandleForHeapStart(); + let heap_inc_size = + self.device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + let texture_index = self.textures.len() as u32; let cpu_desc = D3D12_CPU_DESCRIPTOR_HANDLE { - ptr: self.cpu_desc.ptr + (tex_index * heap_inc_size) as usize, + ptr: cpu_heap_start.ptr + (texture_index * heap_inc_size) as usize, }; let gpu_desc = D3D12_GPU_DESCRIPTOR_HANDLE { - ptr: self.gpu_desc.ptr + (tex_index * heap_inc_size) as u64, + ptr: gpu_heap_start.ptr + (texture_index * heap_inc_size) as u64, }; - 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, - MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN, - CreationNodeMask: Default::default(), - VisibleNodeMask: Default::default(), - }, - D3D12_HEAP_FLAG_NONE, - &D3D12_RESOURCE_DESC { - Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D, - Alignment: 0, - Width: width as _, - Height: height as _, - DepthOrArraySize: 1, - MipLevels: 1, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, - Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN, - Flags: D3D12_RESOURCE_FLAG_NONE, - }, - D3D12_RESOURCE_STATE_COPY_DEST, - None, - v, - ) - })?); + let texture: ID3D12Resource = util::try_out_ptr(|v| unsafe { + self.device.CreateCommittedResource( + &D3D12_HEAP_PROPERTIES { + Type: D3D12_HEAP_TYPE_DEFAULT, + 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_TEXTURE2D, + Alignment: 0, + Width: width as _, + Height: height as _, + DepthOrArraySize: 1, + MipLevels: 1, + Format: DXGI_FORMAT_R8G8B8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, + Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN, + Flags: D3D12_RESOURCE_FLAG_NONE, + }, + D3D12_RESOURCE_STATE_COPY_DEST, + None, + v, + ) + })?; - let mut upload_buffer: Option = None; let upload_pitch = width * 4; let upload_size = height * upload_pitch; - unsafe { + let upload_buffer: ID3D12Resource = util::try_out_ptr(|v| unsafe { self.device.CreateCommittedResource( &D3D12_HEAP_PROPERTIES { Type: D3D12_HEAP_TYPE_UPLOAD, @@ -985,39 +988,26 @@ impl RenderEngine { }, D3D12_RESOURCE_STATE_GENERIC_READ, None, - &mut upload_buffer, + v, ) - }?; - let upload_buffer = ManuallyDrop::new(upload_buffer); - - let range = D3D12_RANGE { Begin: 0, End: upload_size as usize }; - if let Some(ub) = upload_buffer.as_ref() { - unsafe { - let mut ptr: *mut u8 = null_mut(); - ub.Map(0, Some(&range), Some(&mut ptr as *mut _ as *mut *mut c_void)).unwrap(); - std::ptr::copy_nonoverlapping(data.as_ptr(), ptr, data.len()); - ub.Unmap(0, Some(&range)); - } - }; - - let fence: ID3D12Fence = unsafe { self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE) }?; + })?; - let event = - unsafe { CreateEventA(None, BOOL::from(false), BOOL::from(false), PCSTR(null())) }?; + let mut upload_buffer_ptr = ptr::null_mut(); + upload_buffer.Map(0, None, Some(&mut upload_buffer_ptr))?; + ptr::copy_nonoverlapping(data.as_ptr(), upload_buffer_ptr as *mut u8, data.len()); + upload_buffer.Unmap(0, None); - let cmd_allocator: ID3D12CommandAllocator = - unsafe { self.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }?; + self.command_allocator.Reset()?; + self.command_list.Reset(&self.command_allocator, None)?; - unsafe { cmd_allocator.SetName(w!("hudhook font texture Command Allocator")) }?; - - let cmd_list: ID3D12GraphicsCommandList = unsafe { - self.device.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &cmd_allocator, None) - }?; - - unsafe { cmd_list.SetName(w!("hudhook font texture Command List")) }?; + let dst_location = D3D12_TEXTURE_COPY_LOCATION { + pResource: ManuallyDrop::new(Some(texture.clone())), + Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { SubresourceIndex: 0 }, + }; let src_location = D3D12_TEXTURE_COPY_LOCATION { - pResource: upload_buffer, + pResource: ManuallyDrop::new(Some(upload_buffer.clone())), Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { PlacedFootprint: D3D12_PLACED_SUBRESOURCE_FOOTPRINT { @@ -1033,264 +1023,50 @@ impl RenderEngine { }, }; - let dst_location = D3D12_TEXTURE_COPY_LOCATION { - pResource: p_texture.clone(), - Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, - Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { SubresourceIndex: 0 }, - }; - - let barrier = D3D12_RESOURCE_BARRIER { - Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, - Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE, - Anonymous: D3D12_RESOURCE_BARRIER_0 { - Transition: ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER { - pResource: p_texture.clone(), - Subresource: D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, - StateBefore: D3D12_RESOURCE_STATE_COPY_DEST, - StateAfter: D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - }), - }, - }; - - unsafe { - cmd_list.CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, None); - cmd_list.ResourceBarrier(&[barrier]); - cmd_list.Close().unwrap(); - self.command_queue.ExecuteCommandLists(&[Some(cmd_list.cast()?)]); - self.command_queue.Signal(&fence, 1)?; - fence.SetEventOnCompletion(1, event)?; - WaitForSingleObject(event, u32::MAX); - }; - - let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC { - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D, - Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, - Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 { - Texture2D: D3D12_TEX2D_SRV { - MostDetailedMip: 0, - MipLevels: 1, - PlaneSlice: Default::default(), - ResourceMinLODClamp: Default::default(), + self.command_list.CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, None); + let barriers = [util::create_barrier( + &texture, + D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + )]; + + self.command_list.ResourceBarrier(&barriers); + self.command_list.Close()?; + self.command_queue.ExecuteCommandLists(&[Some(self.command_list.cast()?)]); + self.command_queue.Signal(self.fence.fence(), self.fence.value())?; + self.fence.wait()?; + self.fence.incr(); + + self.device.CreateShaderResourceView( + &texture, + Some(&D3D12_SHADER_RESOURCE_VIEW_DESC { + Format: DXGI_FORMAT_R8G8B8A8_UNORM, + ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D, + Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 { + Texture2D: D3D12_TEX2D_SRV { + MostDetailedMip: 0, + MipLevels: 1, + PlaneSlice: Default::default(), + ResourceMinLODClamp: Default::default(), + }, }, - }, - }; - - unsafe { CloseHandle(event)? }; - - unsafe { - self.device.CreateShaderResourceView(p_texture.as_ref(), Some(&srv_desc), cpu_desc) - }; - - let tex_id = TextureId::from(gpu_desc.ptr as usize); - - drop(ManuallyDrop::into_inner(src_location.pResource)); - - Ok((ManuallyDrop::into_inner(p_texture).unwrap(), tex_id)) - } - - unsafe fn create_font_texture(&mut self) -> Result<()> { - let ctx = Rc::clone(&self.ctx); - let mut ctx = ctx.borrow_mut(); - let fonts = ctx.fonts(); - let texture = fonts.build_rgba32_texture(); - - self.resize_texture_heap()?; - let (p_texture, tex_id) = - self.create_texture_inner(texture.data, texture.width, texture.height, 0)?; - - drop(self.font_texture_resource.take()); - self.font_texture_resource = Some(p_texture); - fonts.tex_id = tex_id; - - Ok(()) - } - - 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()); - }); - } - - 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; - - 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], - ]; - - [[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]); - 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(()); - } - - 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); - }, + }), + cpu_desc, ); - 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)); - }; - - 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)); - }; - } - - self.setup_render_state(draw_data, idx); + let texture_id = TextureId::from(gpu_desc.ptr as usize); + self.textures.push(Texture { resource: texture.clone(), id: texture_id }); - let mut vtx_offset = 0usize; - let mut idx_offset = 0usize; + barriers.into_iter().for_each(util::drop_barrier); - 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, - }; + // Apparently, leaking the upload buffer into the location is necessary. + // Uncommenting the following line consistently leads to a crash, which + // points to a double-free, but I don't know why: upload_buffer should + // stay alive with a positive refcount until the end of this block. + // let _ = ManuallyDrop::into_inner(src_location.pResource); + let _ = ManuallyDrop::into_inner(dst_location.pResource); - 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(()) + Ok(texture_id) } } diff --git a/src/renderer/input.rs b/src/renderer/input.rs index fd7d9d7b..090ba028 100644 --- a/src/renderer/input.rs +++ b/src/renderer/input.rs @@ -4,7 +4,6 @@ use std::ffi::c_void; 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::*; use windows::Win32::UI::Input::{ @@ -13,8 +12,7 @@ use windows::Win32::UI::Input::{ }; use windows::Win32::UI::WindowsAndMessaging::*; -use crate::renderer::{RenderEngine, RenderState}; -use crate::ImguiRenderLoop; +use crate::pipeline::Pipeline; pub type WndProcType = unsafe extern "system" fn(hwnd: HWND, umsg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT; @@ -237,22 +235,15 @@ fn handle_input(io: &mut Io, state: u32, WPARAM(wparam): WPARAM, LPARAM(lparam): // Window procedure //////////////////////////////////////////////////////////////////////////////// -#[must_use] pub fn imgui_wnd_proc_impl( hwnd: HWND, umsg: u32, WPARAM(wparam): WPARAM, LPARAM(lparam): LPARAM, - wnd_proc: WndProcType, - mut render_engine: MutexGuard, - imgui_render_loop: T, -) -> LRESULT -where - T: AsRef, -{ - let ctx = render_engine.ctx(); - let mut ctx = ctx.borrow_mut(); - let io = ctx.io_mut(); + pipeline: &mut Pipeline, +) { + let io = pipeline.context().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 => { @@ -297,25 +288,11 @@ where 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); + WM_SIZE | WM_PAINT => { + pipeline.resize(); }, _ => {}, }; - 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(ctx); - drop(render_engine); - - if should_block_messages { - return LRESULT(1); - } - - unsafe { CallWindowProcW(Some(wnd_proc), hwnd, umsg, WPARAM(wparam), LPARAM(lparam)) } + pipeline.render_loop().on_wnd_proc(hwnd, umsg, WPARAM(wparam), LPARAM(lparam)); } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 139ab47e..9e9e06d1 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,8 +1,6 @@ //! The [`hudhook`](crate) overlay rendering engine. mod engine; -mod input; +pub(crate) mod input; mod keys; -mod state; pub use engine::RenderEngine; -pub use state::RenderState; diff --git a/src/renderer/state.rs b/src/renderer/state.rs deleted file mode 100644 index ff1f26d5..00000000 --- a/src/renderer/state.rs +++ /dev/null @@ -1,193 +0,0 @@ -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::renderer::input::{imgui_wnd_proc_impl, WndProcType}; -use crate::renderer::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); - -/// Global renderer state manager. -/// -/// Clients should **never** use this. It is reserved for -/// [`Hooks`](crate::Hooks) implementors. -pub struct RenderState; - -impl RenderState { - /// Call this when the [`HWND`] you want to render to is first available. - /// Pass a callback that returns the target [`HWND`]. - /// - /// # Example - /// - /// ``` - /// 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 - /// }); - /// ``` - pub 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 - } - - /// Call this with the [`ImguiRenderLoop`] object that is passed to - /// your [`Hooks`](crate::Hooks) implementor via - /// [`Hooks::from_render_loop`](crate::Hooks::from_render_loop). - /// - /// # Example - /// - /// Check [`ImguiDx12Hooks`](crate::hooks::dx12::ImguiDx12Hooks). - pub fn set_render_loop(t: T) { - unsafe { RENDER_LOOP.get_or_init(|| Box::new(t)) }; - } - - /// Return whether a render operation is currently undergoing. - /// - /// If your hook goes through a DirectX `Present` call of some sorts, the - /// hooked function will probably be recursively called. Use this in - /// your `Present` hook to avoid double locking. - pub fn is_locked() -> bool { - RENDER_LOCK.load(Ordering::SeqCst) - } - - /// Render the UI and composite it over the passed [`HWND`]. - /// - /// Make sure that the passed [`HWND`] is the one returned by - /// [`RenderState::setup`]. - pub 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); - 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); - - if let Err(e) = render_engine.render(|ui| render_loop.render(ui)) { - error!("Render: {e:?}"); - } - - RENDER_LOCK.store(false, Ordering::SeqCst); - } - - /// Resize the engine. Generally only needs to be called automatically as a - /// consequence of the `WM_SIZE` event. - pub 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:?}"); - } - } - } - - /// Free the render engine resources. Should be called from the - /// [`Hooks::unhook`](crate::Hooks::unhook) implementation. - pub 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/util.rs b/src/util.rs index b4d61028..a146ba42 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,21 @@ -use windows::Win32::Foundation::{HWND, RECT}; +use std::fmt::Display; +use std::mem::ManuallyDrop; +use std::sync::atomic::{AtomicU64, Ordering}; + +use tracing::{debug, error}; +use windows::Win32::Foundation::{HANDLE, HWND, RECT}; +use windows::Win32::Graphics::Direct3D::ID3DBlob; +use windows::Win32::Graphics::Direct3D12::{ + D3D12GetDebugInterface, ID3D12Debug, ID3D12Device, ID3D12Fence, ID3D12Resource, + D3D12_FENCE_FLAG_NONE, D3D12_RESOURCE_BARRIER, D3D12_RESOURCE_BARRIER_0, + D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_BARRIER_FLAG_NONE, + D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_STATES, + D3D12_RESOURCE_TRANSITION_BARRIER, +}; +use windows::Win32::Graphics::Dxgi::{ + DXGIGetDebugInterface1, IDXGIInfoQueue, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE, +}; +use windows::Win32::System::Threading::{CreateEventExW, WaitForSingleObjectEx, CREATE_EVENT}; use windows::Win32::UI::WindowsAndMessaging::GetClientRect; /// Helper for fallible [`windows`] APIs that have an out-param with a default @@ -42,6 +59,88 @@ where } } +/// Helper for fallible [`windows`] APIs that have an optional pointer +/// out-param and an optional pointer err-param. +/// +/// # Example +/// +/// ``` +/// let blob: ID3DBlob = util::try_out_err_blob(|v, err_blob| { +/// D3D12SerializeRootSignature( +/// &root_signature_desc, +/// D3D_ROOT_SIGNATURE_VERSION_1_0, +/// v, +/// Some(err_blob), +/// ) +/// }) +/// .map_err(print_err_blob("Compiling vertex shader"))?; +/// ``` +pub fn try_out_err_blob(mut f: F) -> Result +where + F: FnMut(&mut Option, &mut Option) -> Result, +{ + let mut t1: Option = None; + let mut t2: Option = None; + match f(&mut t1, &mut t2) { + Ok(_) => Ok(t1.unwrap()), + Err(e) => Err((e, t2.unwrap())), + } +} + +/// Use together with [`try_out_err_blob`] for printing Direct3D error blobs. +pub fn print_error_blob(msg: D) -> impl Fn((E, ID3DBlob)) -> E { + move |(e, err_blob): (E, ID3DBlob)| { + 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!("{msg}: {s}"); + e + } +} + +/// Enables the Direct3D12 debug interface. It will not panic if the interface +/// is not available. Call this from your application before a DirectX 12 device +/// is initialized. It could fail in DirectX 12 host applications that will +/// have initialized their device already, but should not fail in other host +/// applications. +pub fn enable_debug_interface() { + let debug_interface: Result = + try_out_ptr(|v| unsafe { D3D12GetDebugInterface(v) }); + + match debug_interface { + Ok(debug_interface) => unsafe { debug_interface.EnableDebugLayer() }, + Err(e) => { + error!("Could not create debug interface: {e:?}") + }, + } +} + +/// Prints the DXGI debug messages on the debug trace. It is used internally for +/// error reporting, but can be used by clients. Requires calling +/// [`enable_debug_interface`] before. +pub fn print_dxgi_debug_messages() { + let Ok(diq): Result = (unsafe { DXGIGetDebugInterface1(0) }) else { + return; + }; + + let n = unsafe { diq.GetNumStoredMessages(DXGI_DEBUG_ALL) }; + for i in 0..n { + let mut msg_len: usize = 0; + unsafe { 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; + unsafe { diq.GetMessage(DXGI_DEBUG_ALL, i, Some(pdiqm), &mut msg_len as _).unwrap() }; + let diqm = unsafe { pdiqm.as_ref().unwrap() }; + debug!( + "[DIQ] {}", + String::from_utf8_lossy(unsafe { + std::slice::from_raw_parts(diqm.pDescription, diqm.DescriptionByteLength - 1) + }) + ); + } + unsafe { diq.ClearStoredMessages(DXGI_DEBUG_ALL) }; +} + /// Helper that returns width and height of a given /// [`windows::Win32::Foundation::HWND`]. pub fn win_size(hwnd: HWND) -> (i32, i32) { @@ -49,3 +148,67 @@ pub fn win_size(hwnd: HWND) -> (i32, i32) { unsafe { GetClientRect(hwnd, &mut rect).unwrap() }; (rect.right - rect.left, rect.bottom - rect.top) } + +pub(crate) fn create_barrier( + resource: &ID3D12Resource, + before: D3D12_RESOURCE_STATES, + after: D3D12_RESOURCE_STATES, +) -> D3D12_RESOURCE_BARRIER { + D3D12_RESOURCE_BARRIER { + Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE, + Anonymous: D3D12_RESOURCE_BARRIER_0 { + Transition: ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER { + pResource: ManuallyDrop::new(Some(resource.clone())), + Subresource: D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + StateBefore: before, + StateAfter: after, + }), + }, + } +} + +pub(crate) fn drop_barrier(barrier: D3D12_RESOURCE_BARRIER) { + let transition = ManuallyDrop::into_inner(unsafe { barrier.Anonymous.Transition }); + let _ = ManuallyDrop::into_inner(transition.pResource); +} + +pub(crate) struct Fence { + fence: ID3D12Fence, + value: AtomicU64, + event: HANDLE, +} + +impl Fence { + pub(crate) fn new(device: &ID3D12Device) -> windows::core::Result { + let fence = unsafe { device.CreateFence(0, D3D12_FENCE_FLAG_NONE) }?; + let value = AtomicU64::new(0); + let event = unsafe { CreateEventExW(None, None, CREATE_EVENT(0), 0x1f0003) }?; + + Ok(Fence { fence, value, event }) + } + + pub(crate) fn fence(&self) -> &ID3D12Fence { + &self.fence + } + + pub(crate) fn value(&self) -> u64 { + self.value.load(Ordering::SeqCst) + } + + pub(crate) fn incr(&self) { + self.value.fetch_add(1, Ordering::SeqCst); + } + + pub(crate) fn wait(&self) -> windows::core::Result<()> { + let value = self.value(); + unsafe { + if self.fence.GetCompletedValue() < value { + self.fence.SetEventOnCompletion(value, self.event)?; + WaitForSingleObjectEx(self.event, u32::MAX, false); + } + } + + Ok(()) + } +} diff --git a/tests/harness/dx11.rs b/tests/harness/dx11.rs index 56304297..2069fa0f 100644 --- a/tests/harness/dx11.rs +++ b/tests/harness/dx11.rs @@ -2,33 +2,35 @@ use std::ffi::CString; use std::mem::MaybeUninit; use std::ptr::null_mut; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; +use std::sync::mpsc::{self, Sender}; +use std::sync::{Arc, OnceLock}; use std::thread::{self, JoinHandle}; +use hudhook::util; use windows::core::{s, PCSTR}; use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, RECT, WPARAM}; use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE_HARDWARE, D3D_FEATURE_LEVEL_11_0}; use windows::Win32::Graphics::Direct3D11::{ - D3D11CreateDeviceAndSwapChain, ID3D11Device, ID3D11DeviceContext, D3D11_CREATE_DEVICE_FLAG, - D3D11_SDK_VERSION, + D3D11CreateDeviceAndSwapChain, ID3D11Device, ID3D11DeviceContext, ID3D11Resource, + D3D11_CREATE_DEVICE_FLAG, D3D11_SDK_VERSION, }; use windows::Win32::Graphics::Dxgi::Common::{ DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_DESC, DXGI_RATIONAL, DXGI_SAMPLE_DESC, }; use windows::Win32::Graphics::Dxgi::{ - DXGIGetDebugInterface1, IDXGIInfoQueue, IDXGISwapChain, DXGI_DEBUG_ALL, - DXGI_INFO_QUEUE_MESSAGE, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_EFFECT_DISCARD, - DXGI_USAGE_RENDER_TARGET_OUTPUT, + IDXGISwapChain, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_EFFECT_DISCARD, DXGI_USAGE_RENDER_TARGET_OUTPUT, }; use windows::Win32::Graphics::Gdi::HBRUSH; use windows::Win32::System::LibraryLoader::GetModuleHandleA; use windows::Win32::UI::WindowsAndMessaging::{ AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, PeekMessageA, PostQuitMessage, RegisterClassA, SetTimer, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, - HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, + HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WM_SIZE, WNDCLASSA, WS_OVERLAPPEDWINDOW, WS_VISIBLE, }; +static RESIZE: OnceLock> = OnceLock::new(); + pub struct Dx11Harness { child: Option>, done: Arc, @@ -63,7 +65,7 @@ impl Dx11Harness { unsafe { AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW | WS_VISIBLE, BOOL::from(false)) }; - let handle = unsafe { + let hwnd = unsafe { CreateWindowExA( WINDOW_EX_STYLE(0), s!("MyClass\0"), @@ -81,7 +83,7 @@ impl Dx11Harness { ) }; - let diq: IDXGIInfoQueue = unsafe { DXGIGetDebugInterface1(0) }.unwrap(); + unsafe { util::enable_debug_interface() }; let mut p_device: Option = None; let mut p_swap_chain: Option = None; @@ -104,7 +106,7 @@ impl Dx11Harness { }, BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, BufferCount: 1, - OutputWindow: handle, + OutputWindow: hwnd, Windowed: BOOL::from(true), SwapEffect: DXGI_SWAP_EFFECT_DISCARD, SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 }, @@ -118,39 +120,40 @@ impl Dx11Harness { .unwrap() }; let swap_chain = p_swap_chain.unwrap(); + let device = p_device.unwrap(); + let context = p_context.unwrap(); + + let backbuf: ID3D11Resource = unsafe { swap_chain.GetBuffer(0).unwrap() }; + + let mut rtv = util::try_out_ptr(|v| unsafe { + device.CreateRenderTargetView(&backbuf, None, Some(v)) + }) + .unwrap(); + + unsafe { SetTimer(hwnd, 0, 100, None) }; + + let (tx, rx) = mpsc::channel(); - unsafe { SetTimer(handle, 0, 100, None) }; + RESIZE.get_or_init(move || tx); 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); - } + unsafe { util::print_dxgi_debug_messages() }; + + unsafe { context.ClearRenderTargetView(&rtv, &[0.2, 0.8, 0.2, 0.8]) }; eprintln!("Present..."); unsafe { swap_chain.Present(1, 0).unwrap() }; eprintln!("Handle message"); - if !handle_message(handle) { + if !handle_message(hwnd) { break; } + if let Some((width, height)) = rx.try_iter().last() { + let desc = + util::try_out_param(|v| unsafe { swap_chain.GetDesc(v) }).unwrap(); + }; + if done.load(Ordering::SeqCst) { break; } @@ -194,6 +197,12 @@ pub unsafe extern "system" fn window_proc( WM_DESTROY => { PostQuitMessage(0); }, + WM_SIZE => { + let (width, height) = hudhook::util::win_size(hwnd); + if let Some(tx) = RESIZE.get() { + tx.send((width as _, height as _)); + } + }, _ => { return DefWindowProcA(hwnd, msg, wparam, lparam); }, diff --git a/tests/harness/dx12.rs b/tests/harness/dx12.rs index 69a2a28d..662f8cce 100644 --- a/tests/harness/dx12.rs +++ b/tests/harness/dx12.rs @@ -1,41 +1,81 @@ use std::ffi::CString; -use std::mem::MaybeUninit; +use std::mem::{ManuallyDrop, MaybeUninit}; use std::ptr::null; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; +use std::sync::mpsc::{self, Sender}; +use std::sync::{Arc, OnceLock}; use std::thread::{self, JoinHandle}; -use tracing::trace; -use windows::core::{s, ComInterface, PCSTR}; +use hudhook::util; +use tracing::{error, trace}; +use windows::core::{s, w, ComInterface, PCSTR}; use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, RECT, WPARAM}; use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0; use windows::Win32::Graphics::Direct3D12::{ - D3D12CreateDevice, D3D12GetDebugInterface, ID3D12CommandAllocator, ID3D12CommandQueue, - ID3D12Debug, ID3D12DescriptorHeap, ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, + D3D12CreateDevice, ID3D12CommandAllocator, ID3D12CommandQueue, ID3D12DescriptorHeap, + ID3D12Device, ID3D12Fence, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_DESCRIPTOR_HEAP_DESC, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_FENCE_FLAG_NONE, D3D12_RESOURCE_BARRIER, + D3D12_RESOURCE_BARRIER_0, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_BARRIER_FLAG_NONE, D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + D3D12_RESOURCE_STATES, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_TRANSITION_BARRIER, }; use windows::Win32::Graphics::Dxgi::Common::{ - DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_DESC, DXGI_MODE_SCALING_UNSPECIFIED, + DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_MODE_DESC, DXGI_MODE_SCALING_UNSPECIFIED, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, DXGI_RATIONAL, DXGI_SAMPLE_DESC, }; use windows::Win32::Graphics::Dxgi::{ - CreateDXGIFactory, DXGIGetDebugInterface1, IDXGIFactory, 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, + CreateDXGIFactory2, IDXGIFactory2, IDXGISwapChain, IDXGISwapChain3, DXGI_CREATE_FACTORY_DEBUG, + 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::HBRUSH; use windows::Win32::System::LibraryLoader::GetModuleHandleA; +use windows::Win32::System::Threading::{ + CreateEventExW, WaitForSingleObjectEx, CREATE_EVENT, INFINITE, +}; use windows::Win32::UI::WindowsAndMessaging::{ AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, PeekMessageA, PostQuitMessage, RegisterClassA, SetTimer, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, - HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WNDCLASSA, + HCURSOR, HICON, HMENU, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WM_QUIT, WM_SIZE, WNDCLASSA, WS_OVERLAPPEDWINDOW, WS_VISIBLE, }; +static RESIZE: OnceLock> = OnceLock::new(); + +pub struct Barrier; + +impl Barrier { + pub fn create( + buf: ID3D12Resource, + before: D3D12_RESOURCE_STATES, + after: D3D12_RESOURCE_STATES, + ) -> [D3D12_RESOURCE_BARRIER; 1] { + let transition_barrier = ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER { + pResource: ManuallyDrop::new(Some(buf)), + Subresource: D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + StateBefore: before, + StateAfter: after, + }); + + [D3D12_RESOURCE_BARRIER { + Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE, + Anonymous: D3D12_RESOURCE_BARRIER_0 { Transition: transition_barrier }, + }] + } + + pub fn drop(barrier: [D3D12_RESOURCE_BARRIER; 1]) { + for barrier in barrier { + let transition = ManuallyDrop::into_inner(unsafe { barrier.Anonymous.Transition }); + let _ = ManuallyDrop::into_inner(transition.pResource); + } + } +} + pub struct Dx12Harness { child: Option>, done: Arc, @@ -88,11 +128,10 @@ impl Dx12Harness { ) }; // lpParam - let mut debug_interface: Option = None; - unsafe { D3D12GetDebugInterface(&mut debug_interface) }.unwrap(); - unsafe { debug_interface.as_ref().unwrap().EnableDebugLayer() }; + unsafe { util::enable_debug_interface() }; - let factory: IDXGIFactory = unsafe { CreateDXGIFactory() }.unwrap(); + let factory: IDXGIFactory2 = + unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG) }.unwrap(); let adapter = unsafe { factory.EnumAdapters(0) }.unwrap(); let mut dev: Option = None; @@ -114,13 +153,14 @@ impl Dx12Harness { dev.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &command_alloc, None) } .unwrap(); + unsafe { command_list.Close().unwrap() }; let swap_chain_desc = DXGI_SWAP_CHAIN_DESC { BufferDesc: DXGI_MODE_DESC { Width: 800, Height: 600, RefreshRate: DXGI_RATIONAL { Numerator: 60, Denominator: 1 }, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, + Format: DXGI_FORMAT_B8G8R8A8_UNORM, ScanlineOrdering: DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, Scaling: DXGI_MODE_SCALING_UNSPECIFIED, }, @@ -169,6 +209,7 @@ impl Dx12Harness { .unwrap() }; + let mut rtv_handles = Vec::new(); let rtv_heap_inc_size = unsafe { dev.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) }; let rtv_start = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() }; @@ -176,53 +217,109 @@ impl Dx12Harness { for i in 0..desc.BufferCount { unsafe { let buf: ID3D12Resource = swap_chain.GetBuffer(i).unwrap(); + buf.SetName(w!("Harness swap chain back buffer")); let rtv_handle = D3D12_CPU_DESCRIPTOR_HANDLE { ptr: rtv_start.ptr + (i * rtv_heap_inc_size) as usize, }; dev.CreateRenderTargetView(&buf, None, rtv_handle); + rtv_handles.push(rtv_handle); } } - let diq: IDXGIInfoQueue = unsafe { DXGIGetDebugInterface1(0) }.unwrap(); - unsafe { SetTimer(hwnd, 0, 100, None) }; + let fence: ID3D12Fence = + unsafe { dev.CreateFence(0, D3D12_FENCE_FLAG_NONE).unwrap() }; + let mut fence_val = 0; + let fence_event = + unsafe { CreateEventExW(None, None, CREATE_EVENT(0), 0x1F0003) }.unwrap(); + + let (tx, rx) = mpsc::channel(); + + RESIZE.get_or_init(move || tx); + 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); + util::print_dxgi_debug_messages(); } + trace!("Clearing"); unsafe { - command_list.Close().unwrap(); + let rtv_barrier = Barrier::create( + swap_chain.GetBuffer(swap_chain.GetCurrentBackBufferIndex()).unwrap(), + D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATE_RENDER_TARGET, + ); + let present_barrier = Barrier::create( + swap_chain.GetBuffer(swap_chain.GetCurrentBackBufferIndex()).unwrap(), + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT, + ); + + let rtv = rtv_handles[swap_chain.GetCurrentBackBufferIndex() as usize]; + command_alloc.Reset().unwrap(); command_list.Reset(&command_alloc, None).unwrap(); - } + command_list.ResourceBarrier(&rtv_barrier); + command_list.ClearRenderTargetView(rtv, &[0.3, 0.8, 0.3, 0.8], None); + command_list.ResourceBarrier(&present_barrier); + command_list.Close().unwrap(); + command_queue.ExecuteCommandLists(&[Some(command_list.cast().unwrap())]); + command_queue.Signal(&fence, fence_val); + + trace!("Present"); + if let Err(e) = swap_chain.Present(1, 0).ok() { + if let Err(e) = dev.GetDeviceRemovedReason() { + error!("Device removed: {e:?}"); + } + util::print_dxgi_debug_messages(); + panic!("{e:?}"); + } + + if fence.GetCompletedValue() < fence_val { + fence.SetEventOnCompletion(fence_val, fence_event); + WaitForSingleObjectEx(fence_event, INFINITE, false); + } - trace!("Present"); - unsafe { swap_chain.Present(1, 0) }.unwrap(); + fence_val += 1; + + Barrier::drop(present_barrier); + Barrier::drop(rtv_barrier); + } trace!("Handle message"); if !handle_message(hwnd) { break; } + trace!("Resize"); + if let Some((width, height)) = rx.try_iter().last() { + rtv_handles.drain(..).for_each(drop); + let desc = + util::try_out_param(|v| unsafe { swap_chain.GetDesc(v) }).unwrap(); + unsafe { + // TODO investigate why this crashes. Are there + // outstanding references + // to backbuffers? + // swap_chain + // .ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, + // desc.Flags) + // .unwrap() + }; + + for i in 0..desc.BufferCount { + 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); + rtv_handles.push(rtv_handle); + } + } + }; + if done.load(Ordering::SeqCst) { break; } @@ -266,6 +363,12 @@ pub unsafe extern "system" fn window_proc( WM_DESTROY => { PostQuitMessage(0); }, + WM_SIZE => { + let (width, height) = hudhook::util::win_size(hwnd); + if let Some(tx) = RESIZE.get() { + tx.send((width as _, height as _)); + } + }, _ => { return DefWindowProcA(hwnd, msg, w_param, l_param); }, diff --git a/tests/harness/dx9.rs b/tests/harness/dx9.rs index 86b17cc0..99d20a8e 100644 --- a/tests/harness/dx9.rs +++ b/tests/harness/dx9.rs @@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread::{self, JoinHandle}; +use hudhook::util; use windows::core::PCSTR; use windows::Win32::Foundation::{BOOL, HWND, LPARAM, LRESULT, RECT, WPARAM}; use windows::Win32::Graphics::Direct3D9::{ @@ -72,6 +73,8 @@ impl Dx9Harness { ) }; + util::enable_debug_interface(); + let direct3d = unsafe { Direct3DCreate9(D3D_SDK_VERSION).unwrap() }; let mut device = None; unsafe { @@ -95,7 +98,7 @@ impl Dx9Harness { loop { eprintln!("Present..."); unsafe { - device.Clear(0, null(), D3DCLEAR_TARGET as _, 0, 1.0, 0); + device.Clear(0, null(), D3DCLEAR_TARGET as _, 0x0022cc22, 1.0, 0); device.Present(null(), null(), None, null()); } @@ -104,6 +107,8 @@ impl Dx9Harness { break; } + util::print_dxgi_debug_messages(); + if done.load(Ordering::SeqCst) { break; } diff --git a/tests/harness/opengl3.rs b/tests/harness/opengl3.rs index c6532100..6f60575a 100644 --- a/tests/harness/opengl3.rs +++ b/tests/harness/opengl3.rs @@ -116,7 +116,7 @@ impl Opengl3Harness { loop { trace!("Debug"); - unsafe { glClearColor(0.0, 0.0, 0.0, 1.0) } + unsafe { glClearColor(0.0, 1.0, 1.0, 1.0) } unsafe { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) }; unsafe { SwapBuffers(window_handle) }; diff --git a/tests/hook.rs b/tests/hook.rs index 82362998..dc26319e 100644 --- a/tests/hook.rs +++ b/tests/hook.rs @@ -5,11 +5,12 @@ use hudhook::renderer::RenderEngine; use hudhook::ImguiRenderLoop; use image::io::Reader as ImageReader; use image::{EncodableLayout, RgbaImage}; -use imgui::{Condition, Image, StyleColor, TextureId}; +use imgui::{Condition, Context, Image, StyleColor, TextureId}; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; pub fn setup_tracing() { + dotenv::dotenv().ok(); tracing_subscriber::registry() .with( fmt::layer().event_format( @@ -27,7 +28,7 @@ pub fn setup_tracing() { pub struct HookExample { frame_times: Vec, - last_time: Instant, + last_time: Option, image: RgbaImage, image_id: Option, image_pos: [f32; 2], @@ -48,7 +49,7 @@ impl HookExample { HookExample { frame_times: Vec::new(), - last_time: Instant::now(), + last_time: None, image, image_id: None, image_pos: [16.0, 16.0], @@ -64,7 +65,7 @@ impl Default for HookExample { } impl ImguiRenderLoop for HookExample { - fn initialize(&mut self, render_engine: &mut RenderEngine) { + fn initialize(&mut self, _ctx: &mut Context, render_engine: &mut RenderEngine) { self.image_id = render_engine .load_image(self.image.as_bytes(), self.image.width() as _, self.image.height() as _) .ok(); @@ -73,16 +74,24 @@ 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(); + if let Some(last_time) = self.last_time.as_mut() { + let duration = last_time.elapsed(); + self.frame_times.push(duration); + *last_time = Instant::now(); + } else { + self.last_time = Some(Instant::now()); + } + + let avg: Duration = if self.frame_times.is_empty() { + Duration::from_nanos(0) + } else { + self.frame_times.iter().sum::() / self.frame_times.len() as u32 + }; - let avg: Duration = - self.frame_times.iter().sum::() / self.frame_times.len() as u32; - let last = self.frame_times.last().unwrap(); + let last = self.frame_times.last().copied().unwrap_or_else(|| Duration::from_nanos(0)); ui.window("Hello world") - .size([368.0, 568.0], Condition::FirstUseEver) + .size([376.0, 568.0], Condition::FirstUseEver) .position([16.0, 16.0], Condition::FirstUseEver) .build(|| { ui.text("Hello world!"); @@ -117,13 +126,13 @@ impl ImguiRenderLoop for HookExample { }); ui.window("Image") - .size([368.0, 568.0], Condition::FirstUseEver) - .position([416.0, 16.0], Condition::FirstUseEver) + .size([376.0, 568.0], Condition::FirstUseEver) + .position([408.0, 16.0], Condition::FirstUseEver) .build(|| { let next_x = self.image_pos[0] + self.image_dir[0]; let next_y = self.image_pos[1] + self.image_dir[1]; - if next_x <= 16. || next_x >= 368. - 16. - self.image.width() as f32 { + if next_x <= 16. || next_x >= 376. - 16. - self.image.width() as f32 { self.image_dir[0] = -self.image_dir[0]; } else { self.image_pos[0] = next_x;