use valo_dl::{DisplayList, DisplayListBuilder, Image};
use valo_geometry::{Color, Rect};
use valo_renderer::{RenderStats, RenderTarget};
#[cfg(target_os = "macos")]
pub fn metal_device_of(device: &wgpu::Device) -> Option<std::ptr::NonNull<std::ffi::c_void>> {
let hal_device = unsafe { device.as_hal::<wgpu::hal::api::Metal>() }?;
let raw = objc2::rc::Retained::as_ptr(hal_device.raw_device());
std::ptr::NonNull::new(raw.cast_mut().cast())
}
#[cfg(target_os = "macos")]
pub struct ExternalMetalTexture {
texture: wgpu::Texture,
view: wgpu::TextureView,
pub format: wgpu::TextureFormat,
pub size: [u32; 2],
}
#[cfg(target_os = "macos")]
impl ExternalMetalTexture {
pub unsafe fn wrap(
device: &wgpu::Device,
texture: std::ptr::NonNull<std::ffi::c_void>,
size: [u32; 2],
format: wgpu::TextureFormat,
) -> Self {
let texture = unsafe {
wrap_metal_texture(
device,
texture,
size,
format,
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
)
};
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
texture,
view,
format,
size,
}
}
pub fn target(&self, clear: Option<Color>) -> RenderTarget<'_> {
RenderTarget {
view: &self.view,
texture: &self.texture,
format: self.format,
size: self.size,
clear,
}
}
}
#[cfg(target_os = "macos")]
pub unsafe fn wrap_metal_texture(
device: &wgpu::Device,
texture: std::ptr::NonNull<std::ffi::c_void>,
size: [u32; 2],
format: wgpu::TextureFormat,
usage: wgpu::TextureUsages,
) -> wgpu::Texture {
let raw = texture
.cast::<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLTexture>>()
.as_ptr();
let retained =
unsafe { objc2::rc::Retained::retain(raw) }.expect("retaining a non-null MTLTexture");
let hal_texture = unsafe {
wgpu::hal::metal::Device::texture_from_raw(
retained,
format,
objc2_metal::MTLTextureType::Type2D,
1,
1,
wgpu::hal::CopyExtent {
width: size[0],
height: size[1],
depth: 1,
},
None,
)
};
let descriptor = wgpu::TextureDescriptor {
label: Some("valo.external-metal-texture"),
size: wgpu::Extent3d {
width: size[0],
height: size[1],
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage,
view_formats: &[],
};
unsafe {
device.create_texture_from_hal::<wgpu::hal::api::Metal>(
hal_texture,
&descriptor,
wgpu::wgt::TextureUses::COLOR_TARGET,
)
}
}
pub struct Surface {
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
device: wgpu::Device,
}
impl Surface {
pub fn new(
instance: &wgpu::Instance,
adapter: &wgpu::Adapter,
device: &wgpu::Device,
target: impl Into<wgpu::SurfaceTarget<'static>>,
size: [u32; 2],
) -> Result<Self, wgpu::CreateSurfaceError> {
let surface = instance.create_surface(target)?;
Ok(Self::from_wgpu_surface(surface, adapter, device, size))
}
pub unsafe fn new_unsafe(
instance: &wgpu::Instance,
adapter: &wgpu::Adapter,
device: &wgpu::Device,
target: wgpu::SurfaceTargetUnsafe,
size: [u32; 2],
) -> Result<Self, wgpu::CreateSurfaceError> {
let surface = unsafe { instance.create_surface_unsafe(target)? };
Ok(Self::from_wgpu_surface(surface, adapter, device, size))
}
pub fn from_wgpu_surface(
surface: wgpu::Surface<'static>,
adapter: &wgpu::Adapter,
device: &wgpu::Device,
size: [u32; 2],
) -> Self {
let caps = surface.get_capabilities(adapter);
let format = caps
.formats
.iter()
.copied()
.find(|f| !f.is_srgb())
.unwrap_or(caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
color_space: wgpu::SurfaceColorSpace::Srgb,
usage: (wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC)
& caps.usages,
format,
width: size[0].max(1),
height: size[1].max(1),
present_mode: wgpu::PresentMode::AutoVsync,
desired_maximum_frame_latency: 2,
alpha_mode: caps.alpha_modes[0],
view_formats: vec![],
};
surface.configure(device, &config);
Self {
surface,
config,
device: device.clone(),
}
}
pub fn resize(&mut self, size: [u32; 2]) {
self.config.width = size[0].max(1);
self.config.height = size[1].max(1);
self.surface.configure(&self.device, &self.config);
}
pub fn size(&self) -> [u32; 2] {
[self.config.width, self.config.height]
}
pub fn format(&self) -> wgpu::TextureFormat {
self.config.format
}
pub fn acquire(&mut self) -> Option<SurfaceFrame> {
use wgpu::CurrentSurfaceTexture as C;
for _ in 0..2 {
match self.surface.get_current_texture() {
C::Success(t) | C::Suboptimal(t) => {
let raw = t.texture.clone();
let view = raw.create_view(&wgpu::TextureViewDescriptor::default());
return Some(SurfaceFrame {
surface_texture: t,
raw,
view,
format: self.config.format,
size: [self.config.width, self.config.height],
});
}
C::Outdated | C::Lost => self.surface.configure(&self.device, &self.config),
_ => return None,
}
}
None
}
}
pub struct SurfaceFrame {
surface_texture: wgpu::SurfaceTexture,
raw: wgpu::Texture,
view: wgpu::TextureView,
pub format: wgpu::TextureFormat,
pub size: [u32; 2],
}
impl SurfaceFrame {
pub fn target(&self, clear: Option<Color>) -> RenderTarget<'_> {
RenderTarget {
view: &self.view,
texture: &self.raw,
format: self.format,
size: self.size,
clear,
}
}
pub fn present(self, queue: &wgpu::Queue) {
queue.present(self.surface_texture);
}
}
pub struct Offscreen {
texture: wgpu::Texture,
view: wgpu::TextureView,
pub format: wgpu::TextureFormat,
pub size: [u32; 2],
}
impl Offscreen {
pub const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
pub fn new(device: &wgpu::Device, size: [u32; 2]) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("valo.offscreen"),
size: wgpu::Extent3d {
width: size[0],
height: size[1],
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Self::FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
texture,
view,
format: Self::FORMAT,
size,
}
}
pub fn target(&self, clear: Option<Color>) -> RenderTarget<'_> {
RenderTarget {
view: &self.view,
texture: &self.texture,
format: self.format,
size: self.size,
clear,
}
}
pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
}
pub struct PersistentCanvas {
front: Image,
back: Image,
size: [u32; 2],
format: wgpu::TextureFormat,
painted: bool,
}
impl PersistentCanvas {
pub fn new(context: &mut crate::Context, size: [u32; 2], format: wgpu::TextureFormat) -> Self {
let size = [size[0].max(1), size[1].max(1)];
Self {
front: backing(context, size, format),
back: backing(context, size, format),
size,
format,
painted: false,
}
}
pub fn size(&self) -> [u32; 2] {
self.size
}
pub fn front(&self) -> &Image {
&self.front
}
pub fn present_to(&self, context: &mut crate::Context, target: &crate::RenderTarget) {
let image = self.front();
let source = Rect::new(0.0, 0.0, image.width(), image.height());
let destination = Rect::new(0.0, 0.0, target.size[0] as f32, target.size[1] as f32);
let mut builder = valo_dl::DisplayListBuilder::new();
builder.draw_image_rect(
image,
source,
destination,
crate::context::EXACT_SAMPLING,
&crate::context::copy_paint(),
);
context.render(&builder.build(), target);
}
pub fn draw(
&mut self,
context: &mut crate::Context,
delta: &std::sync::Arc<DisplayList>,
clear: Option<Color>,
) -> RenderStats {
let mut frame = DisplayListBuilder::new();
if clear.is_none() && self.painted {
frame.draw_image_rect(
&self.front,
self.whole(),
self.whole(),
crate::context::EXACT_SAMPLING,
&crate::context::copy_paint(),
);
}
frame.draw_display_list(delta);
let list = frame.build();
let stats = context.render(
&list,
&self.back_target(clear.unwrap_or(Color::TRANSPARENT)),
);
std::mem::swap(&mut self.front, &mut self.back);
self.painted = true;
stats
}
pub fn resize(&mut self, context: &mut crate::Context, size: [u32; 2]) {
let size = [size[0].max(1), size[1].max(1)];
if size == self.size {
return;
}
*self = Self::new(context, size, self.format);
}
fn whole(&self) -> Rect {
Rect::new(0.0, 0.0, self.size[0] as f32, self.size[1] as f32)
}
fn back_target(&self, clear: Color) -> RenderTarget<'_> {
RenderTarget {
view: self.back.view(),
texture: self.back.texture(),
format: self.format,
size: self.size,
clear: Some(clear),
}
}
}
fn backing(context: &mut crate::Context, size: [u32; 2], format: wgpu::TextureFormat) -> Image {
let texture = context.device().create_texture(&wgpu::TextureDescriptor {
label: Some("valo.canvas.backing"),
size: wgpu::Extent3d {
width: size[0],
height: size[1],
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
context.import_image(texture, size)
}