blob: b41e210ee2427e5c65cb5b701682fcf69890f938 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! rutabaga_core: Cross-platform, Rust-based, Wayland and Vulkan centric GPU virtualization.
use std::collections::BTreeMap as Map;
use std::sync::Arc;
use base::ExternalMapping;
use data_model::VolatileSlice;
use crate::cross_domain::CrossDomain;
#[cfg(feature = "gfxstream")]
use crate::gfxstream::Gfxstream;
use crate::rutabaga_2d::Rutabaga2D;
use crate::rutabaga_utils::*;
#[cfg(feature = "virgl_renderer")]
use crate::virgl_renderer::VirglRenderer;
/// Information required for 2D functionality.
pub struct Rutabaga2DInfo {
pub width: u32,
pub height: u32,
pub host_mem: Vec<u8>,
}
/// A Rutabaga resource, supporting 2D and 3D rutabaga features. Assumes a single-threaded library.
pub struct RutabagaResource {
pub resource_id: u32,
pub handle: Option<Arc<RutabagaHandle>>,
pub blob: bool,
pub blob_mem: u32,
pub blob_flags: u32,
pub map_info: Option<u32>,
pub info_2d: Option<Rutabaga2DInfo>,
pub info_3d: Option<Resource3DInfo>,
pub vulkan_info: Option<VulkanInfo>,
pub backing_iovecs: Option<Vec<RutabagaIovec>>,
}
/// A RutabagaComponent is a building block of the Virtual Graphics Interface (VGI). Each component
/// on it's own is sufficient to virtualize graphics on many Google products. These components wrap
/// libraries like gfxstream or virglrenderer, and Rutabaga's own 2D and cross-domain prototype
/// functionality.
///
/// Most methods return a `RutabagaResult` that indicate the success, failure, or requested data for
/// the given command.
pub trait RutabagaComponent {
/// Implementations should return the version and size of the given capset_id. (0, 0) is
/// returned by default.
fn get_capset_info(&self, _capset_id: u32) -> (u32, u32) {
(0, 0)
}
/// Implementations should return the capabilites of given a `capset_id` and `version`. A
/// zero-sized array is returned by default.
fn get_capset(&self, _capset_id: u32, _version: u32) -> Vec<u8> {
Vec::new()
}
/// Implementations should set their internal context to be the reserved context 0.
fn force_ctx_0(&self) {}
/// Implementations must create a fence that represents the completion of prior work. This is
/// required for synchronization with the guest kernel.
fn create_fence(&mut self, _fence_data: RutabagaFenceData) -> RutabagaResult<()> {
Err(RutabagaError::Unsupported)
}
/// Implementations must return the last completed fence_id.
fn poll(&self) -> u32 {
0
}
/// Implementations must create a resource with the given metadata. For 2D rutabaga components,
/// this a system memory allocation. For 3D components, this is typically a GL texture or
/// buffer. Vulkan components should use blob resources instead.
fn create_3d(
&self,
_resource_id: u32,
_resource_create_3d: ResourceCreate3D,
) -> RutabagaResult<RutabagaResource> {
Err(RutabagaError::Unsupported)
}
/// Implementations must attach `vecs` to the resource.
fn attach_backing(
&self,
_resource_id: u32,
_vecs: &mut Vec<RutabagaIovec>,
) -> RutabagaResult<()> {
Ok(())
}
/// Implementations must detach `vecs` from the resource.
fn detach_backing(&self, _resource_id: u32) {}
/// Implementations must release the guest kernel reference on the resource.
fn unref_resource(&self, _resource_id: u32) {}
/// Implementations must perform the transfer write operation. For 2D rutabaga components, this
/// done via memcpy(). For 3D components, this is typically done via glTexSubImage(..).
fn transfer_write(
&self,
_ctx_id: u32,
_resource: &mut RutabagaResource,
_transfer: Transfer3D,
) -> RutabagaResult<()> {
Err(RutabagaError::Unsupported)
}
/// Implementations must perform the transfer read operation. For 2D rutabaga components, this
/// done via memcpy(). For 3D components, this is typically done via glReadPixels(..).
fn transfer_read(
&self,
_ctx_id: u32,
_resource: &mut RutabagaResource,
_transfer: Transfer3D,
_buf: Option<VolatileSlice>,
) -> RutabagaResult<()> {
Err(RutabagaError::Unsupported)
}
/// Implementations must create a blob resource on success. The memory parameters, size, and
/// usage of the blob resource is given by `resource_create_blob`.
fn create_blob(
&mut self,
_ctx_id: u32,
_resource_id: u32,
_resource_create_blob: ResourceCreateBlob,
_backing_iovecs: Vec<RutabagaIovec>,
) -> RutabagaResult<RutabagaResource> {
Err(RutabagaError::Unsupported)
}
/// Implementations must map the blob resource on success. This is typically done by
/// glMapBufferRange(...) or vkMapMemory.
fn map(&self, _resource_id: u32) -> RutabagaResult<ExternalMapping> {
Err(RutabagaError::Unsupported)
}
/// Implementations must return a RutabagaHandle of the fence on success.
fn export_fence(&self, _fence_id: u32) -> RutabagaResult<RutabagaHandle> {
Err(RutabagaError::Unsupported)
}
/// Implementations must create a context for submitting commands. The command stream of the
/// context is determined by `context_init`. For virgl contexts, it is a Gallium/TGSI command
/// stream. For gfxstream contexts, it's an autogenerated Vulkan or GLES streams.
fn create_context(
&self,
_ctx_id: u32,
_context_init: u32,
) -> RutabagaResult<Box<dyn RutabagaContext>> {
Err(RutabagaError::Unsupported)
}
}
pub trait RutabagaContext {
/// Implementations must return a RutabagaResource given the `resource_create_blob` parameters.
fn context_create_blob(
&mut self,
_resource_id: u32,
_resource_create_blob: ResourceCreateBlob,
) -> RutabagaResult<RutabagaResource> {
Err(RutabagaError::Unsupported)
}
/// Implementations must handle the context-specific command stream.
fn submit_cmd(&mut self, _commands: &mut [u8]) -> RutabagaResult<()>;
/// Implementations may use `resource` in this context's command stream.
fn attach(&mut self, _resource: &mut RutabagaResource);
/// Implementations must stop using `resource` in this context's command stream.
fn detach(&mut self, _resource: &RutabagaResource);
/// Implementations must create a fence on specified `fence_ctx_idx` in `fence_data`. This
/// allows for multiple syncrhonizations timelines per RutabagaContext.
fn context_create_fence(&mut self, _fence_data: RutabagaFenceData) -> RutabagaResult<()> {
Err(RutabagaError::Unsupported)
}
/// Implementations must return an array of fences that have completed. This will be used by
/// the cross-domain context for asynchronous Tx/Rx.
fn context_poll(&mut self) -> Option<Vec<RutabagaFenceData>> {
None
}
}
#[derive(Copy, Clone)]
struct RutabagaCapsetInfo {
pub capset_id: u32,
pub component: RutabagaComponentType,
}
/// The global libary handle used to query capability sets, create resources and contexts.
///
/// Currently, Rutabaga only supports one default component. Many components running at the
/// same time is a stretch goal of Rutabaga GFX.
///
/// Not thread-safe, but can be made so easily. Making non-Rutabaga, C/C++ components
/// thread-safe is more difficult.
pub struct Rutabaga {
resources: Map<u32, RutabagaResource>,
components: Map<RutabagaComponentType, Box<dyn RutabagaComponent>>,
contexts: Map<u32, Box<dyn RutabagaContext>>,
default_component: RutabagaComponentType,
capset_info: Vec<RutabagaCapsetInfo>,
}
impl Rutabaga {
fn capset_id_to_component_type(&self, capset_id: u32) -> RutabagaResult<RutabagaComponentType> {
let component = self
.capset_info
.iter()
.find(|capset_info| capset_info.capset_id == capset_id)
.ok_or(RutabagaError::InvalidCapset)?
.component;
Ok(component)
}
fn capset_index_to_component_info(&self, index: u32) -> RutabagaResult<RutabagaCapsetInfo> {
let idx = index as usize;
if idx >= self.capset_info.len() {
return Err(RutabagaError::InvalidCapset);
}
Ok(self.capset_info[idx])
}
/// Gets the version and size for the capabilty set `index`.
pub fn get_capset_info(&self, index: u32) -> RutabagaResult<(u32, u32, u32)> {
let capset_info = self.capset_index_to_component_info(index)?;
let component = self
.components
.get(&capset_info.component)
.ok_or(RutabagaError::InvalidComponent)?;
let (capset_version, capset_size) = component.get_capset_info(capset_info.capset_id);
Ok((capset_info.capset_id, capset_version, capset_size))
}
/// Gets the capability set for the `capset_id` and `version`.
/// Each capability set is associated with a context type, which is associated
/// with a rutabaga component.
pub fn get_capset(&self, capset_id: u32, version: u32) -> RutabagaResult<Vec<u8>> {
// The default workaround is just until context types are fully supported in all
// Google kernels.
let component_type = self
.capset_id_to_component_type(capset_id)
.unwrap_or(self.default_component);
let component = self
.components
.get(&component_type)
.ok_or(RutabagaError::InvalidComponent)?;
Ok(component.get_capset(capset_id, version))
}
/// Forces context zero for the default rutabaga component.
pub fn force_ctx_0(&self) {
if let Some(component) = self.components.get(&self.default_component) {
component.force_ctx_0();
}
}
/// Creates a fence with the given `fence_data`.
/// If the flags include RUTABAGA_FLAG_PARAM_FENCE_CTX_IDX, then the fence is created on a
/// specific timeline on the specific context.
pub fn create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
if fence_data.flags & RUTABAGA_FLAG_INFO_FENCE_CTX_IDX != 0 {
let ctx = self
.contexts
.get_mut(&fence_data.ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
ctx.context_create_fence(fence_data)?;
} else {
let component = self
.components
.get_mut(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
component.create_fence(fence_data)?;
}
Ok(())
}
/// Polls all rutabaga components and contexts, and returns a vector of RutabagaFenceData
/// describing which fences have completed.
pub fn poll(&mut self) -> Vec<RutabagaFenceData> {
let mut completed_fences: Vec<RutabagaFenceData> = Vec::new();
// Poll the default component -- this the global timeline which does not take into account
// `ctx_id` or `fence_ctx_idx`. This path exists for OpenGL legacy reasons and 2D mode.
let component = self
.components
.get_mut(&self.default_component)
.ok_or(0)
.unwrap();
let global_fence_id = component.poll();
completed_fences.push(RutabagaFenceData {
flags: RUTABAGA_FLAG_FENCE,
fence_id: global_fence_id as u64,
ctx_id: 0,
fence_ctx_idx: 0,
});
for ctx in self.contexts.values_mut() {
if let Some(ref mut ctx_completed_fences) = ctx.context_poll() {
completed_fences.append(ctx_completed_fences);
}
}
completed_fences
}
/// Creates a resource with the `resource_create_3d` metadata.
pub fn resource_create_3d(
&mut self,
resource_id: u32,
resource_create_3d: ResourceCreate3D,
) -> RutabagaResult<()> {
let component = self
.components
.get_mut(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
if self.resources.contains_key(&resource_id) {
return Err(RutabagaError::InvalidResourceId);
}
let resource = component.create_3d(resource_id, resource_create_3d)?;
self.resources.insert(resource_id, resource);
Ok(())
}
/// Attaches `vecs` to the resource.
pub fn attach_backing(
&mut self,
resource_id: u32,
mut vecs: Vec<RutabagaIovec>,
) -> RutabagaResult<()> {
let component = self
.components
.get_mut(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
let mut resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
component.attach_backing(resource_id, &mut vecs)?;
resource.backing_iovecs = Some(vecs);
Ok(())
}
/// Detaches any previously attached iovecs from the resource.
pub fn detach_backing(&mut self, resource_id: u32) -> RutabagaResult<()> {
let component = self
.components
.get_mut(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
component.detach_backing(resource_id);
resource.backing_iovecs = None;
Ok(())
}
/// Releases guest kernel reference on the resource.
pub fn unref_resource(&mut self, resource_id: u32) -> RutabagaResult<()> {
let component = self
.components
.get_mut(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
self.resources
.remove(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
component.unref_resource(resource_id);
Ok(())
}
/// For HOST3D_GUEST resources, copies from the attached iovecs to the host resource. For
/// HOST3D resources, this may flush caches, though this feature is unused by guest userspace.
pub fn transfer_write(
&mut self,
ctx_id: u32,
resource_id: u32,
transfer: Transfer3D,
) -> RutabagaResult<()> {
let component = self
.components
.get(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
component.transfer_write(ctx_id, resource, transfer)
}
/// 1) If specified, copies to `buf` from the host resource.
/// 2) Otherwise, for HOST3D_GUEST resources, copies to the attached iovecs from the host
/// resource. For HOST3D resources, this may invalidate caches, though this feature is
/// unused by guest userspace.
pub fn transfer_read(
&mut self,
ctx_id: u32,
resource_id: u32,
transfer: Transfer3D,
buf: Option<VolatileSlice>,
) -> RutabagaResult<()> {
let component = self
.components
.get(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
component.transfer_read(ctx_id, resource, transfer, buf)
}
/// Creates a blob resource with the `ctx_id` and `resource_create_blob` metadata.
/// Associates `iovecs` with the resource, if there are any.
pub fn resource_create_blob(
&mut self,
ctx_id: u32,
resource_id: u32,
resource_create_blob: ResourceCreateBlob,
iovecs: Vec<RutabagaIovec>,
) -> RutabagaResult<()> {
if self.resources.contains_key(&resource_id) {
return Err(RutabagaError::InvalidResourceId);
}
// For the cross-domain context, we'll need to create the blob resource via a home-grown
// rutabaga context rather than one from an external C/C++ component. Use `ctx_id` to check
// if it happens to be a cross-domain context.
if ctx_id > 0 {
let ctx = self
.contexts
.get_mut(&ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
if let Ok(resource) = ctx.context_create_blob(resource_id, resource_create_blob) {
self.resources.insert(resource_id, resource);
return Ok(());
}
}
let component = self
.components
.get_mut(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
let resource = component.create_blob(ctx_id, resource_id, resource_create_blob, iovecs)?;
self.resources.insert(resource_id, resource);
Ok(())
}
/// Returns a memory mapping of the blob resource.
pub fn map(&self, resource_id: u32) -> RutabagaResult<ExternalMapping> {
let component = self
.components
.get(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
if !self.resources.contains_key(&resource_id) {
return Err(RutabagaError::InvalidResourceId);
}
component.map(resource_id)
}
/// Returns the `map_info` of the blob resource. The valid values for `map_info`
/// are defined in the virtio-gpu spec.
pub fn map_info(&self, resource_id: u32) -> RutabagaResult<u32> {
let resource = self
.resources
.get(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
let map_info = resource.map_info.ok_or(RutabagaError::SpecViolation)?;
Ok(map_info)
}
/// Returns the 3D info associated with the resource, if any.
pub fn query(&self, resource_id: u32) -> RutabagaResult<Resource3DInfo> {
let resource = self
.resources
.get(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
let info_3d = resource.info_3d.ok_or(RutabagaError::Unsupported)?;
Ok(info_3d)
}
/// Exports a blob resource. See virtio-gpu spec for blob flag use flags.
pub fn export_blob(&mut self, resource_id: u32) -> RutabagaResult<RutabagaHandle> {
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
// We can inspect blob flags only once guest minigbm is fully transitioned to blob.
let share_mask = RUTABAGA_BLOB_FLAG_USE_SHAREABLE | RUTABAGA_BLOB_FLAG_USE_CROSS_DEVICE;
let shareable = (resource.blob_flags & share_mask != 0) || !resource.blob;
let opt = resource.handle.take();
match (opt, shareable) {
(Some(handle), true) => {
let clone = handle.try_clone()?;
resource.handle = Some(handle);
Ok(clone)
}
(Some(handle), false) => {
// Exactly one strong reference in this case.
let hnd = Arc::try_unwrap(handle).map_err(|_| RutabagaError::SpecViolation)?;
Ok(hnd)
}
_ => Err(RutabagaError::Unsupported),
}
}
/// Exports the given fence for import into other processes.
pub fn export_fence(&self, fence_id: u32) -> RutabagaResult<RutabagaHandle> {
let component = self
.components
.get(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
component.export_fence(fence_id)
}
/// Creates a context with the given `ctx_id` and `context_init` variable.
/// `context_init` is used to determine which rutabaga component creates the context.
pub fn create_context(&mut self, ctx_id: u32, context_init: u32) -> RutabagaResult<()> {
// The default workaround is just until context types are fully supported in all
// Google kernels.
let capset_id = context_init & RUTABAGA_CONTEXT_INIT_CAPSET_ID_MASK;
let component_type = self
.capset_id_to_component_type(capset_id)
.unwrap_or(self.default_component);
let component = self
.components
.get_mut(&component_type)
.ok_or(RutabagaError::InvalidComponent)?;
if self.contexts.contains_key(&ctx_id) {
return Err(RutabagaError::InvalidContextId);
}
let ctx = component.create_context(ctx_id, context_init)?;
self.contexts.insert(ctx_id, ctx);
Ok(())
}
/// Destroys the context given by `ctx_id`.
pub fn destroy_context(&mut self, ctx_id: u32) -> RutabagaResult<()> {
self.contexts
.remove(&ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
Ok(())
}
/// Attaches the resource given by `resource_id` to the context given by `ctx_id`.
pub fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> RutabagaResult<()> {
let ctx = self
.contexts
.get_mut(&ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
let mut resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
ctx.attach(&mut resource);
Ok(())
}
/// Detaches the resource given by `resource_id` from the context given by `ctx_id`.
pub fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> RutabagaResult<()> {
let ctx = self
.contexts
.get_mut(&ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
ctx.detach(&resource);
Ok(())
}
/// Submits `commands` to the context given by `ctx_id`.
pub fn submit_command(&mut self, ctx_id: u32, commands: &mut [u8]) -> RutabagaResult<()> {
let ctx = self
.contexts
.get_mut(&ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
ctx.submit_cmd(commands)
}
}
/// Rutabaga Builder, following the Rust builder pattern.
pub struct RutabagaBuilder {
display_width: Option<u32>,
display_height: Option<u32>,
default_component: RutabagaComponentType,
virglrenderer_flags: Option<VirglRendererFlags>,
gfxstream_flags: Option<GfxstreamFlags>,
channels: Option<Vec<RutabagaChannel>>,
}
impl RutabagaBuilder {
/// Create new a RutabagaBuilder.
pub fn new(default_component: RutabagaComponentType) -> RutabagaBuilder {
RutabagaBuilder {
display_width: None,
display_height: None,
default_component,
virglrenderer_flags: None,
gfxstream_flags: None,
channels: None,
}
}
/// Set display width for the RutabagaBuilder
pub fn set_display_width(mut self, display_width: u32) -> RutabagaBuilder {
self.display_width = Some(display_width);
self
}
/// Set display height for the RutabagaBuilder
pub fn set_display_height(mut self, display_height: u32) -> RutabagaBuilder {
self.display_height = Some(display_height);
self
}
/// Set virglrenderer flags for the RutabagaBuilder
pub fn set_virglrenderer_flags(
mut self,
virglrenderer_flags: VirglRendererFlags,
) -> RutabagaBuilder {
self.virglrenderer_flags = Some(virglrenderer_flags);
self
}
/// Set gfxstream flags for the RutabagaBuilder
pub fn set_gfxstream_flags(mut self, gfxstream_flags: GfxstreamFlags) -> RutabagaBuilder {
self.gfxstream_flags = Some(gfxstream_flags);
self
}
/// Set rutabaga channels for the RutabagaBuilder
pub fn set_rutabaga_channels(
mut self,
channels: Option<Vec<RutabagaChannel>>,
) -> RutabagaBuilder {
self.channels = channels;
self
}
/// Builds Rutabaga and returns a handle to it.
///
/// This should be only called once per every virtual machine instance. Rutabaga tries to
/// intialize all 3D components which have been built. In 2D mode, only the 2D component is
/// initialized.
pub fn build(self) -> RutabagaResult<Rutabaga> {
let mut rutabaga_components: Map<RutabagaComponentType, Box<dyn RutabagaComponent>> =
Default::default();
let mut rutabaga_capsets: Vec<RutabagaCapsetInfo> = Default::default();
if self.default_component == RutabagaComponentType::Rutabaga2D {
let rutabaga_2d = Rutabaga2D::init()?;
rutabaga_components.insert(RutabagaComponentType::Rutabaga2D, rutabaga_2d);
} else {
#[cfg(feature = "virgl_renderer")]
{
let virglrenderer_flags = self
.virglrenderer_flags
.ok_or(RutabagaError::InvalidRutabagaBuild)?;
let virgl = VirglRenderer::init(virglrenderer_flags)?;
rutabaga_components.insert(RutabagaComponentType::VirglRenderer, virgl);
rutabaga_capsets.push(RutabagaCapsetInfo {
capset_id: RUTABAGA_CAPSET_VIRGL,
component: RutabagaComponentType::VirglRenderer,
});
rutabaga_capsets.push(RutabagaCapsetInfo {
capset_id: RUTABAGA_CAPSET_VIRGL2,
component: RutabagaComponentType::VirglRenderer,
});
rutabaga_capsets.push(RutabagaCapsetInfo {
capset_id: RUTABAGA_CAPSET_VENUS,
component: RutabagaComponentType::VirglRenderer,
});
}
#[cfg(feature = "gfxstream")]
{
let display_width = self
.display_width
.ok_or(RutabagaError::InvalidRutabagaBuild)?;
let display_height = self
.display_height
.ok_or(RutabagaError::InvalidRutabagaBuild)?;
let gfxstream_flags = self
.gfxstream_flags
.ok_or(RutabagaError::InvalidRutabagaBuild)?;
let gfxstream = Gfxstream::init(display_width, display_height, gfxstream_flags)?;
rutabaga_components.insert(RutabagaComponentType::Gfxstream, gfxstream);
rutabaga_capsets.push(RutabagaCapsetInfo {
capset_id: RUTABAGA_CAPSET_GFXSTREAM,
component: RutabagaComponentType::Gfxstream,
});
}
let cross_domain = CrossDomain::init(self.channels)?;
rutabaga_components.insert(RutabagaComponentType::CrossDomain, cross_domain);
rutabaga_capsets.push(RutabagaCapsetInfo {
capset_id: RUTABAGA_CAPSET_CROSS_DOMAIN,
component: RutabagaComponentType::CrossDomain,
});
}
Ok(Rutabaga {
components: rutabaga_components,
resources: Default::default(),
contexts: Default::default(),
default_component: self.default_component,
capset_info: rutabaga_capsets,
})
}
}