Files
GSS2Rework/GSS2.UI.Core/Views/CameraView.axaml.cs
T
amkovkov b7a095f84d fix: CameraView
TWO lines (нет слов, одни эмоции бля)
2026-04-13 18:36:53 +03:00

702 lines
29 KiB
C#

using System.Numerics;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using Avalonia;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Controls;
using Avalonia.OpenGL;
using Avalonia.Threading;
using Avalonia.Input;
using GSS2.UI.Core.ViewModels;
using Avalonia.Markup.Xaml;
using GSS2.Core.UI.Extensions;
namespace GSS2.UI.Core.Views;
public partial class CameraView : OpenGlControlBase
{
private class OpenGLContext
{
[DllImport("libEGL.so.1")] public static extern IntPtr eglQueryString(IntPtr dpy, int name);
[DllImport("libEGL.so.1")] public static extern IntPtr eglGetCurrentDisplay();
[DllImport("libEGL.so.1")] public static extern IntPtr eglGetProcAddress(string procname);
[DllImport("libGL.so.1")] public static extern void glUniform1i(int location, int falue);
[DllImport("libGL.so.1")] public static extern void glUniformMatrix3x2fv(int location, int count, bool transpose, IntPtr value);
[DllImport("libGL.so.1")] public static extern void glUniformMatrix3fv(int location, int count, bool transpose, IntPtr value);
public const uint DRM_FORMAT_BGRX8888 = 0x34325258; // XR24 XRGB8888
public const int GL_TRIANGLE_STRIP = 0x0005;
public const int GL_TEXTURE_WRAP_S = 0x2802;
public const int GL_TEXTURE_WRAP_T = 0x2803;
public const int GL_CLAMP_TO_EDGE = 0x812F;
public const int EGL_LINUX_DMA_BUF_EXT = 0x3270;
public const int EGL_LINUX_DRM_FOURCC_EXT = 0x3271;
public const int EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272;
public const int EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273;
public const int EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274;
public delegate IntPtr GlEGLImageTargetTexture2DOESDelegate(int target, IntPtr image);
public delegate IntPtr EglCreateImageKHRDelegate(IntPtr dpy, IntPtr ctx, int target, IntPtr buffer, int[] attribs);
public delegate bool EglDestroyImageKHRDelegate(IntPtr dpy, IntPtr image);
private const string VERTEX_SHADER = @"
#version 300 es
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aUv;
uniform mat4 uMvp;
uniform mat3 uUv;
out vec2 vUv;
void main()
{
vec3 uv = uUv * vec3(aUv, 1.0);
vUv = uv.xy;
gl_Position = uMvp * vec4(aPos, 0.0, 1.0);
}
";
private const string FRAGMENT_SHADER = @"
#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,
];
public IntPtr EglDisplay { get; }
public int Texture { get; }
public int Program { get; }
public int UTexLocation { get; }
public int UMvpLocation { get; }
public int UUvLocation { get; }
public int VAO { get; }
public int VBO { get; }
public EglCreateImageKHRDelegate EglCreateImageKHR { get; }
public EglDestroyImageKHRDelegate EglDestroyImageKHR { get; }
public GlEGLImageTargetTexture2DOESDelegate GlEGLImageTargetTexture2DOES { get; }
public OpenGLContext(GlInterface gl)
{
var glExtensions = gl.GetString(GlConsts.GL_EXTENSIONS);
if (glExtensions is null)
throw new Exception("GL extensions query returned null string");
if (!glExtensions.Contains("GL_OES_EGL_image") &&
!glExtensions.Contains("GL_OES_EGL_image_external"))
throw new Exception("Required GL extensions \"GL_OES_EGL_image_external\" or \"GL_OES_EGL_image\" not found");
EglDisplay = eglGetCurrentDisplay();
if (EglDisplay == IntPtr.Zero)
throw new Exception("EGL display is nullptr");
var eglExtensions = Marshal.PtrToStringAnsi(eglQueryString(EglDisplay, EglConsts.EGL_EXTENSIONS));
if (eglExtensions is null)
throw new Exception("EGL extensions query returned null");
if (!eglExtensions.Contains("EGL_EXT_image_dma_buf_import"))
throw new Exception("Required EGL extension \"EGL_EXT_image_dma_buf_import\" not found");
var pEglCreateImageKHR = eglGetProcAddress("eglCreateImageKHR");
if (pEglCreateImageKHR == IntPtr.Zero)
throw new Exception("Required EGL method \"eglCreateImageKHR\" not found");
EglCreateImageKHR = Marshal.GetDelegateForFunctionPointer<EglCreateImageKHRDelegate>(pEglCreateImageKHR);
var pEglDestroyImageKHR = eglGetProcAddress("eglDestroyImageKHR");
if (pEglDestroyImageKHR == IntPtr.Zero)
throw new Exception("Required EGL method \"eglDestroyImageKHR\" not found");
EglDestroyImageKHR = Marshal.GetDelegateForFunctionPointer<EglDestroyImageKHRDelegate>(pEglDestroyImageKHR);
var pGlEGLImageTargetTexture2DOES = eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (pGlEGLImageTargetTexture2DOES == IntPtr.Zero)
throw new Exception("Required EGL method \"glEGLImageTargetTexture2DOES\" not found");
GlEGLImageTargetTexture2DOES = Marshal.GetDelegateForFunctionPointer<GlEGLImageTargetTexture2DOESDelegate>(pGlEGLImageTargetTexture2DOES);
Texture = gl.GenTexture(); ThrowIfOpenGLError(gl);
gl.BindTexture(GlConsts.GL_TEXTURE_2D, Texture); ThrowIfOpenGLError(gl);
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); ThrowIfOpenGLError(gl);
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); ThrowIfOpenGLError(gl);
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MIN_FILTER, GlConsts.GL_LINEAR); ThrowIfOpenGLError(gl);
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MAG_FILTER, GlConsts.GL_LINEAR); ThrowIfOpenGLError(gl);
int vertexShader = gl.CreateShader(GlConsts.GL_VERTEX_SHADER); ThrowIfOpenGLError(gl);
gl.ShaderSourceString(vertexShader, VERTEX_SHADER); ThrowIfOpenGLError(gl);
gl.CompileShader(vertexShader);
int fragmentShader = gl.CreateShader(GlConsts.GL_FRAGMENT_SHADER); ThrowIfOpenGLError(gl);
gl.ShaderSourceString(fragmentShader, FRAGMENT_SHADER); ThrowIfOpenGLError(gl);
gl.CompileShader(fragmentShader); ThrowIfOpenGLError(gl);
Program = gl.CreateProgram(); ThrowIfOpenGLError(gl);
gl.AttachShader(Program, vertexShader); ThrowIfOpenGLError(gl);
gl.AttachShader(Program, fragmentShader); ThrowIfOpenGLError(gl);
gl.LinkProgram(Program); ThrowIfOpenGLError(gl);
gl.DeleteShader(vertexShader); ThrowIfOpenGLError(gl);
gl.DeleteShader(fragmentShader); ThrowIfOpenGLError(gl);
UTexLocation = gl.GetUniformLocationString(Program, "uTex"); ThrowIfOpenGLError(gl);
UMvpLocation = gl.GetUniformLocationString(Program, "uMvp"); ThrowIfOpenGLError(gl);
UUvLocation = gl.GetUniformLocationString(Program, "uUv"); ThrowIfOpenGLError(gl);
VAO = gl.GenVertexArray(); ThrowIfOpenGLError(gl);
VBO = gl.GenBuffer(); ThrowIfOpenGLError(gl);
gl.BindVertexArray(VAO); ThrowIfOpenGLError(gl);
gl.BindBuffer(GlConsts.GL_ARRAY_BUFFER, VBO); ThrowIfOpenGLError(gl);
unsafe
{
fixed (float* pVertices = Vertices)
gl.BufferData(GlConsts.GL_ARRAY_BUFFER, Vertices.Length * sizeof(float), new IntPtr(pVertices), GlConsts.GL_STATIC_DRAW); ThrowIfOpenGLError(gl);
}
gl.EnableVertexAttribArray(0); ThrowIfOpenGLError(gl);
gl.VertexAttribPointer(0, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), IntPtr.Zero); ThrowIfOpenGLError(gl);
gl.EnableVertexAttribArray(1); ThrowIfOpenGLError(gl);
gl.VertexAttribPointer(1, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), 2 * sizeof(float)); ThrowIfOpenGLError(gl);
gl.BindVertexArray(0); ThrowIfOpenGLError(gl);
}
private void ThrowIfOpenGLError(GlInterface gl)
{
var error = gl.GetError();
if (error != 0)
throw new Exception($"OpenGL context initialization error: {error}");
}
}
private readonly ILogger<CameraView> _logger;
private readonly Dictionary<int, IntPtr> _importedDmaBuffers = new Dictionary<int, nint>();
private bool _isViewFinderStarted = false;
private OpenGLContext? _openGLContext = null;
private Point? _lastMousePosition = null;
private Dictionary<int, Point>? _lastTouchPositions = null;
private float _zoom = 1;
private float _panX = 0;
private float _panY = 0;
public static readonly StyledProperty<Color> BackgroundProperty = AvaloniaProperty.Register<CameraView, Color>(nameof(Background), defaultValue: Colors.Transparent);
public Color Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
public static readonly StyledProperty<Stretch> StretchModeProperty = AvaloniaProperty.Register<CameraView, Stretch>(nameof(StretchMode), defaultValue: Stretch.Uniform);
public Stretch StretchMode
{
get => GetValue(StretchModeProperty);
set => SetValue(StretchModeProperty, value);
}
public static readonly StyledProperty<TileMode> TileModeProperty = AvaloniaProperty.Register<CameraView, TileMode>(nameof(TileMode), defaultValue: TileMode.None);
public TileMode TileMode
{
get => GetValue(TileModeProperty);
set => SetValue(TileModeProperty, value);
}
public CameraView(ILogger<CameraView> logger)
{
_logger = logger;
AvaloniaXamlLoader.Load(this);
_logger.LogInformation("{} initialized", nameof(CameraView));
DoubleTapped += (_, _) =>
{
_zoom = 1f;
_panX = 0f;
_panY = 0f;
};
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs ea)
{
if (DataContext is CameraViewModel viewModel)
{
try
{
viewModel.StartViewFinder();
viewModel.RenderRequested += RenderRequested;
_isViewFinderStarted = true;
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Exception while view finder start");
}
}
else
_logger.LogCritical("Cannot start view finder, data context is null or not camera view model");
base.OnAttachedToVisualTree(ea);
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs ea)
{
if (_isViewFinderStarted && DataContext is CameraViewModel viewModel)
{
try
{
viewModel.StopViewFinder();
foreach (var (fd, image) in _importedDmaBuffers)
_openGLContext?.EglDestroyImageKHR!(_openGLContext.EglDisplay, image);
_isViewFinderStarted = false;
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Exception while view finder stop");
}
viewModel.RenderRequested -= RenderRequested;
}
else if (_isViewFinderStarted)
_logger.LogCritical("Cannot stop view finder, data context is null or not camera view model");
_isViewFinderStarted = false;
base.OnDetachedFromLogicalTree(ea);
}
protected override void OnPointerWheelChanged(PointerWheelEventArgs ea)
{
if (ea.Pointer.Type == PointerType.Mouse)
{
var position = ea.GetPosition(this);
// Рассчитываем позицию в координатах изображения (до масштабирования)
var imageX = (position.X - Bounds.Width / 2 - _panX) / _zoom;
var imageY = (-position.Y + Bounds.Height / 2 - _panY) / _zoom; // Ось Y инвертирована
// Применяем масштаб
float zoomFactor = ea.Delta.Y > 0 ? 1.1f : 1 / 1.1f;
_zoom *= zoomFactor;
_zoom = Math.Clamp(_zoom, 1f, 10f);
_zoom = (float)Math.Round(_zoom, 2);
if (_zoom != 1f)
{
// Пересчитываем смещение так, чтобы указатель остался на месте
_panX = (float)(position.X - Bounds.Width / 2 - imageX * _zoom);
_panY = (float)(-position.Y + Bounds.Height / 2 - imageY * _zoom); // Ось Y инвертирована
}
else
{
// Если зум == 1, то возвращаем изображение к исходному смещению
_panX = 0f;
_panY = 0f;
}
}
base.OnPointerWheelChanged(ea);
}
protected override void OnPointerPressed(PointerPressedEventArgs ea)
{
if (ea.Pointer.Type == PointerType.Mouse)
{
if (ea.Properties.IsLeftButtonPressed)
{
if (ea.ClickCount == 2) // По двойному клику возвращаем изображение к смещение и зум к стандартным значениям
{
_zoom = 1f;
_panX = 0f;
_panY = 0f;
}
else
_lastMousePosition = ea.GetPosition(this);
}
}
else if (ea.Pointer.Type == PointerType.Touch)
{
if (_lastTouchPositions is null)
_lastTouchPositions = new Dictionary<int, Point>();
if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2)
_lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this));
else if (_lastTouchPositions.ContainsKey(ea.Pointer.Id))
_lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this);
}
base.OnPointerPressed(ea);
}
protected override void OnPointerReleased(PointerReleasedEventArgs ea)
{
if (ea.Pointer.Type == PointerType.Mouse)
{
if (!ea.Properties.IsLeftButtonPressed)
_lastMousePosition = null;
}
else if (ea.Pointer.Type == PointerType.Touch)
{
if (_lastTouchPositions is not null && _lastTouchPositions.ContainsKey(ea.Pointer.Id))
_lastTouchPositions.Remove(ea.Pointer.Id);
}
base.OnPointerReleased(ea);
}
protected override void OnPointerMoved(PointerEventArgs ea)
{
if (ea.Pointer.Type == PointerType.Mouse)
{
if (_lastMousePosition is null || _zoom == 1) // Если кнопка не нажата или зум == 1, то ничего не делаем
{
base.OnPointerMoved(ea);
return;
}
var position = ea.GetPosition(this);
_panX -= (float)(_lastMousePosition.Value.X - position.X);
_panY += (float)(_lastMousePosition.Value.Y - position.Y); // Ось Y инвертирована
_lastMousePosition = position;
}
else if (ea.Pointer.Type == PointerType.Touch)
{
if (_lastTouchPositions is null)
{
base.OnPointerMoved(ea);
return;
}
if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2)
{
_lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this));
base.OnPointerMoved(ea);
return;
}
if (_lastTouchPositions.Count() == 1 && _zoom != 1) // Если есть только одно прикосновение и зум != 1, то только перемещение
{
var position = ea.GetPosition(this);
_panX -= (float)(_lastTouchPositions[ea.Pointer.Id].X - position.X);
_panY += (float)(_lastTouchPositions[ea.Pointer.Id].Y - position.Y); // Ось Y инвертирована
_lastTouchPositions[ea.Pointer.Id] = position;
}
else if (_lastTouchPositions.Count() == 2) // Если два прикосновения, то перемещение с приближением
{
var lastPosition1 = _lastTouchPositions.Skip(0).First().Value;
var lastPosition2 = _lastTouchPositions.Skip(1).First().Value;
var lastDistance = Point.Distance(lastPosition1, lastPosition2);
var lastCenter = PointHelper.Center(lastPosition1, lastPosition2);
_lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this);
var newPosition1 = _lastTouchPositions.Skip(0).First().Value;
var newPosition2 = _lastTouchPositions.Skip(1).First().Value;
var newDistance = Point.Distance(newPosition1, newPosition2);
var newCenter = PointHelper.Center(newPosition1, newPosition2);
var zoomFactor = newDistance / lastDistance;
var newZoom = (float)(_zoom * zoomFactor);
newZoom = (float)Math.Clamp(newZoom, 1f, 10f);
var imageX = (newCenter.X - Bounds.Width / 2 - _panX) / _zoom;
var imageY = (-newCenter.Y + Bounds.Height / 2 - _panY) / _zoom; // Ось Y инвертирована
_zoom = newZoom;
if (_zoom > 1f)
{
_panX = (float)(newCenter.X - Bounds.Width / 2 - imageX * _zoom);
_panY = (float)(-newCenter.Y + Bounds.Height / 2 - imageY * _zoom); // Ось Y инвертирована
}
else
{
_zoom = 1f;
_panX = 0f;
_panY = 0f;
}
}
}
base.OnPointerMoved(ea);
}
public override void Render(DrawingContext context)
{
context.FillRectangle(new SolidColorBrush(Background), new Rect(0, 0, Bounds.Width, Bounds.Height));
base.Render(context);
}
private void RenderRequested(object? sender, EventArgs ea) => Dispatcher.UIThread.Invoke(() => RequestNextFrameRendering());
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));
try
{
_openGLContext = new OpenGLContext(gl);
_logger.LogInformation("OpenGL context initialized");
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Error while OpenGL initialization");
}
base.OnOpenGlInit(gl);
}
protected override void OnOpenGlRender(GlInterface gl, int fb)
{
if (_openGLContext is null)
{
_logger.LogError("OpenGL render called with uninitialized EGL");
return;
}
try
{
(LibCameraSharp.FrameBuffer, LibCameraSharp.StreamConfiguration)? frame = null;
while (frame is null && (DataContext as CameraViewModel)?.CanGrabViewFinderFrame() == true)
frame = (DataContext as CameraViewModel)?.GrabViewFinderFrame();
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 validation 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 validation error, expected frame height >= 0, got {}", streamConfiguration.Size.Height);
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
return;
}
if (streamConfiguration.PixelFormat.Fourcc != OpenGLContext.DRM_FORMAT_BGRX8888)
{
_logger.LogError("Cannot render frame, stream configuration validation error, expected fourcc: 0x{:X}, got 0x{:X}", OpenGLContext.DRM_FORMAT_BGRX8888, streamConfiguration.PixelFormat.Fourcc);
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
return;
}
if (streamConfiguration.PixelFormat.Modifier != 0)
{
_logger.LogError("Cannot render frame, stream configuration validation 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 validation 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 validation error, FD >= 0, got 0x{:X}", fd);
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
return;
}
var stretchMode = StretchMode;
var tileMode = TileMode;
// 1. Очищаем экран
gl.Disable(GlConsts.GL_SCISSOR_TEST);
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height);
gl.ClearColor(0f, 0f, 0f, 0f);
gl.Clear(GlConsts.GL_COLOR_BUFFER_BIT);
// 2. Создаём EGL изображение из dma-buf
IntPtr image;
if (_importedDmaBuffers.ContainsKey(fd))
{
// 2.1. Если EGL изображение для fd уже создано, то берём его из словаря
image = _importedDmaBuffers[fd];
}
else
{
// 2.2. Если EGL изображение для fd не создано, то создаём
var attribs = new[]
{
EglConsts.EGL_WIDTH, (int)width,
EglConsts.EGL_HEIGHT, (int)height,
OpenGLContext.EGL_LINUX_DRM_FOURCC_EXT, unchecked((int)OpenGLContext.DRM_FORMAT_BGRX8888),
OpenGLContext.EGL_DMA_BUF_PLANE0_FD_EXT, fd,
OpenGLContext.EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
OpenGLContext.EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)(plane.Length / height),
EglConsts.EGL_NONE
};
image = _openGLContext.EglCreateImageKHR.Invoke(_openGLContext.EglDisplay, IntPtr.Zero, OpenGLContext.EGL_LINUX_DMA_BUF_EXT, IntPtr.Zero, attribs);
_importedDmaBuffers.Add(fd, image);
}
// 3. Биндим текстуры текстуры
gl.ActiveTexture(GlConsts.GL_TEXTURE0);
gl.BindTexture(GlConsts.GL_TEXTURE_2D, _openGLContext.Texture);
// 4. Привязываем EGL изображение к текстуре
_openGLContext.GlEGLImageTargetTexture2DOES!(GlConsts.GL_TEXTURE_2D, image);
// 5. Вычисляем матрицы трансформаций
var mvp = ComputeMvp(stretchMode, width, height);
var zoom = Matrix4x4.CreateScale(_zoom, _zoom, 1f);
var translate = Matrix4x4.CreateTranslation((float)(_panX / Bounds.Width * 2), (float)(_panY / Bounds.Height * 2), 0);
mvp *= zoom;
mvp *= translate;
var mvpArray = new[]
{
mvp.M11, mvp.M12, mvp.M13, mvp.M14,
mvp.M21, mvp.M22, mvp.M23, mvp.M24,
mvp.M31, mvp.M32, mvp.M33, mvp.M34,
mvp.M41, mvp.M42, mvp.M43, mvp.M44,
};
unsafe
{
fixed (float* pMvp = mvpArray)
gl.UniformMatrix4fv(_openGLContext.UMvpLocation, 1, false, pMvp);
}
var uv = ComputeUv(tileMode);
var uvArray = new[]
{
uv.M11, uv.M12, 0f,
uv.M21, uv.M22, 0f,
uv.M31, uv.M32, 1f
};
unsafe
{
fixed (float* pUv = uvArray)
OpenGLContext.glUniformMatrix3fv(_openGLContext.UUvLocation, 1, false, new IntPtr(pUv));
}
// 6. Запускаем шейдер
gl.UseProgram(_openGLContext.Program);
OpenGLContext.glUniform1i(_openGLContext.UTexLocation, 0);
// 7. Рисуем вершины на экране
gl.BindVertexArray(_openGLContext.VAO);
gl.DrawArrays(OpenGLContext.GL_TRIANGLE_STRIP, 0, 4);
// 8. Уничтожаем EGL изображение
// _openGLContext.EglDestroyImageKHR!(_openGLContext.EglDisplay, image);
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception while OpenGL render");
}
}
private Matrix4x4 ComputeMvp(Stretch stretch, float imageWidth, float imageHeight)
{
float viewWidth = (float)Bounds.Width;
float viewHeight = (float)Bounds.Height;
float sx = 1f;
float sy = 1f;
float viewAspect = viewWidth / viewHeight;
float imgAspect = imageWidth / imageHeight;
switch (stretch)
{
case Stretch.None:
sx = imageWidth / viewWidth;
sy = imageHeight / viewHeight;
break;
case Stretch.Fill:
break;
case Stretch.Uniform:
if (imgAspect > viewAspect)
sy = viewAspect / imgAspect;
else
sx = imgAspect / viewAspect;
break;
case Stretch.UniformToFill:
if (imgAspect > viewAspect)
sx = imgAspect / viewAspect;
else
sy = viewAspect / imgAspect;
break;
}
return Matrix4x4.CreateScale(sx, sy, 1f);
}
private Matrix3x2 ComputeUv(TileMode mode)
{
float sx = 1f;
float sy = 1f;
float ox = 0f;
float oy = 0f;
switch (mode)
{
case TileMode.Tile:
sx = sy = 2f;
break;
case TileMode.FlipX:
sx = -1f;
ox = 1f;
break;
case TileMode.FlipY:
sy = -1f;
oy = 1f;
break;
case TileMode.FlipXY:
sx = sy = -1f;
ox = oy = 1f;
break;
}
return new Matrix3x2(
sx, 0,
0, sy,
ox, oy
);
}
private (float sx, float sy) ComputeScale(Stretch mode, float imageWidth, float imageHeight)
{
float viewWidth = (float)Bounds.Width;
float viewHeight = (float)Bounds.Height;
float scaleX = viewWidth / imageWidth;
float scaleY = viewHeight / imageHeight;
switch (mode)
{
case Stretch.None:
return (imageWidth / viewWidth, imageHeight / viewHeight);
case Stretch.Fill:
return (1f, 1f);
case Stretch.Uniform:
var sMin = MathF.Min(scaleX, scaleY);
return (imageWidth * sMin / viewWidth, imageHeight * sMin / viewHeight);
case Stretch.UniformToFill:
var sMax = MathF.Max(scaleX, scaleY);
return (imageWidth * sMax / viewWidth, imageHeight * sMax / viewHeight);
default:
return (1f, 1f);
}
}
}