committed by
GitHub
54 changed files with 6611 additions and 406 deletions
@ -0,0 +1,29 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ControlCatalog.Pages.OpenGlPage" |
||||
|
xmlns:pages="clr-namespace:ControlCatalog.Pages"> |
||||
|
<Grid> |
||||
|
<pages:OpenGlPageControl x:Name="GL"/> |
||||
|
<StackPanel> |
||||
|
<TextBlock Text="{Binding #GL.Info}"/> |
||||
|
</StackPanel> |
||||
|
<Grid ColumnDefinitions="*,Auto" Margin="20"> |
||||
|
<StackPanel Grid.Column="1" MinWidth="300"> |
||||
|
<TextBlock>Yaw</TextBlock> |
||||
|
<Slider Value="{Binding Yaw, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
||||
|
<TextBlock>Pitch</TextBlock> |
||||
|
<Slider Value="{Binding Pitch, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
||||
|
<TextBlock>Roll</TextBlock> |
||||
|
<Slider Value="{Binding Roll, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
||||
|
<StackPanel Orientation="Horizontal"> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock> |
||||
|
</StackPanel> |
||||
|
<Slider Value="{Binding Disco, Mode=TwoWay, ElementName=GL}" Maximum="1"/> |
||||
|
</StackPanel> |
||||
|
</Grid> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,401 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.Platform.Interop; |
||||
|
using Avalonia.Threading; |
||||
|
using static Avalonia.OpenGL.GlConsts; |
||||
|
// ReSharper disable StringLiteralTypo
|
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class OpenGlPage : UserControl |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public class OpenGlPageControl : OpenGlControlBase |
||||
|
{ |
||||
|
private float _yaw; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> YawProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Yaw", o => o.Yaw, (o, v) => o.Yaw = v); |
||||
|
|
||||
|
public float Yaw |
||||
|
{ |
||||
|
get => _yaw; |
||||
|
set => SetAndRaise(YawProperty, ref _yaw, value); |
||||
|
} |
||||
|
|
||||
|
private float _pitch; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> PitchProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Pitch", o => o.Pitch, (o, v) => o.Pitch = v); |
||||
|
|
||||
|
public float Pitch |
||||
|
{ |
||||
|
get => _pitch; |
||||
|
set => SetAndRaise(PitchProperty, ref _pitch, value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private float _roll; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> RollProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Roll", o => o.Roll, (o, v) => o.Roll = v); |
||||
|
|
||||
|
public float Roll |
||||
|
{ |
||||
|
get => _roll; |
||||
|
set => SetAndRaise(RollProperty, ref _roll, value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private float _disco; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> DiscoProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Disco", o => o.Disco, (o, v) => o.Disco = v); |
||||
|
|
||||
|
public float Disco |
||||
|
{ |
||||
|
get => _disco; |
||||
|
set => SetAndRaise(DiscoProperty, ref _disco, value); |
||||
|
} |
||||
|
|
||||
|
private string _info; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v); |
||||
|
|
||||
|
public string Info |
||||
|
{ |
||||
|
get => _info; |
||||
|
private set => SetAndRaise(InfoProperty, ref _info, value); |
||||
|
} |
||||
|
|
||||
|
static OpenGlPageControl() |
||||
|
{ |
||||
|
AffectsRender<OpenGlPageControl>(YawProperty, PitchProperty, RollProperty, DiscoProperty); |
||||
|
} |
||||
|
|
||||
|
private int _vertexShader; |
||||
|
private int _fragmentShader; |
||||
|
private int _shaderProgram; |
||||
|
private int _vertexBufferObject; |
||||
|
private int _indexBufferObject; |
||||
|
private int _vertexArrayObject; |
||||
|
private GlExtrasInterface _glExt; |
||||
|
|
||||
|
private string GetShader(bool fragment, string shader) |
||||
|
{ |
||||
|
var version = (GlVersion.Type == GlProfileType.OpenGL ? |
||||
|
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 150 : 120 : |
||||
|
100); |
||||
|
var data = "#version " + version + "\n"; |
||||
|
if (GlVersion.Type == GlProfileType.OpenGLES) |
||||
|
data += "precision mediump float;\n"; |
||||
|
if (version >= 150) |
||||
|
{ |
||||
|
shader = shader.Replace("attribute", "in"); |
||||
|
if (fragment) |
||||
|
shader = shader |
||||
|
.Replace("varying", "in") |
||||
|
.Replace("//DECLAREGLFRAG", "out vec4 outFragColor;") |
||||
|
.Replace("gl_FragColor", "outFragColor"); |
||||
|
else |
||||
|
shader = shader.Replace("varying", "out"); |
||||
|
} |
||||
|
|
||||
|
data += shader; |
||||
|
|
||||
|
return data; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private string VertexShaderSource => GetShader(false, @"
|
||||
|
attribute vec3 aPos; |
||||
|
attribute vec3 aNormal; |
||||
|
uniform mat4 uModel; |
||||
|
uniform mat4 uProjection; |
||||
|
uniform mat4 uView; |
||||
|
|
||||
|
varying vec3 FragPos; |
||||
|
varying vec3 VecPos; |
||||
|
varying vec3 Normal; |
||||
|
uniform float uTime; |
||||
|
uniform float uDisco; |
||||
|
void main() |
||||
|
{ |
||||
|
float discoScale = sin(uTime * 10.0) / 10.0; |
||||
|
float distortionX = 1.0 + uDisco * cos(uTime * 20.0) / 10.0; |
||||
|
|
||||
|
float scale = 1.0 + uDisco * discoScale; |
||||
|
|
||||
|
vec3 scaledPos = aPos; |
||||
|
scaledPos.x = scaledPos.x * distortionX; |
||||
|
|
||||
|
scaledPos *= scale; |
||||
|
gl_Position = uProjection * uView * uModel * vec4(scaledPos, 1.0); |
||||
|
FragPos = vec3(uModel * vec4(aPos, 1.0)); |
||||
|
VecPos = aPos; |
||||
|
Normal = normalize(vec3(uModel * vec4(aNormal, 1.0))); |
||||
|
} |
||||
|
");
|
||||
|
|
||||
|
private string FragmentShaderSource => GetShader(true, @"
|
||||
|
varying vec3 FragPos; |
||||
|
varying vec3 VecPos; |
||||
|
varying vec3 Normal; |
||||
|
uniform float uMaxY; |
||||
|
uniform float uMinY; |
||||
|
uniform float uTime; |
||||
|
uniform float uDisco; |
||||
|
//DECLAREGLFRAG
|
||||
|
|
||||
|
void main() |
||||
|
{ |
||||
|
float y = (VecPos.y - uMinY) / (uMaxY - uMinY); |
||||
|
float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + uTime * 40.0 + y * 50.0); |
||||
|
float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - uTime * 20.0 - y * 30.0); |
||||
|
|
||||
|
vec3 discoColor = vec3( |
||||
|
0.5 + abs(0.5 - y) * cos(uTime * 10.0), |
||||
|
0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)), |
||||
|
0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0)))); |
||||
|
|
||||
|
vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25); |
||||
|
objectColor = objectColor * (1.0 - uDisco) + discoColor * uDisco; |
||||
|
|
||||
|
float ambientStrength = 0.3; |
||||
|
vec3 lightColor = vec3(1.0, 1.0, 1.0); |
||||
|
vec3 lightPos = vec3(uMaxY * 2.0, uMaxY * 2.0, uMaxY * 2.0); |
||||
|
vec3 ambient = ambientStrength * lightColor; |
||||
|
|
||||
|
|
||||
|
vec3 norm = normalize(Normal); |
||||
|
vec3 lightDir = normalize(lightPos - FragPos); |
||||
|
|
||||
|
float diff = max(dot(norm, lightDir), 0.0); |
||||
|
vec3 diffuse = diff * lightColor; |
||||
|
|
||||
|
vec3 result = (ambient + diffuse) * objectColor; |
||||
|
gl_FragColor = vec4(result, 1.0); |
||||
|
|
||||
|
} |
||||
|
");
|
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)] |
||||
|
private struct Vertex |
||||
|
{ |
||||
|
public Vector3 Position; |
||||
|
public Vector3 Normal; |
||||
|
} |
||||
|
|
||||
|
private readonly Vertex[] _points; |
||||
|
private readonly ushort[] _indices; |
||||
|
private readonly float _minY; |
||||
|
private readonly float _maxY; |
||||
|
|
||||
|
|
||||
|
public OpenGlPageControl() |
||||
|
{ |
||||
|
var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin")); |
||||
|
using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name))) |
||||
|
{ |
||||
|
var buf = new byte[sr.ReadInt32()]; |
||||
|
sr.Read(buf, 0, buf.Length); |
||||
|
var points = new float[buf.Length / 4]; |
||||
|
Buffer.BlockCopy(buf, 0, points, 0, buf.Length); |
||||
|
buf = new byte[sr.ReadInt32()]; |
||||
|
sr.Read(buf, 0, buf.Length); |
||||
|
_indices = new ushort[buf.Length / 2]; |
||||
|
Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length); |
||||
|
_points = new Vertex[points.Length / 3]; |
||||
|
for (var primitive = 0; primitive < points.Length / 3; primitive++) |
||||
|
{ |
||||
|
var srci = primitive * 3; |
||||
|
_points[primitive] = new Vertex |
||||
|
{ |
||||
|
Position = new Vector3(points[srci], points[srci + 1], points[srci + 2]) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < _indices.Length; i += 3) |
||||
|
{ |
||||
|
Vector3 a = _points[_indices[i]].Position; |
||||
|
Vector3 b = _points[_indices[i + 1]].Position; |
||||
|
Vector3 c = _points[_indices[i + 2]].Position; |
||||
|
var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b)); |
||||
|
|
||||
|
_points[_indices[i]].Normal += normal; |
||||
|
_points[_indices[i + 1]].Normal += normal; |
||||
|
_points[_indices[i + 2]].Normal += normal; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < _points.Length; i++) |
||||
|
{ |
||||
|
_points[i].Normal = Vector3.Normalize(_points[i].Normal); |
||||
|
_maxY = Math.Max(_maxY, _points[i].Position.Y); |
||||
|
_minY = Math.Min(_minY, _points[i].Position.Y); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void CheckError(GlInterface gl) |
||||
|
{ |
||||
|
int err; |
||||
|
while ((err = gl.GetError()) != GL_NO_ERROR) |
||||
|
Console.WriteLine(err); |
||||
|
} |
||||
|
|
||||
|
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb) |
||||
|
{ |
||||
|
CheckError(GL); |
||||
|
_glExt = new GlExtrasInterface(GL); |
||||
|
|
||||
|
Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}"; |
||||
|
|
||||
|
// Load the source of the vertex shader and compile it.
|
||||
|
_vertexShader = GL.CreateShader(GL_VERTEX_SHADER); |
||||
|
Console.WriteLine(GL.CompileShaderAndGetError(_vertexShader, VertexShaderSource)); |
||||
|
|
||||
|
// Load the source of the fragment shader and compile it.
|
||||
|
_fragmentShader = GL.CreateShader(GL_FRAGMENT_SHADER); |
||||
|
Console.WriteLine(GL.CompileShaderAndGetError(_fragmentShader, FragmentShaderSource)); |
||||
|
|
||||
|
// Create the shader program, attach the vertex and fragment shaders and link the program.
|
||||
|
_shaderProgram = GL.CreateProgram(); |
||||
|
GL.AttachShader(_shaderProgram, _vertexShader); |
||||
|
GL.AttachShader(_shaderProgram, _fragmentShader); |
||||
|
const int positionLocation = 0; |
||||
|
const int normalLocation = 1; |
||||
|
GL.BindAttribLocationString(_shaderProgram, positionLocation, "aPos"); |
||||
|
GL.BindAttribLocationString(_shaderProgram, normalLocation, "aNormal"); |
||||
|
Console.WriteLine(GL.LinkProgramAndGetError(_shaderProgram)); |
||||
|
CheckError(GL); |
||||
|
|
||||
|
// Create the vertex buffer object (VBO) for the vertex data.
|
||||
|
_vertexBufferObject = GL.GenBuffer(); |
||||
|
// Bind the VBO and copy the vertex data into it.
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
||||
|
CheckError(GL); |
||||
|
var vertexSize = Marshal.SizeOf<Vertex>(); |
||||
|
fixed (void* pdata = _points) |
||||
|
GL.BufferData(GL_ARRAY_BUFFER, new IntPtr(_points.Length * vertexSize), |
||||
|
new IntPtr(pdata), GL_STATIC_DRAW); |
||||
|
|
||||
|
_indexBufferObject = GL.GenBuffer(); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
||||
|
CheckError(GL); |
||||
|
fixed (void* pdata = _indices) |
||||
|
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), |
||||
|
GL_STATIC_DRAW); |
||||
|
CheckError(GL); |
||||
|
_vertexArrayObject = _glExt.GenVertexArray(); |
||||
|
_glExt.BindVertexArray(_vertexArrayObject); |
||||
|
CheckError(GL); |
||||
|
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT, |
||||
|
0, vertexSize, IntPtr.Zero); |
||||
|
GL.VertexAttribPointer(normalLocation, 3, GL_FLOAT, |
||||
|
0, vertexSize, new IntPtr(12)); |
||||
|
GL.EnableVertexAttribArray(positionLocation); |
||||
|
GL.EnableVertexAttribArray(normalLocation); |
||||
|
CheckError(GL); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected override void OnOpenGlDeinit(GlInterface GL, int fb) |
||||
|
{ |
||||
|
// Unbind everything
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, 0); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); |
||||
|
_glExt.BindVertexArray(0); |
||||
|
GL.UseProgram(0); |
||||
|
|
||||
|
// Delete all resources.
|
||||
|
GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject }); |
||||
|
_glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject }); |
||||
|
GL.DeleteProgram(_shaderProgram); |
||||
|
GL.DeleteShader(_fragmentShader); |
||||
|
GL.DeleteShader(_vertexShader); |
||||
|
} |
||||
|
|
||||
|
static Stopwatch St = Stopwatch.StartNew(); |
||||
|
protected override unsafe void OnOpenGlRender(GlInterface gl, int fb) |
||||
|
{ |
||||
|
gl.ClearColor(0, 0, 0, 0); |
||||
|
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
||||
|
gl.Enable(GL_DEPTH_TEST); |
||||
|
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height); |
||||
|
var GL = gl; |
||||
|
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
||||
|
_glExt.BindVertexArray(_vertexArrayObject); |
||||
|
GL.UseProgram(_shaderProgram); |
||||
|
CheckError(GL); |
||||
|
var projection = |
||||
|
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(Bounds.Width / Bounds.Height), |
||||
|
0.01f, 1000); |
||||
|
|
||||
|
|
||||
|
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0)); |
||||
|
var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll); |
||||
|
var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel"); |
||||
|
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView"); |
||||
|
var projectionLoc = GL.GetUniformLocationString(_shaderProgram, "uProjection"); |
||||
|
var maxYLoc = GL.GetUniformLocationString(_shaderProgram, "uMaxY"); |
||||
|
var minYLoc = GL.GetUniformLocationString(_shaderProgram, "uMinY"); |
||||
|
var timeLoc = GL.GetUniformLocationString(_shaderProgram, "uTime"); |
||||
|
var discoLoc = GL.GetUniformLocationString(_shaderProgram, "uDisco"); |
||||
|
GL.UniformMatrix4fv(modelLoc, 1, false, &model); |
||||
|
GL.UniformMatrix4fv(viewLoc, 1, false, &view); |
||||
|
GL.UniformMatrix4fv(projectionLoc, 1, false, &projection); |
||||
|
GL.Uniform1f(maxYLoc, _maxY); |
||||
|
GL.Uniform1f(minYLoc, _minY); |
||||
|
GL.Uniform1f(timeLoc, (float)St.Elapsed.TotalSeconds); |
||||
|
GL.Uniform1f(discoLoc, _disco); |
||||
|
CheckError(GL); |
||||
|
GL.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero); |
||||
|
|
||||
|
CheckError(GL); |
||||
|
if (_disco > 0.01) |
||||
|
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); |
||||
|
} |
||||
|
|
||||
|
class GlExtrasInterface : GlInterfaceBase<GlInterface.GlContextInfo> |
||||
|
{ |
||||
|
public GlExtrasInterface(GlInterface gl) : base(gl.GetProcAddress, gl.ContextInfo) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public delegate void GlDeleteVertexArrays(int count, int[] buffers); |
||||
|
[GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)] |
||||
|
[GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")] |
||||
|
public GlDeleteVertexArrays DeleteVertexArrays { get; } |
||||
|
|
||||
|
public delegate void GlBindVertexArray(int array); |
||||
|
[GlMinVersionEntryPoint("glBindVertexArray", 3,0)] |
||||
|
[GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")] |
||||
|
public GlBindVertexArray BindVertexArray { get; } |
||||
|
public delegate void GlGenVertexArrays(int n, int[] rv); |
||||
|
|
||||
|
[GlMinVersionEntryPoint("glGenVertexArrays",3,0)] |
||||
|
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")] |
||||
|
public GlGenVertexArrays GenVertexArrays { get; } |
||||
|
|
||||
|
public int GenVertexArray() |
||||
|
{ |
||||
|
var rv = new int[1]; |
||||
|
GenVertexArrays(1, rv); |
||||
|
return rv[0]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
@ -0,0 +1,50 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
|
||||
|
namespace Avalonia.Utilities |
||||
|
{ |
||||
|
public class DisposableLock |
||||
|
{ |
||||
|
private readonly object _lock = new object(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to take a lock
|
||||
|
/// </summary>
|
||||
|
/// <returns>IDisposable if succeeded to obtain the lock</returns>
|
||||
|
public IDisposable TryLock() |
||||
|
{ |
||||
|
if (Monitor.TryEnter(_lock)) |
||||
|
return new UnlockDisposable(_lock); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enters a waiting lock
|
||||
|
/// </summary>
|
||||
|
public IDisposable Lock() |
||||
|
{ |
||||
|
Monitor.Enter(_lock); |
||||
|
return new UnlockDisposable(_lock); |
||||
|
} |
||||
|
|
||||
|
private sealed class UnlockDisposable : IDisposable |
||||
|
{ |
||||
|
private object _lock; |
||||
|
|
||||
|
public UnlockDisposable(object @lock) |
||||
|
{ |
||||
|
_lock = @lock; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
object @lock = Interlocked.Exchange(ref _lock, null); |
||||
|
|
||||
|
if (@lock != null) |
||||
|
{ |
||||
|
Monitor.Exit(@lock); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Platform.Interop; |
||||
|
|
||||
|
namespace Avalonia.OpenGL |
||||
|
{ |
||||
|
public class GlBasicInfoInterface : GlBasicInfoInterface<object> |
||||
|
{ |
||||
|
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public delegate void GlGetIntegerv(int name, out int rv); |
||||
|
public delegate IntPtr GlGetString(int v); |
||||
|
public delegate IntPtr GlGetStringi(int v, int v1); |
||||
|
} |
||||
|
|
||||
|
public class GlBasicInfoInterface<TContextInfo> : GlInterfaceBase<TContextInfo> |
||||
|
{ |
||||
|
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress, TContextInfo context) : base(getProcAddress, context) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContextInfo context) : base(nativeGetProcAddress, context) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[GlEntryPoint("glGetIntegerv")] |
||||
|
public GlBasicInfoInterface.GlGetIntegerv GetIntegerv { get; } |
||||
|
|
||||
|
|
||||
|
[GlEntryPoint("glGetString")] |
||||
|
public GlBasicInfoInterface.GlGetString GetStringNative { get; } |
||||
|
|
||||
|
[GlEntryPoint("glGetStringi")] |
||||
|
public GlBasicInfoInterface.GlGetStringi GetStringiNative { get; } |
||||
|
|
||||
|
public string GetString(int v) |
||||
|
{ |
||||
|
var ptr = GetStringNative(v); |
||||
|
if (ptr != IntPtr.Zero) |
||||
|
return Marshal.PtrToStringAnsi(ptr); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public string GetString(int v, int index) |
||||
|
{ |
||||
|
var ptr = GetStringiNative(v, index); |
||||
|
if (ptr != IntPtr.Zero) |
||||
|
return Marshal.PtrToStringAnsi(ptr); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public List<string> GetExtensions() |
||||
|
{ |
||||
|
var sp = GetString(GlConsts.GL_EXTENSIONS); |
||||
|
if (sp != null) |
||||
|
return sp.Split(' ').ToList(); |
||||
|
GetIntegerv(GlConsts.GL_NUM_EXTENSIONS, out int count); |
||||
|
var rv = new List<string>(count); |
||||
|
for (var c = 0; c < count; c++) |
||||
|
rv.Add(GetString(GlConsts.GL_EXTENSIONS, c)); |
||||
|
return rv; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -1,8 +0,0 @@ |
|||||
namespace Avalonia.OpenGL |
|
||||
{ |
|
||||
public enum GlDisplayType |
|
||||
{ |
|
||||
OpenGL2, |
|
||||
OpenGLES2 |
|
||||
} |
|
||||
} |
|
||||
@ -1,53 +1,79 @@ |
|||||
using System; |
using System; |
||||
|
using System.Linq; |
||||
using System.Reflection; |
using System.Reflection; |
||||
using System.Runtime.InteropServices; |
using System.Runtime.InteropServices; |
||||
using Avalonia.Platform.Interop; |
using Avalonia.Platform.Interop; |
||||
|
|
||||
namespace Avalonia.OpenGL |
namespace Avalonia.OpenGL |
||||
{ |
{ |
||||
public class GlInterfaceBase |
public class GlInterfaceBase : GlInterfaceBase<object> |
||||
{ |
{ |
||||
|
public GlInterfaceBase(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null) |
||||
private readonly Func<string, bool, IntPtr> _getProcAddress; |
{ |
||||
public GlInterfaceBase(Func<string, bool, IntPtr> getProcAddress) |
} |
||||
|
|
||||
|
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class GlInterfaceBase<TContext> |
||||
|
{ |
||||
|
private readonly Func<string, IntPtr> _getProcAddress; |
||||
|
public GlInterfaceBase(Func<string, IntPtr> getProcAddress, TContext context) |
||||
{ |
{ |
||||
_getProcAddress = getProcAddress; |
_getProcAddress = getProcAddress; |
||||
foreach (var prop in this.GetType().GetProperties()) |
foreach (var prop in this.GetType().GetProperties()) |
||||
{ |
{ |
||||
var a = prop.GetCustomAttribute<GlEntryPointAttribute>(); |
var attrs = prop.GetCustomAttributes() |
||||
if (a != null) |
.Where(a => |
||||
|
a is IGlEntryPointAttribute || a is IGlEntryPointAttribute<TContext>) |
||||
|
.ToList(); |
||||
|
if(attrs.Count == 0) |
||||
|
continue; |
||||
|
|
||||
|
var isOptional = prop.GetCustomAttribute<GlOptionalEntryPoint>() != null; |
||||
|
|
||||
|
var fieldName = $"<{prop.Name}>k__BackingField"; |
||||
|
var field = prop.DeclaringType.GetField(fieldName, |
||||
|
BindingFlags.Instance | BindingFlags.NonPublic); |
||||
|
if (field == null) |
||||
|
throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); |
||||
|
|
||||
|
|
||||
|
IntPtr proc = IntPtr.Zero; |
||||
|
foreach (var attr in attrs) |
||||
{ |
{ |
||||
var fieldName = $"<{prop.Name}>k__BackingField"; |
if (attr is IGlEntryPointAttribute<TContext> typed) |
||||
var field = prop.DeclaringType.GetField(fieldName, |
proc = typed.GetProcAddress(context, getProcAddress); |
||||
BindingFlags.Instance | BindingFlags.NonPublic); |
else if (attr is IGlEntryPointAttribute untyped) |
||||
if (field == null) |
proc = untyped.GetProcAddress(getProcAddress); |
||||
throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); |
|
||||
var proc = getProcAddress(a.EntryPoint, a.Optional); |
|
||||
if (proc != IntPtr.Zero) |
if (proc != IntPtr.Zero) |
||||
field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType)); |
break; |
||||
} |
} |
||||
|
|
||||
|
if (proc != IntPtr.Zero) |
||||
|
field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType)); |
||||
|
else if (!isOptional) |
||||
|
throw new OpenGlException("Unable to find a suitable GL function for " + prop.Name); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
protected static Func<string, bool, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) => |
protected static Func<string, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) => |
||||
(proc, optional) => |
(proc) => |
||||
{ |
{ |
||||
using (var u = new Utf8Buffer(proc)) |
using (var u = new Utf8Buffer(proc)) |
||||
{ |
{ |
||||
var rv = func(u); |
var rv = func(u); |
||||
if (rv == IntPtr.Zero && !optional) |
|
||||
throw new OpenGlException("Missing function " + proc); |
|
||||
return rv; |
return rv; |
||||
} |
} |
||||
}; |
}; |
||||
|
|
||||
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : this(ConvertNative(nativeGetProcAddress)) |
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContext context) : this(ConvertNative(nativeGetProcAddress), context) |
||||
{ |
{ |
||||
|
|
||||
} |
} |
||||
|
|
||||
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true); |
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); |
||||
public IntPtr GetProcAddress(string proc, bool optional) => _getProcAddress(proc, optional); |
|
||||
|
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,22 @@ |
|||||
|
namespace Avalonia.OpenGL |
||||
|
{ |
||||
|
public enum GlProfileType |
||||
|
{ |
||||
|
OpenGL, |
||||
|
OpenGLES |
||||
|
} |
||||
|
|
||||
|
public struct GlVersion |
||||
|
{ |
||||
|
public GlProfileType Type { get; } |
||||
|
public int Major { get; } |
||||
|
public int Minor { get; } |
||||
|
|
||||
|
public GlVersion(GlProfileType type, int major, int minor) |
||||
|
{ |
||||
|
Type = type; |
||||
|
Major = major; |
||||
|
Minor = minor; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,8 +1,13 @@ |
|||||
|
using System; |
||||
|
|
||||
namespace Avalonia.OpenGL |
namespace Avalonia.OpenGL |
||||
{ |
{ |
||||
public interface IGlContext |
public interface IGlContext : IDisposable |
||||
{ |
{ |
||||
IGlDisplay Display { get; } |
GlVersion Version { get; } |
||||
void MakeCurrent(); |
GlInterface GlInterface { get; } |
||||
|
int SampleCount { get; } |
||||
|
int StencilSize { get; } |
||||
|
IDisposable MakeCurrent(); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,11 +0,0 @@ |
|||||
namespace Avalonia.OpenGL |
|
||||
{ |
|
||||
public interface IGlDisplay |
|
||||
{ |
|
||||
GlDisplayType Type { get; } |
|
||||
GlInterface GlInterface { get; } |
|
||||
void ClearContext(); |
|
||||
int SampleCount { get; } |
|
||||
int StencilSize { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,9 @@ |
|||||
|
using Avalonia.OpenGL.Imaging; |
||||
|
|
||||
|
namespace Avalonia.OpenGL |
||||
|
{ |
||||
|
public interface IOpenGlAwarePlatformRenderInterface |
||||
|
{ |
||||
|
IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.OpenGL.Imaging |
||||
|
{ |
||||
|
public interface IOpenGlTextureBitmapImpl : IBitmapImpl |
||||
|
{ |
||||
|
IDisposable Lock(); |
||||
|
void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling); |
||||
|
void SetDirty(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.OpenGL.Imaging |
||||
|
{ |
||||
|
public class OpenGlTextureBitmap : Bitmap, IAffectsRender |
||||
|
{ |
||||
|
private IOpenGlTextureBitmapImpl _impl; |
||||
|
static IOpenGlTextureBitmapImpl CreateOrThrow() |
||||
|
{ |
||||
|
if (!(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() is IOpenGlAwarePlatformRenderInterface |
||||
|
glAware)) |
||||
|
throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration"); |
||||
|
return glAware.CreateOpenGlTextureBitmap(); |
||||
|
} |
||||
|
|
||||
|
public OpenGlTextureBitmap() |
||||
|
: base(CreateOrThrow()) |
||||
|
{ |
||||
|
_impl = (IOpenGlTextureBitmapImpl)PlatformImpl.Item; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Lock() => _impl.Lock(); |
||||
|
|
||||
|
public void SetTexture(int textureId, int internalFormat, PixelSize size, double dpiScaling) |
||||
|
{ |
||||
|
_impl.SetBackBuffer(textureId, internalFormat, size, dpiScaling); |
||||
|
SetIsDirty(); |
||||
|
} |
||||
|
|
||||
|
public void SetIsDirty() |
||||
|
{ |
||||
|
if (Dispatcher.UIThread.CheckAccess()) |
||||
|
CallInvalidated(); |
||||
|
else |
||||
|
Dispatcher.UIThread.Post(CallInvalidated); |
||||
|
} |
||||
|
|
||||
|
private void CallInvalidated() => Invalidated?.Invoke(this, EventArgs.Empty); |
||||
|
|
||||
|
public event EventHandler Invalidated; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,215 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Logging; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.OpenGL.Imaging; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.VisualTree; |
||||
|
using static Avalonia.OpenGL.GlConsts; |
||||
|
|
||||
|
namespace Avalonia.OpenGL |
||||
|
{ |
||||
|
public abstract class OpenGlControlBase : Control |
||||
|
{ |
||||
|
private IGlContext _context; |
||||
|
private int _fb, _texture, _renderBuffer; |
||||
|
private OpenGlTextureBitmap _bitmap; |
||||
|
private PixelSize _oldSize; |
||||
|
private bool _glFailed; |
||||
|
protected GlVersion GlVersion { get; private set; } |
||||
|
public sealed override void Render(DrawingContext context) |
||||
|
{ |
||||
|
if(!EnsureInitialized()) |
||||
|
return; |
||||
|
|
||||
|
using (_context.MakeCurrent()) |
||||
|
{ |
||||
|
using (_bitmap.Lock()) |
||||
|
{ |
||||
|
var gl = _context.GlInterface; |
||||
|
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); |
||||
|
if (_oldSize != GetPixelSize()) |
||||
|
ResizeTexture(gl); |
||||
|
|
||||
|
OnOpenGlRender(gl, _fb); |
||||
|
gl.Flush(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
context.DrawImage(_bitmap, new Rect(_bitmap.Size), Bounds); |
||||
|
base.Render(context); |
||||
|
} |
||||
|
|
||||
|
void DoCleanup(bool callUserDeinit) |
||||
|
{ |
||||
|
if (_context != null) |
||||
|
{ |
||||
|
using (_context.MakeCurrent()) |
||||
|
{ |
||||
|
var gl = _context.GlInterface; |
||||
|
gl.BindTexture(GL_TEXTURE_2D, 0); |
||||
|
gl.BindFramebuffer(GL_FRAMEBUFFER, 0); |
||||
|
gl.DeleteFramebuffers(1, new[] { _fb }); |
||||
|
using (_bitmap.Lock()) |
||||
|
_bitmap.SetTexture(0, 0, new PixelSize(1, 1), 1); |
||||
|
gl.DeleteTextures(1, new[] { _texture }); |
||||
|
gl.DeleteRenderbuffers(1, new[] { _renderBuffer }); |
||||
|
_bitmap.Dispose(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (callUserDeinit) |
||||
|
OnOpenGlDeinit(_context.GlInterface, _fb); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_context.Dispose(); |
||||
|
_context = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
||||
|
{ |
||||
|
DoCleanup(true); |
||||
|
base.OnDetachedFromVisualTree(e); |
||||
|
} |
||||
|
|
||||
|
bool EnsureInitialized() |
||||
|
{ |
||||
|
if (_context != null) |
||||
|
return true; |
||||
|
|
||||
|
if (_glFailed) |
||||
|
return false; |
||||
|
|
||||
|
var feature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>(); |
||||
|
if (feature == null) |
||||
|
return false; |
||||
|
try |
||||
|
{ |
||||
|
_context = feature.CreateContext(); |
||||
|
|
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", |
||||
|
"Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e); |
||||
|
_glFailed = true; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
GlVersion = _context.Version; |
||||
|
try |
||||
|
{ |
||||
|
_bitmap = new OpenGlTextureBitmap(); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
_context.Dispose(); |
||||
|
_context = null; |
||||
|
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", |
||||
|
"Unable to initialize OpenGL: unable to create OpenGlTextureBitmap: {exception}", e); |
||||
|
_glFailed = true; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
using (_context.MakeCurrent()) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_oldSize = GetPixelSize(); |
||||
|
var gl = _context.GlInterface; |
||||
|
var oneArr = new int[1]; |
||||
|
gl.GenFramebuffers(1, oneArr); |
||||
|
_fb = oneArr[0]; |
||||
|
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); |
||||
|
|
||||
|
gl.GenTextures(1, oneArr); |
||||
|
_texture = oneArr[0]; |
||||
|
|
||||
|
ResizeTexture(gl); |
||||
|
|
||||
|
gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); |
||||
|
|
||||
|
var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); |
||||
|
if (status != GL_FRAMEBUFFER_COMPLETE) |
||||
|
{ |
||||
|
int code; |
||||
|
while ((code = gl.GetError()) != 0) |
||||
|
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", |
||||
|
"Unable to initialize OpenGL FBO: {code}", code); |
||||
|
|
||||
|
_glFailed = true; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
catch(Exception e) |
||||
|
{ |
||||
|
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", |
||||
|
"Unable to initialize OpenGL FBO: {exception}", e); |
||||
|
_glFailed = true; |
||||
|
} |
||||
|
|
||||
|
if (!_glFailed) |
||||
|
OnOpenGlInit(_context.GlInterface, _fb); |
||||
|
} |
||||
|
|
||||
|
if (_glFailed) |
||||
|
{ |
||||
|
DoCleanup(false); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void ResizeTexture(GlInterface gl) |
||||
|
{ |
||||
|
var size = GetPixelSize(); |
||||
|
|
||||
|
gl.GetIntegerv( GL_TEXTURE_BINDING_2D, out var oldTexture); |
||||
|
gl.BindTexture(GL_TEXTURE_2D, _texture); |
||||
|
gl.TexImage2D(GL_TEXTURE_2D, 0, |
||||
|
GlVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8, |
||||
|
size.Width, size.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero); |
||||
|
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
||||
|
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
||||
|
gl.BindTexture(GL_TEXTURE_2D, oldTexture); |
||||
|
|
||||
|
gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer); |
||||
|
gl.DeleteRenderbuffers(1, new[] { _renderBuffer }); |
||||
|
var oneArr = new int[1]; |
||||
|
gl.GenRenderbuffers(1, oneArr); |
||||
|
_renderBuffer = oneArr[0]; |
||||
|
gl.BindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); |
||||
|
gl.RenderbufferStorage(GL_RENDERBUFFER, |
||||
|
GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, |
||||
|
size.Width, size.Height); |
||||
|
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffer); |
||||
|
gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer); |
||||
|
using (_bitmap.Lock()) |
||||
|
_bitmap.SetTexture(_texture, GL_RGBA8, size, 1); |
||||
|
} |
||||
|
|
||||
|
PixelSize GetPixelSize() |
||||
|
{ |
||||
|
var scaling = VisualRoot.RenderScaling; |
||||
|
return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)), |
||||
|
Math.Max(1, (int)(Bounds.Height * scaling))); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
protected virtual void OnOpenGlInit(GlInterface gl, int fb) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected virtual void OnOpenGlDeinit(GlInterface gl, int fb) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected abstract void OnOpenGlRender(GlInterface gl, int fb); |
||||
|
} |
||||
|
} |
||||
@ -1,50 +1,11 @@ |
|||||
using System; |
using System; |
||||
using System.Threading; |
using System.Threading; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
namespace Avalonia.Rendering |
namespace Avalonia.Rendering |
||||
{ |
{ |
||||
public class ManagedDeferredRendererLock : IDeferredRendererLock |
public class ManagedDeferredRendererLock : DisposableLock, IDeferredRendererLock |
||||
{ |
{ |
||||
private readonly object _lock = new object(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Tries to lock the target surface or window
|
|
||||
/// </summary>
|
|
||||
/// <returns>IDisposable if succeeded to obtain the lock</returns>
|
|
||||
public IDisposable TryLock() |
|
||||
{ |
|
||||
if (Monitor.TryEnter(_lock)) |
|
||||
return new UnlockDisposable(_lock); |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Enters a waiting lock, only use from platform code, not from the renderer
|
|
||||
/// </summary>
|
|
||||
public IDisposable Lock() |
|
||||
{ |
|
||||
Monitor.Enter(_lock); |
|
||||
return new UnlockDisposable(_lock); |
|
||||
} |
|
||||
|
|
||||
private sealed class UnlockDisposable : IDisposable |
|
||||
{ |
|
||||
private object _lock; |
|
||||
|
|
||||
public UnlockDisposable(object @lock) |
|
||||
{ |
|
||||
_lock = @lock; |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
object @lock = Interlocked.Exchange(ref _lock, null); |
|
||||
|
|
||||
if (@lock != null) |
|
||||
{ |
|
||||
Monitor.Exit(@lock); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,16 +1,19 @@ |
|||||
using System; |
using System; |
||||
|
using SkiaSharp; |
||||
|
|
||||
namespace Avalonia.Skia |
namespace Avalonia.Skia |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Custom Skia render target.
|
/// Custom Skia render target.
|
||||
/// </summary>
|
/// </summary>
|
||||
public interface ICustomSkiaRenderTarget : IDisposable |
public interface ISkiaGpuRenderTarget : IDisposable |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Start rendering to this render target.
|
/// Start rendering to this render target.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <returns></returns>
|
/// <returns></returns>
|
||||
ICustomSkiaRenderSession BeginRendering(); |
ISkiaGpuRenderSession BeginRenderingSession(); |
||||
|
|
||||
|
bool IsCorrupted { get; } |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.OpenGL.Imaging; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
class GlSkiaGpu : IOpenGlAwareSkiaGpu |
||||
|
{ |
||||
|
private GRContext _grContext; |
||||
|
|
||||
|
public GlSkiaGpu(IWindowingPlatformGlFeature gl, long? maxResourceBytes) |
||||
|
{ |
||||
|
var context = gl.MainContext; |
||||
|
using (context.MakeCurrent()) |
||||
|
{ |
||||
|
using (var iface = context.Version.Type == GlProfileType.OpenGL ? |
||||
|
GRGlInterface.AssembleGlInterface((_, proc) => context.GlInterface.GetProcAddress(proc)) : |
||||
|
GRGlInterface.AssembleGlesInterface((_, proc) => context.GlInterface.GetProcAddress(proc))) |
||||
|
{ |
||||
|
_grContext = GRContext.Create(GRBackend.OpenGL, iface); |
||||
|
if (maxResourceBytes.HasValue) |
||||
|
{ |
||||
|
_grContext.GetResourceCacheLimits(out var maxResources, out _); |
||||
|
_grContext.SetResourceCacheLimits(maxResources, maxResourceBytes.Value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
foreach (var surface in surfaces) |
||||
|
{ |
||||
|
if (surface is IGlPlatformSurface glSurface) |
||||
|
{ |
||||
|
return new GlRenderTarget(_grContext, glSurface); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap() => new OpenGlTextureBitmapImpl(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.OpenGL.Imaging; |
||||
|
using Avalonia.Skia.Helpers; |
||||
|
using Avalonia.Utilities; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
class OpenGlTextureBitmapImpl : IOpenGlTextureBitmapImpl, IDrawableBitmapImpl |
||||
|
{ |
||||
|
private DisposableLock _lock = new DisposableLock(); |
||||
|
private int _textureId; |
||||
|
private int _internalFormat; |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
using (Lock()) |
||||
|
{ |
||||
|
_textureId = 0; |
||||
|
PixelSize = new PixelSize(1, 1); |
||||
|
Version++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Vector Dpi { get; private set; } = new Vector(96, 96); |
||||
|
public PixelSize PixelSize { get; private set; } = new PixelSize(1, 1); |
||||
|
public int Version { get; private set; } = 0; |
||||
|
|
||||
|
public void Save(string fileName) => throw new System.NotSupportedException(); |
||||
|
public void Save(Stream stream) => throw new System.NotSupportedException(); |
||||
|
|
||||
|
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) |
||||
|
{ |
||||
|
// For now silently ignore
|
||||
|
if (context.GrContext == null) |
||||
|
return; |
||||
|
|
||||
|
using (Lock()) |
||||
|
{ |
||||
|
if (_textureId == 0) |
||||
|
return; |
||||
|
using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false, |
||||
|
new GRGlTextureInfo( |
||||
|
GlConsts.GL_TEXTURE_2D, (uint)_textureId, |
||||
|
(uint)_internalFormat))) |
||||
|
using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft, |
||||
|
SKColorType.Rgba8888)) |
||||
|
{ |
||||
|
// Again, silently ignore, if something went wrong it's not our fault
|
||||
|
if (surface == null) |
||||
|
return; |
||||
|
|
||||
|
using (var snapshot = surface.Snapshot()) |
||||
|
context.Canvas.DrawImage(snapshot, sourceRect, destRect, paint); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IDisposable Lock() => _lock.Lock(); |
||||
|
|
||||
|
public void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling) |
||||
|
{ |
||||
|
using (_lock.Lock()) |
||||
|
{ |
||||
|
_textureId = textureId; |
||||
|
_internalFormat = internalFormat; |
||||
|
PixelSize = pixelSize; |
||||
|
Dpi = new Vector(96 * dpiScaling, 96 * dpiScaling); |
||||
|
Version++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void SetDirty() |
||||
|
{ |
||||
|
using (_lock.Lock()) |
||||
|
Version++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue