use super::resource::{BitmapSourceGraphics, BitmapSourceResource};
use super::task_pool::WintfTaskPool;
use crate::com::d2d::{D2D1CommandListExt, D2D1DeviceContextExt};
use crate::ecs::graphics::{GraphicsCommandList, GraphicsCore, format_entity_name};
use crate::ecs::layout::Arrangement;
use bevy_ecs::name::Name;
use bevy_ecs::prelude::*;
use std::path::{Path, PathBuf};
use tracing::{trace, warn};
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
use windows::Win32::Graphics::Direct2D::{
D2D1_BITMAP_OPTIONS_NONE, D2D1_BITMAP_PROPERTIES1, ID2D1DeviceContext,
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM;
use windows::Win32::Graphics::Imaging::IWICBitmapSource;
use windows::core::Result;
use windows_numerics::Matrix3x2;
pub fn resolve_path(path: &str) -> std::io::Result<PathBuf> {
let path = Path::new(path);
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
let exe_path = std::env::current_exe()?;
let exe_dir = exe_path.parent().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::NotFound, "exe directory not found")
})?;
Ok(exe_dir.join(path))
}
}
#[cfg(test)]
pub fn test_asset_path(name: &str) -> PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
PathBuf::from(manifest_dir)
.join("tests")
.join("assets")
.join(name)
}
pub use crate::com::wic::load_bitmap_source;
pub struct InsertBitmapSourceResource {
pub entity: Entity,
pub source: IWICBitmapSource,
}
unsafe impl Send for InsertBitmapSourceResource {}
impl Command for InsertBitmapSourceResource {
type Out = ();
fn apply(self, world: &mut World) {
if let Ok(mut entity_ref) = world.get_entity_mut(self.entity) {
entity_ref.insert(BitmapSourceResource::new(self.source));
}
}
}
pub fn draw_bitmap_sources(
mut commands: Commands,
mut query: Query<
(
Entity,
&BitmapSourceResource,
&mut BitmapSourceGraphics,
&Arrangement,
Option<&GraphicsCommandList>,
Option<&Name>,
),
Or<(Changed<BitmapSourceResource>, Changed<Arrangement>)>,
>,
graphics_core: Option<Res<GraphicsCore>>,
) {
let Some(graphics_core) = graphics_core else {
warn!("GraphicsCore not available, skipping draw_bitmap_sources");
return;
};
for (entity, resource, mut graphics, arrangement, cmd_list_opt, name) in query.iter_mut() {
let entity_name = format_entity_name(entity, name);
trace!(
entity = %entity_name,
width = arrangement.size.width,
height = arrangement.size.height,
"Drawing bitmap source"
);
if !graphics.is_valid() {
let dc = match graphics_core.device_context() {
Some(dc) => dc,
None => {
warn!(entity = %entity_name, "DeviceContext not available");
continue;
}
};
match create_d2d_bitmap(dc, resource.source()) {
Ok(bitmap) => {
graphics.set_bitmap(bitmap);
}
Err(e) => {
warn!(
entity = %entity_name,
error = ?e,
"Failed to create D2D bitmap"
);
continue;
}
}
}
let dc = match graphics_core.device_context() {
Some(dc) => dc,
None => continue,
};
let command_list = match unsafe { dc.CreateCommandList() } {
Ok(cl) => cl,
Err(e) => {
warn!(
entity = %entity_name,
error = ?e,
"Failed to create CommandList"
);
continue;
}
};
unsafe {
dc.SetTarget(&command_list);
}
unsafe {
dc.set_transform(&Matrix3x2::identity());
dc.BeginDraw();
if let Some(bitmap) = graphics.bitmap() {
use crate::com::d2d::D2D1DeviceContextExt;
use windows::Win32::Graphics::Direct2D::D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC;
let dest_rect: windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F =
crate::ecs::Rect {
left: 0.0,
top: 0.0,
right: arrangement.size.width,
bottom: arrangement.size.height,
}
.into();
dc.draw_bitmap(
bitmap,
Some(&dest_rect),
1.0,
D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC,
None,
None,
);
}
let _ = dc.EndDraw(None, None);
}
if let Err(err) = command_list.close() {
warn!(
entity = %entity_name,
error = ?err,
"Failed to close CommandList"
);
continue;
}
let new_cmd_list = GraphicsCommandList::new(command_list);
match cmd_list_opt {
Some(existing) if *existing != new_cmd_list => {
commands.entity(entity).insert(new_cmd_list);
}
None => {
commands.entity(entity).insert(new_cmd_list);
}
_ => {}
}
}
}
fn create_d2d_bitmap(
dc: &ID2D1DeviceContext,
source: &IWICBitmapSource,
) -> Result<windows::Win32::Graphics::Direct2D::ID2D1Bitmap1> {
use std::mem::ManuallyDrop;
use windows::Win32::Graphics::Direct2D::ID2D1ColorContext;
let (dpi_x, dpi_y) = unsafe {
let mut x = 0.0f32;
let mut y = 0.0f32;
dc.GetDpi(&mut x, &mut y);
(x, y)
};
let props = D2D1_BITMAP_PROPERTIES1 {
pixelFormat: D2D1_PIXEL_FORMAT {
format: DXGI_FORMAT_B8G8R8A8_UNORM,
alphaMode: windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED,
},
dpiX: dpi_x,
dpiY: dpi_y,
bitmapOptions: D2D1_BITMAP_OPTIONS_NONE,
colorContext: ManuallyDrop::new(None::<ID2D1ColorContext>),
};
unsafe { dc.CreateBitmapFromWicBitmap(source, Some(&props)) }
}
pub fn drain_task_pool_commands(world: &mut World) {
let commands: Vec<_> = if let Some(task_pool) = world.get_resource::<WintfTaskPool>() {
task_pool.drain_commands()
} else {
Vec::new()
};
for cmd in commands {
cmd(world);
}
}
use super::alpha_mask::AlphaMask;
use crate::com::wic::WICBitmapSourceExt;
use crate::ecs::layout::{HitTest, HitTestMode};
use tracing::error;
struct SetAlphaMaskCommand {
entity: Entity,
mask: AlphaMask,
}
impl Command for SetAlphaMaskCommand {
type Out = ();
fn apply(self, world: &mut World) {
if let Ok(mut entity_ref) = world.get_entity_mut(self.entity) {
if let Some(mut resource) = entity_ref.get_mut::<BitmapSourceResource>() {
resource.set_alpha_mask(self.mask);
}
}
}
}
pub fn generate_alpha_mask_system(
query: Query<(Entity, &BitmapSourceResource, &HitTest), Added<BitmapSourceResource>>,
task_pool: Option<Res<WintfTaskPool>>,
) {
let Some(task_pool) = task_pool else {
return;
};
for (entity, resource, hit_test) in query.iter() {
if hit_test.mode != HitTestMode::AlphaMask {
continue;
}
if resource.alpha_mask().is_some() {
continue;
}
let source = resource.source();
let (width, height) = match source.get_size() {
Ok(size) => size,
Err(e) => {
error!(entity = ?entity, error = ?e, "Failed to get bitmap size for alpha mask");
continue;
}
};
let stride = width * 4; let buffer_size = (stride * height) as usize;
let mut buffer = vec![0u8; buffer_size];
if let Err(e) = source.copy_pixels(None, stride, &mut buffer) {
error!(entity = ?entity, error = ?e, "Failed to copy pixels for alpha mask");
continue;
}
task_pool.spawn(move |tx| async move {
let mask = AlphaMask::from_pbgra32(&buffer, width, height, stride);
let cmd: super::task_pool::BoxedCommand = Box::new(move |world: &mut World| {
SetAlphaMaskCommand { entity, mask }.apply(world);
});
let _ = tx.send(cmd);
});
}
}