using System.Runtime.InteropServices; using Avalonia; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Controls; using Avalonia.OpenGL; using Avalonia.Threading; using GSS2.Test.ViewModels; namespace GSS2.Test.Views; public unsafe partial class CameraView : OpenGlControlBase { private readonly ILogger _logger; private bool _isEglInitialized = false; private IntPtr? _eglDisplay = null; private int? _texture = null; private int? _program = null; private int? _uTexLocation = null; private int? _vao = null; private int? _vbo = null; private eglCreateImageKHR_t? _eglCreateImageKHR = null; private eglDestroyImageKHR_t? _eglDestroyImageKHR = null; private glEGLImageTargetTexture2DOES_t? _glEGLImageTargetTexture2DOES = null; private System.Timers.Timer? _renderTimer = null; public CameraView(ILogger logger) { _logger = logger; _logger.LogInformation("{} initialized", nameof(CameraView)); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { (DataContext as CameraViewModel)?.StartViewFinder(); base.OnAttachedToVisualTree(e); _renderTimer = new System.Timers.Timer(33) { AutoReset = true }; _renderTimer.Elapsed += OnRendering; _renderTimer.Start(); } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { (DataContext as CameraViewModel)?.StopViewFinder(); base.OnDetachedFromLogicalTree(e); } private void OnRendering(object? sender, EventArgs e) { Dispatcher.UIThread.Invoke(() => { RequestNextFrameRendering(); }); } [DllImport("libEGL.so.1")] private static extern IntPtr eglQueryString(IntPtr dpy, int name); [DllImport("libEGL.so.1")] private static extern IntPtr eglGetCurrentDisplay(); [DllImport("libEGL.so.1")] static extern IntPtr eglGetProcAddress(string procname); [DllImport("libGL.so.1")] static extern void glUniform1i(int location, int falue); delegate IntPtr glEGLImageTargetTexture2DOES_t(int target, IntPtr image); delegate IntPtr eglCreateImageKHR_t(IntPtr dpy, IntPtr ctx, int target, IntPtr buffer, int[] attribs); delegate bool eglDestroyImageKHR_t(IntPtr dpy, IntPtr image); private const uint DRM_FORMAT_BGRX8888 = 0x34325258; // XR24 XRGB8888 private const int EGL_LINUX_DMA_BUF_EXT = 0x3270; private const int EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272; private const int EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273; private const int EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274; private const int EGL_LINUX_DRM_FOURCC_EXT = 0x3271; private const int GL_CLAMP_TO_EDGE = 0x812F; private const int GL_TEXTURE_WRAP_S = 0x2802; private const int GL_TEXTURE_WRAP_T = 0x2803; private const int GL_TRIANGLE_STRIP = 0x0005; private const string VertexShader = @" #version 300 es layout (location = 0) in vec2 aPos; layout (location = 1) in vec2 aUv; out vec2 vUv; void main() { vUv = aUv; gl_Position = vec4(aPos, 0.0, 1.0); } "; private const string FragmentShader = @" #version 300 es precision mediump float; in vec2 vUv; uniform sampler2D uTex; out vec4 fragColor; void main() { fragColor = texture(uTex, vUv); } "; private readonly float[] Vertices = [ -1f, -1f, 0f, 1f, 1f, -1f, 1f, 1f, -1f, 1f, 0f, 0f, 1f, 1f, 1f, 0f, ]; protected override void OnOpenGlInit(GlInterface gl) { _logger.LogInformation("OpenGL initialized"); _logger.LogInformation("GL version: {}", gl.GetString(GlConsts.GL_VERSION)); _logger.LogInformation("GL vendor: {}", gl.GetString(GlConsts.GL_VENDOR)); _logger.LogInformation("GL renderer: {}", gl.GetString(GlConsts.GL_RENDERER)); var glExtensions = gl.GetString(GlConsts.GL_EXTENSIONS); if (glExtensions is null) { _logger.LogCritical("Cannot use EGL, GL extentions query returned nullptr"); base.OnOpenGlInit(gl); return; } _logger.LogInformation("GL extentions: [\n\t{}\n]", glExtensions.Trim().Replace(" ", "\n\t")); if (!glExtensions.Contains("GL_OES_EGL_image")) { _logger.LogWarning("Required GL extention \"GL_OES_EGL_image\" not found"); if (!glExtensions.Contains("GL_OES_EGL_image_external")) { _logger.LogCritical("Required GL extention \"GL_OES_EGL_image_external\" also not found"); base.OnOpenGlInit(gl); return; } } _eglDisplay = eglGetCurrentDisplay(); if (_eglDisplay == IntPtr.Zero) { _logger.LogCritical("Cannot use EGL, current EGL display is nullptr"); base.OnOpenGlInit(gl); return; } _logger.LogInformation("EGL display: 0x{:X}", _eglDisplay); var eglExtensions = Marshal.PtrToStringAnsi(eglQueryString(_eglDisplay.Value, EglConsts.EGL_EXTENSIONS)); if (eglExtensions is null) { _logger.LogCritical("Cannot use EGL, EGL extentions query returned nullptr"); base.OnOpenGlInit(gl); return; } _logger.LogInformation("EGL extensions: [\n\t{}\n]", eglExtensions.Trim().Replace(" ", "\n\t")); if (!eglExtensions.Contains("EGL_EXT_image_dma_buf_import")) { _logger.LogCritical("Cannot use EGL, required EGL extention \"EGL_EXT_image_dma_buf_import\" not found"); base.OnOpenGlInit(gl); return; } var pEglCreateImageKHR = eglGetProcAddress("eglCreateImageKHR"); if (pEglCreateImageKHR == IntPtr.Zero) { _logger.LogCritical("Cannot use EGL, required EGL method \"eglCreateImageKHR\" not found"); base.OnOpenGlInit(gl); return; } _eglCreateImageKHR = Marshal.GetDelegateForFunctionPointer(pEglCreateImageKHR); var pEglDestroyImageKHR = eglGetProcAddress("eglDestroyImageKHR"); if (pEglDestroyImageKHR == IntPtr.Zero) { _logger.LogCritical("Cannot use EGL, required EGL method \"eglDestroyImageKHR\" not found"); base.OnOpenGlInit(gl); return; } _eglDestroyImageKHR = Marshal.GetDelegateForFunctionPointer(pEglDestroyImageKHR); var pGlEGLImageTargetTexture2DOES = eglGetProcAddress("glEGLImageTargetTexture2DOES"); if (pGlEGLImageTargetTexture2DOES == IntPtr.Zero) { _logger.LogCritical("Cannot use EGL, required EGL method \"glEGLImageTargetTexture2DOES\" not found"); base.OnOpenGlInit(gl); return; } _glEGLImageTargetTexture2DOES = Marshal.GetDelegateForFunctionPointer(pGlEGLImageTargetTexture2DOES); _texture = gl.GenTexture(); gl.BindTexture(GlConsts.GL_TEXTURE_2D, _texture.Value); gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MIN_FILTER, GlConsts.GL_LINEAR); gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MAG_FILTER, GlConsts.GL_LINEAR); int vs = gl.CreateShader(GlConsts.GL_VERTEX_SHADER); gl.ShaderSourceString(vs, VertexShader); gl.CompileShader(vs); int fs = gl.CreateShader(GlConsts.GL_FRAGMENT_SHADER); gl.ShaderSourceString(fs, FragmentShader); gl.CompileShader(fs); _program = gl.CreateProgram(); gl.AttachShader(_program.Value, vs); gl.AttachShader(_program.Value, fs); gl.LinkProgram(_program.Value); gl.DeleteShader(vs); gl.DeleteShader(fs); _uTexLocation = gl.GetUniformLocationString(_program.Value, "uTex"); _vao = gl.GenVertexArray(); _vbo = gl.GenBuffer(); gl.BindVertexArray(_vao.Value); gl.BindBuffer(GlConsts.GL_ARRAY_BUFFER, _vbo.Value); fixed (float* pVertices = Vertices) gl.BufferData(GlConsts.GL_ARRAY_BUFFER, Vertices.Length * sizeof(float), new IntPtr(pVertices), GlConsts.GL_STATIC_DRAW); // position gl.EnableVertexAttribArray(0); gl.VertexAttribPointer(0, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), IntPtr.Zero); // uv gl.EnableVertexAttribArray(1); gl.VertexAttribPointer(1, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), 2 * sizeof(float)); gl.BindVertexArray(0); _isEglInitialized = true; } protected override void OnOpenGlRender(GlInterface gl, int fb) { if (!_isEglInitialized || _eglDisplay is null || _eglDisplay.Value == IntPtr.Zero || _texture is null || _program is null || _uTexLocation is null || _vao is null || _vbo is null) { _logger.LogError("OpenGL render called with uninitialized EGL"); return; } try { (LibCameraSharp.FrameBuffer, LibCameraSharp.StreamConfiguration)? frame = null; do { frame = (DataContext as CameraViewModel)?.GrabViewFinderFrame(); } while (frame is null); if (frame is null) return; var (frameBuffer, streamConfiguration) = frame.Value; // 0. Проверка что полученый фрейм ожидаемый праильный формат var width = streamConfiguration.Size.Width; if (width <= 0) { _logger.LogError("Cannot render frame, stream configuration vaildation error, expected frame width >= 0, got {}", streamConfiguration.Size.Width); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); return; } var height = streamConfiguration.Size.Height; if (height <= 0) { _logger.LogError("Cannot render frame, stream configuration vaildation error, expected frame height >= 0, got {}", streamConfiguration.Size.Height); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); return; } if (streamConfiguration.PixelFormat.Fourcc != DRM_FORMAT_BGRX8888) { _logger.LogError("Cannot render frame, stream configuration vaildation error, expected forcc: 0x{:X}, got 0x{:X}", DRM_FORMAT_BGRX8888, streamConfiguration.PixelFormat.Fourcc); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); return; } if (streamConfiguration.PixelFormat.Modifier != 0) { _logger.LogError("Cannot render frame, stream configuration vaildation error, expected DRM modifier: 0x{:X}, got 0x{:X}", 0, streamConfiguration.PixelFormat.Modifier); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); return; } if (frameBuffer.Planes.Count() != 1) { _logger.LogError("Cannot render frame, planes vaildation error, expected planes count: 0, got {}", frameBuffer.Planes.Count()); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); return; } var plane = frameBuffer.Planes.First(); if (plane.Length != width * height * 4) { _logger.LogError("Cannot render frame, plane validation error, expected plane size 0x{:X}, got 0x{:X}", width * height * 4, plane.Length); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); return; } var fd = plane.Fd.Get(); if (fd <= 0) { _logger.LogError("Cannot render frame, FD vaildation error, FD >= 0, got 0x{:X}", fd); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); return; } // 1. Очистка экрана gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height); gl.ClearColor( Colors.CornflowerBlue.R / 255f, Colors.CornflowerBlue.G / 255f, Colors.CornflowerBlue.B / 255f, Colors.CornflowerBlue.A / 255f ); gl.Clear(GlConsts.GL_COLOR_BUFFER_BIT); // 2. Создание EGLImage из dma-buf var eglImage = CreateEglImageFromDmabuf(_eglDisplay.Value, fd, (int)width, (int)height, (int)(width * 4)); // 3. Биндинг текстуры gl.ActiveTexture(GlConsts.GL_TEXTURE0); gl.BindTexture(GlConsts.GL_TEXTURE_2D, _texture.Value); // 4. Привязка EGLImage к текстуре _glEGLImageTargetTexture2DOES!(GlConsts.GL_TEXTURE_2D, eglImage); // 5. Запуск шейдера gl.UseProgram(_program.Value); glUniform1i(_uTexLocation.Value, 0); // 6. Отрисовка gl.BindVertexArray(_vao.Value); gl.DrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 7. Уничтожение EGLImage _eglDestroyImageKHR!(_eglDisplay.Value, eglImage); (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer); } catch (Exception ex) { _logger.LogError(ex, "Exception whiel OpenGL render"); } } private IntPtr CreateEglImageFromDmabuf(IntPtr eglDisplay, int fd, int width, int height, int stride) { var attribs = new[] { EglConsts.EGL_WIDTH, width, EglConsts.EGL_HEIGHT, height, EGL_LINUX_DRM_FOURCC_EXT, unchecked((int)DRM_FORMAT_BGRX8888), EGL_DMA_BUF_PLANE0_FD_EXT, fd, EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, EGL_DMA_BUF_PLANE0_PITCH_EXT, stride, EglConsts.EGL_NONE }; var image = _eglCreateImageKHR?.Invoke(eglDisplay, IntPtr.Zero, EGL_LINUX_DMA_BUF_EXT, IntPtr.Zero, attribs); if (image is null || image == IntPtr.Zero) throw new Exception("eglCreateImageKHR failed"); return image.Value; } }