lumen-engine 0.2.2

Core composition model and rendering orchestration for Lumen.
Documentation
#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
use std::sync::OnceLock;

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
use lumen_ffmpeg::{
    CudaContext, CudaDeviceAllocation, CudaDriver, CudaVideoFrame, GpuVideoInput,
    ImportedCudaExternalImage, import_owned_vulkan_opaque_fd_image,
};
#[cfg(all(target_os = "macos", feature = "metal"))]
use lumen_ffmpeg::{GpuVideoInput, MetalPixelBufferFrame, MetalPixelBufferPool, MetalTextureCache};

use crate::error::RenderError;

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
pub struct CudaNvencTargetPool {
    driver: &'static CudaDriver,
    cuda_ordinal: i32,
    size: lumen_gpu::Size,
    format: lumen_gpu::wgpu::TextureFormat,
    vulkan_device: lumen_gpu::VulkanDeviceInfo,
}

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
pub struct CudaNvencTarget {
    driver: &'static CudaDriver,
    external: lumen_gpu::ExternalTexture,
    cuda_frame: CudaDeviceAllocation<'static>,
    imported: ImportedCudaExternalImage,
    exportable: lumen_gpu::ExportableVulkanTexture,
    context: CudaContext<'static>,
}

#[cfg(all(target_os = "macos", feature = "metal"))]
pub struct MetalVideoToolboxTargetPool {
    texture_cache: MetalTextureCache,
    pixel_buffer_pool: MetalPixelBufferPool,
    size: lumen_gpu::Size,
    format: lumen_gpu::wgpu::TextureFormat,
}

#[cfg(all(target_os = "macos", feature = "metal"))]
pub struct MetalVideoToolboxTarget {
    pixel_frame: MetalPixelBufferFrame,
    external: lumen_gpu::ExternalTexture,
}

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
impl CudaNvencTargetPool {
    pub fn rgba8(renderer: &lumen_gpu::Renderer, size: lumen_gpu::Size) -> crate::Result<Self> {
        let bootstrap = renderer
            .create_exportable_vulkan_texture(
                Some("lumen CUDA/NVENC target bootstrap"),
                size,
                lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
                cuda_nvenc_usage(),
            )
            .map_err(|error| RenderError::Gpu {
                details: error.to_string(),
            })?;
        let vulkan_device = bootstrap.device_info().clone();
        let driver = cuda_driver().map_err(|details| RenderError::Gpu { details })?;
        let cuda_ordinal = driver
            .devices()
            .map_err(|details| RenderError::Gpu { details })?
            .into_iter()
            .find(|device| device.uuid == vulkan_device.device_uuid)
            .map(|device| device.ordinal)
            .ok_or_else(|| RenderError::Gpu {
                details: format!(
                    "no CUDA device UUID matched Vulkan device {} ({})",
                    vulkan_device.name,
                    format_uuid(&vulkan_device.device_uuid)
                ),
            })?;
        drop(bootstrap);
        Ok(Self {
            driver,
            cuda_ordinal,
            size,
            format: lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
            vulkan_device,
        })
    }

    pub fn acquire(&self, renderer: &lumen_gpu::Renderer) -> crate::Result<CudaNvencTarget> {
        let context = self
            .driver
            .create_primary_context_for_ordinal(self.cuda_ordinal)
            .map_err(|details| RenderError::Gpu { details })?;
        let exportable = renderer
            .create_exportable_vulkan_texture(
                Some("lumen CUDA/NVENC render target"),
                self.size,
                self.format,
                cuda_nvenc_usage(),
            )
            .map_err(|error| RenderError::Gpu {
                details: error.to_string(),
            })?;
        let imported = import_owned_vulkan_opaque_fd_image(
            self.driver,
            exportable
                .memory_fd()
                .try_clone()
                .map_err(|error| RenderError::Gpu {
                    details: format!("failed to duplicate Vulkan memory fd: {error}"),
                })?,
            exportable.allocation_size(),
            self.size.width,
            self.size.height,
        )
        .map_err(|details| RenderError::Gpu { details })?;
        let cuda_frame = self
            .driver
            .allocate_rgba_frame(self.size.width, self.size.height)
            .map_err(|details| RenderError::Gpu { details })?;
        let external = lumen_gpu::ExternalTexture::new(
            exportable.texture_arc(),
            lumen_gpu::TextureDesc {
                domain: lumen_gpu::TextureDomain::full_frame(self.size),
                format: self.format,
                usage: cuda_nvenc_usage(),
            },
        );
        Ok(CudaNvencTarget {
            driver: self.driver,
            external,
            cuda_frame,
            imported,
            exportable,
            context,
        })
    }

    pub fn format(&self) -> lumen_gpu::wgpu::TextureFormat {
        self.format
    }

    pub fn size(&self) -> lumen_gpu::Size {
        self.size
    }

    pub fn vulkan_device(&self) -> &lumen_gpu::VulkanDeviceInfo {
        &self.vulkan_device
    }

    pub fn cuda_ordinal(&self) -> i32 {
        self.cuda_ordinal
    }

    pub fn driver(&self) -> &'static CudaDriver {
        self.driver
    }
}

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
impl CudaNvencTarget {
    pub fn external_texture(&self) -> lumen_gpu::ExternalTexture {
        self.external.clone()
    }

    pub fn copy_rendered_frame_to_cuda(&self) -> crate::Result<()> {
        self.context
            .set_current()
            .map_err(|details| RenderError::Gpu { details })?;
        Ok(self
            .driver
            .copy_image_to_rgba_frame(&self.imported, &self.cuda_frame)
            .map_err(|details| RenderError::Gpu { details })?)
    }

    pub fn video_frame(&self, pts: Option<i64>) -> CudaVideoFrame {
        self.cuda_frame.as_video_frame(pts)
    }

    pub fn video_input<'a>(&'a self, frame: &'a CudaVideoFrame) -> GpuVideoInput<'a> {
        GpuVideoInput::Cuda(frame)
    }

    pub fn allocation_size(&self) -> u64 {
        self.exportable.allocation_size()
    }

    pub fn row_pitch(&self) -> u64 {
        self.exportable.row_pitch()
    }

    pub fn memory_fd_raw(&self) -> i32 {
        self.exportable.memory_fd_raw()
    }

    pub fn memory_type_index(&self) -> u32 {
        self.exportable.memory_type_index()
    }
}

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
impl Drop for CudaNvencTarget {
    fn drop(&mut self) {
        let _ = self.context.set_current();
    }
}

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
fn cuda_driver() -> Result<&'static CudaDriver, String> {
    static CUDA_DRIVER: OnceLock<CudaDriver> = OnceLock::new();
    if let Some(driver) = CUDA_DRIVER.get() {
        return Ok(driver);
    }
    let _ = CUDA_DRIVER.set(CudaDriver::load()?);
    CUDA_DRIVER
        .get()
        .ok_or_else(|| "CUDA driver was not initialized".to_string())
}

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
fn cuda_nvenc_usage() -> lumen_gpu::wgpu::TextureUsages {
    lumen_gpu::wgpu::TextureUsages::RENDER_ATTACHMENT
        | lumen_gpu::wgpu::TextureUsages::TEXTURE_BINDING
        | lumen_gpu::wgpu::TextureUsages::STORAGE_BINDING
        | lumen_gpu::wgpu::TextureUsages::COPY_DST
        | lumen_gpu::wgpu::TextureUsages::COPY_SRC
}

#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
fn format_uuid(uuid: &[u8; 16]) -> String {
    uuid.iter()
        .map(|byte| format!("{byte:02x}"))
        .collect::<Vec<_>>()
        .join("")
}

#[cfg(all(target_os = "macos", feature = "metal"))]
impl MetalVideoToolboxTargetPool {
    pub fn bgra8(renderer: &lumen_gpu::Renderer, size: lumen_gpu::Size) -> crate::Result<Self> {
        let metal_device =
            lumen_gpu::metal_device_from_wgpu(&renderer.device).map_err(|error| {
                RenderError::Gpu {
                    details: error.to_string(),
                }
            })?;
        let texture_cache =
            MetalTextureCache::create(&metal_device).map_err(|error| RenderError::Gpu {
                details: error.to_string(),
            })?;
        let pixel_buffer_pool =
            MetalPixelBufferPool::bgra8(size.width, size.height).map_err(|error| {
                RenderError::Gpu {
                    details: error.to_string(),
                }
            })?;
        Ok(Self {
            texture_cache,
            pixel_buffer_pool,
            size,
            format: lumen_gpu::wgpu::TextureFormat::Bgra8Unorm,
        })
    }

    pub fn acquire(
        &mut self,
        renderer: &lumen_gpu::Renderer,
        frame: u32,
    ) -> crate::Result<MetalVideoToolboxTarget> {
        let pixel_frame = self
            .pixel_buffer_pool
            .create_frame(Some(i64::from(frame)))
            .map_err(|error| RenderError::Gpu {
                details: error.to_string(),
            })?;
        let metal_texture = pixel_frame
            .create_texture(
                &self.texture_cache,
                objc2_metal::MTLPixelFormat::BGRA8Unorm,
                0,
            )
            .map_err(|error| RenderError::Gpu {
                details: error.to_string(),
            })?;
        let texture = lumen_gpu::texture_from_metal(
            &renderer.device,
            metal_texture,
            &lumen_gpu::wgpu::TextureDescriptor {
                label: Some("lumen VideoToolbox render target"),
                size: self.size.as_extent(),
                mip_level_count: 1,
                sample_count: 1,
                dimension: lumen_gpu::wgpu::TextureDimension::D2,
                format: self.format,
                usage: external_output_usage(),
                view_formats: &[],
            },
        );
        Ok(MetalVideoToolboxTarget {
            pixel_frame,
            external: lumen_gpu::ExternalTexture::from_texture(
                texture,
                lumen_gpu::TextureDesc {
                    domain: lumen_gpu::TextureDomain::full_frame(self.size),
                    format: self.format,
                    usage: external_output_usage(),
                },
            ),
        })
    }

    pub fn format(&self) -> lumen_gpu::wgpu::TextureFormat {
        self.format
    }

    pub fn size(&self) -> lumen_gpu::Size {
        self.size
    }
}

#[cfg(all(target_os = "macos", feature = "metal"))]
impl MetalVideoToolboxTarget {
    pub fn external_texture(&self) -> lumen_gpu::ExternalTexture {
        self.external.clone()
    }

    pub fn video_input(&self) -> GpuVideoInput<'_> {
        GpuVideoInput::MetalPixelBuffer(&self.pixel_frame)
    }
}

#[cfg(all(target_os = "macos", feature = "metal"))]
fn external_output_usage() -> lumen_gpu::wgpu::TextureUsages {
    lumen_gpu::wgpu::TextureUsages::RENDER_ATTACHMENT
        | lumen_gpu::wgpu::TextureUsages::TEXTURE_BINDING
        | lumen_gpu::wgpu::TextureUsages::COPY_SRC
}