rutabaga_gfx/
rutabaga_core.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! rutabaga_core: Cross-platform, Rust-based, Wayland and Vulkan centric GPU virtualization.
6use std::collections::BTreeMap as Map;
7use std::convert::TryInto;
8use std::io::IoSliceMut;
9use std::io::Read;
10use std::io::Write;
11use std::sync::Arc;
12
13use crate::cross_domain::CrossDomain;
14#[cfg(feature = "gfxstream")]
15use crate::gfxstream::Gfxstream;
16use crate::rutabaga_2d::Rutabaga2D;
17use crate::rutabaga_os::MemoryMapping;
18use crate::rutabaga_os::SafeDescriptor;
19use crate::rutabaga_snapshot::RutabagaResourceSnapshot;
20use crate::rutabaga_snapshot::RutabagaSnapshot;
21use crate::rutabaga_utils::*;
22#[cfg(feature = "virgl_renderer")]
23use crate::virgl_renderer::VirglRenderer;
24
25const RUTABAGA_DEFAULT_WIDTH: u32 = 1280;
26const RUTABAGA_DEFAULT_HEIGHT: u32 = 1024;
27
28/// Information required for 2D functionality.
29pub struct Rutabaga2DInfo {
30    pub width: u32,
31    pub height: u32,
32    pub host_mem: Vec<u8>,
33}
34
35/// A Rutabaga resource, supporting 2D and 3D rutabaga features.  Assumes a single-threaded library.
36pub struct RutabagaResource {
37    pub resource_id: u32,
38    pub handle: Option<Arc<RutabagaHandle>>,
39    pub blob: bool,
40    pub blob_mem: u32,
41    pub blob_flags: u32,
42    pub map_info: Option<u32>,
43    pub info_2d: Option<Rutabaga2DInfo>,
44    pub info_3d: Option<Resource3DInfo>,
45    pub vulkan_info: Option<VulkanInfo>,
46    pub backing_iovecs: Option<Vec<RutabagaIovec>>,
47
48    /// Bitmask of components that have already imported this resource
49    pub component_mask: u8,
50    pub size: u64,
51    pub mapping: Option<MemoryMapping>,
52}
53
54/// A RutabagaComponent is a building block of the Virtual Graphics Interface (VGI).  Each component
55/// on it's own is sufficient to virtualize graphics on many Google products.  These components wrap
56/// libraries like gfxstream or virglrenderer, and Rutabaga's own 2D and cross-domain prototype
57/// functionality.
58///
59/// Most methods return a `RutabagaResult` that indicate the success, failure, or requested data for
60/// the given command.
61pub trait RutabagaComponent {
62    /// Implementations should return the version and size of the given capset_id.  (0, 0) is
63    /// returned by default.
64    fn get_capset_info(&self, _capset_id: u32) -> (u32, u32) {
65        (0, 0)
66    }
67
68    /// Implementations should return the capabilites of given a `capset_id` and `version`.  A
69    /// zero-sized array is returned by default.
70    fn get_capset(&self, _capset_id: u32, _version: u32) -> Vec<u8> {
71        Vec::new()
72    }
73
74    /// Implementations should set their internal context to be the reserved context 0.
75    fn force_ctx_0(&self) {}
76
77    /// Implementations must create a fence that represents the completion of prior work.  This is
78    /// required for synchronization with the guest kernel.
79    fn create_fence(&mut self, _fence: RutabagaFence) -> RutabagaResult<()> {
80        Ok(())
81    }
82
83    /// Used only by VirglRenderer to poll when its poll_descriptor is signaled.
84    fn event_poll(&self) {}
85
86    /// Used only by VirglRenderer to return a poll_descriptor that is signaled when a poll() is
87    /// necessary.
88    fn poll_descriptor(&self) -> Option<SafeDescriptor> {
89        None
90    }
91
92    /// Implementations must create a resource with the given metadata.  For 2D rutabaga components,
93    /// this a system memory allocation.  For 3D components, this is typically a GL texture or
94    /// buffer.  Vulkan components should use blob resources instead.
95    fn create_3d(
96        &self,
97        resource_id: u32,
98        _resource_create_3d: ResourceCreate3D,
99    ) -> RutabagaResult<RutabagaResource> {
100        Ok(RutabagaResource {
101            resource_id,
102            handle: None,
103            blob: false,
104            blob_mem: 0,
105            blob_flags: 0,
106            map_info: None,
107            info_2d: None,
108            info_3d: None,
109            vulkan_info: None,
110            backing_iovecs: None,
111            component_mask: 0,
112            size: 0,
113            mapping: None,
114        })
115    }
116
117    /// Implementations must attach `vecs` to the resource.
118    fn attach_backing(
119        &self,
120        _resource_id: u32,
121        _vecs: &mut Vec<RutabagaIovec>,
122    ) -> RutabagaResult<()> {
123        Ok(())
124    }
125
126    /// Implementations must detach `vecs` from the resource.
127    fn detach_backing(&self, _resource_id: u32) {}
128
129    /// Implementations must release the guest kernel reference on the resource.
130    fn unref_resource(&self, _resource_id: u32) {}
131
132    /// Implementations must perform the transfer write operation.  For 2D rutabaga components, this
133    /// done via memcpy().  For 3D components, this is typically done via glTexSubImage(..).
134    fn transfer_write(
135        &self,
136        _ctx_id: u32,
137        _resource: &mut RutabagaResource,
138        _transfer: Transfer3D,
139    ) -> RutabagaResult<()> {
140        Ok(())
141    }
142
143    /// Implementations must perform the transfer read operation.  For 2D rutabaga components, this
144    /// done via memcpy().  For 3D components, this is typically done via glReadPixels(..).
145    fn transfer_read(
146        &self,
147        _ctx_id: u32,
148        _resource: &mut RutabagaResource,
149        _transfer: Transfer3D,
150        _buf: Option<IoSliceMut>,
151    ) -> RutabagaResult<()> {
152        Ok(())
153    }
154
155    /// Implementations must flush the given resource to the display.
156    fn resource_flush(&self, _resource_id: &mut RutabagaResource) -> RutabagaResult<()> {
157        Err(RutabagaError::Unsupported)
158    }
159
160    /// Implementations must create a blob resource on success.  The memory parameters, size, and
161    /// usage of the blob resource is given by `resource_create_blob`.
162    fn create_blob(
163        &mut self,
164        _ctx_id: u32,
165        _resource_id: u32,
166        _resource_create_blob: ResourceCreateBlob,
167        _iovec_opt: Option<Vec<RutabagaIovec>>,
168        _handle_opt: Option<RutabagaHandle>,
169    ) -> RutabagaResult<RutabagaResource> {
170        Err(RutabagaError::Unsupported)
171    }
172
173    /// Implementations must map the blob resource on success.  This is typically done by
174    /// glMapBufferRange(...) or vkMapMemory.
175    fn map(&self, _resource_id: u32) -> RutabagaResult<RutabagaMapping> {
176        Err(RutabagaError::Unsupported)
177    }
178
179    /// Implementations must unmap the blob resource on success.  This is typically done by
180    /// glUnmapBuffer(...) or vkUnmapMemory.
181    fn unmap(&self, _resource_id: u32) -> RutabagaResult<()> {
182        Err(RutabagaError::Unsupported)
183    }
184
185    /// Implementations must return a RutabagaHandle of the fence on success.
186    fn export_fence(&self, _fence_id: u64) -> RutabagaResult<RutabagaHandle> {
187        Err(RutabagaError::Unsupported)
188    }
189
190    /// Implementations must create a context for submitting commands.  The command stream of the
191    /// context is determined by `context_init`.  For virgl contexts, it is a Gallium/TGSI command
192    /// stream.  For gfxstream contexts, it's an autogenerated Vulkan or GLES streams.
193    fn create_context(
194        &self,
195        _ctx_id: u32,
196        _context_init: u32,
197        _context_name: Option<&str>,
198        _fence_handler: RutabagaFenceHandler,
199    ) -> RutabagaResult<Box<dyn RutabagaContext>> {
200        Err(RutabagaError::Unsupported)
201    }
202}
203
204pub trait RutabagaContext {
205    /// Implementations must return a RutabagaResource given the `resource_create_blob` parameters.
206    fn context_create_blob(
207        &mut self,
208        _resource_id: u32,
209        _resource_create_blob: ResourceCreateBlob,
210        _handle_opt: Option<RutabagaHandle>,
211    ) -> RutabagaResult<RutabagaResource> {
212        Err(RutabagaError::Unsupported)
213    }
214
215    /// Implementations must handle the context-specific command stream.
216    fn submit_cmd(&mut self, _commands: &mut [u8], _fence_ids: &[u64]) -> RutabagaResult<()>;
217
218    /// Implementations may use `resource` in this context's command stream.
219    fn attach(&mut self, _resource: &mut RutabagaResource);
220
221    /// Implementations must stop using `resource` in this context's command stream.
222    fn detach(&mut self, _resource: &RutabagaResource);
223
224    /// Implementations must create a fence on specified `ring_idx` in `fence`.  This
225    /// allows for multiple synchronizations timelines per RutabagaContext.
226    fn context_create_fence(&mut self, _fence: RutabagaFence) -> RutabagaResult<()> {
227        Err(RutabagaError::Unsupported)
228    }
229
230    /// Implementations must return the component type associated with the context.
231    fn component_type(&self) -> RutabagaComponentType;
232}
233
234#[derive(Copy, Clone)]
235struct RutabagaCapsetInfo {
236    pub capset_id: u32,
237    pub component: RutabagaComponentType,
238    pub name: &'static str,
239}
240
241const RUTABAGA_CAPSETS: [RutabagaCapsetInfo; 9] = [
242    RutabagaCapsetInfo {
243        capset_id: RUTABAGA_CAPSET_VIRGL,
244        component: RutabagaComponentType::VirglRenderer,
245        name: "virgl",
246    },
247    RutabagaCapsetInfo {
248        capset_id: RUTABAGA_CAPSET_VIRGL2,
249        component: RutabagaComponentType::VirglRenderer,
250        name: "virgl2",
251    },
252    RutabagaCapsetInfo {
253        capset_id: RUTABAGA_CAPSET_GFXSTREAM_VULKAN,
254        component: RutabagaComponentType::Gfxstream,
255        name: "gfxstream-vulkan",
256    },
257    RutabagaCapsetInfo {
258        capset_id: RUTABAGA_CAPSET_VENUS,
259        component: RutabagaComponentType::VirglRenderer,
260        name: "venus",
261    },
262    RutabagaCapsetInfo {
263        capset_id: RUTABAGA_CAPSET_CROSS_DOMAIN,
264        component: RutabagaComponentType::CrossDomain,
265        name: "cross-domain",
266    },
267    RutabagaCapsetInfo {
268        capset_id: RUTABAGA_CAPSET_DRM,
269        component: RutabagaComponentType::VirglRenderer,
270        name: "drm",
271    },
272    RutabagaCapsetInfo {
273        capset_id: RUTABAGA_CAPSET_GFXSTREAM_MAGMA,
274        component: RutabagaComponentType::Gfxstream,
275        name: "gfxstream-magma",
276    },
277    RutabagaCapsetInfo {
278        capset_id: RUTABAGA_CAPSET_GFXSTREAM_GLES,
279        component: RutabagaComponentType::Gfxstream,
280        name: "gfxstream-gles",
281    },
282    RutabagaCapsetInfo {
283        capset_id: RUTABAGA_CAPSET_GFXSTREAM_COMPOSER,
284        component: RutabagaComponentType::Gfxstream,
285        name: "gfxstream-composer",
286    },
287];
288
289pub fn calculate_capset_mask<'a, I: Iterator<Item = &'a str>>(context_names: I) -> u64 {
290    let mut capset_mask = 0;
291    for name in context_names {
292        if let Some(capset) = RUTABAGA_CAPSETS.iter().find(|capset| capset.name == name) {
293            capset_mask |= 1 << capset.capset_id;
294        };
295    }
296
297    capset_mask
298}
299
300pub fn calculate_capset_names(capset_mask: u64) -> Vec<String> {
301    RUTABAGA_CAPSETS
302        .iter()
303        .filter(|capset| capset_mask & (1 << capset.capset_id) != 0)
304        .map(|capset| capset.name.to_string())
305        .collect()
306}
307
308fn calculate_component(component_mask: u8) -> RutabagaResult<RutabagaComponentType> {
309    if component_mask.count_ones() != 1 {
310        return Err(RutabagaError::SpecViolation("can't infer single component"));
311    }
312
313    match component_mask.trailing_zeros() {
314        0 => Ok(RutabagaComponentType::Rutabaga2D),
315        1 => Ok(RutabagaComponentType::VirglRenderer),
316        2 => Ok(RutabagaComponentType::Gfxstream),
317        3 => Ok(RutabagaComponentType::CrossDomain),
318        _ => Err(RutabagaError::InvalidComponent),
319    }
320}
321
322/// The global libary handle used to query capability sets, create resources and contexts.
323///
324/// Currently, Rutabaga only supports one default component.  Many components running at the
325/// same time is a stretch goal of Rutabaga GFX.
326///
327/// Not thread-safe, but can be made so easily.  Making non-Rutabaga, C/C++ components
328/// thread-safe is more difficult.
329pub struct Rutabaga {
330    resources: Map<u32, RutabagaResource>,
331    contexts: Map<u32, Box<dyn RutabagaContext>>,
332    // Declare components after resources and contexts such that it is dropped last.
333    components: Map<RutabagaComponentType, Box<dyn RutabagaComponent>>,
334    default_component: RutabagaComponentType,
335    capset_info: Vec<RutabagaCapsetInfo>,
336    fence_handler: RutabagaFenceHandler,
337}
338
339impl Rutabaga {
340    /// Take a snapshot of Rutabaga's current state. The snapshot is serialized into an opaque byte
341    /// stream and written to `w`.
342    ///
343    /// Only supports Mode2D.
344    pub fn snapshot(&self, w: &mut impl Write) -> RutabagaResult<()> {
345        // We current only support snapshotting Rutabaga2D.
346        if !(self.contexts.is_empty()
347            && self
348                .components
349                .keys()
350                .all(|t| *t == RutabagaComponentType::Rutabaga2D)
351            && self.default_component == RutabagaComponentType::Rutabaga2D
352            && self.capset_info.is_empty())
353        {
354            return Err(RutabagaError::Unsupported);
355        }
356        let snapshot = RutabagaSnapshot {
357            resources: self
358                .resources
359                .iter()
360                .map(|(i, r)| {
361                    if !(r.handle.is_none()
362                        && !r.blob
363                        && r.blob_mem == 0
364                        && r.blob_flags == 0
365                        && r.map_info.is_none()
366                        && r.info_3d.is_none()
367                        && r.vulkan_info.is_none()
368                        && r.component_mask == 1 << (RutabagaComponentType::Rutabaga2D as u8)
369                        && r.mapping.is_none())
370                    {
371                        return Err(RutabagaError::Unsupported);
372                    }
373                    let info = r.info_2d.as_ref().ok_or(RutabagaError::Unsupported)?;
374                    assert_eq!(
375                        usize::try_from(info.width * info.height * 4).unwrap(),
376                        info.host_mem.len()
377                    );
378                    assert_eq!(usize::try_from(r.size).unwrap(), info.host_mem.len());
379                    let s = RutabagaResourceSnapshot {
380                        resource_id: r.resource_id,
381                        width: info.width,
382                        height: info.height,
383                    };
384                    Ok((*i, s))
385                })
386                .collect::<RutabagaResult<_>>()?,
387        };
388
389        snapshot.serialize_to(w).map_err(RutabagaError::IoError)
390    }
391
392    /// Restore Rutabaga to a previously snapshot'd state.
393    ///
394    /// Snapshotting on one host machine and then restoring on another ("host migration") might
395    /// work for very similar machines but isn't explicitly supported yet.
396    ///
397    /// Rutabaga will recreate resources internally, but it's the VMM's responsibility to re-attach
398    /// backing iovecs and re-map the memory after re-creation. Specifically:
399    ///
400    /// * Mode2D
401    ///    * The VMM must call `Rutabaga::attach_backing` calls for all resources that had backing
402    ///      memory at the time of the snapshot.
403    /// * ModeVirglRenderer
404    ///    * Not supported.
405    /// * ModeGfxstream
406    ///    * Not supported.
407    ///
408    /// NOTES: This is required because the pointers to backing memory aren't stable, help from the
409    /// VMM is necessary. In an alternative approach, the VMM could supply Rutabaga with callbacks
410    /// to translate to/from stable guest physical addresses, but it is unclear how well that
411    /// approach would scale to support 3D modes, which have others problems that require VMM help,
412    /// like resource handles.
413    pub fn restore(&mut self, r: &mut impl Read) -> RutabagaResult<()> {
414        let snapshot = RutabagaSnapshot::deserialize_from(r).map_err(RutabagaError::IoError)?;
415
416        // We currently only support restoring to a fresh Rutabaga2D instance.
417        if !(self.resources.is_empty()
418            && self.contexts.is_empty()
419            && self
420                .components
421                .keys()
422                .all(|t| *t == RutabagaComponentType::Rutabaga2D)
423            && self.default_component == RutabagaComponentType::Rutabaga2D
424            && self.capset_info.is_empty())
425        {
426            return Err(RutabagaError::Unsupported);
427        }
428        self.resources = snapshot
429            .resources
430            .into_iter()
431            .map(|(i, s)| {
432                let size = u64::from(s.width * s.height * 4);
433                let r = RutabagaResource {
434                    resource_id: s.resource_id,
435                    handle: None,
436                    blob: false,
437                    blob_mem: 0,
438                    blob_flags: 0,
439                    map_info: None,
440                    info_2d: Some(Rutabaga2DInfo {
441                        width: s.width,
442                        height: s.height,
443                        host_mem: vec![0; usize::try_from(size).unwrap()],
444                    }),
445                    info_3d: None,
446                    vulkan_info: None,
447                    // NOTE: `RutabagaResource::backing_iovecs` isn't snapshotted because the
448                    // pointers won't be valid at restore time, see the `Rutabaga::restore` doc. If
449                    // the client doesn't attach new iovecs, the restored resource will behave as
450                    // if they had been detached (instead of segfaulting on the stale iovec
451                    // pointers).
452                    backing_iovecs: None,
453                    component_mask: 1 << (RutabagaComponentType::Rutabaga2D as u8),
454                    size,
455                    mapping: None,
456                };
457                (i, r)
458            })
459            .collect();
460
461        Ok(())
462    }
463
464    fn capset_id_to_component_type(&self, capset_id: u32) -> RutabagaResult<RutabagaComponentType> {
465        let component = self
466            .capset_info
467            .iter()
468            .find(|capset_info| capset_info.capset_id == capset_id)
469            .ok_or(RutabagaError::InvalidCapset)?
470            .component;
471
472        Ok(component)
473    }
474
475    fn capset_index_to_component_info(&self, index: u32) -> RutabagaResult<RutabagaCapsetInfo> {
476        let idx = index as usize;
477        if idx >= self.capset_info.len() {
478            return Err(RutabagaError::InvalidCapset);
479        }
480
481        Ok(self.capset_info[idx])
482    }
483
484    /// Gets the version and size for the capabilty set `index`.
485    pub fn get_capset_info(&self, index: u32) -> RutabagaResult<(u32, u32, u32)> {
486        let capset_info = self.capset_index_to_component_info(index)?;
487
488        let component = self
489            .components
490            .get(&capset_info.component)
491            .ok_or(RutabagaError::InvalidComponent)?;
492
493        let (capset_version, capset_size) = component.get_capset_info(capset_info.capset_id);
494        Ok((capset_info.capset_id, capset_version, capset_size))
495    }
496
497    /// Gets the capability set for the `capset_id` and `version`.
498    /// Each capability set is associated with a context type, which is associated
499    /// with a rutabaga component.
500    pub fn get_capset(&self, capset_id: u32, version: u32) -> RutabagaResult<Vec<u8>> {
501        // The default workaround is just until context types are fully supported in all
502        // Google kernels.
503        let component_type = self
504            .capset_id_to_component_type(capset_id)
505            .unwrap_or(self.default_component);
506
507        let component = self
508            .components
509            .get(&component_type)
510            .ok_or(RutabagaError::InvalidComponent)?;
511
512        Ok(component.get_capset(capset_id, version))
513    }
514
515    /// Gets the number of capsets
516    pub fn get_num_capsets(&self) -> u32 {
517        self.capset_info.len() as u32
518    }
519
520    /// Forces context zero for the default rutabaga component.
521    pub fn force_ctx_0(&self) {
522        if let Some(component) = self.components.get(&self.default_component) {
523            component.force_ctx_0();
524        }
525    }
526
527    /// Creates a fence with the given `fence`.
528    /// If the flags include RUTABAGA_FLAG_INFO_RING_IDX, then the fence is created on a
529    /// specific timeline on the specific context.
530    pub fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
531        if fence.flags & RUTABAGA_FLAG_INFO_RING_IDX != 0 {
532            let ctx = self
533                .contexts
534                .get_mut(&fence.ctx_id)
535                .ok_or(RutabagaError::InvalidContextId)?;
536
537            ctx.context_create_fence(fence)?;
538        } else {
539            let component = self
540                .components
541                .get_mut(&self.default_component)
542                .ok_or(RutabagaError::InvalidComponent)?;
543
544            component.create_fence(fence)?;
545        }
546
547        Ok(())
548    }
549
550    /// Polls the default rutabaga component.
551    pub fn event_poll(&self) {
552        if let Some(component) = self.components.get(&self.default_component) {
553            component.event_poll();
554        }
555    }
556
557    /// Returns a pollable descriptor for the default rutabaga component. In practice, it is only
558    /// not None if the default component is virglrenderer.
559    pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
560        let component = self.components.get(&self.default_component).or(None)?;
561        component.poll_descriptor()
562    }
563
564    /// Creates a resource with the `resource_create_3d` metadata.
565    pub fn resource_create_3d(
566        &mut self,
567        resource_id: u32,
568        resource_create_3d: ResourceCreate3D,
569    ) -> RutabagaResult<()> {
570        let component = self
571            .components
572            .get_mut(&self.default_component)
573            .ok_or(RutabagaError::InvalidComponent)?;
574
575        if self.resources.contains_key(&resource_id) {
576            return Err(RutabagaError::InvalidResourceId);
577        }
578
579        let resource = component.create_3d(resource_id, resource_create_3d)?;
580        self.resources.insert(resource_id, resource);
581        Ok(())
582    }
583
584    /// Attaches `vecs` to the resource.
585    pub fn attach_backing(
586        &mut self,
587        resource_id: u32,
588        mut vecs: Vec<RutabagaIovec>,
589    ) -> RutabagaResult<()> {
590        let component = self
591            .components
592            .get_mut(&self.default_component)
593            .ok_or(RutabagaError::InvalidComponent)?;
594
595        let resource = self
596            .resources
597            .get_mut(&resource_id)
598            .ok_or(RutabagaError::InvalidResourceId)?;
599
600        component.attach_backing(resource_id, &mut vecs)?;
601        resource.backing_iovecs = Some(vecs);
602        Ok(())
603    }
604
605    /// Detaches any previously attached iovecs from the resource.
606    pub fn detach_backing(&mut self, resource_id: u32) -> RutabagaResult<()> {
607        let component = self
608            .components
609            .get_mut(&self.default_component)
610            .ok_or(RutabagaError::InvalidComponent)?;
611
612        let resource = self
613            .resources
614            .get_mut(&resource_id)
615            .ok_or(RutabagaError::InvalidResourceId)?;
616
617        component.detach_backing(resource_id);
618        resource.backing_iovecs = None;
619        Ok(())
620    }
621
622    /// Releases guest kernel reference on the resource.
623    pub fn unref_resource(&mut self, resource_id: u32) -> RutabagaResult<()> {
624        let component = self
625            .components
626            .get_mut(&self.default_component)
627            .ok_or(RutabagaError::InvalidComponent)?;
628
629        self.resources
630            .remove(&resource_id)
631            .ok_or(RutabagaError::InvalidResourceId)?;
632
633        component.unref_resource(resource_id);
634        Ok(())
635    }
636
637    /// For HOST3D_GUEST resources, copies from the attached iovecs to the host resource.  For
638    /// HOST3D resources, this may flush caches, though this feature is unused by guest userspace.
639    pub fn transfer_write(
640        &mut self,
641        ctx_id: u32,
642        resource_id: u32,
643        transfer: Transfer3D,
644    ) -> RutabagaResult<()> {
645        let component = self
646            .components
647            .get(&self.default_component)
648            .ok_or(RutabagaError::InvalidComponent)?;
649
650        let resource = self
651            .resources
652            .get_mut(&resource_id)
653            .ok_or(RutabagaError::InvalidResourceId)?;
654
655        component.transfer_write(ctx_id, resource, transfer)
656    }
657
658    /// 1) If specified, copies to `buf` from the host resource.
659    /// 2) Otherwise, for HOST3D_GUEST resources, copies to the attached iovecs from the host
660    ///    resource.  For HOST3D resources, this may invalidate caches, though this feature is
661    ///    unused by guest userspace.
662    pub fn transfer_read(
663        &mut self,
664        ctx_id: u32,
665        resource_id: u32,
666        transfer: Transfer3D,
667        buf: Option<IoSliceMut>,
668    ) -> RutabagaResult<()> {
669        let component = self
670            .components
671            .get(&self.default_component)
672            .ok_or(RutabagaError::InvalidComponent)?;
673
674        let resource = self
675            .resources
676            .get_mut(&resource_id)
677            .ok_or(RutabagaError::InvalidResourceId)?;
678
679        component.transfer_read(ctx_id, resource, transfer, buf)
680    }
681
682    pub fn resource_flush(&mut self, resource_id: u32) -> RutabagaResult<()> {
683        let component = self
684            .components
685            .get(&self.default_component)
686            .ok_or(RutabagaError::Unsupported)?;
687
688        let resource = self
689            .resources
690            .get_mut(&resource_id)
691            .ok_or(RutabagaError::InvalidResourceId)?;
692
693        component.resource_flush(resource)
694    }
695
696    /// Creates a blob resource with the `ctx_id` and `resource_create_blob` metadata.
697    /// Associates `iovecs` with the resource, if there are any.  Associates externally
698    /// created `handle` with the resource, if there is any.
699    pub fn resource_create_blob(
700        &mut self,
701        ctx_id: u32,
702        resource_id: u32,
703        resource_create_blob: ResourceCreateBlob,
704        iovecs: Option<Vec<RutabagaIovec>>,
705        handle: Option<RutabagaHandle>,
706    ) -> RutabagaResult<()> {
707        if self.resources.contains_key(&resource_id) {
708            return Err(RutabagaError::InvalidResourceId);
709        }
710
711        let component = self
712            .components
713            .get_mut(&self.default_component)
714            .ok_or(RutabagaError::InvalidComponent)?;
715
716        let mut context = None;
717        // For the cross-domain context, we'll need to create the blob resource via a home-grown
718        // rutabaga context rather than one from an external C/C++ component.  Use `ctx_id` and
719        // the component type if it happens to be a cross-domain context.
720        if ctx_id > 0 {
721            let ctx = self
722                .contexts
723                .get_mut(&ctx_id)
724                .ok_or(RutabagaError::InvalidContextId)?;
725
726            if ctx.component_type() == RutabagaComponentType::CrossDomain {
727                context = Some(ctx);
728            }
729        }
730
731        let resource = match context {
732            Some(ctx) => ctx.context_create_blob(resource_id, resource_create_blob, handle)?,
733            None => {
734                component.create_blob(ctx_id, resource_id, resource_create_blob, iovecs, handle)?
735            }
736        };
737
738        self.resources.insert(resource_id, resource);
739        Ok(())
740    }
741
742    /// Returns a memory mapping of the blob resource.
743    pub fn map(&mut self, resource_id: u32) -> RutabagaResult<RutabagaMapping> {
744        let resource = self
745            .resources
746            .get_mut(&resource_id)
747            .ok_or(RutabagaError::InvalidResourceId)?;
748
749        let component_type = calculate_component(resource.component_mask)?;
750        if component_type == RutabagaComponentType::CrossDomain {
751            let handle_opt = resource.handle.take();
752            match handle_opt {
753                Some(handle) => {
754                    if handle.handle_type != RUTABAGA_MEM_HANDLE_TYPE_SHM {
755                        return Err(RutabagaError::SpecViolation(
756                            "expected a shared memory handle",
757                        ));
758                    }
759
760                    let clone = handle.try_clone()?;
761                    let resource_size: usize = resource.size.try_into()?;
762                    let map_info = resource
763                        .map_info
764                        .ok_or(RutabagaError::SpecViolation("no map info available"))?;
765
766                    // Creating the mapping closes the cloned descriptor.
767                    let mapping = MemoryMapping::from_safe_descriptor(
768                        clone.os_handle,
769                        resource_size,
770                        map_info,
771                    )?;
772                    let rutabaga_mapping = mapping.as_rutabaga_mapping();
773                    resource.handle = Some(handle);
774                    resource.mapping = Some(mapping);
775
776                    return Ok(rutabaga_mapping);
777                }
778                None => return Err(RutabagaError::SpecViolation("expected a handle to map")),
779            }
780        }
781
782        let component = self
783            .components
784            .get(&component_type)
785            .ok_or(RutabagaError::InvalidComponent)?;
786
787        component.map(resource_id)
788    }
789
790    /// Unmaps the blob resource from the default component
791    pub fn unmap(&mut self, resource_id: u32) -> RutabagaResult<()> {
792        let resource = self
793            .resources
794            .get_mut(&resource_id)
795            .ok_or(RutabagaError::InvalidResourceId)?;
796
797        let component_type = calculate_component(resource.component_mask)?;
798        if component_type == RutabagaComponentType::CrossDomain {
799            resource.mapping = None;
800            return Ok(());
801        }
802
803        let component = self
804            .components
805            .get(&component_type)
806            .ok_or(RutabagaError::InvalidComponent)?;
807
808        component.unmap(resource_id)
809    }
810
811    /// Returns the `map_info` of the blob resource. The valid values for `map_info`
812    /// are defined in the virtio-gpu spec.
813    pub fn map_info(&self, resource_id: u32) -> RutabagaResult<u32> {
814        let resource = self
815            .resources
816            .get(&resource_id)
817            .ok_or(RutabagaError::InvalidResourceId)?;
818
819        resource
820            .map_info
821            .ok_or(RutabagaError::SpecViolation("no map info available"))
822    }
823
824    /// Returns the `vulkan_info` of the blob resource, which consists of the physical device
825    /// index and memory index associated with the resource.
826    pub fn vulkan_info(&self, resource_id: u32) -> RutabagaResult<VulkanInfo> {
827        let resource = self
828            .resources
829            .get(&resource_id)
830            .ok_or(RutabagaError::InvalidResourceId)?;
831
832        resource.vulkan_info.ok_or(RutabagaError::InvalidVulkanInfo)
833    }
834
835    /// Returns the 3D info associated with the resource, if any.
836    pub fn query(&self, resource_id: u32) -> RutabagaResult<Resource3DInfo> {
837        let resource = self
838            .resources
839            .get(&resource_id)
840            .ok_or(RutabagaError::InvalidResourceId)?;
841
842        resource
843            .info_3d
844            .ok_or(RutabagaError::SpecViolation("no 3d info available"))
845    }
846
847    /// Exports a blob resource.  See virtio-gpu spec for blob flag use flags.
848    pub fn export_blob(&mut self, resource_id: u32) -> RutabagaResult<RutabagaHandle> {
849        let resource = self
850            .resources
851            .get_mut(&resource_id)
852            .ok_or(RutabagaError::InvalidResourceId)?;
853
854        // We can inspect blob flags only once guest minigbm is fully transitioned to blob.
855        let share_mask = RUTABAGA_BLOB_FLAG_USE_SHAREABLE | RUTABAGA_BLOB_FLAG_USE_CROSS_DEVICE;
856        let shareable = (resource.blob_flags & share_mask != 0) || !resource.blob;
857
858        let opt = resource.handle.take();
859
860        match (opt, shareable) {
861            (Some(handle), true) => {
862                let clone = handle.try_clone()?;
863                resource.handle = Some(handle);
864                Ok(clone)
865            }
866            (Some(handle), false) => {
867                // Exactly one strong reference in this case.
868                let hnd =
869                    Arc::try_unwrap(handle).map_err(|_| RutabagaError::InvalidRutabagaHandle)?;
870                Ok(hnd)
871            }
872            _ => Err(RutabagaError::InvalidRutabagaHandle),
873        }
874    }
875
876    /// Exports the given fence for import into other processes.
877    pub fn export_fence(&self, fence_id: u64) -> RutabagaResult<RutabagaHandle> {
878        let component = self
879            .components
880            .get(&self.default_component)
881            .ok_or(RutabagaError::InvalidComponent)?;
882
883        component.export_fence(fence_id)
884    }
885
886    /// Creates a context with the given `ctx_id` and `context_init` variable.
887    /// `context_init` is used to determine which rutabaga component creates the context.
888    pub fn create_context(
889        &mut self,
890        ctx_id: u32,
891        context_init: u32,
892        context_name: Option<&str>,
893    ) -> RutabagaResult<()> {
894        // The default workaround is just until context types are fully supported in all
895        // Google kernels.
896        let capset_id = context_init & RUTABAGA_CONTEXT_INIT_CAPSET_ID_MASK;
897        let component_type = self
898            .capset_id_to_component_type(capset_id)
899            .unwrap_or(self.default_component);
900
901        let component = self
902            .components
903            .get_mut(&component_type)
904            .ok_or(RutabagaError::InvalidComponent)?;
905
906        if self.contexts.contains_key(&ctx_id) {
907            return Err(RutabagaError::InvalidContextId);
908        }
909
910        let ctx = component.create_context(
911            ctx_id,
912            context_init,
913            context_name,
914            self.fence_handler.clone(),
915        )?;
916        self.contexts.insert(ctx_id, ctx);
917        Ok(())
918    }
919
920    /// Destroys the context given by `ctx_id`.
921    pub fn destroy_context(&mut self, ctx_id: u32) -> RutabagaResult<()> {
922        self.contexts
923            .remove(&ctx_id)
924            .ok_or(RutabagaError::InvalidContextId)?;
925        Ok(())
926    }
927
928    /// Attaches the resource given by `resource_id` to the context given by `ctx_id`.
929    pub fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> RutabagaResult<()> {
930        let ctx = self
931            .contexts
932            .get_mut(&ctx_id)
933            .ok_or(RutabagaError::InvalidContextId)?;
934
935        let resource = self
936            .resources
937            .get_mut(&resource_id)
938            .ok_or(RutabagaError::InvalidResourceId)?;
939
940        ctx.attach(resource);
941        Ok(())
942    }
943
944    /// Detaches the resource given by `resource_id` from the context given by `ctx_id`.
945    pub fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> RutabagaResult<()> {
946        let ctx = self
947            .contexts
948            .get_mut(&ctx_id)
949            .ok_or(RutabagaError::InvalidContextId)?;
950
951        let resource = self
952            .resources
953            .get_mut(&resource_id)
954            .ok_or(RutabagaError::InvalidResourceId)?;
955
956        ctx.detach(resource);
957        Ok(())
958    }
959
960    /// submits `commands` to the context given by `ctx_id`.
961    pub fn submit_command(
962        &mut self,
963        ctx_id: u32,
964        commands: &mut [u8],
965        fence_ids: &[u64],
966    ) -> RutabagaResult<()> {
967        let ctx = self
968            .contexts
969            .get_mut(&ctx_id)
970            .ok_or(RutabagaError::InvalidContextId)?;
971
972        ctx.submit_cmd(commands, fence_ids)
973    }
974}
975
976/// Rutabaga Builder, following the Rust builder pattern.
977#[derive(Clone)]
978pub struct RutabagaBuilder {
979    display_width: u32,
980    display_height: u32,
981    default_component: RutabagaComponentType,
982    gfxstream_flags: GfxstreamFlags,
983    virglrenderer_flags: VirglRendererFlags,
984    capset_mask: u64,
985    channels: Option<Vec<RutabagaChannel>>,
986    debug_handler: Option<RutabagaDebugHandler>,
987}
988
989impl RutabagaBuilder {
990    /// Create new a RutabagaBuilder.
991    pub fn new(default_component: RutabagaComponentType, capset_mask: u64) -> RutabagaBuilder {
992        let virglrenderer_flags = VirglRendererFlags::new()
993            .use_thread_sync(true)
994            .use_async_fence_cb(true);
995        let gfxstream_flags = GfxstreamFlags::new();
996        RutabagaBuilder {
997            display_width: RUTABAGA_DEFAULT_WIDTH,
998            display_height: RUTABAGA_DEFAULT_HEIGHT,
999            default_component,
1000            gfxstream_flags,
1001            virglrenderer_flags,
1002            capset_mask,
1003            channels: None,
1004            debug_handler: None,
1005        }
1006    }
1007
1008    /// Set display width for the RutabagaBuilder
1009    pub fn set_display_width(mut self, display_width: u32) -> RutabagaBuilder {
1010        self.display_width = display_width;
1011        self
1012    }
1013
1014    /// Set display height for the RutabagaBuilder
1015    pub fn set_display_height(mut self, display_height: u32) -> RutabagaBuilder {
1016        self.display_height = display_height;
1017        self
1018    }
1019
1020    /// Sets use EGL flags in gfxstream + virglrenderer.
1021    pub fn set_use_egl(mut self, v: bool) -> RutabagaBuilder {
1022        self.gfxstream_flags = self.gfxstream_flags.use_egl(v);
1023        self.virglrenderer_flags = self.virglrenderer_flags.use_egl(v);
1024        self
1025    }
1026
1027    /// Sets use GLES in gfxstream + virglrenderer.
1028    pub fn set_use_gles(mut self, v: bool) -> RutabagaBuilder {
1029        self.gfxstream_flags = self.gfxstream_flags.use_gles(v);
1030        self.virglrenderer_flags = self.virglrenderer_flags.use_gles(v);
1031        self
1032    }
1033
1034    /// Sets use GLX flags in gfxstream + virglrenderer.
1035    pub fn set_use_glx(mut self, v: bool) -> RutabagaBuilder {
1036        self.gfxstream_flags = self.gfxstream_flags.use_glx(v);
1037        self.virglrenderer_flags = self.virglrenderer_flags.use_glx(v);
1038        self
1039    }
1040
1041    /// Sets use surfaceless flags in gfxstream + virglrenderer.
1042    pub fn set_use_surfaceless(mut self, v: bool) -> RutabagaBuilder {
1043        self.gfxstream_flags = self.gfxstream_flags.use_surfaceless(v);
1044        self.virglrenderer_flags = self.virglrenderer_flags.use_surfaceless(v);
1045        self
1046    }
1047
1048    /// Sets use Vulkan in gfxstream + virglrenderer.
1049    pub fn set_use_vulkan(mut self, v: bool) -> RutabagaBuilder {
1050        self.gfxstream_flags = self.gfxstream_flags.use_vulkan(v);
1051        self.virglrenderer_flags = self.virglrenderer_flags.use_venus(v);
1052        self
1053    }
1054
1055    /// Sets use external blob in gfxstream + virglrenderer.
1056    pub fn set_use_external_blob(mut self, v: bool) -> RutabagaBuilder {
1057        self.gfxstream_flags = self.gfxstream_flags.use_external_blob(v);
1058        self.virglrenderer_flags = self.virglrenderer_flags.use_external_blob(v);
1059        self
1060    }
1061
1062    /// Sets use system blob in gfxstream.
1063    pub fn set_use_system_blob(mut self, v: bool) -> RutabagaBuilder {
1064        self.gfxstream_flags = self.gfxstream_flags.use_system_blob(v);
1065        self
1066    }
1067
1068    /// Sets use render server in virglrenderer.
1069    pub fn set_use_render_server(mut self, v: bool) -> RutabagaBuilder {
1070        self.virglrenderer_flags = self.virglrenderer_flags.use_render_server(v);
1071        self
1072    }
1073
1074    /// Use the Vulkan swapchain to draw on the host window for gfxstream.
1075    pub fn set_wsi(mut self, v: RutabagaWsi) -> RutabagaBuilder {
1076        self.gfxstream_flags = self.gfxstream_flags.set_wsi(v);
1077        self
1078    }
1079
1080    /// Set rutabaga channels for the RutabagaBuilder
1081    pub fn set_rutabaga_channels(
1082        mut self,
1083        channels: Option<Vec<RutabagaChannel>>,
1084    ) -> RutabagaBuilder {
1085        self.channels = channels;
1086        self
1087    }
1088
1089    /// Set rutabaga channels for the RutabagaBuilder
1090    pub fn set_debug_handler(
1091        mut self,
1092        debug_handler: Option<RutabagaDebugHandler>,
1093    ) -> RutabagaBuilder {
1094        self.debug_handler = debug_handler;
1095        self
1096    }
1097
1098    /// Builds Rutabaga and returns a handle to it.
1099    ///
1100    /// This should be only called once per every virtual machine instance.  Rutabaga tries to
1101    /// intialize all 3D components which have been built. In 2D mode, only the 2D component is
1102    /// initialized.
1103    pub fn build(
1104        mut self,
1105        fence_handler: RutabagaFenceHandler,
1106        #[allow(unused_variables)] rutabaga_server_descriptor: Option<SafeDescriptor>,
1107    ) -> RutabagaResult<Rutabaga> {
1108        let mut rutabaga_components: Map<RutabagaComponentType, Box<dyn RutabagaComponent>> =
1109            Default::default();
1110
1111        #[allow(unused_mut)]
1112        let mut rutabaga_capsets: Vec<RutabagaCapsetInfo> = Default::default();
1113
1114        let capset_enabled =
1115            |capset_id: u32| -> bool { (self.capset_mask & (1 << capset_id)) != 0 };
1116
1117        let mut push_capset = |capset_id: u32| {
1118            if let Some(capset) = RUTABAGA_CAPSETS
1119                .iter()
1120                .find(|capset| capset_id == capset.capset_id)
1121            {
1122                if self.capset_mask != 0 {
1123                    if capset_enabled(capset.capset_id) {
1124                        rutabaga_capsets.push(*capset);
1125                    }
1126                } else {
1127                    // Unconditionally push capset -- this should eventually be deleted when context types are
1128                    // always specified by crosvm launchers.
1129                    rutabaga_capsets.push(*capset);
1130                }
1131            };
1132        };
1133
1134        if self.capset_mask != 0 {
1135            let supports_gfxstream = capset_enabled(RUTABAGA_CAPSET_GFXSTREAM_VULKAN)
1136                | capset_enabled(RUTABAGA_CAPSET_GFXSTREAM_MAGMA)
1137                | capset_enabled(RUTABAGA_CAPSET_GFXSTREAM_GLES)
1138                | capset_enabled(RUTABAGA_CAPSET_GFXSTREAM_COMPOSER);
1139            let supports_virglrenderer = capset_enabled(RUTABAGA_CAPSET_VIRGL2)
1140                | capset_enabled(RUTABAGA_CAPSET_VENUS)
1141                | capset_enabled(RUTABAGA_CAPSET_DRM);
1142
1143            if supports_gfxstream {
1144                self.default_component = RutabagaComponentType::Gfxstream;
1145            } else if supports_virglrenderer {
1146                self.default_component = RutabagaComponentType::VirglRenderer;
1147            } else {
1148                self.default_component = RutabagaComponentType::CrossDomain;
1149            }
1150
1151            self.virglrenderer_flags = self
1152                .virglrenderer_flags
1153                .use_virgl(capset_enabled(RUTABAGA_CAPSET_VIRGL2))
1154                .use_venus(capset_enabled(RUTABAGA_CAPSET_VENUS))
1155                .use_drm(capset_enabled(RUTABAGA_CAPSET_DRM));
1156
1157            self.gfxstream_flags = self
1158                .gfxstream_flags
1159                .use_gles(capset_enabled(RUTABAGA_CAPSET_GFXSTREAM_GLES))
1160                .use_vulkan(capset_enabled(RUTABAGA_CAPSET_GFXSTREAM_VULKAN))
1161        }
1162
1163        // Make sure that disabled components are not used as default.
1164        #[cfg(not(feature = "virgl_renderer"))]
1165        if self.default_component == RutabagaComponentType::VirglRenderer {
1166            return Err(RutabagaError::InvalidRutabagaBuild(
1167                "virgl renderer feature not enabled",
1168            ));
1169        }
1170        #[cfg(not(feature = "gfxstream"))]
1171        if self.default_component == RutabagaComponentType::Gfxstream {
1172            return Err(RutabagaError::InvalidRutabagaBuild(
1173                "gfxstream feature not enabled",
1174            ));
1175        }
1176
1177        if self.default_component == RutabagaComponentType::Rutabaga2D {
1178            let rutabaga_2d = Rutabaga2D::init(fence_handler.clone())?;
1179            rutabaga_components.insert(RutabagaComponentType::Rutabaga2D, rutabaga_2d);
1180        } else {
1181            #[cfg(feature = "virgl_renderer")]
1182            if self.default_component == RutabagaComponentType::VirglRenderer {
1183                let virgl = VirglRenderer::init(
1184                    self.virglrenderer_flags,
1185                    fence_handler.clone(),
1186                    rutabaga_server_descriptor,
1187                )?;
1188                rutabaga_components.insert(RutabagaComponentType::VirglRenderer, virgl);
1189
1190                push_capset(RUTABAGA_CAPSET_VIRGL);
1191                push_capset(RUTABAGA_CAPSET_VIRGL2);
1192                push_capset(RUTABAGA_CAPSET_VENUS);
1193                push_capset(RUTABAGA_CAPSET_DRM);
1194            }
1195
1196            #[cfg(feature = "gfxstream")]
1197            if self.default_component == RutabagaComponentType::Gfxstream {
1198                let gfxstream = Gfxstream::init(
1199                    self.display_width,
1200                    self.display_height,
1201                    self.gfxstream_flags,
1202                    fence_handler.clone(),
1203                    self.debug_handler.clone(),
1204                )?;
1205
1206                rutabaga_components.insert(RutabagaComponentType::Gfxstream, gfxstream);
1207
1208                push_capset(RUTABAGA_CAPSET_GFXSTREAM_VULKAN);
1209                push_capset(RUTABAGA_CAPSET_GFXSTREAM_MAGMA);
1210                push_capset(RUTABAGA_CAPSET_GFXSTREAM_GLES);
1211                push_capset(RUTABAGA_CAPSET_GFXSTREAM_COMPOSER);
1212            }
1213
1214            let cross_domain = CrossDomain::init(self.channels, fence_handler.clone())?;
1215            rutabaga_components.insert(RutabagaComponentType::CrossDomain, cross_domain);
1216            push_capset(RUTABAGA_CAPSET_CROSS_DOMAIN);
1217        }
1218
1219        Ok(Rutabaga {
1220            resources: Default::default(),
1221            contexts: Default::default(),
1222            components: rutabaga_components,
1223            default_component: self.default_component,
1224            capset_info: rutabaga_capsets,
1225            fence_handler,
1226        })
1227    }
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232    use crate::*;
1233
1234    fn new_2d() -> Rutabaga {
1235        RutabagaBuilder::new(RutabagaComponentType::Rutabaga2D, 0)
1236            .build(RutabagaHandler::new(|_| {}), None)
1237            .unwrap()
1238    }
1239
1240    #[test]
1241    fn snapshot_restore_2d_no_resources() {
1242        let mut buffer = std::io::Cursor::new(Vec::new());
1243
1244        let rutabaga1 = new_2d();
1245        rutabaga1.snapshot(&mut buffer).unwrap();
1246
1247        let mut rutabaga1 = new_2d();
1248        rutabaga1.restore(&mut &buffer.get_ref()[..]).unwrap();
1249    }
1250
1251    #[test]
1252    fn snapshot_restore_2d_one_resource() {
1253        let resource_id = 123;
1254        let resource_create_3d = ResourceCreate3D {
1255            target: RUTABAGA_PIPE_TEXTURE_2D,
1256            format: 1,
1257            bind: RUTABAGA_PIPE_BIND_RENDER_TARGET,
1258            width: 100,
1259            height: 200,
1260            depth: 1,
1261            array_size: 1,
1262            last_level: 0,
1263            nr_samples: 0,
1264            flags: 0,
1265        };
1266
1267        let mut buffer = std::io::Cursor::new(Vec::new());
1268
1269        let mut rutabaga1 = new_2d();
1270        rutabaga1
1271            .resource_create_3d(resource_id, resource_create_3d)
1272            .unwrap();
1273        rutabaga1
1274            .attach_backing(
1275                resource_id,
1276                vec![RutabagaIovec {
1277                    base: std::ptr::null_mut(),
1278                    len: 456,
1279                }],
1280            )
1281            .unwrap();
1282        rutabaga1.snapshot(&mut buffer).unwrap();
1283
1284        let mut rutabaga2 = new_2d();
1285        rutabaga2.restore(&mut &buffer.get_ref()[..]).unwrap();
1286
1287        assert_eq!(rutabaga2.resources.len(), 1);
1288        let rutabaga_resource = rutabaga2.resources.get(&resource_id).unwrap();
1289        assert_eq!(rutabaga_resource.resource_id, resource_id);
1290        assert_eq!(
1291            rutabaga_resource.info_2d.as_ref().unwrap().width,
1292            resource_create_3d.width
1293        );
1294        assert_eq!(
1295            rutabaga_resource.info_2d.as_ref().unwrap().height,
1296            resource_create_3d.height
1297        );
1298        // NOTE: We attached an backing iovec, but it should be gone post-restore.
1299        assert!(rutabaga_resource.backing_iovecs.is_none());
1300    }
1301}