
This allows debugging Zed with Renderdoc, and also fixes an issue where glyphs' bounds were miscalculated for certain sizes and scale factors. Release Notes: - N/A --------- Co-authored-by: Kate <kate@zed.dev> Co-authored-by: Julia <julia@zed.dev> Co-authored-by: Junkui Zhang <364772080@qq.com>
309 lines
9.9 KiB
Rust
309 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],
|
|
) {
|
|
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),
|
|
}
|
|
}
|
|
}
|