use super::super::Handle;
use super::types::*;
use crate::fitz::geometry::Matrix;
pub trait GpuDevice: Send + Sync {
fn backend(&self) -> GpuBackendType;
fn capabilities(&self) -> &GpuCapabilities;
fn name(&self) -> &str {
&self.capabilities().device_name
}
fn create_texture(&self, width: u32, height: u32, format: GpuFormat) -> GpuResult<GpuTexture>;
fn destroy_texture(&self, texture: &GpuTexture) -> GpuResult<()>;
fn upload_texture(&self, texture: &mut GpuTexture, data: &[u8], stride: u32) -> GpuResult<()>;
fn download_texture(&self, texture: &GpuTexture, data: &mut [u8], stride: u32)
-> GpuResult<()>;
fn clear_texture(&self, texture: &mut GpuTexture, color: [f32; 4]) -> GpuResult<()>;
fn create_shader(&self, vertex_src: &str, fragment_src: &str) -> GpuResult<GpuShader>;
fn destroy_shader(&self, shader: &GpuShader) -> GpuResult<()>;
fn create_buffer(&self, size: usize, usage: GpuBufferUsage) -> GpuResult<GpuBuffer>;
fn destroy_buffer(&self, buffer: &GpuBuffer) -> GpuResult<()>;
fn upload_buffer(&self, buffer: &mut GpuBuffer, data: &[u8], offset: usize) -> GpuResult<()>;
fn render_page(
&self,
page: Handle,
texture: &mut GpuTexture,
transform: &Matrix,
) -> GpuResult<()>;
fn composite(
&self,
src: &GpuTexture,
dst: &mut GpuTexture,
x: i32,
y: i32,
blend_mode: GpuBlendMode,
) -> GpuResult<()>;
fn draw_quad(
&self,
texture: &GpuTexture,
dst: &mut GpuTexture,
src_rect: [f32; 4],
dst_rect: [f32; 4],
color: [f32; 4],
) -> GpuResult<()>;
fn flush(&self) -> GpuResult<()>;
fn finish(&self) -> GpuResult<()>;
}
pub fn is_backend_available(backend: GpuBackendType) -> bool {
match backend {
GpuBackendType::Auto => {
is_backend_available(GpuBackendType::Vulkan)
|| is_backend_available(GpuBackendType::Metal)
|| is_backend_available(GpuBackendType::DirectX12)
|| is_backend_available(GpuBackendType::OpenGL)
}
GpuBackendType::OpenGL => {
cfg!(any(
target_os = "linux",
target_os = "windows",
target_os = "macos"
))
}
GpuBackendType::Vulkan => {
cfg!(any(
target_os = "linux",
target_os = "windows",
target_os = "android"
))
}
GpuBackendType::Metal => {
cfg!(any(target_os = "macos", target_os = "ios"))
}
GpuBackendType::DirectX11 | GpuBackendType::DirectX12 => {
cfg!(target_os = "windows")
}
}
}
pub fn best_backend() -> GpuBackendType {
#[cfg(target_os = "macos")]
{
GpuBackendType::Metal
}
#[cfg(target_os = "windows")]
{
if is_backend_available(GpuBackendType::DirectX12) {
GpuBackendType::DirectX12
} else if is_backend_available(GpuBackendType::Vulkan) {
GpuBackendType::Vulkan
} else {
GpuBackendType::OpenGL
}
}
#[cfg(target_os = "linux")]
{
if is_backend_available(GpuBackendType::Vulkan) {
GpuBackendType::Vulkan
} else {
GpuBackendType::OpenGL
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
GpuBackendType::OpenGL
}
}
pub fn create_device(backend: GpuBackendType) -> GpuResult<Box<dyn GpuDevice + Send + Sync>> {
let actual_backend = if backend == GpuBackendType::Auto {
best_backend()
} else {
backend
};
if !is_backend_available(actual_backend) {
return Err(GpuError::BackendNotAvailable(actual_backend));
}
match actual_backend {
GpuBackendType::Auto => unreachable!(),
GpuBackendType::OpenGL => Ok(Box::new(super::opengl::OpenGLDevice::new()?)),
GpuBackendType::Vulkan => Ok(Box::new(super::vulkan::VulkanDevice::new()?)),
#[cfg(target_os = "macos")]
GpuBackendType::Metal => Ok(Box::new(super::metal::MetalDevice::new()?)),
#[cfg(not(target_os = "macos"))]
GpuBackendType::Metal => Err(GpuError::BackendNotAvailable(GpuBackendType::Metal)),
#[cfg(target_os = "windows")]
GpuBackendType::DirectX11 => Ok(Box::new(super::directx::DirectX11Device::new()?)),
#[cfg(target_os = "windows")]
GpuBackendType::DirectX12 => Ok(Box::new(super::directx::DirectX12Device::new()?)),
#[cfg(not(target_os = "windows"))]
GpuBackendType::DirectX11 | GpuBackendType::DirectX12 => {
Err(GpuError::BackendNotAvailable(actual_backend))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_best_backend() {
let backend = best_backend();
assert!(is_backend_available(backend));
}
#[test]
fn test_is_backend_available_auto() {
let _ = is_backend_available(GpuBackendType::Auto);
}
#[test]
fn test_create_device_opengl() {
if is_backend_available(GpuBackendType::OpenGL) {
let device = create_device(GpuBackendType::OpenGL).unwrap();
assert_eq!(device.backend(), GpuBackendType::OpenGL);
assert!(!device.name().is_empty());
}
}
#[test]
fn test_create_device_metal_unavailable() {
if !cfg!(target_os = "macos") {
let result = create_device(GpuBackendType::Metal);
assert!(result.is_err());
}
}
#[test]
fn test_create_device_directx_unavailable() {
if !cfg!(target_os = "windows") {
let result = create_device(GpuBackendType::DirectX12);
assert!(result.is_err());
}
}
#[test]
fn test_format_bytes() {
assert_eq!(GpuFormat::Rgba8.bytes_per_pixel(), 4);
assert_eq!(GpuFormat::Rgb8.bytes_per_pixel(), 3);
assert_eq!(GpuFormat::R8.bytes_per_pixel(), 1);
assert_eq!(GpuFormat::Rgba16f.bytes_per_pixel(), 8);
assert_eq!(GpuFormat::Rgba32f.bytes_per_pixel(), 16);
}
}