ZIm/crates/gpui/src/platform/windows/directx_atlas.rs
2025-07-30 15:51:03 -07:00

310 lines
9.9 KiB
Rust

use collections::FxHashMap;
use etagere::BucketedAtlasAllocator;
use parking_lot::Mutex;
use windows::Win32::Graphics::{
Direct3D11::{
D3D11_BIND_SHADER_RESOURCE, D3D11_BOX, D3D11_CPU_ACCESS_WRITE, D3D11_TEXTURE2D_DESC,
D3D11_USAGE_DEFAULT, ID3D11Device, ID3D11DeviceContext, ID3D11ShaderResourceView,
ID3D11Texture2D,
},
Dxgi::Common::*,
};
use crate::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
Point, Size, platform::AtlasTextureList,
};
pub(crate) struct DirectXAtlas(Mutex<DirectXAtlasState>);
struct DirectXAtlasState {
device: ID3D11Device,
device_context: ID3D11DeviceContext,
monochrome_textures: AtlasTextureList<DirectXAtlasTexture>,
polychrome_textures: AtlasTextureList<DirectXAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
}
struct DirectXAtlasTexture {
id: AtlasTextureId,
bytes_per_pixel: u32,
allocator: BucketedAtlasAllocator,
texture: ID3D11Texture2D,
view: [Option<ID3D11ShaderResourceView>; 1],
live_atlas_keys: u32,
}
impl DirectXAtlas {
pub(crate) fn new(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Self {
DirectXAtlas(Mutex::new(DirectXAtlasState {
device: device.clone(),
device_context: device_context.clone(),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
tiles_by_key: Default::default(),
}))
}
pub(crate) fn get_texture_view(
&self,
id: AtlasTextureId,
) -> [Option<ID3D11ShaderResourceView>; 1] {
let lock = self.0.lock();
let tex = lock.texture(id);
tex.view.clone()
}
pub(crate) fn handle_device_lost(
&self,
device: &ID3D11Device,
device_context: &ID3D11DeviceContext,
) {
let mut lock = self.0.lock();
lock.device = device.clone();
lock.device_context = device_context.clone();
lock.monochrome_textures = AtlasTextureList::default();
lock.polychrome_textures = AtlasTextureList::default();
lock.tiles_by_key.clear();
}
}
impl PlatformAtlas for DirectXAtlas {
fn get_or_insert_with<'a>(
&self,
key: &AtlasKey,
build: &mut dyn FnMut() -> anyhow::Result<
Option<(Size<DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
>,
) -> anyhow::Result<Option<AtlasTile>> {
let mut lock = self.0.lock();
if let Some(tile) = lock.tiles_by_key.get(key) {
Ok(Some(tile.clone()))
} else {
let Some((size, bytes)) = build()? else {
return Ok(None);
};
let tile = lock
.allocate(size, key.texture_kind())
.ok_or_else(|| anyhow::anyhow!("failed to allocate"))?;
let texture = lock.texture(tile.texture_id);
texture.upload(&lock.device_context, tile.bounds, &bytes);
lock.tiles_by_key.insert(key.clone(), tile.clone());
Ok(Some(tile))
}
}
fn remove(&self, key: &AtlasKey) {
let mut lock = self.0.lock();
let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
return;
};
let textures = match id.kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
};
let Some(texture_slot) = textures.textures.get_mut(id.index as usize) else {
return;
};
if let Some(mut texture) = texture_slot.take() {
texture.decrement_ref_count();
if texture.is_unreferenced() {
textures.free_list.push(texture.id.index as usize);
lock.tiles_by_key.remove(key);
} else {
*texture_slot = Some(texture);
}
}
}
}
impl DirectXAtlasState {
fn allocate(
&mut self,
size: Size<DevicePixels>,
texture_kind: AtlasTextureKind,
) -> Option<AtlasTile> {
{
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
};
if let Some(tile) = textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
{
return Some(tile);
}
}
let texture = self.push_texture(size, texture_kind)?;
texture.allocate(size)
}
fn push_texture(
&mut self,
min_size: Size<DevicePixels>,
kind: AtlasTextureKind,
) -> Option<&mut DirectXAtlasTexture> {
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(1024),
height: DevicePixels(1024),
};
// Max texture size for DirectX. See:
// https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits
const MAX_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(16384),
height: DevicePixels(16384),
};
let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);
let pixel_format;
let bind_flag;
let bytes_per_pixel;
match kind {
AtlasTextureKind::Monochrome => {
pixel_format = DXGI_FORMAT_R8_UNORM;
bind_flag = D3D11_BIND_SHADER_RESOURCE;
bytes_per_pixel = 1;
}
AtlasTextureKind::Polychrome => {
pixel_format = DXGI_FORMAT_B8G8R8A8_UNORM;
bind_flag = D3D11_BIND_SHADER_RESOURCE;
bytes_per_pixel = 4;
}
}
let texture_desc = D3D11_TEXTURE2D_DESC {
Width: size.width.0 as u32,
Height: size.height.0 as u32,
MipLevels: 1,
ArraySize: 1,
Format: pixel_format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: bind_flag.0 as u32,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
MiscFlags: 0,
};
let mut texture: Option<ID3D11Texture2D> = None;
unsafe {
// This only returns None if the device is lost, which we will recreate later.
// So it's ok to return None here.
self.device
.CreateTexture2D(&texture_desc, None, Some(&mut texture))
.ok()?;
}
let texture = texture.unwrap();
let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
};
let index = texture_list.free_list.pop();
let view = unsafe {
let mut view = None;
self.device
.CreateShaderResourceView(&texture, None, Some(&mut view))
.ok()?;
[view]
};
let atlas_texture = DirectXAtlasTexture {
id: AtlasTextureId {
index: index.unwrap_or(texture_list.textures.len()) as u32,
kind,
},
bytes_per_pixel,
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
texture,
view,
live_atlas_keys: 0,
};
if let Some(ix) = index {
texture_list.textures[ix] = Some(atlas_texture);
texture_list.textures.get_mut(ix).unwrap().as_mut()
} else {
texture_list.textures.push(Some(atlas_texture));
texture_list.textures.last_mut().unwrap().as_mut()
}
}
fn texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture {
let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
};
textures[id.index as usize].as_ref().unwrap()
}
}
impl DirectXAtlasTexture {
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(size.into())?;
let tile = AtlasTile {
texture_id: self.id,
tile_id: allocation.id.into(),
bounds: Bounds {
origin: allocation.rectangle.min.into(),
size,
},
padding: 0,
};
self.live_atlas_keys += 1;
Some(tile)
}
fn upload(
&self,
device_context: &ID3D11DeviceContext,
bounds: Bounds<DevicePixels>,
bytes: &[u8],
) {
println!("{:?}", bounds);
unsafe {
device_context.UpdateSubresource(
&self.texture,
0,
Some(&D3D11_BOX {
left: bounds.left().0 as u32,
top: bounds.top().0 as u32,
front: 0,
right: bounds.right().0 as u32,
bottom: bounds.bottom().0 as u32,
back: 1,
}),
bytes.as_ptr() as _,
bounds.size.width.to_bytes(self.bytes_per_pixel as u8),
0,
);
}
}
fn decrement_ref_count(&mut self) {
self.live_atlas_keys -= 1;
}
fn is_unreferenced(&mut self) -> bool {
self.live_atlas_keys == 0
}
}
impl From<Size<DevicePixels>> for etagere::Size {
fn from(size: Size<DevicePixels>) -> Self {
etagere::Size::new(size.width.into(), size.height.into())
}
}
impl From<etagere::Point> for Point<DevicePixels> {
fn from(value: etagere::Point) -> Self {
Point {
x: DevicePixels::from(value.x),
y: DevicePixels::from(value.y),
}
}
}