From bc49dd11c01eb3aa6de5f459d710069f3a49b724 Mon Sep 17 00:00:00 2001 From: Jonathan Schwender Date: Fri, 23 Aug 2024 17:40:07 +0800 Subject: [PATCH] ohos: Add generic surface support OpenHarmony API-12 adds compatibility for functions like `eglGetNativeClientBufferANDROID` so we can mostly use the same code as on android for the textures Signed-off-by: Jonathan Schwender --- src/platform/egl/context.rs | 2 - src/platform/egl/ohos_ffi.rs | 67 +++++++++ src/platform/egl/surface/ohos_surface.rs | 181 +++++++++++++++++++++-- 3 files changed, 238 insertions(+), 12 deletions(-) diff --git a/src/platform/egl/context.rs b/src/platform/egl/context.rs index bdd96176..2fe737bf 100644 --- a/src/platform/egl/context.rs +++ b/src/platform/egl/context.rs @@ -212,7 +212,6 @@ impl Device { .. }) => (egl_surface, egl_surface), Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), - #[cfg(android_platform)] Framebuffer::Surface(Surface { objects: SurfaceObjects::HardwareBuffer { .. }, .. @@ -367,7 +366,6 @@ impl Device { .. }) => (egl_surface, egl_surface), Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), - #[cfg(android_platform)] Framebuffer::Surface(Surface { objects: SurfaceObjects::HardwareBuffer { .. }, .. diff --git a/src/platform/egl/ohos_ffi.rs b/src/platform/egl/ohos_ffi.rs index 726b4931..16c27272 100644 --- a/src/platform/egl/ohos_ffi.rs +++ b/src/platform/egl/ohos_ffi.rs @@ -45,3 +45,70 @@ extern "C" { ... ) -> i32; } + +// Bindings to `native_buffer` components we use. Official Documentation: +// https://docs.openharmony.cn/pages/v5.0/en/application-dev/graphics/native-buffer-guidelines.md + +#[repr(C)] +pub struct OH_NativeBuffer { + _opaque: [u8; 0], +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct OH_NativeBuffer_Config { + /// Width in pixels + pub width: i32, + /// Height in pixels + pub height: i32, + /// One of PixelFormat + pub format: OH_NativeBuffer_Format, + /// Combination of buffer usage + pub usage: OH_NativeBuffer_Usage, + /// the stride of memory + pub stride: i32, +} + +#[repr(transparent)] +#[derive(Debug, Copy, Clone)] +pub struct OH_NativeBuffer_Format(core::ffi::c_int); + +impl OH_NativeBuffer_Format { + /// RGBA8888 format + pub const RGBA_8888: OH_NativeBuffer_Format = OH_NativeBuffer_Format(12); +} + +bitflags! { + #[repr(transparent)] + #[derive(Debug, Copy, Clone)] + pub struct OH_NativeBuffer_Usage: core::ffi::c_int { + /// CPU read buffer + const CPU_READ = 1; + /// CPU write memory + const CPU_WRITE = 1 << 1; + /// Direct memory access (DMA) buffer + const MEM_DMA = 1 << 3; + /// For GPU write case + const HW_RENDER = 1 << 8; + /// For GPU read case + const HW_TEXTURE = 1 << 9; + /// Often be mapped for direct CPU reads + const CPU_READ_OFTEN = 1 << 16; + /// 512 bytes alignment + const ALIGNMENT_512 = 1 << 18; + } +} + +extern "C" { + /// Alloc an `OH_NativeBuffer`` that matches the passed config. + /// + /// A new `OH_NativeBuffer` instance is created each time this function is called. + /// NULL is returned if allocation fails. + pub fn OH_NativeBuffer_Alloc(config: *const OH_NativeBuffer_Config) -> *mut OH_NativeBuffer; + /// Decreases the reference count of a OH_NativeBuffer and, when the reference count reaches 0, + /// destroys this OH_NativeBuffer. + /// + /// Since API-9 + pub fn OH_NativeBuffer_Unreference(buffer: *mut OH_NativeBuffer) -> i32; + +} diff --git a/src/platform/egl/surface/ohos_surface.rs b/src/platform/egl/surface/ohos_surface.rs index 76f85c59..f2d14d73 100644 --- a/src/platform/egl/surface/ohos_surface.rs +++ b/src/platform/egl/surface/ohos_surface.rs @@ -10,25 +10,42 @@ use euclid::default::Size2D; use log::info; use crate::egl; -use crate::egl::types::EGLSurface; +use crate::egl::types::{EGLSurface, EGLint}; use crate::gl; use crate::gl::types::{GLenum, GLuint}; +use crate::gl_utils; +use crate::platform::generic; use crate::platform::generic::egl::device::EGL_FUNCTIONS; +use crate::platform::generic::egl::ffi::EGLImageKHR; use crate::platform::generic::egl::ffi::EGL_EXTENSION_FUNCTIONS; +use crate::platform::generic::egl::ffi::EGL_IMAGE_PRESERVED_KHR; +use crate::platform::generic::egl::ffi::EGL_NATIVE_BUFFER_ANDROID; use crate::platform::generic::egl::ffi::EGL_NO_IMAGE_KHR; +use crate::renderbuffers::Renderbuffers; use crate::{Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType}; use super::super::context::{Context, GL_FUNCTIONS}; use super::super::device::Device; use super::super::ohos_ffi::{ - NativeWindowOperation, OHNativeWindow, OH_NativeWindow_NativeWindowHandleOpt, + NativeWindowOperation, OHNativeWindow, OH_NativeBuffer, OH_NativeBuffer_Alloc, + OH_NativeBuffer_Config, OH_NativeBuffer_Format, OH_NativeBuffer_Unreference, + OH_NativeBuffer_Usage, OH_NativeWindow_NativeWindowHandleOpt, }; use super::{Surface, SurfaceTexture}; const SURFACE_GL_TEXTURE_TARGET: GLenum = gl::TEXTURE_2D; pub(crate) enum SurfaceObjects { - Window { egl_surface: EGLSurface }, + HardwareBuffer { + hardware_buffer: *mut OH_NativeBuffer, + egl_image: EGLImageKHR, + framebuffer_object: GLuint, + texture_object: GLuint, + renderbuffers: Renderbuffers, + }, + Window { + egl_surface: EGLSurface, + }, } /// An OHOS native window. @@ -58,10 +75,65 @@ impl Device { fn create_generic_surface( &mut self, - _context: &Context, - _size: &Size2D, + context: &Context, + size: &Size2D, ) -> Result { - Err(Error::Unimplemented) + let _guard = self.temporarily_make_context_current(context)?; + + let usage = OH_NativeBuffer_Usage::HW_RENDER | OH_NativeBuffer_Usage::HW_TEXTURE; + + let config = OH_NativeBuffer_Config { + width: size.width, + height: size.height, + format: OH_NativeBuffer_Format::RGBA_8888, + usage: usage, + stride: 10, // used same magic number as android. I have no idea + }; + + GL_FUNCTIONS.with(|gl| { + unsafe { + let hardware_buffer = OH_NativeBuffer_Alloc(&config as *const _); + assert!(!hardware_buffer.is_null(), "Failed to create native buffer"); + + // Create an EGL image, and bind it to a texture. + let egl_image = self.create_egl_image(context, hardware_buffer); + + // Initialize and bind the image to the texture. + let texture_object = + generic::egl::surface::bind_egl_image_to_gl_texture(gl, egl_image); + + // Create the framebuffer, and bind the texture to it. + let framebuffer_object = gl_utils::create_and_bind_framebuffer( + gl, + SURFACE_GL_TEXTURE_TARGET, + texture_object, + ); + + // Bind renderbuffers as appropriate. + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + let renderbuffers = Renderbuffers::new(gl, size, &context_attributes); + renderbuffers.bind_to_current_framebuffer(gl); + + debug_assert_eq!( + gl.CheckFramebufferStatus(gl::FRAMEBUFFER), + gl::FRAMEBUFFER_COMPLETE + ); + + Ok(Surface { + size: *size, + context_id: context.id, + objects: SurfaceObjects::HardwareBuffer { + hardware_buffer, + egl_image, + framebuffer_object, + texture_object, + renderbuffers, + }, + destroyed: false, + }) + } + }) } unsafe fn create_window_surface( @@ -110,14 +182,34 @@ impl Device { /// in another context. /// /// Calling this method on a widget surface returns a `WidgetAttached` error. - /// On OpenHarmony, currently only widget surfaces are implemented in surfman, so - /// this method unconditionally returns the `WidgetAttached` error. pub fn create_surface_texture( &self, - _context: &mut Context, + context: &mut Context, surface: Surface, ) -> Result { - Err((Error::WidgetAttached, surface)) + unsafe { + match surface.objects { + SurfaceObjects::Window { .. } => return Err((Error::WidgetAttached, surface)), + SurfaceObjects::HardwareBuffer { + hardware_buffer, .. + } => GL_FUNCTIONS.with(|gl| { + let _guard = match self.temporarily_make_context_current(context) { + Ok(guard) => guard, + Err(err) => return Err((err, surface)), + }; + + let local_egl_image = self.create_egl_image(context, hardware_buffer); + let texture_object = + generic::egl::surface::bind_egl_image_to_gl_texture(gl, local_egl_image); + Ok(SurfaceTexture { + surface, + local_egl_image, + texture_object, + phantom: PhantomData, + }) + }), + } + } } /// Displays the contents of a widget surface on screen. @@ -138,6 +230,7 @@ impl Device { egl.SwapBuffers(self.egl_display, egl_surface); Ok(()) } + SurfaceObjects::HardwareBuffer { .. } => Err(Error::NoWidgetAttached), } }) } @@ -153,6 +246,42 @@ impl Device { Ok(()) } + #[allow(non_snake_case)] + unsafe fn create_egl_image( + &self, + _: &Context, + hardware_buffer: *mut OH_NativeBuffer, + ) -> EGLImageKHR { + // Get the native client buffer. + // Note: `eglGetNativeClientBufferANDROID` is available starting with OpenHarmony 5.0. + // Its availability is documented here: https://docs.openharmony.cn/pages/v5.0/en/application-dev/reference/native-lib/egl-symbol.md + let eglGetNativeClientBufferANDROID = + EGL_EXTENSION_FUNCTIONS.GetNativeClientBufferANDROID.expect( + "Where's the `EGL_ANDROID_get_native_client_buffer` \ + extension?", + ); + let client_buffer = eglGetNativeClientBufferANDROID(hardware_buffer as *const _); + assert!(!client_buffer.is_null()); + + // Create the EGL image. + let egl_image_attributes = [ + EGL_IMAGE_PRESERVED_KHR as EGLint, + egl::TRUE as EGLint, + egl::NONE as EGLint, + 0, + ]; + let egl_image = (EGL_EXTENSION_FUNCTIONS.CreateImageKHR)( + self.egl_display, + egl::NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, + client_buffer, + egl_image_attributes.as_ptr(), + ); + assert_ne!(egl_image, EGL_NO_IMAGE_KHR); + info!("surfman created an EGL image succesfully!"); + egl_image + } + /// Destroys a surface. /// /// The supplied context must be the context the surface is associated with, or this returns @@ -171,6 +300,34 @@ impl Device { unsafe { match surface.objects { + SurfaceObjects::HardwareBuffer { + ref mut hardware_buffer, + ref mut egl_image, + ref mut framebuffer_object, + ref mut texture_object, + ref mut renderbuffers, + } => { + GL_FUNCTIONS.with(|gl| { + gl.BindFramebuffer(gl::FRAMEBUFFER, 0); + gl.DeleteFramebuffers(1, framebuffer_object); + *framebuffer_object = 0; + + renderbuffers.destroy(gl); + + gl.DeleteTextures(1, texture_object); + *texture_object = 0; + + let egl_display = self.egl_display; + let result = + (EGL_EXTENSION_FUNCTIONS.DestroyImageKHR)(egl_display, *egl_image); + assert_ne!(result, egl::FALSE); + *egl_image = EGL_NO_IMAGE_KHR; + + let res = OH_NativeBuffer_Unreference(*hardware_buffer); + assert_eq!(res, 0, "OH_NativeBuffer_Unreference failed"); + *hardware_buffer = ptr::null_mut(); + }); + } SurfaceObjects::Window { ref mut egl_surface, } => EGL_FUNCTIONS.with(|egl| { @@ -242,6 +399,9 @@ impl Device { id: surface.id(), context_id: surface.context_id, framebuffer_object: match surface.objects { + SurfaceObjects::HardwareBuffer { + framebuffer_object, .. + } => framebuffer_object, SurfaceObjects::Window { .. } => 0, }, } @@ -267,6 +427,7 @@ impl NativeWidget { impl Surface { pub(super) fn id(&self) -> SurfaceID { match self.objects { + SurfaceObjects::HardwareBuffer { egl_image, .. } => SurfaceID(egl_image as usize), SurfaceObjects::Window { egl_surface } => SurfaceID(egl_surface as usize), } }