use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock, PoisonError, Weak};
use crate::markers::Markers;
use crate::media::ImageData;
pub const WGPU_LAYER_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
pub const WGPU_LAYER_USAGE: wgpu::TextureUsages = wgpu::TextureUsages::TEXTURE_BINDING
.union(wgpu::TextureUsages::COPY_SRC)
.union(wgpu::TextureUsages::COPY_DST);
#[derive(Clone)]
pub struct SharedGpu {
inner: Arc<SharedGpuInner>,
}
struct SharedGpuInner {
id: u64,
device: wgpu::Device,
queue: wgpu::Queue,
queue_lock: Arc<Mutex<()>>,
state: Mutex<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
}
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
static ACTIVE: Mutex<Weak<SharedGpuInner>> = Mutex::new(Weak::new());
static LAYERS: Markers<WgpuLayer> = Markers::new("fframes-wgpu:");
impl SharedGpu {
pub fn new(device: wgpu::Device, queue: wgpu::Queue, queue_lock: Arc<Mutex<()>>) -> Self {
Self {
inner: Arc::new(SharedGpuInner {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
device,
queue,
queue_lock,
state: Mutex::new(HashMap::new()),
}),
}
}
pub fn id(&self) -> u64 {
self.inner.id
}
pub fn device(&self) -> &wgpu::Device {
&self.inner.device
}
pub fn queue(&self) -> &wgpu::Queue {
&self.inner.queue
}
pub fn submit<I: IntoIterator<Item = wgpu::CommandBuffer>>(
&self,
command_buffers: I,
) -> wgpu::SubmissionIndex {
let _queue = self
.inner
.queue_lock
.lock()
.unwrap_or_else(PoisonError::into_inner);
self.inner.queue.submit(command_buffers)
}
pub fn queue_lock(&self) -> Arc<Mutex<()>> {
Arc::clone(&self.inner.queue_lock)
}
pub fn state<T: Any + Send + Sync>(&self, init: impl FnOnce(&SharedGpu) -> T) -> Arc<T> {
let mut state = self
.inner
.state
.lock()
.unwrap_or_else(PoisonError::into_inner);
let entry = state
.entry(TypeId::of::<T>())
.or_insert_with(|| Arc::new(init(self)));
Arc::clone(entry)
.downcast::<T>()
.unwrap_or_else(|_| unreachable!("state is keyed by its type"))
}
}
impl std::fmt::Debug for SharedGpu {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SharedGpu")
.field("id", &self.inner.id)
.finish_non_exhaustive()
}
}
pub fn register_shared_gpu(gpu: &SharedGpu) {
*ACTIVE.lock().unwrap_or_else(PoisonError::into_inner) = Arc::downgrade(&gpu.inner);
}
pub fn shared_gpu() -> Option<SharedGpu> {
let inner = ACTIVE
.lock()
.unwrap_or_else(PoisonError::into_inner)
.upgrade()?;
Some(SharedGpu { inner })
}
pub fn finish_wgpu_layer(encoder: &mut wgpu::CommandEncoder, texture: &wgpu::Texture) {
encoder.transition_resources(
std::iter::empty(),
std::iter::once(wgpu::TextureTransition {
texture,
selector: None,
state: wgpu::TextureUses::RESOURCE,
}),
);
}
pub fn wgpu_layer(gpu: &SharedGpu, texture: &wgpu::Texture) -> ImageData<'static> {
let (width, height) = (texture.width(), texture.height());
if texture.format() != WGPU_LAYER_FORMAT || !texture.usage().contains(WGPU_LAYER_USAGE) {
log::warn!(
"fframes: a wgpu layer needs a {WGPU_LAYER_FORMAT:?} texture with {WGPU_LAYER_USAGE:?}, got {:?} with {:?}",
texture.format(),
texture.usage()
);
return LAYERS.empty(width, height);
}
LAYERS.create(
WgpuLayer {
gpu: gpu.clone(),
texture: texture.clone(),
pixels: OnceLock::new(),
},
width,
height,
)
}
pub fn resolve_wgpu_layer(image: &Arc<usvgr::PreloadedImageData>) -> Option<Arc<WgpuLayer>> {
LAYERS.resolve(image)
}
pub fn release_wgpu_layers(gpu: &SharedGpu) {
LAYERS.retain(|layer| layer.gpu.id() != gpu.id());
}
pub struct WgpuLayer {
gpu: SharedGpu,
texture: wgpu::Texture,
pixels: OnceLock<Option<Arc<usvgr::PreloadedImageData>>>,
}
impl WgpuLayer {
pub fn gpu_id(&self) -> u64 {
self.gpu.id()
}
pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
pub fn pixels(&self) -> Option<Arc<usvgr::PreloadedImageData>> {
self.pixels.get_or_init(|| self.read_back()).clone()
}
fn read_back(&self) -> Option<Arc<usvgr::PreloadedImageData>> {
let (width, height) = (self.texture.width(), self.texture.height());
let row = width * 4;
let padded = row.next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
let device = self.gpu.device();
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("fframes wgpu layer readback"),
size: u64::from(padded) * u64::from(height),
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
encoder.copy_texture_to_buffer(
self.texture.as_image_copy(),
wgpu::TexelCopyBufferInfo {
buffer: &buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded),
rows_per_image: Some(height),
},
},
self.texture.size(),
);
finish_wgpu_layer(&mut encoder, &self.texture);
let submission = self.gpu.submit([encoder.finish()]);
let slice = buffer.slice(..);
let (sender, receiver) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = sender.send(result);
});
device
.poll(wgpu::PollType::Wait {
submission_index: Some(submission),
timeout: None,
})
.ok()?;
receiver.recv().ok()?.ok()?;
let mapped = slice.get_mapped_range().ok()?;
let rgba: Vec<u8> = mapped
.chunks(padded as usize)
.flat_map(|line| &line[..row as usize])
.copied()
.collect();
drop(mapped);
buffer.unmap();
Some(Arc::new(usvgr::PreloadedImageData {
data: std::borrow::Cow::Owned(rgba),
width,
height,
id: format!("fframes-wgpu-pixels:{}", self.gpu.id()),
}))
}
}
impl std::fmt::Debug for WgpuLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WgpuLayer")
.field("gpu", &self.gpu.id())
.field("width", &self.texture.width())
.field("height", &self.texture.height())
.finish_non_exhaustive()
}
}