├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── README.md ├── egui-directx11 ├── Cargo.toml ├── README.md ├── examples │ └── directx11-sample.rs └── src │ ├── backup.rs │ ├── lib.rs │ ├── mesh.rs │ ├── painter.rs │ ├── shader │ ├── mod.rs │ ├── pixel.bin │ ├── shader.hlsl │ └── vertex.bin │ └── texture.rs ├── egui-directx12 ├── Cargo.toml └── src │ └── lib.rs ├── egui-directx9 ├── Cargo.toml ├── README.md ├── examples │ └── directx9-sample.rs └── src │ └── lib.rs └── egui-win32 ├── Cargo.toml ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | /target -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | ".\\egui-directx11\\Cargo.toml", 4 | ".\\egui-directx11\\Cargo.toml" 5 | ], 6 | "rust-analyzer.showUnlinkedFileNotification": false 7 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["egui-directx9", "egui-directx11", "egui-directx12", "egui-win32"] 3 | 4 | 5 | [workspace.metadata] 6 | license = "MIT" 7 | 8 | [workspace.dependencies] 9 | windows = "0.48.0" 10 | egui = "0.22.0" 11 | 12 | # My blessed decisions. 13 | thiserror = "1.0.40" 14 | tracing = "0.1.37" 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Egui DirectX Renderer Implementations 2 | 3 | Renderer implementations only. Libraries don't provide Win32 input handling. Consult Egui-Win32 companion crate 4 | Helps with simplicity and keeping the libraries focused. 5 | Workspace structure for the crates, refer to base Cargo.toml; all crates will be enforced to use a consistent version of windows and egui crates. 6 | 7 | ## Egui Direct X9 8 | Renderer backend for Direct X9 for Egui 9 | Initial Source: https://crates.io/crates/egui-d3d9 10 | 11 | ## Egui Direct X11 12 | Renderer backend for Direct X11 for Egui 13 | Initial Source: https://crates.io/crates/egui-d3d11 14 | 15 | ## Egui Direct X12 16 | Renderer backend for Direct X12 for Egui 17 | ... 18 | 19 | ## Egui Win32 20 | Input collector backend for Egui using Windows Api 21 | 22 | Initial Source: https://crates.io/crates/egui-d3d9 23 | 24 | Initial Source: https://crates.io/crates/egui-d3d11 -------------------------------------------------------------------------------- /egui-directx11/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui-directx11" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | thiserror = { workspace = true } 11 | egui = { workspace = true } 12 | windows = { workspace = true, features = [ 13 | "Win32_Foundation", 14 | "Foundation_Numerics", 15 | "Win32_Graphics_Direct3D", 16 | "Win32_Graphics_Direct3D11", 17 | "Win32_Graphics_Dxgi", 18 | "Win32_System_SystemServices", 19 | "Win32_Graphics_Dxgi_Common", 20 | "Win32_Graphics_Direct3D_Fxc", 21 | "Win32_UI_WindowsAndMessaging", 22 | ] } 23 | 24 | [dev-dependencies] 25 | winit = "0.26" 26 | -------------------------------------------------------------------------------- /egui-directx11/README.md: -------------------------------------------------------------------------------- 1 | # Egui Direct X11 2 | 3 | Renderer backend for Direct X11 for Egui 4 | 5 | Initial Source: https://crates.io/crates/egui-d3d11 -------------------------------------------------------------------------------- /egui-directx11/examples/directx11-sample.rs: -------------------------------------------------------------------------------- 1 | use egui_directx11::DirectX11Renderer; 2 | use std::mem::transmute; 3 | use windows::core::ComInterface; 4 | use windows::Win32::Foundation::*; 5 | use windows::Win32::Graphics::Direct3D::*; 6 | use windows::Win32::Graphics::Direct3D11::*; 7 | use windows::Win32::Graphics::Dxgi::Common::*; 8 | use windows::Win32::Graphics::Dxgi::*; 9 | use winit::dpi::LogicalSize; 10 | use winit::event::{Event, WindowEvent}; 11 | use winit::event_loop::EventLoop; 12 | use winit::platform::windows::*; 13 | use winit::window::WindowBuilder; 14 | 15 | const WINDOW_WIDTH: f64 = 760.0; 16 | const WINDOW_HEIGHT: f64 = 760.0; 17 | 18 | type Result = std::result::Result>; 19 | 20 | fn create_device_with_type(drive_type: D3D_DRIVER_TYPE) -> Result { 21 | let mut flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; 22 | 23 | if cfg!(debug_assertions) { 24 | flags |= D3D11_CREATE_DEVICE_DEBUG; 25 | } 26 | 27 | let mut device = None; 28 | let feature_levels = [D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_10_0]; 29 | let mut fl = D3D_FEATURE_LEVEL_11_1; 30 | unsafe { 31 | Ok(D3D11CreateDevice( 32 | None, 33 | drive_type, 34 | HMODULE::default(), 35 | flags, 36 | Some(&feature_levels), 37 | D3D11_SDK_VERSION, 38 | Some(&mut device), 39 | Some(&mut fl), 40 | None, 41 | ) 42 | .map(|()| device.unwrap())?) 43 | } 44 | } 45 | 46 | fn create_device() -> Result { 47 | create_device_with_type(D3D_DRIVER_TYPE_HARDWARE) 48 | } 49 | 50 | fn create_swapchain(device: &ID3D11Device, window: HWND) -> Result { 51 | let factory = get_dxgi_factory(device)?; 52 | 53 | let sc_desc = DXGI_SWAP_CHAIN_DESC { 54 | BufferDesc: DXGI_MODE_DESC { 55 | Width: 0, 56 | Height: 0, 57 | RefreshRate: DXGI_RATIONAL { 58 | Numerator: 60, 59 | Denominator: 1, 60 | }, 61 | Format: DXGI_FORMAT_R8G8B8A8_UNORM, 62 | ..Default::default() 63 | }, 64 | SampleDesc: DXGI_SAMPLE_DESC { 65 | Count: 1, 66 | Quality: 0, 67 | }, 68 | BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, 69 | BufferCount: 3, 70 | OutputWindow: window, 71 | Windowed: true.into(), 72 | SwapEffect: DXGI_SWAP_EFFECT_DISCARD, 73 | Flags: DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.0 as u32, 74 | }; 75 | 76 | let mut swapchain = None; 77 | let _hresult = unsafe { factory.CreateSwapChain(device, &sc_desc, &mut swapchain) }; 78 | Ok(swapchain.unwrap()) 79 | } 80 | 81 | fn get_dxgi_factory(device: &ID3D11Device) -> Result { 82 | let dxdevice = device.cast::()?; 83 | unsafe { Ok(dxdevice.GetAdapter()?.GetParent()?) } 84 | } 85 | 86 | fn main() -> Result<()> { 87 | let event_loop = EventLoop::new(); 88 | let window = WindowBuilder::new() 89 | .with_title("egui-directx11") 90 | .with_inner_size(LogicalSize { 91 | width: WINDOW_WIDTH, 92 | height: WINDOW_HEIGHT, 93 | }) 94 | .build(&event_loop) 95 | .unwrap(); 96 | 97 | let device = create_device()?; 98 | let swapchain = unsafe { create_swapchain(&device, transmute(window.hwnd()))? }; 99 | 100 | struct State { 101 | counter: u32, 102 | } 103 | let mut shared_state = State { counter: 5 }; 104 | let mut dx_renderer: DirectX11Renderer = 105 | DirectX11Renderer::init_from_swapchain(&swapchain, egui::Context::default())?; 106 | 107 | event_loop.run(move |event, _, control_flow| match event { 108 | Event::MainEventsCleared => { 109 | window.request_redraw(); 110 | } 111 | 112 | Event::RedrawRequested(_) => { 113 | // collect input here 114 | let input = egui::RawInput::default(); 115 | dx_renderer 116 | .paint(&swapchain, &mut shared_state, input, |ctx, state| { 117 | egui::Window::new("Test Window").show(ctx, |ui| { 118 | ui.label("Hi"); 119 | ui.add(egui::Slider::new(&mut state.counter, 0..=120).text("test state")); 120 | }); 121 | }) 122 | .expect("successful render"); 123 | 124 | // you handle the swapchain present 125 | let _ = unsafe { swapchain.Present(1, 0) }; 126 | } 127 | Event::WindowEvent { 128 | event: WindowEvent::CloseRequested, 129 | .. 130 | } => *control_flow = winit::event_loop::ControlFlow::Exit, 131 | Event::WindowEvent { 132 | event: 133 | WindowEvent::Resized(winit::dpi::PhysicalSize { 134 | height: _height, 135 | width: _width, 136 | }), 137 | .. 138 | } => (), 139 | Event::LoopDestroyed => (), 140 | _event => (), 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /egui-directx11/src/backup.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, mem::zeroed}; 2 | use windows::Win32::{ 3 | Foundation::RECT, 4 | Graphics::{ 5 | Direct3D::D3D_PRIMITIVE_TOPOLOGY, 6 | Direct3D11::{ 7 | ID3D11BlendState, ID3D11Buffer, ID3D11ClassInstance, ID3D11DepthStencilState, 8 | ID3D11DeviceContext, ID3D11GeometryShader, ID3D11InputLayout, ID3D11PixelShader, 9 | ID3D11RasterizerState, ID3D11SamplerState, ID3D11ShaderResourceView, 10 | ID3D11VertexShader, D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT, 11 | D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT, 12 | D3D11_VIEWPORT, D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE, 13 | }, 14 | Dxgi::Common::DXGI_FORMAT, 15 | }, 16 | }; 17 | 18 | /// Structe used to backup all data from directx context. 19 | /// Thanks ImGui. 20 | #[derive(Default)] 21 | pub struct BackupState(RefCell); 22 | 23 | impl BackupState { 24 | #[inline] 25 | pub fn save(&self, context: &ID3D11DeviceContext) { 26 | unsafe { 27 | self.0.borrow_mut().save(context); 28 | } 29 | } 30 | 31 | #[inline] 32 | pub fn restore(&self, context: &ID3D11DeviceContext) { 33 | unsafe { 34 | self.0.borrow_mut().restore(context); 35 | } 36 | } 37 | } 38 | 39 | #[derive(Default)] 40 | struct InnerState { 41 | scissor_rects: [RECT; D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE as _], 42 | scissor_count: u32, 43 | 44 | viewports: [D3D11_VIEWPORT; D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE as _], 45 | viewport_count: u32, 46 | 47 | raster_state: Option, 48 | 49 | blend_state: Option, 50 | blend_factor: [f32; 4], 51 | blend_mask: u32, 52 | 53 | depth_stencil_state: Option, 54 | stencil_ref: u32, 55 | 56 | pixel_shader_resources: Array< 57 | { (D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT - 1) as usize }, 58 | ID3D11ShaderResourceView, 59 | >, 60 | samplers: Array<{ (D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT - 1) as usize }, ID3D11SamplerState>, 61 | 62 | vertex_shader: Option, 63 | vertex_shader_instances: Array<256, ID3D11ClassInstance>, 64 | vertex_shader_instances_count: u32, 65 | 66 | geometry_shader: Option, 67 | geometry_shader_instances: Array<256, ID3D11ClassInstance>, 68 | geomentry_shader_instances_count: u32, 69 | 70 | pixel_shader: Option, 71 | pixel_shader_instances: Array<256, ID3D11ClassInstance>, 72 | pixel_shader_instances_count: u32, 73 | 74 | constant_buffers: 75 | Array<{ (D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT - 1) as usize }, ID3D11Buffer>, 76 | primitive_topology: D3D_PRIMITIVE_TOPOLOGY, 77 | 78 | index_buffer: Option, 79 | index_buffer_format: DXGI_FORMAT, 80 | index_buffer_offest: u32, 81 | 82 | vertex_buffer: Option, 83 | vertex_buffer_strides: u32, 84 | vertex_buffer_offsets: u32, 85 | 86 | input_layout: Option, 87 | } 88 | 89 | impl InnerState { 90 | #[inline] 91 | pub unsafe fn save(&mut self, ctx: &ID3D11DeviceContext) { 92 | ctx.RSGetScissorRects( 93 | &mut self.scissor_count, 94 | Some(self.scissor_rects.as_mut_ptr()), 95 | ); 96 | ctx.RSGetViewports(&mut self.viewport_count, Some(self.viewports.as_mut_ptr())); 97 | self.raster_state = ctx.RSGetState().ok(); 98 | ctx.OMGetBlendState( 99 | Some(&mut self.blend_state), 100 | Some(self.blend_factor.as_mut_ptr()), 101 | Some(&mut self.blend_mask), 102 | ); 103 | ctx.OMGetDepthStencilState( 104 | Some(&mut self.depth_stencil_state), 105 | Some(&mut self.stencil_ref), 106 | ); 107 | ctx.PSGetShaderResources(0, Some(self.pixel_shader_resources.as_mut_slice())); 108 | ctx.PSGetSamplers(0, Some(self.samplers.as_mut_slice())); 109 | self.pixel_shader_instances_count = 256; 110 | self.vertex_shader_instances_count = 256; 111 | self.geomentry_shader_instances_count = 256; 112 | 113 | ctx.PSGetShader( 114 | &mut self.pixel_shader, 115 | Some(self.pixel_shader_instances.as_mut_ptr()), 116 | Some(&mut self.pixel_shader_instances_count), 117 | ); 118 | ctx.VSGetShader( 119 | &mut self.vertex_shader, 120 | Some(self.vertex_shader_instances.as_mut_ptr()), 121 | Some(&mut self.vertex_shader_instances_count), 122 | ); 123 | ctx.GSGetShader( 124 | &mut self.geometry_shader, 125 | Some(self.geometry_shader_instances.as_mut_ptr()), 126 | Some(&mut self.geomentry_shader_instances_count), 127 | ); 128 | 129 | ctx.VSGetConstantBuffers(0, Some(self.constant_buffers.as_mut_slice())); 130 | self.primitive_topology = ctx.IAGetPrimitiveTopology(); 131 | ctx.IAGetIndexBuffer( 132 | Some(&mut self.index_buffer), 133 | Some(&mut self.index_buffer_format), 134 | Some(&mut self.index_buffer_offest), 135 | ); 136 | ctx.IAGetVertexBuffers( 137 | 0, 138 | 1, 139 | Some(&mut self.vertex_buffer), 140 | Some(&mut self.vertex_buffer_strides), 141 | Some(&mut self.vertex_buffer_offsets), 142 | ); 143 | self.input_layout = ctx.IAGetInputLayout().ok(); 144 | } 145 | 146 | #[inline] 147 | pub unsafe fn restore(&mut self, ctx: &ID3D11DeviceContext) { 148 | ctx.RSSetScissorRects(Some( 149 | &self.scissor_rects.as_slice()[..self.scissor_count as usize], 150 | )); 151 | ctx.RSSetViewports(Some( 152 | &self.viewports.as_slice()[..self.viewport_count as usize], 153 | )); 154 | ctx.RSSetState(self.raster_state.take().as_ref()); 155 | ctx.OMSetBlendState( 156 | self.blend_state.take().as_ref(), 157 | Some(self.blend_factor.as_ptr()), 158 | self.blend_mask, 159 | ); 160 | ctx.OMSetDepthStencilState(self.depth_stencil_state.take().as_ref(), self.stencil_ref); 161 | ctx.PSSetShaderResources(0, Some(self.pixel_shader_resources.as_slice())); 162 | ctx.PSSetSamplers(0, Some(self.samplers.as_slice())); 163 | ctx.PSSetShader( 164 | self.pixel_shader.take().as_ref(), 165 | Some( 166 | &self.pixel_shader_instances.as_slice() 167 | [..self.pixel_shader_instances_count as usize], 168 | ), 169 | ); 170 | self.pixel_shader_instances.release(); 171 | 172 | ctx.VSSetShader( 173 | self.vertex_shader.take().as_ref(), 174 | Some( 175 | &self.vertex_shader_instances.as_slice() 176 | [..self.vertex_shader_instances_count as usize], 177 | ), 178 | ); 179 | self.vertex_shader_instances.release(); 180 | 181 | ctx.GSSetShader( 182 | self.geometry_shader.take().as_ref(), 183 | Some( 184 | &self.geometry_shader_instances.as_slice() 185 | [..self.geomentry_shader_instances_count as usize], 186 | ), 187 | ); 188 | self.geometry_shader_instances.release(); 189 | 190 | ctx.VSSetConstantBuffers(0, Some(self.constant_buffers.as_slice())); 191 | ctx.IASetPrimitiveTopology(self.primitive_topology); 192 | ctx.IASetIndexBuffer( 193 | self.index_buffer.take().as_ref(), 194 | self.index_buffer_format, 195 | self.index_buffer_offest, 196 | ); 197 | ctx.IASetVertexBuffers( 198 | 0, 199 | 1, 200 | Some(&self.vertex_buffer.take()), 201 | Some(&self.vertex_buffer_strides), 202 | Some(&self.vertex_buffer_offsets), 203 | ); 204 | ctx.IASetInputLayout(self.input_layout.take().as_ref()); 205 | } 206 | } 207 | 208 | struct Array([Option; N]); 209 | impl Array { 210 | #[inline] 211 | pub fn as_mut_ptr(&mut self) -> *mut Option { 212 | &mut self.0[0] 213 | } 214 | 215 | #[inline] 216 | pub fn as_slice(&self) -> &[Option] { 217 | self.0.as_slice() 218 | } 219 | 220 | #[inline] 221 | pub fn as_mut_slice(&mut self) -> &mut [Option] { 222 | self.0.as_mut_slice() 223 | } 224 | 225 | #[inline] 226 | pub fn release(&mut self) { 227 | self.0.iter().for_each(drop); 228 | } 229 | } 230 | 231 | impl Default for Array { 232 | fn default() -> Self { 233 | unsafe { zeroed() } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /egui-directx11/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod backup; 2 | mod mesh; 3 | mod painter; 4 | mod shader; 5 | mod texture; 6 | 7 | pub use painter::*; 8 | 9 | #[derive(Debug, thiserror::Error)] 10 | pub enum RenderError { 11 | #[error("Unrecoverable error occured {0}")] 12 | General(&'static str), 13 | 14 | #[error("Windows error {0}")] 15 | Win(#[from] windows::core::Error), 16 | } 17 | -------------------------------------------------------------------------------- /egui-directx11/src/mesh.rs: -------------------------------------------------------------------------------- 1 | use egui::{epaint::Vertex, Mesh, Pos2, Rect, Rgba, TextureId}; 2 | use std::mem::size_of; 3 | use windows::Win32::Graphics::Direct3D11::{ 4 | ID3D11Buffer, ID3D11Device, D3D11_BIND_INDEX_BUFFER, D3D11_BIND_VERTEX_BUFFER, 5 | D3D11_BUFFER_DESC, D3D11_SUBRESOURCE_DATA, D3D11_USAGE_DEFAULT, 6 | }; 7 | 8 | use crate::RenderError; 9 | 10 | pub struct GpuMesh { 11 | pub indices: Vec, 12 | pub vertices: Vec, 13 | pub clip: Rect, 14 | pub texture_id: TextureId, 15 | } 16 | 17 | impl GpuMesh { 18 | pub fn from_mesh((w, h): (f32, f32), mesh: Mesh, scissors: Rect) -> Option { 19 | if mesh.indices.is_empty() || mesh.indices.len() % 3 != 0 { 20 | None 21 | } else { 22 | let vertices = mesh 23 | .vertices 24 | .into_iter() 25 | .map(|v| GpuVertex { 26 | pos: Pos2::new( 27 | (v.pos.x - w / 2.) / (w / 2.), 28 | (v.pos.y - h / 2.) / -(h / 2.), 29 | ), 30 | uv: v.uv, 31 | color: v.color.into(), 32 | }) 33 | .collect(); 34 | 35 | Some(Self { 36 | texture_id: mesh.texture_id, 37 | indices: mesh.indices, 38 | clip: scissors, 39 | vertices, 40 | }) 41 | } 42 | } 43 | } 44 | 45 | #[repr(C)] 46 | pub struct GpuVertex { 47 | pos: Pos2, 48 | uv: Pos2, 49 | color: Rgba, 50 | } 51 | 52 | impl From for GpuVertex { 53 | fn from(v: Vertex) -> Self { 54 | Self { 55 | pos: v.pos, 56 | uv: v.uv, 57 | color: v.color.into(), 58 | } 59 | } 60 | } 61 | 62 | pub fn create_vertex_buffer( 63 | device: &ID3D11Device, 64 | mesh: &GpuMesh, 65 | ) -> Result { 66 | let desc = D3D11_BUFFER_DESC { 67 | ByteWidth: (mesh.vertices.len() * size_of::()) as u32, 68 | Usage: D3D11_USAGE_DEFAULT, 69 | BindFlags: D3D11_BIND_VERTEX_BUFFER, 70 | ..Default::default() 71 | }; 72 | 73 | let init = D3D11_SUBRESOURCE_DATA { 74 | pSysMem: mesh.vertices.as_ptr() as _, 75 | ..Default::default() 76 | }; 77 | 78 | unsafe { 79 | let mut output = None; 80 | device.CreateBuffer(&desc, Some(&init), Some(&mut output))?; 81 | output.ok_or(RenderError::General("Failed to create vertex buffer")) 82 | } 83 | } 84 | 85 | pub fn create_index_buffer( 86 | device: &ID3D11Device, 87 | mesh: &GpuMesh, 88 | ) -> Result { 89 | let desc = D3D11_BUFFER_DESC { 90 | ByteWidth: (mesh.indices.len() * size_of::()) as u32, 91 | Usage: D3D11_USAGE_DEFAULT, 92 | BindFlags: D3D11_BIND_INDEX_BUFFER, 93 | ..Default::default() 94 | }; 95 | 96 | let init = D3D11_SUBRESOURCE_DATA { 97 | pSysMem: mesh.indices.as_ptr() as _, 98 | ..Default::default() 99 | }; 100 | 101 | unsafe { 102 | let mut output = None; 103 | device.CreateBuffer(&desc, Some(&init), Some(&mut output))?; 104 | output.ok_or(RenderError::General("Failed to create index buffer")) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /egui-directx11/src/painter.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | backup::BackupState, 3 | mesh::{create_index_buffer, create_vertex_buffer, GpuMesh, GpuVertex}, 4 | shader::CompiledShaders, 5 | texture::TextureAllocator, 6 | RenderError, 7 | }; 8 | use egui::{epaint::Primitive, Context}; 9 | use std::mem::size_of; 10 | use windows::{ 11 | core::HRESULT, 12 | s, 13 | Win32::{ 14 | Foundation::{HWND, RECT}, 15 | Graphics::{ 16 | Direct3D::D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, 17 | Direct3D11::{ 18 | ID3D11Device, ID3D11DeviceContext, ID3D11InputLayout, ID3D11RenderTargetView, 19 | ID3D11Texture2D, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_BLEND_DESC, 20 | D3D11_BLEND_INV_SRC_ALPHA, D3D11_BLEND_ONE, D3D11_BLEND_OP_ADD, 21 | D3D11_BLEND_SRC_ALPHA, D3D11_COLOR_WRITE_ENABLE_ALL, D3D11_COMPARISON_ALWAYS, 22 | D3D11_CULL_NONE, D3D11_FILL_SOLID, D3D11_FILTER_MIN_MAG_MIP_LINEAR, 23 | D3D11_INPUT_ELEMENT_DESC, D3D11_INPUT_PER_VERTEX_DATA, D3D11_RASTERIZER_DESC, 24 | D3D11_RENDER_TARGET_BLEND_DESC, D3D11_SAMPLER_DESC, D3D11_TEXTURE_ADDRESS_BORDER, 25 | D3D11_VIEWPORT, 26 | }, 27 | Dxgi::{ 28 | Common::{ 29 | DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_R32_UINT, 30 | }, 31 | IDXGISwapChain, DXGI_SWAP_CHAIN_DESC, 32 | }, 33 | }, 34 | UI::WindowsAndMessaging::GetClientRect, 35 | }, 36 | }; 37 | 38 | /// Heart and soul of this integration. 39 | /// Main methods you are going to use are: 40 | /// * [`Self::present`] - Should be called inside of hook or before present. 41 | /// * [`Self::resize_buffers`] - Should be called **INSTEAD** of swapchain's `ResizeBuffers`. 42 | /// * [`Self::wnd_proc`] - Should be called on each `WndProc`. 43 | pub struct DirectX11Renderer { 44 | render_view: Option, 45 | tex_alloc: TextureAllocator, 46 | input_layout: ID3D11InputLayout, 47 | shaders: CompiledShaders, 48 | backup: BackupState, 49 | hwnd: HWND, 50 | context: egui::Context, 51 | } 52 | 53 | impl DirectX11Renderer { 54 | const INPUT_ELEMENTS_DESC: [D3D11_INPUT_ELEMENT_DESC; 3] = [ 55 | D3D11_INPUT_ELEMENT_DESC { 56 | SemanticName: s!("POSITION"), 57 | SemanticIndex: 0, 58 | Format: DXGI_FORMAT_R32G32_FLOAT, 59 | InputSlot: 0, 60 | AlignedByteOffset: 0, 61 | InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, 62 | InstanceDataStepRate: 0, 63 | }, 64 | D3D11_INPUT_ELEMENT_DESC { 65 | SemanticName: s!("TEXCOORD"), 66 | SemanticIndex: 0, 67 | Format: DXGI_FORMAT_R32G32_FLOAT, 68 | InputSlot: 0, 69 | AlignedByteOffset: D3D11_APPEND_ALIGNED_ELEMENT, 70 | InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, 71 | InstanceDataStepRate: 0, 72 | }, 73 | D3D11_INPUT_ELEMENT_DESC { 74 | SemanticName: s!("COLOR"), 75 | SemanticIndex: 0, 76 | Format: DXGI_FORMAT_R32G32B32A32_FLOAT, 77 | InputSlot: 0, 78 | AlignedByteOffset: D3D11_APPEND_ALIGNED_ELEMENT, 79 | InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, 80 | InstanceDataStepRate: 0, 81 | }, 82 | ]; 83 | } 84 | 85 | impl DirectX11Renderer { 86 | /// Create a new directx11 renderer from a swapchain 87 | pub fn init_from_swapchain( 88 | swapchain: &IDXGISwapChain, 89 | context: egui::Context, 90 | ) -> Result { 91 | unsafe { 92 | let mut swap_chain_desc = DXGI_SWAP_CHAIN_DESC::default(); 93 | swapchain.GetDesc(&mut swap_chain_desc)?; 94 | 95 | let hwnd = swap_chain_desc.OutputWindow; 96 | if hwnd.0 == -1 { 97 | return Err(RenderError::General( 98 | "Trying to initialize from a swapchain with an invalid hwnd", 99 | )); 100 | } 101 | let dev: ID3D11Device = swapchain.GetDevice()?; 102 | let backbuffer: ID3D11Texture2D = swapchain.GetBuffer(0)?; 103 | 104 | let mut render_view = None; 105 | dev.CreateRenderTargetView(&backbuffer, None, Some(&mut render_view))?; 106 | 107 | let shaders = CompiledShaders::new(&dev)?; 108 | let mut input_layout = None; 109 | dev.CreateInputLayout( 110 | &Self::INPUT_ELEMENTS_DESC, 111 | shaders.bytecode(), 112 | Some(&mut input_layout), 113 | )?; 114 | let input_layout = 115 | input_layout.ok_or(RenderError::General("failed to initialize input layout"))?; 116 | 117 | Ok(Self { 118 | tex_alloc: TextureAllocator::default(), 119 | backup: BackupState::default(), 120 | input_layout, 121 | render_view, 122 | shaders, 123 | hwnd, 124 | context, 125 | }) 126 | } 127 | } 128 | } 129 | 130 | impl DirectX11Renderer { 131 | /// Present call. Should be called once per original present call, before or inside of hook. 132 | #[allow(clippy::cast_ref_to_mut)] 133 | pub fn paint( 134 | &mut self, 135 | swap_chain: &IDXGISwapChain, 136 | shared_state: &mut State, 137 | input: egui::RawInput, 138 | paint: PaintFn, 139 | ) -> Result<(), RenderError> 140 | where 141 | PaintFn: Fn(&Context, &mut State) + 'static, 142 | { 143 | unsafe { 144 | let (dev, ctx) = &get_device_and_context(swap_chain)?; 145 | self.backup.save(ctx); 146 | let screen = self.get_screen_size(); 147 | let output = self.context.run(input, |ctx| paint(ctx, shared_state)); 148 | 149 | if !output.textures_delta.is_empty() { 150 | self.tex_alloc 151 | .process_deltas(dev, ctx, output.textures_delta)?; 152 | } 153 | 154 | if output.shapes.is_empty() { 155 | self.backup.restore(ctx); 156 | return Ok(()); 157 | } 158 | 159 | let primitives = self 160 | .context 161 | .tessellate(output.shapes) 162 | .into_iter() 163 | .filter_map(|prim| { 164 | if let Primitive::Mesh(mesh) = prim.primitive { 165 | GpuMesh::from_mesh(screen, mesh, prim.clip_rect) 166 | } else { 167 | panic!("Paint callbacks are not yet supported") 168 | } 169 | }) 170 | .collect::>(); 171 | 172 | self.set_blend_state(dev, ctx)?; 173 | self.set_raster_options(dev, ctx)?; 174 | self.set_sampler_state(dev, ctx)?; 175 | 176 | ctx.RSSetViewports(Some(&[self.get_viewport()])); 177 | ctx.OMSetRenderTargets(Some(&[self.render_view.clone()]), None); 178 | ctx.IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 179 | ctx.IASetInputLayout(&self.input_layout); 180 | 181 | for mesh in primitives { 182 | let idx = create_index_buffer(dev, &mesh)?; 183 | let vtx = create_vertex_buffer(dev, &mesh)?; 184 | 185 | let texture = self.tex_alloc.get_by_id(mesh.texture_id); 186 | 187 | ctx.RSSetScissorRects(Some(&[RECT { 188 | left: mesh.clip.left() as _, 189 | top: mesh.clip.top() as _, 190 | right: mesh.clip.right() as _, 191 | bottom: mesh.clip.bottom() as _, 192 | }])); 193 | 194 | if texture.is_some() { 195 | ctx.PSSetShaderResources(0, Some(&[texture])); 196 | } 197 | 198 | ctx.IASetVertexBuffers( 199 | 0, 200 | 1, 201 | Some(&Some(vtx)), 202 | Some(&(size_of::() as _)), 203 | Some(&0), 204 | ); 205 | ctx.IASetIndexBuffer(&idx, DXGI_FORMAT_R32_UINT, 0); 206 | ctx.VSSetShader(&self.shaders.vertex, Some(&[])); 207 | ctx.PSSetShader(&self.shaders.pixel, Some(&[])); 208 | 209 | ctx.DrawIndexed(mesh.indices.len() as _, 0, 0); 210 | } 211 | 212 | self.backup.restore(ctx); 213 | } 214 | 215 | Ok(()) 216 | } 217 | 218 | /// Call when resizing buffers. 219 | /// Do not call the original function before it, instead call it inside of the `original` closure. 220 | /// # Behavior 221 | /// In `origin` closure make sure to call the original `ResizeBuffers`. 222 | pub fn resize_buffers( 223 | &mut self, 224 | swap_chain: &IDXGISwapChain, 225 | original: impl FnOnce() -> HRESULT, 226 | ) -> Result { 227 | unsafe { 228 | drop(self.render_view.take()); 229 | let result = original(); 230 | let backbuffer: ID3D11Texture2D = swap_chain.GetBuffer(0)?; 231 | let device: ID3D11Device = swap_chain.GetDevice()?; 232 | device.CreateRenderTargetView(&backbuffer, None, Some(&mut self.render_view))?; 233 | Ok(result) 234 | } 235 | } 236 | } 237 | 238 | impl DirectX11Renderer { 239 | #[inline] 240 | fn get_screen_size(&self) -> (f32, f32) { 241 | let mut rect = RECT::default(); 242 | unsafe { 243 | GetClientRect(self.hwnd, &mut rect); 244 | } 245 | ( 246 | (rect.right - rect.left) as f32, 247 | (rect.bottom - rect.top) as f32, 248 | ) 249 | } 250 | 251 | #[inline] 252 | fn get_viewport(&self) -> D3D11_VIEWPORT { 253 | let (w, h) = self.get_screen_size(); 254 | D3D11_VIEWPORT { 255 | TopLeftX: 0., 256 | TopLeftY: 0., 257 | Width: w, 258 | Height: h, 259 | MinDepth: 0., 260 | MaxDepth: 1., 261 | } 262 | } 263 | 264 | fn set_blend_state( 265 | &self, 266 | dev: &ID3D11Device, 267 | ctx: &ID3D11DeviceContext, 268 | ) -> Result<(), RenderError> { 269 | let mut targets: [D3D11_RENDER_TARGET_BLEND_DESC; 8] = Default::default(); 270 | targets[0].BlendEnable = true.into(); 271 | targets[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; 272 | targets[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; 273 | targets[0].BlendOp = D3D11_BLEND_OP_ADD; 274 | targets[0].SrcBlendAlpha = D3D11_BLEND_ONE; 275 | targets[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; 276 | targets[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; 277 | targets[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as _; 278 | 279 | let blend_desc = D3D11_BLEND_DESC { 280 | AlphaToCoverageEnable: false.into(), 281 | IndependentBlendEnable: false.into(), 282 | RenderTarget: targets, 283 | }; 284 | 285 | unsafe { 286 | let mut blend_state = None; 287 | dev.CreateBlendState(&blend_desc, Some(&mut blend_state))?; 288 | let blend_state = 289 | blend_state.ok_or(RenderError::General("Unable to set blend state"))?; 290 | ctx.OMSetBlendState(&blend_state, Some([0., 0., 0., 0.].as_ptr()), 0xffffffff); 291 | } 292 | 293 | Ok(()) 294 | } 295 | 296 | fn set_raster_options( 297 | &self, 298 | dev: &ID3D11Device, 299 | ctx: &ID3D11DeviceContext, 300 | ) -> Result<(), RenderError> { 301 | let raster_desc = D3D11_RASTERIZER_DESC { 302 | FillMode: D3D11_FILL_SOLID, 303 | CullMode: D3D11_CULL_NONE, 304 | FrontCounterClockwise: false.into(), 305 | DepthBias: false.into(), 306 | DepthBiasClamp: 0., 307 | SlopeScaledDepthBias: 0., 308 | DepthClipEnable: false.into(), 309 | ScissorEnable: true.into(), 310 | MultisampleEnable: false.into(), 311 | AntialiasedLineEnable: false.into(), 312 | }; 313 | 314 | unsafe { 315 | let mut options = None; 316 | dev.CreateRasterizerState(&raster_desc, Some(&mut options))?; 317 | let options = options.ok_or(RenderError::General("Unable to set options"))?; 318 | ctx.RSSetState(&options); 319 | Ok(()) 320 | } 321 | } 322 | 323 | fn set_sampler_state( 324 | &self, 325 | dev: &ID3D11Device, 326 | ctx: &ID3D11DeviceContext, 327 | ) -> Result<(), RenderError> { 328 | let desc = D3D11_SAMPLER_DESC { 329 | Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, 330 | AddressU: D3D11_TEXTURE_ADDRESS_BORDER, 331 | AddressV: D3D11_TEXTURE_ADDRESS_BORDER, 332 | AddressW: D3D11_TEXTURE_ADDRESS_BORDER, 333 | MipLODBias: 0., 334 | ComparisonFunc: D3D11_COMPARISON_ALWAYS, 335 | MinLOD: 0., 336 | MaxLOD: 0., 337 | BorderColor: [1., 1., 1., 1.], 338 | ..Default::default() 339 | }; 340 | 341 | unsafe { 342 | let mut sampler = None; 343 | dev.CreateSamplerState(&desc, Some(&mut sampler))?; 344 | ctx.PSSetSamplers(0, Some(&[sampler])); 345 | Ok(()) 346 | } 347 | } 348 | } 349 | 350 | unsafe fn get_device_and_context( 351 | swap: &IDXGISwapChain, 352 | ) -> Result<(ID3D11Device, ID3D11DeviceContext), RenderError> { 353 | let device: ID3D11Device = swap.GetDevice()?; 354 | let context = device.GetImmediateContext()?; 355 | Ok((device, context)) 356 | } 357 | -------------------------------------------------------------------------------- /egui-directx11/src/shader/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, slice::from_raw_parts}; 2 | 3 | use windows::{ 4 | core::PCSTR, 5 | s, 6 | Win32::Graphics::{ 7 | Direct3D::{ 8 | Fxc::{D3DCompile, D3DCOMPILE_DEBUG, D3DCOMPILE_ENABLE_STRICTNESS}, 9 | ID3DBlob, 10 | }, 11 | Direct3D11::{ID3D11Device, ID3D11PixelShader, ID3D11VertexShader}, 12 | }, 13 | }; 14 | 15 | use crate::RenderError; 16 | 17 | trait Shader: Sized { 18 | const ENTRY: PCSTR; 19 | const TARGET: PCSTR; 20 | 21 | unsafe fn create_shader(device: &ID3D11Device, blob: &ShaderData) -> Result; 22 | } 23 | 24 | impl Shader for ID3D11VertexShader { 25 | const ENTRY: PCSTR = s!("vs_main"); 26 | const TARGET: PCSTR = s!("vs_5_0"); 27 | 28 | unsafe fn create_shader(device: &ID3D11Device, blob: &ShaderData) -> Result { 29 | let mut output = None; 30 | match blob { 31 | ShaderData::EmbeddedData(arr) => { 32 | device.CreateVertexShader(arr, None, Some(&mut output))?; 33 | output.ok_or(RenderError::General( 34 | "Unable to create vertex shader for embedded data", 35 | )) 36 | } 37 | ShaderData::CompiledBlob(blob) => { 38 | device.CreateVertexShader( 39 | from_raw_parts(blob.GetBufferPointer() as _, blob.GetBufferSize()), 40 | None, 41 | Some(&mut output), 42 | )?; 43 | output.ok_or(RenderError::General( 44 | "Unable to create vertex shader for compiled blob", 45 | )) 46 | } 47 | } 48 | } 49 | } 50 | 51 | impl Shader for ID3D11PixelShader { 52 | const ENTRY: PCSTR = s!("ps_main"); 53 | const TARGET: PCSTR = s!("ps_5_0"); 54 | 55 | unsafe fn create_shader(device: &ID3D11Device, blob: &ShaderData) -> Result { 56 | let mut output = None; 57 | match blob { 58 | ShaderData::EmbeddedData(arr) => { 59 | device.CreatePixelShader(arr, None, Some(&mut output))?; 60 | output.ok_or(RenderError::General( 61 | "Unable to create pixel shader for embedded data", 62 | )) 63 | } 64 | ShaderData::CompiledBlob(blob) => { 65 | device.CreatePixelShader( 66 | from_raw_parts(blob.GetBufferPointer() as _, blob.GetBufferSize()), 67 | None, 68 | Some(&mut output), 69 | )?; 70 | output.ok_or(RenderError::General( 71 | "Unable to create pixel shader for compiled blob", 72 | )) 73 | } 74 | } 75 | } 76 | } 77 | 78 | pub enum ShaderData { 79 | EmbeddedData(&'static [u8]), 80 | CompiledBlob(ID3DBlob), 81 | } 82 | 83 | pub struct CompiledShaders { 84 | pub vertex: ID3D11VertexShader, 85 | pub pixel: ID3D11PixelShader, 86 | cache: ShaderData, 87 | } 88 | 89 | impl CompiledShaders { 90 | pub fn new(device: &ID3D11Device) -> Result { 91 | if cfg!(feature = "force-compile") { 92 | let (vcache, vertex) = Self::compile_shader::(device)?; 93 | let (_pcache, pixel) = Self::compile_shader::(device)?; 94 | 95 | if cfg!(feature = "save-blob") { 96 | unsafe { 97 | std::fs::OpenOptions::new() 98 | .write(true) 99 | .read(true) 100 | .create(true) 101 | .open("vertex.bin") 102 | .unwrap() 103 | .write_all(std::slice::from_raw_parts( 104 | vcache.GetBufferPointer() as *mut u8, 105 | vcache.GetBufferSize(), 106 | )) 107 | .unwrap(); 108 | 109 | std::fs::OpenOptions::new() 110 | .write(true) 111 | .read(true) 112 | .create(true) 113 | .open("pixel.bin") 114 | .unwrap() 115 | .write_all(std::slice::from_raw_parts( 116 | _pcache.GetBufferPointer() as *mut u8, 117 | _pcache.GetBufferSize(), 118 | )) 119 | .unwrap(); 120 | } 121 | } 122 | 123 | Ok(Self { 124 | vertex, 125 | pixel, 126 | cache: ShaderData::CompiledBlob(vcache), 127 | }) 128 | } else { 129 | unsafe { 130 | let cache = ShaderData::EmbeddedData(include_bytes!("vertex.bin")); 131 | let vertex = ID3D11VertexShader::create_shader(device, &cache)?; 132 | let pixel = ID3D11PixelShader::create_shader( 133 | device, 134 | &ShaderData::EmbeddedData(include_bytes!("pixel.bin")), 135 | )?; 136 | 137 | Ok(Self { 138 | cache, 139 | vertex, 140 | pixel, 141 | }) 142 | } 143 | } 144 | } 145 | 146 | pub fn bytecode(&self) -> &[u8] { 147 | match &self.cache { 148 | ShaderData::EmbeddedData(arr) => arr, 149 | ShaderData::CompiledBlob(blob) => unsafe { 150 | from_raw_parts(blob.GetBufferPointer() as _, blob.GetBufferSize()) 151 | }, 152 | } 153 | } 154 | 155 | fn compile_shader(device: &ID3D11Device) -> Result<(ID3DBlob, S), RenderError> { 156 | const SHADER_TEXT: &str = include_str!("shader.hlsl"); 157 | 158 | let mut flags = D3DCOMPILE_ENABLE_STRICTNESS; 159 | if cfg!(debug_assertions) { 160 | flags |= D3DCOMPILE_DEBUG; 161 | } 162 | 163 | let mut code = None; 164 | let mut error = None; 165 | 166 | unsafe { 167 | if D3DCompile( 168 | SHADER_TEXT.as_ptr() as _, 169 | SHADER_TEXT.len(), 170 | None, 171 | None, 172 | None, 173 | S::ENTRY, 174 | S::TARGET, 175 | flags, 176 | 0, 177 | &mut code, 178 | Some(&mut error), 179 | ) 180 | .is_err() 181 | { 182 | if !cfg!(feature = "no-msgs") { 183 | panic!( 184 | "{}", 185 | std::str::from_utf8_unchecked(std::slice::from_raw_parts( 186 | error.as_ref().unwrap().GetBufferPointer() as *const u8, 187 | error.as_ref().unwrap().GetBufferSize(), 188 | )) 189 | ); 190 | } else { 191 | panic!(); 192 | } 193 | } else { 194 | Ok(( 195 | code.clone().unwrap(), 196 | S::create_shader(device, &ShaderData::CompiledBlob(code.unwrap()))?, 197 | )) 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /egui-directx11/src/shader/pixel.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohchase/egui-directx/dd6241308d3b234d8bb86dcbd58ab8d6a3eaf5a2/egui-directx11/src/shader/pixel.bin -------------------------------------------------------------------------------- /egui-directx11/src/shader/shader.hlsl: -------------------------------------------------------------------------------- 1 | struct vs_in { 2 | float2 position : POSITION; 3 | float2 uv : TEXCOORD; 4 | float4 color : COLOR; 5 | }; 6 | 7 | struct vs_out { 8 | float4 clip : SV_POSITION; 9 | float2 uv : TEXCOORD; 10 | float4 color : COLOR; 11 | }; 12 | 13 | vs_out vs_main(vs_in input) { 14 | vs_out output; 15 | output.clip = float4(input.position, 0.0, 1.0); 16 | output.uv = input.uv; 17 | output.color = input.color; 18 | 19 | return output; 20 | } 21 | 22 | sampler sampler0; 23 | Texture2D texture0; 24 | 25 | float4 ps_main(vs_out input) : SV_TARGET { 26 | return pow(input.color, 1.0 / 2.2) * texture0.Sample(sampler0, input.uv); 27 | } -------------------------------------------------------------------------------- /egui-directx11/src/shader/vertex.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohchase/egui-directx/dd6241308d3b234d8bb86dcbd58ab8d6a3eaf5a2/egui-directx11/src/shader/vertex.bin -------------------------------------------------------------------------------- /egui-directx11/src/texture.rs: -------------------------------------------------------------------------------- 1 | use egui::{Color32, ImageData, TextureId, TexturesDelta}; 2 | use std::{collections::HashMap, mem::size_of, slice::from_raw_parts_mut}; 3 | use windows::Win32::Graphics::{ 4 | Direct3D::D3D11_SRV_DIMENSION_TEXTURE2D, 5 | Direct3D11::{ 6 | ID3D11Device, ID3D11DeviceContext, ID3D11ShaderResourceView, ID3D11Texture2D, 7 | D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, D3D11_MAPPED_SUBRESOURCE, 8 | D3D11_MAP_WRITE_DISCARD, D3D11_SHADER_RESOURCE_VIEW_DESC, 9 | D3D11_SHADER_RESOURCE_VIEW_DESC_0, D3D11_SUBRESOURCE_DATA, D3D11_TEX2D_SRV, 10 | D3D11_TEXTURE2D_DESC, D3D11_USAGE_DYNAMIC, 11 | }, 12 | Dxgi::Common::{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SAMPLE_DESC}, 13 | }; 14 | 15 | use crate::RenderError; 16 | 17 | struct ManagedTexture { 18 | resource: ID3D11ShaderResourceView, 19 | texture: ID3D11Texture2D, 20 | pixels: Vec, 21 | width: usize, 22 | } 23 | 24 | #[derive(Default)] 25 | pub struct TextureAllocator { 26 | allocated: HashMap, 27 | } 28 | 29 | impl TextureAllocator { 30 | pub fn process_deltas( 31 | &mut self, 32 | dev: &ID3D11Device, 33 | ctx: &ID3D11DeviceContext, 34 | delta: TexturesDelta, 35 | ) -> Result<(), RenderError> { 36 | for (tid, delta) in delta.set { 37 | if delta.is_whole() { 38 | self.allocate_new(dev, tid, delta.image)?; 39 | } else { 40 | let _did_update = self.update_partial(ctx, tid, delta.image, delta.pos.unwrap())?; 41 | } 42 | } 43 | 44 | for tid in delta.free { 45 | self.free(tid); 46 | } 47 | 48 | Ok(()) 49 | } 50 | 51 | pub fn get_by_id(&self, tid: TextureId) -> Option { 52 | self.allocated.get(&tid).map(|t| t.resource.clone()) 53 | } 54 | } 55 | 56 | impl TextureAllocator { 57 | fn allocate_new( 58 | &mut self, 59 | dev: &ID3D11Device, 60 | tid: TextureId, 61 | image: ImageData, 62 | ) -> Result<(), RenderError> { 63 | let tex = Self::allocate_texture(dev, image)?; 64 | self.allocated.insert(tid, tex); 65 | Ok(()) 66 | } 67 | 68 | fn free(&mut self, tid: TextureId) -> bool { 69 | self.allocated.remove(&tid).is_some() 70 | } 71 | 72 | fn update_partial( 73 | &mut self, 74 | ctx: &ID3D11DeviceContext, 75 | tid: TextureId, 76 | image: ImageData, 77 | [nx, ny]: [usize; 2], 78 | ) -> Result { 79 | if let Some(old) = self.allocated.get_mut(&tid) { 80 | let subr = unsafe { 81 | let mut output = D3D11_MAPPED_SUBRESOURCE::default(); 82 | ctx.Map( 83 | &old.texture, 84 | 0, 85 | D3D11_MAP_WRITE_DISCARD, 86 | 0, 87 | Some(&mut output), 88 | )?; 89 | output 90 | }; 91 | 92 | match image { 93 | ImageData::Font(f) => unsafe { 94 | let data = from_raw_parts_mut(subr.pData as *mut Color32, old.pixels.len()); 95 | data.as_mut_ptr() 96 | .copy_from_nonoverlapping(old.pixels.as_ptr(), old.pixels.len()); 97 | 98 | let new: Vec = f 99 | .pixels 100 | .iter() 101 | .map(|a| Color32::from_rgba_premultiplied(255, 255, 255, (a * 255.) as u8)) 102 | .collect(); 103 | 104 | for y in 0..f.height() { 105 | for x in 0..f.width() { 106 | let whole = (ny + y) * old.width + nx + x; 107 | let frac = y * f.width() + x; 108 | old.pixels[whole] = new[frac]; 109 | data[whole] = new[frac]; 110 | } 111 | } 112 | }, 113 | _ => unreachable!(), 114 | } 115 | 116 | unsafe { 117 | ctx.Unmap(&old.texture, 0); 118 | } 119 | 120 | Ok(true) 121 | } else { 122 | Ok(false) 123 | } 124 | } 125 | 126 | fn allocate_texture( 127 | dev: &ID3D11Device, 128 | image: ImageData, 129 | ) -> Result { 130 | let desc = D3D11_TEXTURE2D_DESC { 131 | Width: image.width() as _, 132 | Height: image.height() as _, 133 | MipLevels: 1, 134 | ArraySize: 1, 135 | Format: DXGI_FORMAT_R8G8B8A8_UNORM, 136 | SampleDesc: DXGI_SAMPLE_DESC { 137 | Count: 1, 138 | Quality: 0, 139 | }, 140 | Usage: D3D11_USAGE_DYNAMIC, 141 | BindFlags: D3D11_BIND_SHADER_RESOURCE, 142 | CPUAccessFlags: D3D11_CPU_ACCESS_WRITE, 143 | ..Default::default() 144 | }; 145 | 146 | // rust is cringe sometimes 147 | let width = image.width(); 148 | let pixels = match image { 149 | ImageData::Color(c) => c.pixels, 150 | ImageData::Font(f) => f 151 | .pixels 152 | .iter() 153 | .map(|a| Color32::from_rgba_premultiplied(255, 255, 255, (a * 255.) as u8)) 154 | .collect(), 155 | }; 156 | 157 | let data = D3D11_SUBRESOURCE_DATA { 158 | pSysMem: pixels.as_ptr() as _, 159 | SysMemPitch: (width * size_of::()) as u32, 160 | SysMemSlicePitch: 0, 161 | }; 162 | 163 | unsafe { 164 | let texture = { 165 | let mut output_texture = None; 166 | dev.CreateTexture2D(&desc, Some(&data), Some(&mut output_texture))?; 167 | output_texture.ok_or(RenderError::General("Unable to create Texture 2D"))? 168 | }; 169 | 170 | let desc = D3D11_SHADER_RESOURCE_VIEW_DESC { 171 | Format: DXGI_FORMAT_R8G8B8A8_UNORM, 172 | ViewDimension: D3D11_SRV_DIMENSION_TEXTURE2D, 173 | Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 { 174 | Texture2D: D3D11_TEX2D_SRV { 175 | MostDetailedMip: 0, 176 | MipLevels: desc.MipLevels, 177 | }, 178 | }, 179 | }; 180 | 181 | let resource = { 182 | let mut output_resource = None; 183 | dev.CreateShaderResourceView(&texture, Some(&desc), Some(&mut output_resource))?; 184 | output_resource.ok_or(RenderError::General("Failed to create shader view"))? 185 | }; 186 | 187 | Ok(ManagedTexture { 188 | width, 189 | resource, 190 | pixels, 191 | texture, 192 | }) 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /egui-directx12/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui-directx12" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /egui-directx12/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /egui-directx9/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui-directx9" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | egui = { workspace = true } 11 | windows = { workspace = true, features = [ 12 | "Win32_Foundation", 13 | "Foundation_Numerics", 14 | "Win32_Graphics_Direct3D", 15 | "Win32_Graphics_Direct3D9", 16 | "Win32_Graphics_Dxgi", 17 | "Win32_System_SystemServices", 18 | ] } 19 | -------------------------------------------------------------------------------- /egui-directx9/README.md: -------------------------------------------------------------------------------- 1 | # Egui Direct X9 2 | 3 | Renderer backend for Direct X9 for Egui 4 | 5 | Initial Source: https://crates.io/crates/egui-d3d9 -------------------------------------------------------------------------------- /egui-directx9/examples/directx9-sample.rs: -------------------------------------------------------------------------------- 1 | pub fn main() {} 2 | -------------------------------------------------------------------------------- /egui-directx9/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /egui-win32/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui-win32" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | egui = { workspace = true } 11 | windows = { workspace = true, features = [ 12 | "Win32_System_WindowsProgramming", 13 | "Win32_Foundation", 14 | "Win32_UI_Input_KeyboardAndMouse", 15 | ] } 16 | clipboard-win = "4.5.0" 17 | -------------------------------------------------------------------------------- /egui-win32/README.md: -------------------------------------------------------------------------------- 1 | # Egui Win32 2 | 3 | Windows32 input handler for egui; 4 | Takes input from windows api , WndProc and converts it into egui compatible RawInput 5 | 6 | ## Sources 7 | https://crates.io/crates/egui-d3d9 8 | https://crates.io/crates/egui-d3d11 -------------------------------------------------------------------------------- /egui-win32/src/lib.rs: -------------------------------------------------------------------------------- 1 | use egui::{Event, Key, Modifiers, PointerButton, Pos2, RawInput, Rect, Vec2}; 2 | use windows::Win32::{ 3 | Foundation::{HWND, RECT}, 4 | System::{ 5 | SystemServices::{MK_CONTROL, MK_SHIFT}, 6 | WindowsProgramming::NtQuerySystemTime, 7 | }, 8 | UI::{ 9 | Input::KeyboardAndMouse::{ 10 | GetAsyncKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, 11 | VK_ESCAPE, VK_HOME, VK_INSERT, VK_LEFT, VK_LSHIFT, VK_NEXT, VK_PRIOR, VK_RETURN, 12 | VK_RIGHT, VK_SPACE, VK_TAB, VK_UP, 13 | }, 14 | WindowsAndMessaging::{ 15 | GetClientRect, KF_REPEAT, WHEEL_DELTA, WM_CHAR, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDBLCLK, 16 | WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDBLCLK, WM_MBUTTONDOWN, WM_MBUTTONUP, 17 | WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDBLCLK, WM_RBUTTONDOWN, 18 | WM_RBUTTONUP, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_XBUTTONDBLCLK, WM_XBUTTONDOWN, 19 | WM_XBUTTONUP, XBUTTON1, XBUTTON2, 20 | }, 21 | }, 22 | }; 23 | 24 | pub struct InputManager { 25 | hwnd: HWND, 26 | events: Vec, 27 | modifiers: Option, 28 | } 29 | 30 | /// High-level overview of recognized `WndProc` messages. 31 | #[repr(u8)] 32 | pub enum InputResult { 33 | Unknown, 34 | MouseMove, 35 | MouseLeft, 36 | MouseRight, 37 | MouseMiddle, 38 | Character, 39 | Scroll, 40 | Zoom, 41 | Key, 42 | } 43 | 44 | type Result = std::result::Result; 45 | 46 | impl InputResult { 47 | #[inline] 48 | pub fn is_valid(&self) -> bool { 49 | !self.is_unknown() 50 | } 51 | 52 | #[inline] 53 | pub fn is_unknown(&self) -> bool { 54 | matches!(*self, InputResult::Unknown) 55 | } 56 | } 57 | 58 | impl InputManager { 59 | pub fn new(hwnd: HWND) -> Self { 60 | Self { 61 | hwnd, 62 | events: vec![], 63 | modifiers: None, 64 | } 65 | } 66 | 67 | pub fn process(&mut self, umsg: u32, wparam: usize, lparam: isize) -> InputResult { 68 | match umsg { 69 | WM_MOUSEMOVE => { 70 | self.alter_modifiers(get_mouse_modifiers(wparam)); 71 | 72 | self.events.push(Event::PointerMoved(get_pos(lparam))); 73 | InputResult::MouseMove 74 | } 75 | WM_LBUTTONDOWN | WM_LBUTTONDBLCLK => { 76 | let modifiers = get_mouse_modifiers(wparam); 77 | self.alter_modifiers(modifiers); 78 | 79 | self.events.push(Event::PointerButton { 80 | pos: get_pos(lparam), 81 | button: PointerButton::Primary, 82 | pressed: true, 83 | modifiers, 84 | }); 85 | InputResult::MouseLeft 86 | } 87 | WM_LBUTTONUP => { 88 | let modifiers = get_mouse_modifiers(wparam); 89 | self.alter_modifiers(modifiers); 90 | 91 | self.events.push(Event::PointerButton { 92 | pos: get_pos(lparam), 93 | button: PointerButton::Primary, 94 | pressed: false, 95 | modifiers, 96 | }); 97 | InputResult::MouseLeft 98 | } 99 | WM_RBUTTONDOWN | WM_RBUTTONDBLCLK => { 100 | let modifiers = get_mouse_modifiers(wparam); 101 | self.alter_modifiers(modifiers); 102 | 103 | self.events.push(Event::PointerButton { 104 | pos: get_pos(lparam), 105 | button: PointerButton::Secondary, 106 | pressed: true, 107 | modifiers, 108 | }); 109 | InputResult::MouseRight 110 | } 111 | WM_RBUTTONUP => { 112 | let modifiers = get_mouse_modifiers(wparam); 113 | self.alter_modifiers(modifiers); 114 | 115 | self.events.push(Event::PointerButton { 116 | pos: get_pos(lparam), 117 | button: PointerButton::Secondary, 118 | pressed: false, 119 | modifiers, 120 | }); 121 | InputResult::MouseRight 122 | } 123 | WM_MBUTTONDOWN | WM_MBUTTONDBLCLK => { 124 | let modifiers = get_mouse_modifiers(wparam); 125 | self.alter_modifiers(modifiers); 126 | 127 | self.events.push(Event::PointerButton { 128 | pos: get_pos(lparam), 129 | button: PointerButton::Middle, 130 | pressed: true, 131 | modifiers, 132 | }); 133 | InputResult::MouseMiddle 134 | } 135 | WM_MBUTTONUP => { 136 | let modifiers = get_mouse_modifiers(wparam); 137 | self.alter_modifiers(modifiers); 138 | 139 | self.events.push(Event::PointerButton { 140 | pos: get_pos(lparam), 141 | button: PointerButton::Middle, 142 | pressed: false, 143 | modifiers, 144 | }); 145 | InputResult::MouseMiddle 146 | } 147 | WM_XBUTTONDOWN | WM_XBUTTONDBLCLK => { 148 | let modifiers = get_mouse_modifiers(wparam); 149 | self.alter_modifiers(modifiers); 150 | 151 | self.events.push(Event::PointerButton { 152 | pos: get_pos(lparam), 153 | button: if (wparam as u32) >> 16 & (XBUTTON1 as u32) != 0 { 154 | PointerButton::Extra1 155 | } else if (wparam as u32) >> 16 & (XBUTTON2 as u32) != 0 { 156 | PointerButton::Extra2 157 | } else { 158 | unreachable!() 159 | }, 160 | pressed: true, 161 | modifiers, 162 | }); 163 | InputResult::MouseMiddle 164 | } 165 | WM_XBUTTONUP => { 166 | let modifiers = get_mouse_modifiers(wparam); 167 | self.alter_modifiers(modifiers); 168 | 169 | self.events.push(Event::PointerButton { 170 | pos: get_pos(lparam), 171 | button: if (wparam as u32) >> 16 & (XBUTTON1 as u32) != 0 { 172 | PointerButton::Extra1 173 | } else if (wparam as u32) >> 16 & (XBUTTON2 as u32) != 0 { 174 | PointerButton::Extra2 175 | } else { 176 | unreachable!() 177 | }, 178 | pressed: false, 179 | modifiers, 180 | }); 181 | InputResult::MouseMiddle 182 | } 183 | WM_CHAR => { 184 | if let Some(ch) = char::from_u32(wparam as _) { 185 | if !ch.is_control() { 186 | self.events.push(Event::Text(ch.into())); 187 | } 188 | } 189 | InputResult::Character 190 | } 191 | WM_MOUSEWHEEL => { 192 | self.alter_modifiers(get_mouse_modifiers(wparam)); 193 | 194 | let delta = (wparam >> 16) as i16 as f32 * 10. / WHEEL_DELTA as f32; 195 | 196 | if wparam & MK_CONTROL.0 as usize != 0 { 197 | self.events 198 | .push(Event::Zoom(if delta > 0. { 1.5 } else { 0.5 })); 199 | InputResult::Zoom 200 | } else { 201 | self.events.push(Event::Scroll(Vec2::new(0., delta))); 202 | InputResult::Scroll 203 | } 204 | } 205 | WM_MOUSEHWHEEL => { 206 | self.alter_modifiers(get_mouse_modifiers(wparam)); 207 | 208 | let delta = (wparam >> 16) as i16 as f32 * 10. / WHEEL_DELTA as f32; 209 | 210 | if wparam & MK_CONTROL.0 as usize != 0 { 211 | self.events 212 | .push(Event::Zoom(if delta > 0. { 1.5 } else { 0.5 })); 213 | InputResult::Zoom 214 | } else { 215 | self.events.push(Event::Scroll(Vec2::new(delta, 0.))); 216 | InputResult::Scroll 217 | } 218 | } 219 | msg @ (WM_KEYDOWN | WM_SYSKEYDOWN) => { 220 | let modifiers = get_key_modifiers(msg); 221 | self.modifiers = Some(modifiers); 222 | 223 | if let Some(key) = get_key(wparam) { 224 | if key == Key::V && modifiers.ctrl { 225 | if let Some(clipboard) = get_clipboard_text() { 226 | self.events.push(Event::Text(clipboard)); 227 | } 228 | } 229 | 230 | if key == Key::C && modifiers.ctrl { 231 | self.events.push(Event::Copy); 232 | } 233 | 234 | if key == Key::X && modifiers.ctrl { 235 | self.events.push(Event::Cut); 236 | } 237 | 238 | self.events.push(Event::Key { 239 | pressed: true, 240 | modifiers, 241 | key, 242 | repeat: lparam & (KF_REPEAT as isize) > 0, 243 | }); 244 | } 245 | InputResult::Key 246 | } 247 | msg @ (WM_KEYUP | WM_SYSKEYUP) => { 248 | let modifiers = get_key_modifiers(msg); 249 | self.modifiers = Some(modifiers); 250 | 251 | if let Some(key) = get_key(wparam) { 252 | self.events.push(Event::Key { 253 | pressed: false, 254 | modifiers, 255 | key, 256 | repeat: false, 257 | }); 258 | } 259 | InputResult::Key 260 | } 261 | _ => InputResult::Unknown, 262 | } 263 | } 264 | 265 | fn alter_modifiers(&mut self, new: Modifiers) { 266 | if let Some(old) = self.modifiers.as_mut() { 267 | *old = new; 268 | } 269 | } 270 | 271 | pub fn collect_input(&mut self) -> Result { 272 | Ok(RawInput { 273 | modifiers: self.modifiers.unwrap_or_default(), 274 | events: std::mem::take(&mut self.events), 275 | screen_rect: Some(self.get_screen_rect()), 276 | time: Some(Self::get_system_time()?), 277 | pixels_per_point: Some(1.), 278 | max_texture_side: None, 279 | predicted_dt: 1. / 60., 280 | hovered_files: vec![], 281 | dropped_files: vec![], 282 | focused: true, 283 | }) 284 | } 285 | 286 | /// Returns time in seconds. 287 | pub fn get_system_time() -> Result { 288 | let mut time = 0; 289 | unsafe { 290 | NtQuerySystemTime(&mut time)?; 291 | } 292 | 293 | // dumb ass, read the docs. egui clearly says `in seconds`. 294 | // Shouldn't have wasted 3 days on this. 295 | // `NtQuerySystemTime` returns how many 100 nanosecond intervals 296 | // past since 1st Jan, 1601. 297 | Ok((time as f64) / 10_000_000.) 298 | } 299 | 300 | #[inline] 301 | pub fn get_screen_size(&self) -> Pos2 { 302 | let mut rect = RECT::default(); 303 | unsafe { 304 | GetClientRect(self.hwnd, &mut rect); 305 | } 306 | 307 | Pos2::new( 308 | (rect.right - rect.left) as f32, 309 | (rect.bottom - rect.top) as f32, 310 | ) 311 | } 312 | 313 | #[inline] 314 | pub fn get_screen_rect(&self) -> Rect { 315 | Rect { 316 | min: Pos2::ZERO, 317 | max: self.get_screen_size(), 318 | } 319 | } 320 | } 321 | 322 | fn get_pos(lparam: isize) -> Pos2 { 323 | let x = (lparam & 0xFFFF) as i16 as f32; 324 | let y = (lparam >> 16 & 0xFFFF) as i16 as f32; 325 | 326 | Pos2::new(x, y) 327 | } 328 | 329 | fn get_mouse_modifiers(wparam: usize) -> Modifiers { 330 | Modifiers { 331 | alt: false, 332 | ctrl: (wparam & MK_CONTROL.0 as usize) != 0, 333 | shift: (wparam & MK_SHIFT.0 as usize) != 0, 334 | mac_cmd: false, 335 | command: (wparam & MK_CONTROL.0 as usize) != 0, 336 | } 337 | } 338 | 339 | fn get_key_modifiers(msg: u32) -> Modifiers { 340 | let ctrl = unsafe { GetAsyncKeyState(VK_CONTROL.0 as _) != 0 }; 341 | let shift = unsafe { GetAsyncKeyState(VK_LSHIFT.0 as _) != 0 }; 342 | 343 | Modifiers { 344 | alt: msg == WM_SYSKEYDOWN, 345 | mac_cmd: false, 346 | command: ctrl, 347 | shift, 348 | ctrl, 349 | } 350 | } 351 | 352 | fn get_key(wparam: usize) -> Option { 353 | match wparam { 354 | 0x30..=0x39 => unsafe { Some(std::mem::transmute::<_, Key>(wparam as u8 - 0x1F)) }, 355 | 0x41..=0x5A => unsafe { Some(std::mem::transmute::<_, Key>(wparam as u8 - 0x26)) }, 356 | 0x70..=0x83 => unsafe { Some(std::mem::transmute::<_, Key>(wparam as u8 - 0x3B)) }, 357 | _ => match VIRTUAL_KEY(wparam as u16) { 358 | VK_DOWN => Some(Key::ArrowDown), 359 | VK_LEFT => Some(Key::ArrowLeft), 360 | VK_RIGHT => Some(Key::ArrowRight), 361 | VK_UP => Some(Key::ArrowUp), 362 | VK_ESCAPE => Some(Key::Escape), 363 | VK_TAB => Some(Key::Tab), 364 | VK_BACK => Some(Key::Backspace), 365 | VK_RETURN => Some(Key::Enter), 366 | VK_SPACE => Some(Key::Space), 367 | VK_INSERT => Some(Key::Insert), 368 | VK_DELETE => Some(Key::Delete), 369 | VK_HOME => Some(Key::Home), 370 | VK_END => Some(Key::End), 371 | VK_PRIOR => Some(Key::PageUp), 372 | VK_NEXT => Some(Key::PageDown), 373 | _ => None, 374 | }, 375 | } 376 | } 377 | 378 | fn get_clipboard_text() -> Option { 379 | clipboard_win::get_clipboard(clipboard_win::formats::Unicode).ok() 380 | } 381 | --------------------------------------------------------------------------------