use crate::{
AnimationCurve, Backdrop, ComponentMask, Context, CornerRadius, DebugStore, EntityId,
ExternalVisual, ExternalVisualMetadata, InteractionState, LayoutPoint, LayoutRect, LayoutSize,
MichiuError, MichiuSoA, MichiuTrace, PlaybackCount, PropertyList, RawWgpuRenderer,
ResultTraceExt, StaticExternalTexture, VisualUpdateContext, WgpuRenderer,
WindowsResultTraceExt, flush_trace, trace_error, trace_lifecycle,
};
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, Mutex, OnceLock},
};
use windows::{
Win32::{
Foundation::{HMODULE, HWND, LPARAM, POINT, RECT, WPARAM},
Graphics::{
Direct3D::{D3D_DRIVER_TYPE_HARDWARE, ID3DInclude_Impl},
Direct3D11::{
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION, D3D11CreateDevice,
ID3D11Device,
},
DirectComposition::{
DCompositionCreateDevice2, IDCompositionDesktopDevice,
IDCompositionDesktopDevice_Impl, IDCompositionDevice, IDCompositionDevice_Impl,
IDCompositionDevice2_Impl, IDCompositionRectangleClip_Impl, IDCompositionTarget,
IDCompositionTarget_Impl, IDCompositionTranslateTransform_Impl,
IDCompositionTranslateTransform3D_Impl, IDCompositionVisual,
IDCompositionVisual_Impl, IDCompositionVisual2, IDCompositionVisual3_Impl,
},
Dxgi::*,
Gdi::InvalidateRect,
Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
},
System::{
Com::{CLSCTX_INPROC_SERVER, CoCreateInstance},
LibraryLoader::GetModuleHandleW,
},
UI::WindowsAndMessaging::{
GWL_EXSTYLE, GetWindowLongW, SetWindowLongW, WM_MOUSEHWHEEL, WM_MOUSEWHEEL,
},
},
core::{Interface, PCWSTR, PWSTR, w},
};
use windows_numerics::Matrix3x2;
pub struct MichiuRenderer {
pub(crate) hwnd: HWND,
pub(crate) layout_size: LayoutSize,
pub(crate) scale_factor: f32,
pub(crate) wic_factory: IWICImagingFactory,
pub(crate) d3d11_device: ID3D11Device,
pub(crate) desktop_device: IDCompositionDesktopDevice,
pub(crate) target: IDCompositionTarget,
pub(crate) root_visual: IDCompositionVisual2,
pub(crate) wgpu_visual: IDCompositionVisual2,
pub(crate) wgpu_renderer: WgpuRenderer,
pub(crate) promoted_visuals: Vec<PromotedVisual>,
pub(crate) pending_dcomp_releases: Vec<PendingDcompRelease>,
pub(crate) current_backdrop: Backdrop,
pub(crate) resize_cooldown_frames: u32,
}
#[derive(Debug, Clone)]
pub struct PendingDcompRelease {
pub entity_id: EntityId,
pub frames_left: u32,
}
#[derive(Debug, Clone)]
pub struct PromotedVisual {
pub entity_id: EntityId,
pub visual: IDCompositionVisual2,
pub transform: Option<windows::core::IUnknown>,
pub is_visible: bool,
}
impl MichiuRenderer {
#[inline]
pub async fn new(
hwnd: HWND,
layout_size: LayoutSize,
scale_factor: f32,
) -> crate::Result<Self> {
let (d3d11_device, desktop_device, target, root_visual, wgpu_visual) =
MichiuRenderer::setup_direct_composition(hwnd)?;
let h_instance = unsafe { GetModuleHandleW(None)? };
let raw_visual_ptr = wgpu_visual.as_raw();
let wgpu_renderer = WgpuRenderer::new(raw_visual_ptr, layout_size, scale_factor).await?;
unsafe {
desktop_device.Commit()?;
}
let wic_factory: IWICImagingFactory =
unsafe { CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)? };
Ok(Self {
hwnd,
layout_size,
scale_factor,
wic_factory,
d3d11_device,
desktop_device,
target,
root_visual,
wgpu_visual,
wgpu_renderer,
promoted_visuals: Vec::new(),
pending_dcomp_releases: Vec::new(),
current_backdrop: Backdrop::None,
resize_cooldown_frames: 0,
})
}
#[expect(clippy::cast_precision_loss)]
#[inline]
pub fn resize(&mut self, new_physical_size: (u32, u32), scale_factor: f32) {
self.scale_factor = scale_factor;
self.layout_size = LayoutSize::new(
new_physical_size.0 as f32 / scale_factor,
new_physical_size.1 as f32 / scale_factor,
);
self.wgpu_renderer.resize(new_physical_size, scale_factor);
self.resize_cooldown_frames = 15;
let _ = unsafe { self.desktop_device.Commit() };
}
#[track_caller]
#[inline]
pub fn draw(&mut self, cx: &mut Context) {
self.wgpu_renderer.render(cx, self.scale_factor);
unsafe {
self.desktop_device
.Commit()
.unwrap_or_trace(None, &mut cx.debug);
};
#[cfg(feature = "trace-lifecycle")]
trace_lifecycle!(None, &mut cx.debug, || MichiuTrace::Commit { add: None });
#[cfg(feature = "trace-lifecycle")]
{
let total_entities = cx.topology.topo_entities.len();
let active_entities = cx.topology.topo_active_entities.len();
let dirty_layouts = cx.layouts.lay_dirty_entities.len();
let dirty_renders = cx.renders.rnd_dirty_entities.len();
#[cfg(feature = "trace-lifecycle")]
trace_lifecycle!(None, &mut cx.debug, || MichiuTrace::ClearDirtyEntities {
total_entities,
active_entities,
dirty_layouts,
dirty_renders,
add: Some(
"Immediately after this recording, the dirty flag is cleared and the log is sent."
),
});
}
cx.clear_dirty();
flush_trace!(cx, self);
}
#[track_caller]
pub fn update_composition_tree(&mut self, cx: &mut Context) {
unsafe {
if self.resize_cooldown_frames > 0 {
self.resize_cooldown_frames -= 1;
}
if let Some(&root_id) = cx.topology.topo_active_entities.first()
&& let Some(visual_prop) = cx.renders.rnd_visual.find(root_id)
{
let target_backdrop = visual_prop.backdrop;
if self.current_backdrop != target_backdrop {
apply_system_backdrop(self.hwnd, target_backdrop);
self.current_backdrop = target_backdrop;
}
}
let mut idx = 0;
while idx < self.pending_dcomp_releases.len() {
if self.pending_dcomp_releases[idx].frames_left > 0 {
self.pending_dcomp_releases[idx].frames_left -= 1;
idx += 1;
} else {
let release = self.pending_dcomp_releases.remove(idx);
let target_id = release.entity_id;
if let Some(pos) = self
.promoted_visuals
.iter()
.position(|v| v.entity_id == target_id)
{
let promoted = &mut self.promoted_visuals[pos];
if promoted.is_visible {
let _ = self.root_visual.RemoveVisual(&promoted.visual);
promoted.is_visible = false;
}
}
}
}
let mut current_promoted_ids = Vec::new();
let visual_entries: Vec<(EntityId, Arc<dyn ExternalVisual>)> = cx
.contents
.cont_external_visual
.iter()
.map(|(id, v)| (id, Arc::clone(v)))
.collect();
for (id, visual_entry) in visual_entries {
let rect = cx.outputs.out_rects.find_or_default(id, &mut cx.debug);
let dcomp_visual = visual_entry.resolve_visual();
let metadata = visual_entry.metadata();
let is_interactive = is_element_interactive(cx, id);
let is_transitioning = is_element_transitioning(cx, id);
let prev_rect = cx.outputs.out_prev_rects.find_or_default(id, &mut cx.debug);
let is_size_changing = (rect.width - prev_rect.width).abs() > 0.01
|| (rect.height - prev_rect.height).abs() > 0.01;
let is_stable = !cx.window.win_is_resized
&& self.resize_cooldown_frames == 0
&& !is_transitioning
&& !is_size_changing;
visual_entry.update(&VisualUpdateContext {
hwnd: self.hwnd,
rect,
scale_factor: self.scale_factor,
is_interactive,
is_stable,
device: &self.wgpu_renderer.device,
queue: &self.wgpu_renderer.queue,
wic_factory: &self.wic_factory,
});
self.sync_visual_layout_and_clip(id, &dcomp_visual, metadata, cx);
if let Some(static_tex) = visual_entry.static_texture() {
cx.contents.cont_external_textures.insert(id, static_tex);
cx.topology
.topo_active_masks
.at_mut(id)
.set(ComponentMask::COMP_EXTERNAL_TEXTURE_CONTENT);
cx.renders.rnd_active_external_visual.remove(&id);
if let Some(pos) = self.promoted_visuals.iter().position(|v| v.entity_id == id)
{
let promoted = &mut self.promoted_visuals[pos];
if promoted.is_visible {
let _ = self.root_visual.RemoveVisual(&promoted.visual);
promoted.is_visible = false;
}
}
self.pending_dcomp_releases.retain(|r| r.entity_id != id);
} else {
current_promoted_ids.push(id);
cx.topology
.topo_active_masks
.at_mut(id)
.unset(ComponentMask::COMP_EXTERNAL_TEXTURE_CONTENT);
self.pending_dcomp_releases.retain(|r| r.entity_id != id);
if let Some(pos) = self.promoted_visuals.iter().position(|v| v.entity_id == id)
{
let promoted = &mut self.promoted_visuals[pos];
if !promoted.is_visible {
let _ = self.root_visual.AddVisual(
&promoted.visual,
false,
&self.wgpu_visual,
);
promoted.is_visible = true;
}
} else {
let _ = self
.root_visual
.AddVisual(&dcomp_visual, false, &self.wgpu_visual);
self.promoted_visuals.push(PromotedVisual {
entity_id: id,
visual: dcomp_visual.clone(),
transform: None,
is_visible: true,
});
}
cx.renders.rnd_active_external_visual.insert(id);
cx.contents.cont_external_textures.remove(id);
}
}
let mut i = 0;
while i < self.promoted_visuals.len() {
let id = self.promoted_visuals[i].entity_id;
if !cx.topology.topo_entities.contains_key(id) {
let promoted = &self.promoted_visuals[i];
if promoted.is_visible {
let _ = self.root_visual.RemoveVisual(&promoted.visual);
}
self.promoted_visuals.remove(i);
continue;
}
let is_pending_release = self
.pending_dcomp_releases
.iter()
.any(|r| r.entity_id == id);
if !current_promoted_ids.contains(&id) && !is_pending_release {
let promoted = &mut self.promoted_visuals[i];
if promoted.is_visible {
let _ = self.root_visual.RemoveVisual(&promoted.visual);
promoted.is_visible = false;
}
}
i += 1;
}
}
}
#[track_caller]
fn sync_visual_layout_and_clip(
&self,
id: EntityId,
visual: &IDCompositionVisual2,
metadata: ExternalVisualMetadata,
cx: &mut Context,
) {
unsafe {
let rect = cx.outputs.out_rects.find_or_default(id, &mut cx.debug);
let clip_rect = cx.outputs.out_clip_rects.find_or_default(id, &mut cx.debug);
let prev_rect = cx.outputs.out_prev_rects.find_or_default(id, &mut cx.debug);
let prev_clip = cx
.outputs
.out_prev_clip_rects
.find_or_default(id, &mut cx.debug);
let has_active_transform_anim =
cx.renders
.rnd_active_transitions
.find(id)
.is_some_and(|list| {
list.iter()
.any(|t| t.property_list == PropertyList::Transform)
});
let is_resizing = cx.window.win_is_resized;
if !is_resizing
&& rect == prev_rect
&& clip_rect == prev_clip
&& !has_active_transform_anim
{
return;
}
let phys_x = rect.x * self.scale_factor;
let phys_y = rect.y * self.scale_factor;
let _ = visual.SetOffsetX2(phys_x);
let _ = visual.SetOffsetY2(phys_y);
if metadata.auto_transform
&& let Some(visual_prop) = cx.renders.rnd_visual.find(id)
{
if let Some(m) = visual_prop.transform {
let m11 = m[0][0];
let m12 = m[0][1];
let m21 = m[1][0];
let m22 = m[1][1];
let tx = m[3][0] * self.scale_factor;
let ty = m[3][1] * self.scale_factor;
let origin = visual_prop
.transform_origin
.map_or([0.5, 0.5], |p| [p.x, p.y]);
let origin_x = origin[0] * rect.width * self.scale_factor;
let origin_y = origin[1] * rect.height * self.scale_factor;
let m31 = (1.0 - m11) * origin_x - m21 * origin_y + tx;
let m32 = -m12 * origin_x + (1.0 - m22) * origin_y + ty;
let dcomp_matrix = Matrix3x2 {
M11: m11,
M12: m12,
M21: m21,
M22: m22,
M31: m31,
M32: m32,
};
let _ = visual.SetTransform2(&raw const dcomp_matrix);
} else {
let identity = Matrix3x2 {
M11: 1.0,
M12: 0.0,
M21: 0.0,
M22: 1.0,
M31: 0.0,
M32: 0.0,
};
let _ = visual.SetTransform2(&raw const identity);
}
}
if metadata.auto_clip
&& let Some(visual_prop) = cx.renders.rnd_visual.find(id)
{
let desktop_device = self.desktop_device.clone();
if let Ok(rectangle_clip) = desktop_device.CreateRectangleClip() {
let clip_left = (clip_rect.x - rect.x).max(0.0) * self.scale_factor;
let clip_top = (clip_rect.y - rect.y).max(0.0) * self.scale_factor;
let clip_right = ((clip_rect.x + clip_rect.width) - rect.x).min(rect.width)
* self.scale_factor;
let clip_bottom = ((clip_rect.y + clip_rect.height) - rect.y).min(rect.height)
* self.scale_factor;
let _ = rectangle_clip.SetLeft2(clip_left);
let _ = rectangle_clip.SetTop2(clip_top);
let _ = rectangle_clip.SetRight2(clip_right);
let _ = rectangle_clip.SetBottom2(clip_bottom);
if let Some(radius) = visual_prop.corner_radius {
let r = radius.top_left * self.scale_factor;
let _ = rectangle_clip.SetTopLeftRadiusX2(r);
let _ = rectangle_clip.SetTopLeftRadiusY2(r);
let _ = rectangle_clip.SetTopRightRadiusX2(r);
let _ = rectangle_clip.SetTopRightRadiusY2(r);
let _ = rectangle_clip.SetBottomLeftRadiusX2(r);
let _ = rectangle_clip.SetBottomLeftRadiusY2(r);
let _ = rectangle_clip.SetBottomRightRadiusX2(r);
let _ = rectangle_clip.SetBottomRightRadiusY2(r);
} else {
let _ = rectangle_clip.SetTopLeftRadiusX2(0.0);
let _ = rectangle_clip.SetTopLeftRadiusY2(0.0);
}
let _ = visual.SetClip(&rectangle_clip);
}
}
}
}
pub(crate) fn setup_direct_composition(
hwnd: HWND,
) -> crate::Result<(
ID3D11Device,
IDCompositionDesktopDevice,
IDCompositionTarget,
IDCompositionVisual2, // root_visual
IDCompositionVisual2, // wgpu_visual
)> {
unsafe {
let manager = DCompDeviceManager::global()?;
let d3d11_device = manager.d3d11_device.clone();
let desktop_device = manager.desktop_device.clone();
let target = desktop_device.CreateTargetForHwnd(hwnd, true)?;
let root_visual = desktop_device.CreateVisual()?;
target.SetRoot(&root_visual)?;
let wgpu_visual = desktop_device.CreateVisual()?;
root_visual.AddVisual(&wgpu_visual, true, None)?;
desktop_device.Commit()?;
Ok((
d3d11_device,
desktop_device,
target,
root_visual,
wgpu_visual,
))
}
}
#[inline]
pub fn dispatch_raw_input_to_external_visual(
cx: &mut Context,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
window_phys_pos: LayoutPoint,
scale_factor: f32,
) -> bool {
crate::dispatch_raw_input_to_external_visual(
cx,
msg,
wparam,
lparam,
window_phys_pos,
scale_factor,
)
}
#[inline]
#[must_use]
pub fn hwnd(&self) -> HWND {
self.hwnd
}
#[inline]
#[must_use]
pub fn layout_size(&self) -> LayoutSize {
self.layout_size
}
#[inline]
#[must_use]
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
#[inline]
#[must_use]
pub fn wic_factory(&self) -> &IWICImagingFactory {
&self.wic_factory
}
#[inline]
#[must_use]
pub fn d3d11_device(&self) -> &ID3D11Device {
&self.d3d11_device
}
#[inline]
#[must_use]
pub fn desktop_device(&self) -> &IDCompositionDesktopDevice {
&self.desktop_device
}
#[inline]
pub fn composition_device(&self) -> windows_core::Result<IDCompositionDevice> {
let result: IDCompositionDevice = self.desktop_device.clone().cast()?;
Ok(result)
}
#[inline]
#[must_use]
pub fn target(&self) -> &IDCompositionTarget {
&self.target
}
#[inline]
#[must_use]
pub fn root_visual(&self) -> &IDCompositionVisual2 {
&self.root_visual
}
#[inline]
#[must_use]
pub fn wgpu_visual(&self) -> &IDCompositionVisual2 {
&self.wgpu_visual
}
#[inline]
#[must_use]
pub fn promoted_visuals(&self) -> &[PromotedVisual] {
&self.promoted_visuals
}
#[inline]
#[must_use]
pub fn pending_dcomp_releases(&self) -> &[PendingDcompRelease] {
&self.pending_dcomp_releases
}
#[inline]
#[must_use]
pub fn current_backdrop(&self) -> &Backdrop {
&self.current_backdrop
}
#[inline]
#[must_use]
pub fn resize_cooldown_frames(&self) -> u32 {
self.resize_cooldown_frames
}
#[inline]
#[must_use]
pub fn wgpu_renderer(&self) -> &WgpuRenderer {
&self.wgpu_renderer
}
#[inline]
#[must_use]
pub fn raw_wgpu_renderer_mut(&mut self) -> RawWgpuRenderer<'_> {
self.wgpu_renderer.raw_wgpu_renderer_mut()
}
#[inline]
#[must_use]
pub fn raw_composed_renderer_mut(&mut self) -> RawComposedRenderer<'_> {
RawComposedRenderer {
hwnd: &mut self.hwnd,
layout_size: &mut self.layout_size,
scale_factor: &mut self.scale_factor,
wic_factory: &mut self.wic_factory,
d3d11_device: &mut self.d3d11_device,
desktop_device: &mut self.desktop_device,
target: &mut self.target,
root_visual: &mut self.root_visual,
wgpu_visual: &mut self.wgpu_visual,
promoted_visuals: &mut self.promoted_visuals,
pending_dcomp_releases: &mut self.pending_dcomp_releases,
current_backdrop: &mut self.current_backdrop,
resize_cooldown_frames: &mut self.resize_cooldown_frames,
}
}
}
pub struct RawComposedRenderer<'a> {
pub hwnd: &'a mut HWND,
pub layout_size: &'a mut LayoutSize,
pub scale_factor: &'a mut f32,
pub wic_factory: &'a mut IWICImagingFactory,
pub d3d11_device: &'a mut ID3D11Device,
pub desktop_device: &'a mut IDCompositionDesktopDevice,
pub target: &'a mut IDCompositionTarget,
pub root_visual: &'a mut IDCompositionVisual2,
pub wgpu_visual: &'a mut IDCompositionVisual2,
pub promoted_visuals: &'a mut Vec<PromotedVisual>,
pub pending_dcomp_releases: &'a mut Vec<PendingDcompRelease>,
pub current_backdrop: &'a mut Backdrop,
pub resize_cooldown_frames: &'a mut u32,
}
#[track_caller]
fn is_element_interactive(cx: &Context, id: EntityId) -> bool {
let is_focused = cx.interaction_id(InteractionState::Focused) == Some(id);
let is_pressed = cx.interaction_id(InteractionState::Pressed) == Some(id);
is_focused || is_pressed
}
fn is_element_transitioning(cx: &Context, id: EntityId) -> bool {
cx.renders
.rnd_active_transitions
.find(id)
.is_some_and(|list| {
list.iter().any(|t| {
t.property_list == PropertyList::Width
|| t.property_list == PropertyList::Height
|| t.property_list == PropertyList::Size
|| t.property_list == PropertyList::Transform
})
})
|| cx
.renders
.rnd_active_animations
.find(id)
.is_some_and(|list| {
list.iter().any(|a| {
a.property == PropertyList::Width
|| a.property == PropertyList::Height
|| a.property == PropertyList::Size
|| a.property == PropertyList::Transform
})
})
}
pub(crate) struct DCompDeviceManager {
pub(crate) d3d11_device: ID3D11Device,
pub(crate) desktop_device: IDCompositionDesktopDevice,
}
unsafe impl Send for DCompDeviceManager {}
unsafe impl Sync for DCompDeviceManager {}
impl DCompDeviceManager {
pub(crate) fn global() -> crate::Result<&'static Self> {
static INSTANCE: OnceLock<DCompDeviceManager> = OnceLock::new();
if let Some(manager) = INSTANCE.get() {
return Ok(manager);
}
let manager = Self::try_create_devices()?;
let _ = INSTANCE.set(manager);
INSTANCE
.get()
.ok_or(MichiuError::UninitializedDeviceManager)
}
fn try_create_devices() -> crate::Result<Self> {
let mut d3d11_device: Option<ID3D11Device> = None;
unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
HMODULE::default(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
None,
D3D11_SDK_VERSION,
Some(&raw mut d3d11_device),
None,
None,
)?;
}
let d3d11_device = d3d11_device.ok_or(MichiuError::D3d11DeviceCreationFailed)?;
let desktop_device: IDCompositionDesktopDevice =
unsafe { DCompositionCreateDevice2(None) }?;
Ok(DCompDeviceManager {
d3d11_device,
desktop_device,
})
}
}
#[link(name = "dwmapi")]
unsafe extern "system" {
fn DwmSetWindowAttribute(
hwnd: HWND,
dwattribute: u32,
pvattribute: *const std::ffi::c_void,
cbattribute: u32,
) -> windows::core::HRESULT;
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Margins {
pub left: i32,
pub right: i32,
pub top: i32,
pub bottom: i32,
}
#[link(name = "dwmapi")]
unsafe extern "system" {
fn DwmExtendFrameIntoClientArea(
hwnd: HWND,
pMarInset: *const Margins,
) -> windows::core::HRESULT;
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) fn apply_system_backdrop(hwnd: HWND, backdrop: Backdrop) {
unsafe {
let enable_host_backdrop: i32 = 1; let _ = DwmSetWindowAttribute(
hwnd,
17, (&raw const enable_host_backdrop).cast(),
std::mem::size_of::<i32>() as u32,
);
let dark_mode: i32 = 1; let _ = DwmSetWindowAttribute(
hwnd,
20, (&raw const dark_mode).cast(),
std::mem::size_of::<i32>() as u32,
);
let use_host_backdrop: i32 = 1; let _ = DwmSetWindowAttribute(
hwnd,
36, (&raw const use_host_backdrop).cast(),
std::mem::size_of::<i32>() as u32,
);
let backdrop_val = backdrop as i32;
let _ = DwmSetWindowAttribute(
hwnd,
38, (&raw const backdrop_val).cast(),
std::mem::size_of::<i32>() as u32,
);
if backdrop == Backdrop::None {
let margins = Margins {
left: 0,
right: 0,
top: 0,
bottom: 0,
};
let _ = DwmExtendFrameIntoClientArea(hwnd, &raw const margins);
} else {
let margins = Margins {
left: -1,
right: -1,
top: -1,
bottom: -1,
};
let _ = DwmExtendFrameIntoClientArea(hwnd, &raw const margins);
}
}
}