editor: run on a shared VAAPI-capable wgpu device (Stage 3a)
Foundation for hardware video decode in BOTH preview and export: wgpu textures can't cross devices, and a hardware-decoded frame is a DMA-BUF-imported texture that needs the import extensions (only addable via wgpu-hal device_from_raw). So eframe + the compositor + decode + encode must share ONE custom device. - vk_device::create_windowed(): the existing import-capable DrmDevice plus VK_KHR_swapchain (the WSI surface instance extensions are already enabled by Instance::init), for use as the editor's main device. - main.rs: on Linux, build the shared device and inject it into eframe via WgpuSetup::Existing (the egui fork supports it); fall back to wgpu's normal device + software decode on any failure, on other platforms, or via LB_NO_SHARED_DEVICE. The DrmDevice's wgpu handles are cloned into eframe (Arc-backed), so the VkDevice persists with them. Runtime-verified: the editor launches and renders identically (canvas/vello, video, panels) on the shared device, and the env-var fallback works.
This commit is contained in:
parent
255e16434e
commit
ca612d7807
|
|
@ -22,12 +22,20 @@ pub struct DrmDevice {
|
||||||
pub raw_instance: ash::Instance,
|
pub raw_instance: ash::Instance,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the device, or `Err` if Vulkan/the extension isn't available (caller falls back).
|
/// Create a headless DMA-BUF-import device (encoder/decoder), or `Err` if Vulkan/the extension
|
||||||
|
/// isn't available (caller falls back).
|
||||||
pub fn create() -> Result<DrmDevice, String> {
|
pub fn create() -> Result<DrmDevice, String> {
|
||||||
unsafe { create_inner() }
|
unsafe { create_inner(false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn create_inner() -> Result<DrmDevice, String> {
|
/// Like [`create`] but also enables `VK_KHR_swapchain` so the device can present to a window —
|
||||||
|
/// for use as the editor's **shared** wgpu device (eframe + compositor + decode + encode all on
|
||||||
|
/// one device, so hardware-decoded DMA-BUF textures are usable by the preview compositor).
|
||||||
|
pub fn create_windowed() -> Result<DrmDevice, String> {
|
||||||
|
unsafe { create_inner(true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_inner(windowed: bool) -> Result<DrmDevice, String> {
|
||||||
use wgpu_hal::vulkan::Api as Vk;
|
use wgpu_hal::vulkan::Api as Vk;
|
||||||
// Bring the HAL Instance trait into scope for `init` / `enumerate_adapters`.
|
// Bring the HAL Instance trait into scope for `init` / `enumerate_adapters`.
|
||||||
use wgpu_hal::Instance as _;
|
use wgpu_hal::Instance as _;
|
||||||
|
|
@ -72,14 +80,19 @@ unsafe fn create_inner() -> Result<DrmDevice, String> {
|
||||||
exposed.adapter.required_device_extensions(exposed.features);
|
exposed.adapter.required_device_extensions(exposed.features);
|
||||||
// Only the genuine extensions; external_memory / bind_memory2 / ycbcr / format_list
|
// Only the genuine extensions; external_memory / bind_memory2 / ycbcr / format_list
|
||||||
// are core in Vulkan 1.1+ (this device is 1.3) so they need no enabling.
|
// are core in Vulkan 1.1+ (this device is 1.3) so they need no enabling.
|
||||||
let extra: &[&'static CStr] = &[
|
let mut extra: Vec<&'static CStr> = vec![
|
||||||
ash::ext::image_drm_format_modifier::NAME,
|
ash::ext::image_drm_format_modifier::NAME,
|
||||||
ash::khr::external_memory_fd::NAME,
|
ash::khr::external_memory_fd::NAME,
|
||||||
ash::ext::external_memory_dma_buf::NAME,
|
ash::ext::external_memory_dma_buf::NAME,
|
||||||
ash::ext::queue_family_foreign::NAME,
|
ash::ext::queue_family_foreign::NAME,
|
||||||
];
|
];
|
||||||
|
// Presentation (windowed shared device only): the WSI surface instance extensions are already
|
||||||
|
// enabled by `Instance::init`; the device needs the swapchain extension to present.
|
||||||
|
if windowed {
|
||||||
|
extra.push(ash::khr::swapchain::NAME);
|
||||||
|
}
|
||||||
for e in extra {
|
for e in extra {
|
||||||
if !ext_names.contains(e) {
|
if !ext_names.contains(&e) {
|
||||||
ext_names.push(e);
|
ext_names.push(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,40 @@ struct Args {
|
||||||
cpu_renderer: bool,
|
cpu_renderer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The default eframe wgpu device setup (wgpu picks the adapter/device). Used on non-Linux, when
|
||||||
|
/// the shared VAAPI device is unavailable, or when disabled via `LB_NO_SHARED_DEVICE`.
|
||||||
|
fn lb_default_wgpu_setup() -> egui_wgpu::WgpuSetup {
|
||||||
|
egui_wgpu::WgpuSetup::CreateNew(egui_wgpu::WgpuSetupCreateNew {
|
||||||
|
device_descriptor: std::sync::Arc::new(|adapter| {
|
||||||
|
let features = adapter.features();
|
||||||
|
// Request SHADER_F16 if available — needed on Mesa/llvmpipe for vello's
|
||||||
|
// unpack2x16float (enables the SHADER_F16_IN_F32 downlevel capability).
|
||||||
|
// TIMESTAMP_QUERY(+INSIDE_ENCODERS) drives the F3 GPU composite timer
|
||||||
|
// (gpu_timer.rs); both are optional and no-op when unsupported.
|
||||||
|
let optional_features = wgpu::Features::SHADER_F16
|
||||||
|
| wgpu::Features::TIMESTAMP_QUERY
|
||||||
|
| wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS;
|
||||||
|
|
||||||
|
let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
|
||||||
|
wgpu::Limits::downlevel_webgl2_defaults()
|
||||||
|
} else {
|
||||||
|
wgpu::Limits::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
wgpu::DeviceDescriptor {
|
||||||
|
label: Some("lightningbeam wgpu device"),
|
||||||
|
required_features: features & optional_features,
|
||||||
|
required_limits: wgpu::Limits {
|
||||||
|
max_texture_dimension_2d: 8192,
|
||||||
|
..base_limits
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
println!("🚀 Starting Lightningbeam Editor...");
|
println!("🚀 Starting Lightningbeam Editor...");
|
||||||
|
|
||||||
|
|
@ -168,38 +202,38 @@ fn main() -> eframe::Result {
|
||||||
viewport_builder = viewport_builder.with_icon(icon);
|
viewport_builder = viewport_builder.with_icon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer the shared VAAPI-capable wgpu device on Linux: eframe + the compositor + hardware
|
||||||
|
// video decode all run on one device, so decoded DMA-BUF frames are usable by the preview
|
||||||
|
// compositor (decode/encode can't share textures across devices). Falls back to wgpu's normal
|
||||||
|
// device (software video decode) when unavailable, on other platforms, or via LB_NO_SHARED_DEVICE.
|
||||||
|
// The DrmDevice's wgpu handles are cloned into eframe (Arc-backed, keep the device alive); the
|
||||||
|
// raw VkDevice persists with them.
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut wgpu_setup = lb_default_wgpu_setup();
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if std::env::var("LB_NO_SHARED_DEVICE").is_ok() {
|
||||||
|
println!("🖥 Shared device disabled via LB_NO_SHARED_DEVICE; default wgpu device (software video decode)");
|
||||||
|
} else {
|
||||||
|
match gpu_video_encoder::vk_device::create_windowed() {
|
||||||
|
Ok(drm) => {
|
||||||
|
println!("🖥 Using shared VAAPI-capable wgpu device (hardware video decode enabled)");
|
||||||
|
wgpu_setup = egui_wgpu::WgpuSetup::Existing(egui_wgpu::WgpuSetupExisting {
|
||||||
|
instance: drm.instance.clone(),
|
||||||
|
adapter: drm.adapter.clone(),
|
||||||
|
device: drm.device.clone(),
|
||||||
|
queue: drm.queue.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("🖥 Shared device unavailable ({e}); default wgpu device (software video decode)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: viewport_builder,
|
viewport: viewport_builder,
|
||||||
wgpu_options: egui_wgpu::WgpuConfiguration {
|
wgpu_options: egui_wgpu::WgpuConfiguration {
|
||||||
wgpu_setup: egui_wgpu::WgpuSetup::CreateNew(egui_wgpu::WgpuSetupCreateNew {
|
wgpu_setup,
|
||||||
device_descriptor: std::sync::Arc::new(|adapter| {
|
|
||||||
let features = adapter.features();
|
|
||||||
// Request SHADER_F16 if available — needed on Mesa/llvmpipe for vello's
|
|
||||||
// unpack2x16float (enables the SHADER_F16_IN_F32 downlevel capability).
|
|
||||||
// TIMESTAMP_QUERY(+INSIDE_ENCODERS) drives the F3 GPU composite timer
|
|
||||||
// (gpu_timer.rs); both are optional and no-op when unsupported.
|
|
||||||
let optional_features = wgpu::Features::SHADER_F16
|
|
||||||
| wgpu::Features::TIMESTAMP_QUERY
|
|
||||||
| wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS;
|
|
||||||
|
|
||||||
let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
|
|
||||||
wgpu::Limits::downlevel_webgl2_defaults()
|
|
||||||
} else {
|
|
||||||
wgpu::Limits::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
wgpu::DeviceDescriptor {
|
|
||||||
label: Some("lightningbeam wgpu device"),
|
|
||||||
required_features: features & optional_features,
|
|
||||||
required_limits: wgpu::Limits {
|
|
||||||
max_texture_dimension_2d: 8192,
|
|
||||||
..base_limits
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue