forked from amkovkov/GranuSightSoftware2
372 lines
14 KiB
C#
372 lines
14 KiB
C#
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<CameraView> _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<CameraView> 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<eglCreateImageKHR_t>(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<eglDestroyImageKHR_t>(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<glEGLImageTargetTexture2DOES_t>(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;
|
|
}
|
|
} |