use bevy::render::renderer::{RenderAdapter, RenderDevice, RenderInstance, RenderQueue};
use burn::tensor::{DType, Device, Tensor};
use burn_cubecl::{CubeBackend, CubeDevice, cubecl};
use burn_wgpu::{CubeTensor, RuntimeOptions, WgpuResource, WgpuSetup, wgpu};
use std::{any::Any, num::NonZeroU64};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeError(pub String);
impl std::fmt::Display for BridgeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for BridgeError {}
#[derive(Clone)]
pub struct SharedGpuBridge {
device: Device,
cube_device: CubeDevice,
queue: wgpu::Queue,
limits: wgpu::Limits,
}
impl SharedGpuBridge {
pub fn from_renderer(
instance: &RenderInstance,
adapter: &RenderAdapter,
device: &RenderDevice,
queue: &RenderQueue,
) -> Result<Self, BridgeError> {
Self::from_setup(WgpuSetup {
instance: instance.clone().into_inner(),
adapter: adapter.clone().into_inner(),
device: device.wgpu_device().clone(),
queue: queue.clone().into_inner(),
backend: adapter.get_info().backend,
})
}
pub fn from_setup(setup: WgpuSetup) -> Result<Self, BridgeError> {
let limits = setup.device.limits();
let queue = setup.queue.clone();
let imported = burn_wgpu::try_init_device(setup, RuntimeOptions::default())
.map_err(|e| BridgeError(e.to_string()))?;
let cube_device = CubeDevice::Wgpu(imported);
Ok(Self {
device: Device::new(cube_device.clone()),
cube_device,
queue,
limits,
})
}
pub fn device(&self) -> &Device {
&self.device
}
pub fn export<const D: usize>(
&self,
tensor: Tensor<D>,
expected_shape: [usize; D],
) -> Result<TensorBufferLease, BridgeError> {
if tensor.dims() != expected_shape || expected_shape.contains(&0) {
return Err(BridgeError("nonempty exact tensor shape required".into()));
}
let elements = expected_shape
.iter()
.try_fold(1usize, |a, &b| a.checked_mul(b))
.ok_or_else(|| BridgeError("tensor element count overflow".into()))?;
let bytes = elements
.checked_mul(4)
.and_then(|v| u64::try_from(v).ok())
.ok_or_else(|| BridgeError("tensor byte count overflow".into()))?;
let primitive: CubeTensor = tensor
.try_into_primitive::<CubeBackend>()
.map_err(|e| BridgeError(format!("an eager CubeCL tensor is required: {e:?}")))?;
if primitive.device != self.cube_device {
return Err(BridgeError(
"tensor belongs to a different GPU runtime".into(),
));
}
if primitive.dtype != DType::F32 || primitive.qparams.is_some() {
return Err(BridgeError(
"only ordinary F32 tensors may be exported".into(),
));
}
if primitive.meta.is_tiled() || !primitive.is_contiguous() {
return Err(BridgeError("tensor must be contiguous and untiled".into()));
}
let managed = primitive
.client
.get_resource::<cubecl::wgpu::WgpuServer<burn_wgpu::AutoCompiler>>(
primitive.handle.clone(),
)
.map_err(|e| BridgeError(e.to_string()))?;
primitive
.client
.flush()
.map_err(|e| BridgeError(e.to_string()))?;
let resource = managed.resource();
validate_binding(resource, bytes, &self.limits)?;
Ok(TensorBufferLease {
resource: resource.clone(),
bytes,
shape: expected_shape.to_vec(),
queue: self.queue.clone(),
_allocation: Box::new(managed),
_tensor: primitive,
})
}
}
fn validate_binding(
resource: &WgpuResource,
bytes: u64,
limits: &wgpu::Limits,
) -> Result<(), BridgeError> {
let end = resource
.offset
.checked_add(bytes)
.ok_or_else(|| BridgeError("buffer range overflow".into()))?;
if bytes == 0 || bytes > resource.size || end > resource.buffer.size() {
return Err(BridgeError(
"tensor bytes exceed its owned buffer range".into(),
));
}
if !resource
.offset
.is_multiple_of(u64::from(limits.min_storage_buffer_offset_alignment))
|| !bytes.is_multiple_of(4)
|| bytes > limits.max_storage_buffer_binding_size
{
return Err(BridgeError(
"storage binding alignment or size limit".into(),
));
}
if !resource
.buffer
.usage()
.contains(wgpu::BufferUsages::STORAGE)
{
return Err(BridgeError("tensor buffer lacks STORAGE usage".into()));
}
Ok(())
}
pub struct TensorBufferLease {
resource: WgpuResource,
bytes: u64,
shape: Vec<usize>,
queue: wgpu::Queue,
_allocation: Box<dyn Any + Send + Sync>,
_tensor: CubeTensor,
}
impl TensorBufferLease {
pub fn shape(&self) -> &[usize] {
&self.shape
}
pub fn byte_len(&self) -> u64 {
self.bytes
}
pub fn binding(&self) -> wgpu::BindingResource<'_> {
wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &self.resource.buffer,
offset: self.resource.offset,
size: NonZeroU64::new(self.bytes),
})
}
pub fn buffer_range(&self) -> (&wgpu::Buffer, u64, u64) {
(&self.resource.buffer, self.resource.offset, self.bytes)
}
pub fn submit_read(
self,
commands: impl IntoIterator<Item = wgpu::CommandBuffer>,
) -> wgpu::SubmissionIndex {
let queue = self.queue.clone();
let index = queue.submit(commands);
queue.on_submitted_work_done(move || drop(self));
index
}
}