From 8bdc664f3e74c7e3f29cc6635437c5e52c8cce35 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 23 Jul 2026 14:11:25 -0700 Subject: [PATCH] Load libva at runtime via dlopen instead of linking Switch the bindgen bindings to dynamic-loading mode so libva is dlopen'd at runtime instead of linked at build time. The build no longer depends on libva at all (only libclang plus the vendored headers), and the resulting binary carries no NEEDED libva, so it loads on a libva-less host and the caller can fall back to another encoder. Previously build.rs probed libva/libva-drm via pkg-config whenever the target OS was Linux, which required libva-dev at build time and made the binary hard-link libva.so.2. It also broke the docs.rs build, since docs.rs builds on Linux with no libva-dev installed. - bindgen_gen.rs: dynamic_library_name("Va") + dynamic_link_require_all(false) - build.rs: drop the pkg-config link step - src/bindings.rs: OnceLock-cached load()/va() over libloading. Load libva-drm.so.2, whose NEEDED libva.so.2 exposes the core va* symbols through dlsym's dependency search. - Display::open_drm_display loads libva up front and returns a new LibvaUnavailable error, so a libva-less host falls back gracefully instead of aborting at process load. - rewrite the 40 va* call sites through the loaded accessor - Cargo.toml: add libloading, drop the pkg-config build-dep Tested on an aarch64 Linux host with no libva installed: the crate builds with rust + clang only, the probe binary has no NEEDED libva, and Display::open() returns None gracefully. Installing the libva runtime libs advances past load() (dlopen resolves) and still falls back cleanly. Real VAAPI hardware encode is unverified (needs an x86 box with a driver). Co-Authored-By: Claude Opus 4.8 --- Cargo.toml | 12 +++++++----- bindgen_gen.rs | 7 +++++++ build.rs | 20 +++++--------------- src/bindings.rs | 27 +++++++++++++++++++++++++++ src/buffer.rs | 11 ++++++----- src/config.rs | 8 ++++---- src/context.rs | 4 ++-- src/display.rs | 33 ++++++++++++++++++++++----------- src/image.rs | 18 +++++++++--------- src/lib.rs | 2 +- src/picture.rs | 11 +++++------ src/surface.rs | 12 ++++++------ 12 files changed, 101 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9db4ea7..ef49fc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,18 +16,20 @@ exclude = ["flake.nix", "flake.lock", "justfile", "deny.toml", "rust-toolchain.t # bindgen reads the vendored libva headers checked into `libva/` (refreshed by # `just vendor`, pinned to a known VA-API version so a system libva bump can't -# drift the bindings); libva itself is linked on Linux via pkg-config (VA-API's -# ABI is stable, so a newer system libva links fine against the pinned bindings). -# The rlib build needs only libclang + the vendored headers, so it compiles on any -# OS (e.g. macOS) for development; the libva link only happens on Linux. +# drift the bindings). libva is not linked: bindgen's dynamic-loading mode emits a +# `Va` struct that dlopen's libva.so at runtime (see bindgen_gen.rs), so the build +# needs only libclang + the vendored headers, the binary carries no NEEDED libva +# (a libva-less host loads and lets the caller fall back), and the crate builds on +# any OS (macOS, docs.rs) without libva installed. [dependencies] anyhow = "1" thiserror = "1" bitflags = "2.5" log = { version = "0", features = ["release_max_level_debug"] } +# Runtime dlopen of libva; the bindgen `Va` struct is built on libloading::Library. +libloading = "0.8" [build-dependencies] bindgen = "0.70.1" -pkg-config = "0.3.31" regex = "1.11.1" diff --git a/bindgen_gen.rs b/bindgen_gen.rs index 25aa298..5a5003e 100644 --- a/bindgen_gen.rs +++ b/bindgen_gen.rs @@ -15,4 +15,11 @@ pub fn vaapi_gen_builder(builder: bindgen::Builder) -> bindgen::Builder { .allowlist_var("VA.*") .allowlist_function("va.*") .allowlist_type(ALLOW_LIST_TYPE) + // Emit a `Va` struct of libloading-resolved function pointers instead of + // link-time `extern "C"` decls, so libva is dlopen'd at runtime (no + // build-time or load-time libva dependency). `require_all(false)` lets a + // system libva older than the vendored headers still load: a missing + // symbol only errors if actually called, not at `Va::new`. + .dynamic_library_name("Va") + .dynamic_link_require_all(false) } diff --git a/build.rs b/build.rs index 5acf38e..e4154db 100644 --- a/build.rs +++ b/build.rs @@ -122,21 +122,11 @@ fn main() { println!("cargo::rustc-cfg=libva_1_10_or_higher"); } - // Bindings are generated from the vendored headers (pinned, drift-proof), but - // the va* symbols still have to resolve, so link the system libva on Linux (the - // only platform with VA-API). VA-API's ABI is stable, so a system libva newer - // than the vendored headers links fine. On other hosts (e.g. macOS) this crate - // only ever builds as an rlib, which leaves the symbols for the final binary, so - // skip linking and keep it compilable for development. - if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") { - pkg_config::Config::new() - .probe("libva") - .expect("libva not found via pkg-config (install libva / libva-dev)"); - pkg_config::Config::new() - .probe("libva-drm") - .expect("libva-drm not found via pkg-config (install libva / libva-dev)"); - } - + // No link step: bindgen emits a `Va` struct that dlopen's libva at runtime + // (see bindgen_gen.rs and src/bindings.rs). So the build needs only libclang + // plus the vendored headers, and the binary carries no NEEDED libva, matching + // how the NVENC backend loads its driver. This is what lets docs.rs (and any + // libva-less Linux host) build the crate. let bindings = vaapi_gen_builder(bindgen::builder()) .header(WRAPPER_PATH) .clang_arg(format!("-I{}", va_h_path.display())) diff --git a/src/bindings.rs b/src/bindings.rs index 71f0283..9703763 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -16,3 +16,30 @@ #![allow(rustdoc::all)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +use std::sync::OnceLock; + +/// The dlopen'd libva symbol table, resolved once on first use. The result is +/// cached (success or failure) so a missing libva is probed at most once. +static VA: OnceLock> = OnceLock::new(); + +/// Loads libva (via `libva-drm.so.2`) on first call and returns the symbol table. +/// +/// `libva-drm.so.2` is linked against `libva.so.2`, so a single dlopen exposes +/// both `vaGetDisplayDRM` and the core `va*` symbols (dlsym on the handle searches +/// its dependencies). Returns `Err` if the library is absent, so a caller on a +/// libva-less host can fall back to another encoder instead of aborting. +pub(crate) fn load() -> Result<&'static Va, &'static str> { + VA.get_or_init(|| unsafe { Va::new("libva-drm.so.2") }.map_err(|e| e.to_string())) + .as_ref() + .map_err(String::as_str) +} + +/// Accessor for the loaded libva symbols. +/// +/// Panics if libva has not loaded. Every entry point that reaches libva goes +/// through a [`crate::Display`], whose constructor calls [`load`] and bails out on +/// failure, so by the time any other `va*` call runs the library is present. +pub(crate) fn va() -> &'static Va { + load().expect("libva not loaded; open a Display first") +} diff --git a/src/buffer.rs b/src/buffer.rs index 76ff2f6..c147fa1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -308,7 +308,7 @@ impl Buffer { // be correct, as `ptr` is just a cast to `*c_void` from a Rust struct, and `size` is // computed from `std::mem::size_of_val`. va_check(unsafe { - bindings::vaCreateBuffer( + bindings::va().vaCreateBuffer( context.display().handle(), context.id(), type_.inner(), @@ -333,7 +333,7 @@ impl Drop for Buffer { fn drop(&mut self) { // Safe because `self` represents a valid buffer, created with // vaCreateBuffers. - let status = va_check(unsafe { bindings::vaDestroyBuffer(self.context.display().handle(), self.id) }); + let status = va_check(unsafe { bindings::va().vaDestroyBuffer(self.context.display().handle(), self.id) }); if status.is_err() { error!("vaDestroyBuffer failed: {}", status.unwrap_err()); @@ -565,7 +565,7 @@ impl<'p> MappedCodedBuffer<'p> { let mut addr = std::ptr::null_mut(); let mut segments = Vec::new(); - va_check(unsafe { bindings::vaMapBuffer(buffer.0.context.display().handle(), buffer.id(), &mut addr) })?; + va_check(unsafe { bindings::va().vaMapBuffer(buffer.0.context.display().handle(), buffer.id(), &mut addr) })?; while !addr.is_null() { let segment: &bindings::VACodedBufferSegment = unsafe { &*(addr as *const bindings::VACodedBufferSegment) }; @@ -600,8 +600,9 @@ impl<'p> MappedCodedBuffer<'p> { impl<'p> Drop for MappedCodedBuffer<'p> { fn drop(&mut self) { - let status = - va_check(unsafe { bindings::vaUnmapBuffer(self.buffer.0.context.display().handle(), self.buffer.id()) }); + let status = va_check(unsafe { + bindings::va().vaUnmapBuffer(self.buffer.0.context.display().handle(), self.buffer.id()) + }); if status.is_err() { error!("vaUnmapBuffer failed: {}", status.unwrap_err()); diff --git a/src/config.rs b/src/config.rs index c67fb56..86e49c6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,7 +44,7 @@ impl Config { // The `attrs` vector is also properly initialized and its actual size is passed to // `vaCreateConfig`, so it is impossible to write past the end of its storage by mistake. va_check(unsafe { - bindings::vaCreateConfig( + bindings::va().vaCreateConfig( display.handle(), profile, entrypoint, @@ -73,7 +73,7 @@ impl Config { // call to `vaQuerySurfaceAttributes`. let attrs_len: std::os::raw::c_uint = 0; va_check(unsafe { - bindings::vaQuerySurfaceAttributes( + bindings::va().vaQuerySurfaceAttributes( self.display.handle(), self.id, std::ptr::null_mut(), @@ -86,7 +86,7 @@ impl Config { // returned by the initial call to vaQuerySurfaceAttributes. We then // pass a valid pointer to it. va_check(unsafe { - bindings::vaQuerySurfaceAttributes( + bindings::va().vaQuerySurfaceAttributes( self.display.handle(), self.id, attrs.as_mut_ptr(), @@ -122,7 +122,7 @@ impl Config { impl Drop for Config { fn drop(&mut self) { // Safe because `self` represents a valid Config. - let status = va_check(unsafe { bindings::vaDestroyConfig(self.display.handle(), self.id) }); + let status = va_check(unsafe { bindings::va().vaDestroyConfig(self.display.handle(), self.id) }); if status.is_err() { error!("vaDestroyConfig failed: {}", status.unwrap_err()); diff --git a/src/context.rs b/src/context.rs index 55d268d..f1a0b12 100644 --- a/src/context.rs +++ b/src/context.rs @@ -51,7 +51,7 @@ impl Context { // and ntargets are properly initialized. Note that render_targets==NULL // is valid so long as ntargets==0. va_check(unsafe { - bindings::vaCreateContext( + bindings::va().vaCreateContext( display.handle(), config.id(), coded_width as i32, @@ -93,7 +93,7 @@ impl Context { impl Drop for Context { fn drop(&mut self) { // Safe because `self` represents a valid VAContext. - let status = va_check(unsafe { bindings::vaDestroyContext(self.display.handle(), self.id) }); + let status = va_check(unsafe { bindings::va().vaDestroyContext(self.display.handle(), self.id) }); if status.is_err() { error!("vaDestroyContext failed: {}", status.unwrap_err()); diff --git a/src/display.rs b/src/display.rs index ef2eb55..64c4c6c 100644 --- a/src/display.rs +++ b/src/display.rs @@ -81,6 +81,8 @@ pub struct Display { /// Error type for `Display::open_drm_display`. #[derive(Debug, Error)] pub enum OpenDrmDisplayError { + #[error("libva could not be loaded: {0}")] + LibvaUnavailable(&'static str), #[error("cannot open DRM device: {0}")] DeviceOpen(io::Error), #[error("vaGetDisplayDRM returned NULL")] @@ -94,6 +96,10 @@ impl Display { /// /// `path` is the path to a DRM device that supports VAAPI, e.g. `/dev/dri/renderD128`. pub fn open_drm_display>(path: P) -> Result, OpenDrmDisplayError> { + // dlopen libva up front so a libva-less host fails here (and the caller + // falls back) instead of panicking on the first va* call below. + bindings::load().map_err(OpenDrmDisplayError::LibvaUnavailable)?; + let file = std::fs::File::options() .read(true) .write(true) @@ -102,7 +108,7 @@ impl Display { // Safe because fd represents a valid file descriptor and the pointer is checked for // NULL afterwards. - let display = unsafe { bindings::vaGetDisplayDRM(file.as_raw_fd()) }; + let display = unsafe { bindings::va().vaGetDisplayDRM(file.as_raw_fd()) }; if display.is_null() { return Err(OpenDrmDisplayError::VaGetDisplayDrm); } @@ -111,7 +117,7 @@ impl Display { let mut minor = 0i32; // Safe because we ensure that the display is valid (i.e not NULL) before calling // vaInitialize. The File will close the DRM fd on drop. - va_check(unsafe { bindings::vaInitialize(display, &mut major, &mut minor) }) + va_check(unsafe { bindings::va().vaInitialize(display, &mut major, &mut minor) }) .map(|()| { Arc::new(Self { handle: display, @@ -146,13 +152,13 @@ impl Display { /// Queries supported profiles by this display by wrapping `vaQueryConfigProfiles`. pub fn query_config_profiles(&self) -> Result, VaError> { // Safe because `self` represents a valid VADisplay. - let mut max_num_profiles = unsafe { bindings::vaMaxNumProfiles(self.handle) }; + let mut max_num_profiles = unsafe { bindings::va().vaMaxNumProfiles(self.handle) }; let mut profiles = Vec::with_capacity(max_num_profiles as usize); // Safe because `self` represents a valid `VADisplay` and the vector has `max_num_profiles` // as capacity. va_check(unsafe { - bindings::vaQueryConfigProfiles(self.handle, profiles.as_mut_ptr(), &mut max_num_profiles) + bindings::va().vaQueryConfigProfiles(self.handle, profiles.as_mut_ptr(), &mut max_num_profiles) })?; // Safe because `profiles` is allocated with a `max_num_profiles` capacity and @@ -172,7 +178,7 @@ impl Display { /// 2.0.0.32L.0005`. pub fn query_vendor_string(&self) -> std::result::Result { // Safe because `self` represents a valid VADisplay. - let vendor_string = unsafe { bindings::vaQueryVendorString(self.handle) }; + let vendor_string = unsafe { bindings::va().vaQueryVendorString(self.handle) }; if vendor_string.is_null() { return Err("vaQueryVendorString() returned NULL"); @@ -188,13 +194,18 @@ impl Display { profile: bindings::VAProfile::Type, ) -> Result, VaError> { // Safe because `self` represents a valid VADisplay. - let mut max_num_entrypoints = unsafe { bindings::vaMaxNumEntrypoints(self.handle) }; + let mut max_num_entrypoints = unsafe { bindings::va().vaMaxNumEntrypoints(self.handle) }; let mut entrypoints = Vec::with_capacity(max_num_entrypoints as usize); // Safe because `self` represents a valid VADisplay and the vector has `max_num_entrypoints` // as capacity. va_check(unsafe { - bindings::vaQueryConfigEntrypoints(self.handle, profile, entrypoints.as_mut_ptr(), &mut max_num_entrypoints) + bindings::va().vaQueryConfigEntrypoints( + self.handle, + profile, + entrypoints.as_mut_ptr(), + &mut max_num_entrypoints, + ) })?; // Safe because `entrypoints` is allocated with a `max_num_entrypoints` capacity, and @@ -221,7 +232,7 @@ impl Display { // Safe because `self` represents a valid VADisplay. The slice length is passed to the C // function, so it is impossible to write past the end of the slice's storage by mistake. va_check(unsafe { - bindings::vaGetConfigAttributes( + bindings::va().vaGetConfigAttributes( self.handle, profile, entrypoint, @@ -317,14 +328,14 @@ impl Display { /// Returns available image formats for this display by wrapping around `vaQueryImageFormats`. pub fn query_image_formats(&self) -> Result, VaError> { // Safe because `self` represents a valid VADisplay. - let mut num_image_formats = unsafe { bindings::vaMaxNumImageFormats(self.handle) }; + let mut num_image_formats = unsafe { bindings::va().vaMaxNumImageFormats(self.handle) }; let mut image_formats = Vec::with_capacity(num_image_formats as usize); // Safe because `self` represents a valid VADisplay. The `image_formats` vector is properly // initialized and a valid size is passed to the C function, so it is impossible to write // past the end of their storage by mistake. va_check(unsafe { - bindings::vaQueryImageFormats(self.handle, image_formats.as_mut_ptr(), &mut num_image_formats) + bindings::va().vaQueryImageFormats(self.handle, image_formats.as_mut_ptr(), &mut num_image_formats) })?; // Safe because the C function will have written exactly `num_image_format` entries, which @@ -341,7 +352,7 @@ impl Drop for Display { fn drop(&mut self) { // Safe because `self` represents a valid VADisplay. unsafe { - bindings::vaTerminate(self.handle); + bindings::va().vaTerminate(self.handle); // The File will close the DRM fd on drop. } } diff --git a/src/image.rs b/src/image.rs index 6e94db7..3e1f1ae 100644 --- a/src/image.rs +++ b/src/image.rs @@ -53,7 +53,7 @@ impl<'a> Image<'a> { // Safe since `picture.inner.context` represents a valid `VAContext` and `image` has been // successfully created at this point. - match va_check(unsafe { bindings::vaMapBuffer(surface.display().handle(), image.buf, &mut addr) }) { + match va_check(unsafe { bindings::va().vaMapBuffer(surface.display().handle(), image.buf, &mut addr) }) { Ok(_) => { // Assert that libva provided us with a coded resolution that is // at least as large as `display_resolution`. @@ -78,7 +78,7 @@ impl<'a> Image<'a> { // Safe because `picture.inner.context` represents a valid `VAContext` and `image` // represents a valid `VAImage`. unsafe { - bindings::vaDestroyImage(surface.display().handle(), image.image_id); + bindings::va().vaDestroyImage(surface.display().handle(), image.image_id); } Err(e) @@ -100,7 +100,7 @@ impl<'a> Image<'a> { let mut image: bindings::VAImage = Default::default(); // Safe because `self` has a valid display handle and ID. - va_check(unsafe { bindings::vaDeriveImage(surface.display().handle(), surface.id(), &mut image) })?; + va_check(unsafe { bindings::va().vaDeriveImage(surface.display().handle(), surface.id(), &mut image) })?; Self::new(surface, image, true, visible_rect) } @@ -124,7 +124,7 @@ impl<'a> Image<'a> { // Safe because `dpy` is a valid display handle. va_check(unsafe { - bindings::vaCreateImage( + bindings::va().vaCreateImage( dpy, &mut format, coded_resolution.0 as i32, @@ -136,7 +136,7 @@ impl<'a> Image<'a> { // Safe because `dpy` is a valid display handle, `picture.surface` is a valid VASurface and // `image` is a valid `VAImage`. match va_check(unsafe { - bindings::vaGetImage( + bindings::va().vaGetImage( dpy, surface.id(), 0, @@ -151,7 +151,7 @@ impl<'a> Image<'a> { Err(e) => { // Safe because `image` is a valid `VAImage`. unsafe { - bindings::vaDestroyImage(dpy, image.image_id); + bindings::va().vaDestroyImage(dpy, image.image_id); } Err(e) @@ -203,7 +203,7 @@ impl<'a> Drop for Image<'a> { // `picture.surface` represents a valid `VASurface` and `image` represents a valid // `VAImage`. unsafe { - bindings::vaPutImage( + bindings::va().vaPutImage( self.display.handle(), self.surface_id, self.image.image_id, @@ -222,9 +222,9 @@ impl<'a> Drop for Image<'a> { unsafe { // Safe since the buffer is mapped in `Image::new`, so `self.image.buf` points to a // valid `VABufferID`. - bindings::vaUnmapBuffer(self.display.handle(), self.image.buf); + bindings::va().vaUnmapBuffer(self.display.handle(), self.image.buf); // Safe since `self.image` represents a valid `VAImage`. - bindings::vaDestroyImage(self.display.handle(), self.image.image_id); + bindings::va().vaDestroyImage(self.display.handle(), self.image.image_id); } } } diff --git a/src/lib.rs b/src/lib.rs index c86d05a..4688c41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ impl std::fmt::Display for VaError { // Safe because `vaErrorStr` will return a pointer to a statically allocated, null // terminated C string. The pointer is guaranteed to never be null. - let err_str = unsafe { CStr::from_ptr(bindings::vaErrorStr(self.0.get())) } + let err_str = unsafe { CStr::from_ptr(bindings::va().vaErrorStr(self.0.get())) } .to_str() .unwrap(); f.write_str(err_str) diff --git a/src/picture.rs b/src/picture.rs index 0dbb1d9..5b2e17e 100644 --- a/src/picture.rs +++ b/src/picture.rs @@ -156,7 +156,7 @@ impl Picture { // Safe because `self.inner.context` represents a valid VAContext and // `self.inner.surface` represents a valid VASurface. let res = va_check(unsafe { - bindings::vaBeginPicture( + bindings::va().vaBeginPicture( self.inner.context.display().handle(), self.inner.context.id(), self.surface().id(), @@ -178,7 +178,7 @@ impl Picture { // passed to the C function, so it is impossible to write past the end of the vector's // storage by mistake. va_check(unsafe { - bindings::vaRenderPicture( + bindings::va().vaRenderPicture( self.inner.context.display().handle(), self.inner.context.id(), Buffer::as_id_vec(&self.inner.buffers).as_mut_ptr(), @@ -196,12 +196,11 @@ impl Picture { /// Wrapper around `vaEndPicture`. pub fn end(self) -> Result, VaError> { // Safe because `self.inner.context` represents a valid `VAContext`. - va_check(unsafe { bindings::vaEndPicture(self.inner.context.display().handle(), self.inner.context.id()) }).map( - |()| Picture { + va_check(unsafe { bindings::va().vaEndPicture(self.inner.context.display().handle(), self.inner.context.id()) }) + .map(|()| Picture { inner: self.inner, phantom: PhantomData, - }, - ) + }) } } diff --git a/src/surface.rs b/src/surface.rs index 2e867ed..5dca700 100644 --- a/src/surface.rs +++ b/src/surface.rs @@ -223,7 +223,7 @@ impl Surface { // Also all the pointers in `attrs` are pointing to valid objects that haven't been // moved or destroyed. match va_check(unsafe { - bindings::vaCreateSurfaces( + bindings::va().vaCreateSurfaces( display.handle(), rt_format, width, @@ -259,7 +259,7 @@ impl Surface { /// is safe to use the render target for a different picture. pub fn sync(&self) -> Result<(), VaError> { // Safe because `self` represents a valid VASurface. - va_check(unsafe { bindings::vaSyncSurface(self.display.handle(), self.id) }) + va_check(unsafe { bindings::va().vaSyncSurface(self.display.handle(), self.id) }) } /// Convenience function to return a VASurfaceID vector. Useful to interface with the C API @@ -272,7 +272,7 @@ impl Surface { pub fn query_status(&self) -> Result { let mut status: bindings::VASurfaceStatus::Type = 0; // Safe because `self` represents a valid VASurface. - va_check(unsafe { bindings::vaQuerySurfaceStatus(self.display.handle(), self.id, &mut status) })?; + va_check(unsafe { bindings::va().vaQuerySurfaceStatus(self.display.handle(), self.id, &mut status) })?; Ok(status) } @@ -282,7 +282,7 @@ impl Surface { // Safe because `self` represents a valid VASurface. va_check(unsafe { - bindings::vaQuerySurfaceError( + bindings::va().vaQuerySurfaceError( self.display.handle(), self.id, bindings::VA_STATUS_ERROR_DECODING_ERROR as i32, @@ -342,7 +342,7 @@ impl Surface { let mut desc: bindings::VADRMPRIMESurfaceDescriptor = Default::default(); va_check(unsafe { - bindings::vaExportSurfaceHandle( + bindings::va().vaExportSurfaceHandle( self.display.handle(), self.id(), bindings::VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, @@ -413,7 +413,7 @@ impl AsMut for Surface { impl Drop for Surface { fn drop(&mut self) { // Safe because `self` represents a valid VASurface. - unsafe { bindings::vaDestroySurfaces(self.display.handle(), &mut self.id, 1) }; + unsafe { bindings::va().vaDestroySurfaces(self.display.handle(), &mut self.id, 1) }; } }