use super::resource::{BitmapSourceGraphics, BitmapSourceResource};
use super::task_pool::WintfTaskPool;
use crate::com::d2d::D2D1CommandListExt;
use crate::com::wic::{WICBitmapDecoderExt, WICFormatConverterExt, WICImagingFactoryExt};
use crate::ecs::graphics::{format_entity_name, GraphicsCommandList, GraphicsCore};
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::core::{Interface, Result};
use windows::Win32::Foundation::GENERIC_READ;
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
use windows::Win32::Graphics::Direct2D::{
ID2D1DeviceContext, D2D1_BITMAP_OPTIONS_NONE, D2D1_BITMAP_PROPERTIES1,
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM;
use windows::Win32::Graphics::Imaging::D2D::IWICImagingFactory2;
use windows::Win32::Graphics::Imaging::{
GUID_WICPixelFormat32bppPBGRA, IWICBitmapSource, WICBitmapDitherTypeNone,
WICBitmapPaletteTypeMedianCut, WICDecodeMetadataCacheOnDemand,
};
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 fn load_bitmap_source(factory: &IWICImagingFactory2, path: &Path) -> Result<IWICBitmapSource> {
use windows::core::HSTRING;
let path_str = path.to_string_lossy();
let path_hstring = HSTRING::from(path_str.as_ref());
let decoder = factory.create_decoder_from_filename(
&path_hstring,
None,
GENERIC_READ,
WICDecodeMetadataCacheOnDemand,
)?;
let frame = decoder.frame(0)?;
let converter = factory.create_format_converter()?;
converter.init(
&frame.cast::<IWICBitmapSource>()?,
&GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,
None,
0.0,
WICBitmapPaletteTypeMedianCut,
)?;
converter.cast()
}
pub struct InsertBitmapSourceResource {
pub entity: Entity,
pub source: IWICBitmapSource,
}
unsafe impl Send for InsertBitmapSourceResource {}
impl Command for InsertBitmapSourceResource {
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.BeginDraw();
if let Some(bitmap) = graphics.bitmap() {
use crate::com::d2d::D2D1DeviceContextExt;
dc.draw_image(bitmap);
}
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 props = D2D1_BITMAP_PROPERTIES1 {
pixelFormat: D2D1_PIXEL_FORMAT {
format: DXGI_FORMAT_B8G8R8A8_UNORM,
alphaMode: windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED,
},
dpiX: 96.0,
dpiY: 96.0,
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 {
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);
});
}
}