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.Linq; |
|||
using System.Reflection; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Platform.Interop; |
|||
|
|||
namespace Avalonia.OpenGL |
|||
{ |
|||
public class GlInterfaceBase |
|||
public class GlInterfaceBase : GlInterfaceBase<object> |
|||
{ |
|||
|
|||
private readonly Func<string, bool, IntPtr> _getProcAddress; |
|||
public GlInterfaceBase(Func<string, bool, IntPtr> getProcAddress) |
|||
public GlInterfaceBase(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null) |
|||
{ |
|||
} |
|||
|
|||
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; |
|||
foreach (var prop in this.GetType().GetProperties()) |
|||
{ |
|||
var a = prop.GetCustomAttribute<GlEntryPointAttribute>(); |
|||
if (a != null) |
|||
var attrs = prop.GetCustomAttributes() |
|||
.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"; |
|||
var field = prop.DeclaringType.GetField(fieldName, |
|||
BindingFlags.Instance | BindingFlags.NonPublic); |
|||
if (field == null) |
|||
throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); |
|||
var proc = getProcAddress(a.EntryPoint, a.Optional); |
|||
if (attr is IGlEntryPointAttribute<TContext> typed) |
|||
proc = typed.GetProcAddress(context, getProcAddress); |
|||
else if (attr is IGlEntryPointAttribute untyped) |
|||
proc = untyped.GetProcAddress(getProcAddress); |
|||
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) => |
|||
(proc, optional) => |
|||
protected static Func<string, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) => |
|||
(proc) => |
|||
{ |
|||
using (var u = new Utf8Buffer(proc)) |
|||
{ |
|||
var rv = func(u); |
|||
if (rv == IntPtr.Zero && !optional) |
|||
throw new OpenGlException("Missing function " + proc); |
|||
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, bool optional) => _getProcAddress(proc, optional); |
|||
|
|||
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); |
|||
} |
|||
} |
|||
|
|||
@ -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 |
|||
{ |
|||
public interface IGlContext |
|||
public interface IGlContext : IDisposable |
|||
{ |
|||
IGlDisplay Display { get; } |
|||
void MakeCurrent(); |
|||
GlVersion Version { get; } |
|||
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.Threading; |
|||
using Avalonia.Utilities; |
|||
|
|||
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 SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
/// <summary>
|
|||
/// Custom Skia render target.
|
|||
/// </summary>
|
|||
public interface ICustomSkiaRenderTarget : IDisposable |
|||
public interface ISkiaGpuRenderTarget : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Start rendering to this render target.
|
|||
/// </summary>
|
|||
/// <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