From 021995ed35d9666adfb8e0a95bbc9c6fa8306b59 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 22 Dec 2019 17:06:19 +0300 Subject: [PATCH] Implemented GlControlBase --- samples/ControlCatalog/ControlCatalog.csproj | 4 +- samples/ControlCatalog/MainView.xaml | 1 + samples/ControlCatalog/Pages/OpenGlPage.xaml | 18 ++ .../ControlCatalog/Pages/OpenGlPage.xaml.cs | 288 ++++++++++++++++++ samples/ControlCatalog/Pages/teapot.bin | Bin 0 -> 15464 bytes src/Avalonia.Native/GlPlatformFeature.cs | 2 +- src/Avalonia.OpenGL/EglDisplay.cs | 4 +- src/Avalonia.OpenGL/GlDisplayType.cs | 6 +- src/Avalonia.OpenGL/GlEntryPointAttribute.cs | 9 +- src/Avalonia.OpenGL/GlInterface.cs | 212 +++++++++++++ src/Avalonia.OpenGL/GlInterfaceBase.cs | 17 +- src/Avalonia.OpenGL/OpenGlControlBase.cs | 149 +++++++++ src/Avalonia.X11/Glx/GlxDisplay.cs | 2 +- .../Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs | 2 +- 14 files changed, 702 insertions(+), 12 deletions(-) create mode 100644 samples/ControlCatalog/Pages/OpenGlPage.xaml create mode 100644 samples/ControlCatalog/Pages/OpenGlPage.xaml.cs create mode 100644 samples/ControlCatalog/Pages/teapot.bin create mode 100644 src/Avalonia.OpenGL/OpenGlControlBase.cs diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 4c2769bcc4..d4c4b64058 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -1,6 +1,7 @@  - netstandard2.0 + netstandard2.0 + true @@ -17,6 +18,7 @@ + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 874560a294..9749c1c4cf 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -14,6 +14,7 @@ + diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml b/samples/ControlCatalog/Pages/OpenGlPage.xaml new file mode 100644 index 0000000000..30f8f76240 --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml @@ -0,0 +1,18 @@ + + + + + + Yaw + + Pitch + + Roll + + + + + diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs new file mode 100644 index 0000000000..791c49ed8f --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -0,0 +1,288 @@ +using System; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using Avalonia; +using Avalonia.Controls; +using Avalonia.OpenGL; +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 YawProperty = + AvaloniaProperty.RegisterDirect("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 PitchProperty = + AvaloniaProperty.RegisterDirect("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 RollProperty = + AvaloniaProperty.RegisterDirect("Roll", o => o.Roll, (o, v) => o.Roll = v); + + public float Roll + { + get => _roll; + set => SetAndRaise(RollProperty, ref _roll, value); + } + + static OpenGlPageControl() + { + AffectsRender(YawProperty, PitchProperty, RollProperty); + } + + private int _vertexShader; + private int _fragmentShader; + private int _shaderProgram; + private int _vertexBufferObject; + private int _indexBufferObject; + private int _vertexArrayObject; + + private string WithVersion(string shader) => + $"#version {(DisplayType == GlDisplayType.OpenGl ? 120 : 100)}\n" + shader; + + private string WithVersionAndPrecision(string shader, string precision) => + WithVersion(DisplayType == GlDisplayType.OpenGLES ? $"precision {precision}\n{shader}" : shader); + + private string VertexShaderSource => WithVersion(@" + attribute vec3 aPos; + attribute vec3 aNormal; + uniform mat4 uModel; + uniform mat4 uProjection; + uniform mat4 uView; + + varying vec3 FragPos; + varying vec3 VecPos; + varying vec3 Normal; + void main() + { + gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0); + FragPos = vec3(uModel * vec4(aPos, 1.0)); + VecPos = aPos; + Normal = normalize(vec3(uModel * vec4(aNormal, 1.0))); + } +"); + + private string FragmentShaderSource => WithVersionAndPrecision(@" + varying vec3 FragPos; + varying vec3 VecPos; + varying vec3 Normal; + uniform float uMaxY; + uniform float uMinY; + void main() + { + float y = (VecPos.y - uMinY) / (uMaxY - uMinY); + vec3 objectColor = vec3((1 - y), 0.40 + y / 4, y * 0.75+0.25); + //vec3 objectColor = normalize(FragPos); + + + float ambientStrength = 0.3; + vec3 lightColor = vec3(1.0, 1.0, 1.0); + vec3 lightPos = vec3(uMaxY * 2, uMaxY * 2, uMaxY * 2); + 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); + + } +", "mediump float"); + + [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) + { + // 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); + var vertexSize = Marshal.SizeOf(); + 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); + fixed (void* pdata = _indices) + GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), + GL_STATIC_DRAW); + + _vertexArrayObject = GL.GenVertexArray(); + GL.BindVertexArray(_vertexArrayObject); + + 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); + GL.BindVertexArray(0); + GL.UseProgram(0); + + // Delete all resources. + GL.DeleteBuffer(_vertexBufferObject); + GL.DeleteBuffer(_indexBufferObject); + GL.DeleteVertexArray(_vertexArrayObject); + GL.DeleteProgram(_shaderProgram); + GL.DeleteShader(_fragmentShader); + GL.DeleteShader(_vertexShader); + } + + 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); + GL.BindVertexArray(_vertexArrayObject); + GL.UseProgram(_shaderProgram); + 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"); + 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.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero); + + CheckError(GL); + } + } +} diff --git a/samples/ControlCatalog/Pages/teapot.bin b/samples/ControlCatalog/Pages/teapot.bin new file mode 100644 index 0000000000000000000000000000000000000000..589eeb912dca4141b6c4d55a767446fe3aad67cf GIT binary patch literal 15464 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 literal 0 HcmV?d00001 diff --git a/src/Avalonia.Native/GlPlatformFeature.cs b/src/Avalonia.Native/GlPlatformFeature.cs index e2231dae65..7ed6eaa266 100644 --- a/src/Avalonia.Native/GlPlatformFeature.cs +++ b/src/Avalonia.Native/GlPlatformFeature.cs @@ -49,7 +49,7 @@ namespace Avalonia.Native }); } - public GlDisplayType Type => GlDisplayType.OpenGL2; + public GlDisplayType Type => GlDisplayType.OpenGl; public GlInterface GlInterface { get; } diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 57787243b2..f93bf7280c 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -81,7 +81,7 @@ namespace Avalonia.OpenGL Attributes = new[] {EGL_NONE}, Api = EGL_OPENGL_API, RenderableTypeBit = EGL_OPENGL_BIT, - Type = GlDisplayType.OpenGL2 + Type = GlDisplayType.OpenGl }, new { @@ -92,7 +92,7 @@ namespace Avalonia.OpenGL }, Api = EGL_OPENGL_ES_API, RenderableTypeBit = EGL_OPENGL_ES2_BIT, - Type = GlDisplayType.OpenGLES2 + Type = GlDisplayType.OpenGLES } }) { diff --git a/src/Avalonia.OpenGL/GlDisplayType.cs b/src/Avalonia.OpenGL/GlDisplayType.cs index 2e5178bc37..6ce7600ec6 100644 --- a/src/Avalonia.OpenGL/GlDisplayType.cs +++ b/src/Avalonia.OpenGL/GlDisplayType.cs @@ -2,7 +2,7 @@ namespace Avalonia.OpenGL { public enum GlDisplayType { - OpenGL2, - OpenGLES2 + OpenGl, + OpenGLES } -} \ No newline at end of file +} diff --git a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs index 016c3d0af9..62bf3567e4 100644 --- a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs +++ b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs @@ -5,13 +5,18 @@ namespace Avalonia.OpenGL [AttributeUsage(AttributeTargets.Property)] public class GlEntryPointAttribute : Attribute { - public string EntryPoint { get; } + public string[] EntryPoints { get; } public bool Optional { get; } public GlEntryPointAttribute(string entryPoint, bool optional = false) { - EntryPoint = entryPoint; + EntryPoints = new []{entryPoint}; Optional = optional; } + + public GlEntryPointAttribute(params string[] entryPoints) + { + EntryPoints = entryPoints; + } } } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 5614ddfb17..33208f8e6c 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; +using System.Text; using Avalonia.Platform.Interop; +using static Avalonia.OpenGL.GlConsts; namespace Avalonia.OpenGL { @@ -74,6 +76,10 @@ namespace Avalonia.OpenGL [GlEntryPoint("glGenFramebuffers")] public GlGenFramebuffers GenFramebuffers { get; } + public delegate void GlDeleteFramebuffers(int count, int[] framebuffers); + [GlEntryPoint("glDeleteFramebuffers")] + public GlDeleteFramebuffers DeleteFramebuffers { get; } + public delegate void GlBindFramebuffer(int target, int fb); [GlEntryPoint("glBindFramebuffer")] public GlBindFramebuffer BindFramebuffer { get; } @@ -106,6 +112,11 @@ namespace Avalonia.OpenGL public delegate void GlBindTexture(int target, int fb); [GlEntryPoint("glBindTexture")] public GlBindTexture BindTexture { get; } + + public delegate void GlDeleteTextures(int count, int[] textures); + [GlEntryPoint("glDeleteTextures")] + public GlDeleteTextures DeleteTextures { get; } + public delegate void GlTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntPtr data); @@ -125,6 +136,207 @@ namespace Avalonia.OpenGL [GlEntryPoint("glDrawBuffers")] public GlDrawBuffers DrawBuffers { get; } + public delegate int GlCreateShader(int shaderType); + [GlEntryPoint("glCreateShader")] + public GlCreateShader CreateShader { get; } + + public delegate void GlShaderSource(int shader, int count, IntPtr strings, IntPtr lengths); + [GlEntryPoint("glShaderSource")] + public GlShaderSource ShaderSource { get; } + + public void ShaderSourceString(int shader, string source) + { + using (var b = new Utf8Buffer(source)) + { + var ptr = b.DangerousGetHandle(); + var len = new IntPtr(b.ByteLen); + ShaderSource(shader, 1, new IntPtr(&ptr), new IntPtr(&len)); + } + } + + public delegate void GlCompileShader(int shader); + [GlEntryPoint("glCompileShader")] + public GlCompileShader CompileShader { get; } + + public delegate void GlGetShaderiv(int shader, int name, int* parameters); + [GlEntryPoint("glGetShaderiv")] + public GlGetShaderiv GetShaderiv { get; } + + public delegate void GlGetShaderInfoLog(int shader, int maxLength, out int length, void*infoLog); + [GlEntryPoint("glGetShaderInfoLog")] + public GlGetShaderInfoLog GetShaderInfoLog { get; } + + public unsafe string CompileShaderAndGetError(int shader, string source) + { + ShaderSourceString(shader, source); + CompileShader(shader); + int compiled; + GetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled != 0) + return null; + int logLength; + GetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength == 0) + logLength = 4096; + var logData = new byte[logLength]; + int len; + fixed (void* ptr = logData) + GetShaderInfoLog(shader, logLength, out len, ptr); + return Encoding.UTF8.GetString(logData,0, len); + } + + public delegate int GlCreateProgram(); + [GlEntryPoint("glCreateProgram")] + public GlCreateProgram CreateProgram { get; } + + public delegate void GlAttachShader(int program, int shader); + [GlEntryPoint("glAttachShader")] + public GlAttachShader AttachShader { get; } + + public delegate void GlLinkProgram(int program); + [GlEntryPoint("glLinkProgram")] + public GlLinkProgram LinkProgram { get; } + + public delegate void GlGetProgramiv(int program, int name, int* parameters); + [GlEntryPoint("glGetProgramiv")] + public GlGetProgramiv GetProgramiv { get; } + + public delegate void GlGetProgramInfoLog(int program, int maxLength, out int len, void* infoLog); + [GlEntryPoint("glGetProgramInfoLog")] + public GlGetProgramInfoLog GetProgramInfoLog { get; } + + public unsafe string LinkProgramAndGetError(int program) + { + LinkProgram(program); + int compiled; + GetProgramiv(program, GL_LINK_STATUS, &compiled); + if (compiled != 0) + return null; + int logLength; + GetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength); + var logData = new byte[logLength]; + int len; + fixed (void* ptr = logData) + GetProgramInfoLog(program, logLength, out len, ptr); + return Encoding.UTF8.GetString(logData,0, len); + } + + public delegate void GlBindAttribLocation(int program, int index, IntPtr name); + [GlEntryPoint("glBindAttribLocation")] + public GlBindAttribLocation BindAttribLocation { get; } + + public void BindAttribLocationString(int program, int index, string name) + { + using (var b = new Utf8Buffer(name)) + BindAttribLocation(program, index, b.DangerousGetHandle()); + } + + public delegate void GlGenBuffers(int len, int[] rv); + [GlEntryPoint("glGenBuffers")] + public GlGenBuffers GenBuffers { get; } + + public int GenBuffer() + { + var rv = new int[1]; + GenBuffers(1, rv); + return rv[0]; + } + + public delegate void GlBindBuffer(int target, int buffer); + [GlEntryPoint("glBindBuffer")] + public GlBindBuffer BindBuffer { get; } + + public delegate void GlBufferData(int target, IntPtr size, IntPtr data, int usage); + [GlEntryPoint("glBufferData")] + public GlBufferData BufferData { get; } + + public delegate int GlGetAttribLocation(int program, IntPtr name); + [GlEntryPoint("glGetAttribLocation")] + public GlGetAttribLocation GetAttribLocation { get; } + + public int GetAttribLocationString(int program, string name) + { + using (var b = new Utf8Buffer(name)) + return GetAttribLocation(program, b.DangerousGetHandle()); + } + + public delegate void GlVertexAttribPointer(int index, int size, int type, + int normalized, int stride, IntPtr pointer); + [GlEntryPoint("glVertexAttribPointer")] + public GlVertexAttribPointer VertexAttribPointer { get; } + + public delegate void GlEnableVertexAttribArray(int index); + [GlEntryPoint("glEnableVertexAttribArray")] + public GlEnableVertexAttribArray EnableVertexAttribArray { get; } + + public delegate void GlUseProgram(int program); + [GlEntryPoint("glUseProgram")] + public GlUseProgram UseProgram { get; } + + public delegate void GlDrawArrays(int mode, int first, IntPtr count); + [GlEntryPoint("glDrawArrays")] + public GlDrawArrays DrawArrays { get; } + + public delegate void GlDrawElements(int mode, int count, int type, IntPtr indices); + [GlEntryPoint("glDrawElements")] + public GlDrawElements DrawElements { get; } + + + public delegate void GlGenVertexArrays(int n, int[] rv); + [GlEntryPoint("glGenVertexArrays", "glGenVertexArraysOES")] + public GlGenVertexArrays GenVertexArrays { get; } + + public int GenVertexArray() + { + var rv = new int[1]; + GenVertexArrays(1, rv); + return rv[0]; + } + + public delegate void GlBindVertexArray(int array); + [GlEntryPoint("glBindVertexArray", "glBindVertexArrayOES")] + public GlBindVertexArray BindVertexArray { get; } + + public delegate int GlGetUniformLocation(int program, IntPtr name); + [GlEntryPoint("glGetUniformLocation")] + public GlGetUniformLocation GetUniformLocation { get; } + + public int GetUniformLocationString(int program, string name) + { + using (var b = new Utf8Buffer(name)) + return GetUniformLocation(program, b.DangerousGetHandle()); + } + + public delegate void GlUniform1f(int location, float falue); + [GlEntryPoint("glUniform1f")] + public GlUniform1f Uniform1f { get; } + + + public delegate void GlUniformMatrix4fv(int location, int count, bool transpose, void* value); + [GlEntryPoint("glUniformMatrix4fv")] + public GlUniformMatrix4fv UniformMatrix4fv { get; } + + public delegate void GlEnable(int what); + [GlEntryPoint("glEnable")] + public GlEnable Enable { get; } + + public delegate void GlDeleteBuffer(int buffer); + [GlEntryPoint("glDeleteBuffer")] + public GlDeleteBuffer DeleteBuffer { get; } + + public delegate void GlDeleteVertexArray(int array); + [GlEntryPoint("glDeleteVertexArray", "glDeleteVertexArrayOES")] + public GlDeleteVertexArray DeleteVertexArray { get; } + + public delegate void GlDeleteProgram(int program); + [GlEntryPoint("glDeleteProgram")] + public GlDeleteProgram DeleteProgram { get; } + + public delegate void GlDeleteShader(int shader); + [GlEntryPoint("glDeleteShader")] + public GlDeleteShader DeleteShader { get; } + + // ReSharper restore UnassignedGetOnlyAutoProperty } } diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs index 89ec0e77d4..445777b065 100644 --- a/src/Avalonia.OpenGL/GlInterfaceBase.cs +++ b/src/Avalonia.OpenGL/GlInterfaceBase.cs @@ -22,7 +22,22 @@ namespace Avalonia.OpenGL BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); - var proc = getProcAddress(a.EntryPoint, a.Optional); + IntPtr proc = IntPtr.Zero; + if (a.EntryPoints.Length == 1) + proc = getProcAddress(a.EntryPoints[0], a.Optional); + else + { + foreach (var ep in a.EntryPoints) + { + proc = getProcAddress(ep, true); + + } + + if (proc == IntPtr.Zero && !a.Optional) + throw new OpenGlException("Unable to resolve function by any of aliases " + + string.Join(", ", a.EntryPoints)); + } + if (proc != IntPtr.Zero) field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType)); } diff --git a/src/Avalonia.OpenGL/OpenGlControlBase.cs b/src/Avalonia.OpenGL/OpenGlControlBase.cs new file mode 100644 index 0000000000..13a60c7574 --- /dev/null +++ b/src/Avalonia.OpenGL/OpenGlControlBase.cs @@ -0,0 +1,149 @@ +using System; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.OpenGL.Imaging; +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 Size _oldSize; + protected GlDisplayType DisplayType { get; private set; } + public sealed override void Render(DrawingContext context) + { + if(!EnsureInitialized()) + return; + using (_context.MakeCurrent()) + { + using (_bitmap.Lock()) + { + var gl = _context.Display.GlInterface; + gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); + if (_oldSize != Bounds.Size) + ResizeTexture(gl); + + OnOpenGlRender(gl, _fb); + gl.Flush(); + } + } + + context.DrawImage(_bitmap, 1, new Rect(_bitmap.Size), Bounds); + base.Render(context); + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + if (_context != null) + { + using (_context.MakeCurrent()) + { + OnOpenGlDeinit(_context.Display.GlInterface, _fb); + var gl = _context.Display.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 }); + } + _bitmap.Dispose(); + + _context.Dispose(); + _context = null; + } + } + base.OnDetachedFromVisualTree(e); + } + + bool EnsureInitialized() + { + if (_context != null) + return true; + + var feature = AvaloniaLocator.Current.GetService(); + if (feature == null) + return false; + _context = feature.CreateContext(); + DisplayType = feature.Display.Type; + try + { + _bitmap = new OpenGlTextureBitmap(); + } + catch (PlatformNotSupportedException) + { + _context.Dispose(); + _context = null; + return false; + } + + using (_context.MakeCurrent()) + { + _oldSize = Bounds.Size; + var gl = _context.Display.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]; + gl.BindTexture(GL_TEXTURE_2D, _texture); + ResizeTexture(gl); + + gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + gl.DrawBuffers(1, new[] { GL_COLOR_ATTACHMENT0 }); + var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) + { + //TODO: Cleanup + return false; + } + OnOpenGlInit(_context.Display.GlInterface, _fb); + } + + return true; + } + + void ResizeTexture(GlInterface gl) + { + var size = GetPixelSize(); + gl.TexImage2D(GL_TEXTURE_2D, 0, 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); + + //TODO: destroy the previous one + var oneArr = new int[1]; + gl.GenRenderbuffers(1, oneArr); + _renderBuffer = oneArr[0]; + gl.BindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); + gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffer); + using (_bitmap.Lock()) + _bitmap.SetTexture(_texture, GL_RGBA8, size, 1); + } + + //TODO: dpi + PixelSize GetPixelSize() => + new PixelSize(Math.Max(1, (int)Bounds.Width), + Math.Max(1, (int)Bounds.Height)); + + + protected virtual void OnOpenGlInit(GlInterface gl, int fb) + { + + } + + protected virtual void OnOpenGlDeinit(GlInterface gl, int fb) + { + + } + + protected abstract void OnOpenGlRender(GlInterface gl, int fb); + } +} diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index 4b78c40737..2522ff35ab 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -10,7 +10,7 @@ namespace Avalonia.X11.Glx private readonly X11Info _x11; private readonly IntPtr _fbconfig; private readonly XVisualInfo* _visual; - public GlDisplayType Type => GlDisplayType.OpenGL2; + public GlDisplayType Type => GlDisplayType.OpenGl; public GlInterface GlInterface { get; } public XVisualInfo* VisualInfo => _visual; diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 3a7eec134e..76c149e53b 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -16,7 +16,7 @@ namespace Avalonia.Skia using (immediateContext.MakeCurrent()) { var display = gl.Display; - using (var iface = display.Type == GlDisplayType.OpenGL2 ? + using (var iface = display.Type == GlDisplayType.OpenGl ? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc)) : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc))) {