Skip to main content

wgpu_native_texture_interop/raw_gl/
linux.rs

1//! GL framebuffer import via Vulkan external memory (Linux/Android).
2//!
3//! Imports a GL framebuffer into a `wgpu::Texture` by:
4//! 1. Creating a Vulkan image with external memory (opaque FD)
5//! 2. Importing the FD into GL via `GL_EXT_memory_object_fd`
6//! 3. Blitting from the source FBO to the shared GL texture
7//! 4. Wrapping the Vulkan image as a `wgpu::Texture`
8
9use ash::vk;
10use dpi::PhysicalSize;
11use glow::HasContext;
12use std::ffi::c_void;
13
14use crate::{HostWgpuContext, InteropError, gl_bindings as gl};
15
16/// Import a GL framebuffer into a `wgpu::Texture` via Vulkan external memory.
17///
18/// # Arguments
19///
20/// * `gl` - A glow GL context for texture and framebuffer operations
21/// * `gl_extension_loader` - Function to load GL extension entry points (e.g.
22///   `GL_EXT_memory_object_fd`). Typically wraps `eglGetProcAddress` or equivalent.
23/// * `source_fbo` - The GL framebuffer object to read from. Pass 0 for the default framebuffer.
24/// * `size` - Dimensions of the framebuffer content to import.
25/// * `host` - The host wgpu context (device + queue) that will own the resulting texture.
26///
27/// The returned texture is `Rgba8Unorm` with top-left origin (Y-flipped during blit).
28pub fn import_gl_framebuffer_vulkan(
29    gl: &glow::Context,
30    gl_extension_loader: &dyn Fn(&str) -> *const c_void,
31    source_fbo: u32,
32    size: PhysicalSize<u32>,
33    host: &HostWgpuContext,
34) -> Result<wgpu::Texture, InteropError> {
35    use gl::Gles2 as Gl;
36
37    unsafe {
38        let hal_device = host.device.as_hal::<wgpu::wgc::api::Vulkan>().ok_or(
39            InteropError::BackendMismatch {
40                expected: "Vulkan",
41                actual: "non-Vulkan",
42            },
43        )?;
44        let vulkan_device = hal_device.raw_device().clone();
45        let vulkan_instance = hal_device.shared_instance().raw_instance();
46        let physical_device = hal_device.raw_physical_device();
47
48        let mut external_memory_image_info = vk::ExternalMemoryImageCreateInfo::default()
49            .handle_types(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD);
50
51        let vulkan_image = vulkan_device
52            .create_image(
53                &vk::ImageCreateInfo::default()
54                    .image_type(vk::ImageType::TYPE_2D)
55                    .format(vk::Format::R8G8B8A8_UNORM)
56                    .extent(vk::Extent3D {
57                        width: size.width,
58                        height: size.height,
59                        depth: 1,
60                    })
61                    .mip_levels(1)
62                    .array_layers(1)
63                    .samples(vk::SampleCountFlags::TYPE_1)
64                    .tiling(vk::ImageTiling::OPTIMAL)
65                    .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT)
66                    .sharing_mode(vk::SharingMode::EXCLUSIVE)
67                    .initial_layout(vk::ImageLayout::UNDEFINED)
68                    .push_next(&mut external_memory_image_info),
69                None,
70            )
71            .map_err(|err| InteropError::Vulkan(err.to_string()))?;
72
73        let memory_requirements = vulkan_device.get_image_memory_requirements(vulkan_image);
74        let memory_properties =
75            vulkan_instance.get_physical_device_memory_properties(physical_device);
76        let memory_type_index = memory_properties.memory_types
77            [..memory_properties.memory_type_count as usize]
78            .iter()
79            .enumerate()
80            .position(|(i, mem_type)| {
81                (memory_requirements.memory_type_bits & (1 << i)) != 0
82                    && mem_type
83                        .property_flags
84                        .contains(vk::MemoryPropertyFlags::DEVICE_LOCAL)
85            })
86            .ok_or_else(|| {
87                InteropError::Vulkan("no DEVICE_LOCAL memory type compatible with image".into())
88            })? as u32;
89
90        let mut dedicated_allocate_info =
91            vk::MemoryDedicatedAllocateInfo::default().image(vulkan_image);
92        let mut export_info = vk::ExportMemoryAllocateInfo::default()
93            .handle_types(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD);
94
95        let memory = vulkan_device
96            .allocate_memory(
97                &vk::MemoryAllocateInfo::default()
98                    .allocation_size(memory_requirements.size)
99                    .memory_type_index(memory_type_index)
100                    .push_next(&mut dedicated_allocate_info)
101                    .push_next(&mut export_info),
102                None,
103            )
104            .map_err(|err| InteropError::Vulkan(err.to_string()))?;
105
106        vulkan_device
107            .bind_image_memory(vulkan_image, memory, 0)
108            .map_err(|err| InteropError::Vulkan(err.to_string()))?;
109
110        let external_memory_fd_api =
111            ash::khr::external_memory_fd::Device::new(&vulkan_instance, &vulkan_device);
112        let memory_handle = external_memory_fd_api
113            .get_memory_fd(
114                &vk::MemoryGetFdInfoKHR::default()
115                    .memory(memory)
116                    .handle_type(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD),
117            )
118            .map_err(|err| InteropError::Vulkan(err.to_string()))?;
119
120        let gl_with_extensions = Gl::load_with(|function_name| gl_extension_loader(function_name));
121
122        if !gl_with_extensions.CreateMemoryObjectsEXT.is_loaded()
123            || !gl_with_extensions.ImportMemoryFdEXT.is_loaded()
124            || !gl_with_extensions.TexStorageMem2DEXT.is_loaded()
125        {
126            vulkan_device.destroy_image(vulkan_image, None);
127            vulkan_device.free_memory(memory, None);
128            return Err(InteropError::OpenGl(
129                "GL_EXT_memory_object_fd not available — the GL driver does not \
130                 support external memory extensions"
131                    .into(),
132            ));
133        }
134
135        let mut memory_object = 0;
136        gl_with_extensions.CreateMemoryObjectsEXT(1, &mut memory_object);
137        gl_with_extensions.MemoryObjectParameterivEXT(
138            memory_object,
139            gl::DEDICATED_MEMORY_OBJECT_EXT,
140            &1,
141        );
142        gl_with_extensions.ImportMemoryFdEXT(
143            memory_object,
144            memory_requirements.size,
145            gl::HANDLE_TYPE_OPAQUE_FD_EXT,
146            memory_handle,
147        );
148
149        let texture = gl.create_texture().map_err(InteropError::OpenGl)?;
150        gl.bind_texture(gl::TEXTURE_2D, Some(texture));
151        gl_with_extensions.TexStorageMem2DEXT(
152            gl::TEXTURE_2D,
153            1,
154            gl::RGBA8,
155            size.width as i32,
156            size.height as i32,
157            memory_object,
158            0,
159        );
160
161        let draw_framebuffer = gl.create_framebuffer().map_err(InteropError::OpenGl)?;
162
163        let read_framebuffer = if source_fbo == 0 {
164            None
165        } else {
166            Some(glow::NativeFramebuffer(
167                std::num::NonZeroU32::new(source_fbo)
168                    .ok_or(InteropError::InvalidFrame("invalid FBO id"))?,
169            ))
170        };
171
172        gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, Some(draw_framebuffer));
173        gl.framebuffer_texture_2d(
174            gl::DRAW_FRAMEBUFFER,
175            gl::COLOR_ATTACHMENT0,
176            gl::TEXTURE_2D,
177            Some(texture),
178            0,
179        );
180
181        gl.bind_framebuffer(gl::READ_FRAMEBUFFER, read_framebuffer);
182        gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, Some(draw_framebuffer));
183        gl.blit_framebuffer(
184            0,
185            0,
186            size.width as i32,
187            size.height as i32,
188            0,
189            size.height as i32,
190            size.width as i32,
191            0,
192            gl::COLOR_BUFFER_BIT,
193            gl::NEAREST,
194        );
195        gl.flush();
196        gl.delete_framebuffer(draw_framebuffer);
197        gl.delete_texture(texture);
198        gl_with_extensions.DeleteMemoryObjectsEXT(1, &memory_object);
199
200        let imported = host
201            .device
202            .create_texture_from_hal::<wgpu::wgc::api::Vulkan>(
203                hal_device.texture_from_raw(
204                    vulkan_image,
205                    &wgpu_hal::TextureDescriptor {
206                        label: None,
207                        size: wgpu::Extent3d {
208                            width: size.width,
209                            height: size.height,
210                            depth_or_array_layers: 1,
211                        },
212                        format: wgpu::TextureFormat::Rgba8Unorm,
213                        dimension: wgpu::TextureDimension::D2,
214                        mip_level_count: 1,
215                        sample_count: 1,
216                        usage: wgpu::TextureUses::RESOURCE | wgpu::TextureUses::COLOR_TARGET,
217                        view_formats: Vec::new(),
218                        memory_flags: wgpu_hal::MemoryFlags::empty(),
219                    },
220                    Some(Box::new(move || {
221                        vulkan_device.destroy_image(vulkan_image, None);
222                        vulkan_device.free_memory(memory, None);
223                    })),
224                    wgpu_hal::vulkan::TextureMemory::External,
225                ),
226                &wgpu::TextureDescriptor {
227                    label: Some("gl-frame-vulkan-import"),
228                    size: wgpu::Extent3d {
229                        width: size.width,
230                        height: size.height,
231                        depth_or_array_layers: 1,
232                    },
233                    format: wgpu::TextureFormat::Rgba8Unorm,
234                    dimension: wgpu::TextureDimension::D2,
235                    mip_level_count: 1,
236                    sample_count: 1,
237                    usage: wgpu::TextureUsages::TEXTURE_BINDING
238                        | wgpu::TextureUsages::RENDER_ATTACHMENT,
239                    view_formats: &[],
240                },
241            );
242
243        Ok(imported)
244    }
245}