diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 18d222d8097..edcbcbe988d 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -30,10 +30,12 @@ + + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 4588b979bee..5e47f36c0bb 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -127,6 +127,9 @@ + + + diff --git a/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml b/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml new file mode 100644 index 00000000000..064d6cf1215 --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml @@ -0,0 +1,30 @@ + + + + + + + + Yaw + + Pitch + + Roll + + + D + I + S + C + O + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml.cs b/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml.cs new file mode 100644 index 00000000000..a4e2964ba12 --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml.cs @@ -0,0 +1,70 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages.OpenGl; + +public partial class GlPageKnobs : UserControl +{ + public GlPageKnobs() + { + AvaloniaXamlLoader.Load(this); + } + + 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); + } + + + private float _disco; + + public static readonly DirectProperty DiscoProperty = + AvaloniaProperty.RegisterDirect("Disco", o => o.Disco, (o, v) => o.Disco = v); + + public float Disco + { + get => _disco; + set => SetAndRaise(DiscoProperty, ref _disco, value); + } + + private string _info = string.Empty; + + public static readonly DirectProperty InfoProperty = + AvaloniaProperty.RegisterDirect("Info", o => o.Info, (o, v) => o.Info = v); + + public string Info + { + get => _info; + private set => SetAndRaise(InfoProperty, ref _info, value); + } +} \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/OpenGl/OpenGlContent.cs b/samples/ControlCatalog/Pages/OpenGl/OpenGlContent.cs new file mode 100644 index 00000000000..c975ca1e85b --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGl/OpenGlContent.cs @@ -0,0 +1,311 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using Avalonia; +using Avalonia.OpenGL; +using static Avalonia.OpenGL.GlConsts; +// ReSharper disable StringLiteralTypo + +namespace ControlCatalog.Pages.OpenGl; + +internal class OpenGlContent +{ + private int _vertexShader; + private int _fragmentShader; + private int _shaderProgram; + private int _vertexBufferObject; + private int _indexBufferObject; + private int _vertexArrayObject; + private GlVersion GlVersion; + + 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 OpenGlContent() + { + 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 static void CheckError(GlInterface gl) + { + int err; + while ((err = gl.GetError()) != GL_NO_ERROR) + Console.WriteLine(err); + } + + public string Info { get; private set; } + + public unsafe void Init(GlInterface GL, GlVersion version) + { + GlVersion = version; + CheckError(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(); + 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 = GL.GenVertexArray(); + GL.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); + + } + + public void Deinit(GlInterface GL) + { + // 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); + } + + static Stopwatch St = Stopwatch.StartNew(); + + public unsafe void OnOpenGlRender(GlInterface gl, int fb, PixelSize size, + float yaw, float pitch, float roll, float disco) + { + gl.Viewport(0, 0, size.Width, size.Height); + gl.ClearDepth(1); + gl.Disable(GL_CULL_FACE); + gl.Disable(GL_SCISSOR_TEST); + gl.DepthFunc(GL_LESS); + gl.DepthMask(1); + + gl.ClearColor(0, 0, 0, 0); + gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + gl.Enable(GL_DEPTH_TEST); + + + var GL = gl; + + GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); + GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); + GL.BindVertexArray(_vertexArrayObject); + GL.UseProgram(_shaderProgram); + CheckError(GL); + var projection = + Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(size.Width / size.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); + } +} \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/OpenGl/OpenGlFbo.cs b/samples/ControlCatalog/Pages/OpenGl/OpenGlFbo.cs new file mode 100644 index 00000000000..c6cc7d920fc --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGl/OpenGlFbo.cs @@ -0,0 +1,118 @@ +using System; +using Avalonia; +using Avalonia.Logging; +using Avalonia.OpenGL; +using SkiaSharp; +using static Avalonia.OpenGL.GlConsts; +namespace ControlCatalog.Pages.OpenGl; + +internal class OpenGlFbo : IDisposable +{ + private readonly GRContext _grContext; + private int _fbo; + private int _depthBuffer; + private int _texture; + private PixelSize _size; + public PixelSize Size => _size; + public GlInterface Gl => Context.GlInterface; + public IGlContext Context { get; } + + public OpenGlFbo(IGlContext context, GRContext grContext) + { + _grContext = grContext; + Context = context; + _fbo = Gl.GenFramebuffer(); + } + + public void Resize(PixelSize size) + { + if(_size == size) + return; + + if (_texture != 0) + Gl.DeleteTexture(_texture); + _texture = 0; + if(_depthBuffer != 0) + Gl.DeleteRenderbuffer(_depthBuffer); + _depthBuffer = 0; + Gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); + + _texture = Gl.GenTexture(); + + var textureFormat = Context.Version.Type == GlProfileType.OpenGLES && Context.Version.Major == 2 + ? GL_RGBA + : GL_RGBA8; + + Gl.BindTexture(GL_TEXTURE_2D, _texture); + Gl.TexImage2D(GL_TEXTURE_2D, 0, textureFormat, size.Width, size.Height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, IntPtr.Zero); + Gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + + _depthBuffer = Gl.GenRenderbuffer(); + var depthFormat = Context.Version.Type == GlProfileType.OpenGLES + ? GL_DEPTH_COMPONENT16 + : GL_DEPTH_COMPONENT; + Gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer); + Gl.RenderbufferStorage(GL_RENDERBUFFER, depthFormat, size.Width, size.Height); + Gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer); + + var status = Gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + IsValid = (status == GL_FRAMEBUFFER_COMPLETE); + if(!IsValid) + { + int code = Gl.GetError(); + Console.WriteLine("Unable to configure OpenGL FBO: " + code); + } + + _size = size; + } + + public bool IsValid { get; private set; } + + public int Fbo => _fbo; + + public SKImage? Snapshot() + { + Gl.Flush(); + _grContext.ResetContext(); + + using var texture = new GRBackendTexture(_size.Width, _size.Height, false, + new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)_texture, SKColorType.Rgba8888.ToGlSizedFormat())); + + var surf = SKSurface.Create(_grContext, texture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); + if (surf == null) + { + using var unformatted = new GRBackendTexture(_size.Width, _size.Height, false, + new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)_texture)); + + surf = SKSurface.Create(_grContext, unformatted, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); + } + + SKImage? rv; + using (surf) + rv = surf?.Snapshot(); + _grContext.Flush(); + return rv; + /* + var target = new GRBackendRenderTarget(_size.Width, _size.Height, 0, 0, + new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); + SKImage rv; + using (var surface = SKSurface.Create(_grContext, target, + GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888, + new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal))) + rv = surface.Snapshot(); + _grContext.Flush(); + return rv;*/ + } + + public void Dispose() + { + if(_fbo != 0) + Gl.DeleteFramebuffer(_fbo); + _fbo = 0; + if (_depthBuffer != 0) + Gl.DeleteRenderbuffer(_depthBuffer); + if(_texture != 0) + Gl.DeleteTexture(_texture); + } +} \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/OpenGl/OpenGlLeasePage.xaml b/samples/ControlCatalog/Pages/OpenGl/OpenGlLeasePage.xaml new file mode 100644 index 00000000000..ffa7cad4d86 --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGl/OpenGlLeasePage.xaml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/OpenGl/OpenGlLeasePage.xaml.cs b/samples/ControlCatalog/Pages/OpenGl/OpenGlLeasePage.xaml.cs new file mode 100644 index 00000000000..91c4ce0dbe9 --- /dev/null +++ b/samples/ControlCatalog/Pages/OpenGl/OpenGlLeasePage.xaml.cs @@ -0,0 +1,216 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using Avalonia.OpenGL; +using Avalonia.Platform; +using Avalonia.Rendering.Composition; +using Avalonia.Skia; +using ControlCatalog.Pages.OpenGl; +using SkiaSharp; +using static Avalonia.OpenGL.GlConsts; + +namespace ControlCatalog.Pages; + +public class OpenGlLeasePage : UserControl +{ + private readonly Control _viewport; + private readonly GlPageKnobs _knobs; + private CompositionCustomVisual? _visual; + + class GlVisual : CompositionCustomVisualHandler + { + private OpenGlContent _content; + private Parameters _parameters; + private bool _contentInitialized; + private OpenGlFbo? _fbo; + private bool _reRender; + private IGlContext? _gl; + + public GlVisual(OpenGlContent content, Parameters parameters) + { + _content = content; + _parameters = parameters; + } + + public override void OnRender(ImmediateDrawingContext drawingContext) + { + if (_parameters.Disco > 0.01f) + RegisterForNextAnimationFrameUpdate(); + var bounds = GetRenderBounds(); + var size = PixelSize.FromSize(bounds.Size, 1); + if (size.Width < 1 || size.Height < 1) + return; + + if(drawingContext.TryGetFeature(out var skiaFeature)) + { + using var skiaLease = skiaFeature.Lease(); + var grContext = skiaLease.GrContext; + if (grContext == null) + return; + SKImage? snapshot; + using (var platformApiLease = skiaLease.TryLeasePlatformGraphicsApi()) + { + if (platformApiLease?.Context is not IGlContext glContext) + return; + + var gl = glContext.GlInterface; + if (_gl != glContext) + { + // The old context is lost + _fbo = null; + _contentInitialized = false; + _gl = glContext; + } + + gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFb); + + _fbo ??= new OpenGlFbo(glContext, grContext); + if (_fbo.Size != size) + _fbo.Resize(size); + + gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo.Fbo); + + + if (!_contentInitialized) + { + _content.Init(gl, glContext.Version); + _contentInitialized = true; + } + + + + _content.OnOpenGlRender(gl, _fbo.Fbo, size, _parameters.Yaw, _parameters.Pitch, + _parameters.Roll, _parameters.Disco); + + snapshot = _fbo.Snapshot(); + gl.BindFramebuffer(GL_FRAMEBUFFER, oldFb); + } + + using(snapshot) + if (snapshot != null) + skiaLease.SkCanvas.DrawImage(snapshot, new SKRect(0, 0, + (float)bounds.Width, (float)bounds.Height)); + } + } + + public override void OnAnimationFrameUpdate() + { + if (_reRender || _parameters.Disco > 0.01f) + { + _reRender = false; + Invalidate(); + } + + base.OnAnimationFrameUpdate(); + } + + public override void OnMessage(object message) + { + if (message is Parameters p) + { + _parameters = p; + _reRender = true; + RegisterForNextAnimationFrameUpdate(); + } + else if (message is DisposeMessage) + { + if (_gl != null) + { + try + { + if (_fbo != null || _contentInitialized) + { + using (_gl.MakeCurrent()) + { + if (_contentInitialized) + _content.Deinit(_gl.GlInterface); + _contentInitialized = false; + _fbo?.Dispose(); + _fbo = null; + } + } + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + + _gl = null; + } + } + + base.OnMessage(message); + } + } + + public class Parameters + { + public float Yaw; + public float Pitch; + public float Roll; + public float Disco; + } + + public class DisposeMessage + { + + } + + public OpenGlLeasePage() + { + AvaloniaXamlLoader.Load(this); + _viewport = this.FindControl("Viewport")!; + _viewport.AttachedToVisualTree += ViewportAttachedToVisualTree; + _viewport.DetachedFromVisualTree += ViewportDetachedFromVisualTree; + _knobs = this.FindControl("Knobs")!; + _knobs.PropertyChanged += KnobsPropertyChanged; + } + + private void KnobsPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs change) + { + if (change.Property == GlPageKnobs.YawProperty + || change.Property == GlPageKnobs.RollProperty + || change.Property == GlPageKnobs.PitchProperty + || change.Property == GlPageKnobs.DiscoProperty) + _visual?.SendHandlerMessage(GetParameters()); + } + + Parameters GetParameters() => new() + { + Yaw = _knobs!.Yaw, Pitch = _knobs.Pitch, Roll = _knobs.Roll, Disco = _knobs.Disco + }; + + private void ViewportAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + var visual = ElementComposition.GetElementVisual(_viewport!); + if(visual == null) + return; + _visual = visual.Compositor.CreateCustomVisual(new GlVisual(new OpenGlContent(), GetParameters())); + ElementComposition.SetElementChildVisual(_viewport, _visual); + UpdateSize(Bounds.Size); + } + + private void UpdateSize(Size size) + { + if (_visual != null) + _visual.Size = new Vector(size.Width, size.Height); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var size = base.ArrangeOverride(finalSize); + UpdateSize(size); + return size; + } + + private void ViewportDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + _visual?.SendHandlerMessage(new DisposeMessage()); + _visual = null; + ElementComposition.SetElementChildVisual(_viewport, null); + base.OnDetachedFromVisualTree(e); + } +} \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml b/samples/ControlCatalog/Pages/OpenGlPage.xaml index 2a97956e308..2d1285566c5 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml @@ -1,29 +1,10 @@ + xmlns:pages="using:ControlCatalog.Pages" + xmlns:openGl="clr-namespace:ControlCatalog.Pages.OpenGl"> - - - - - - Yaw - - Pitch - - Roll - - - D - I - S - C - O - - - - + diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index c77d65ddf19..494a672b947 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -1,375 +1,56 @@ -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.Markup.Xaml; using Avalonia.OpenGL; using Avalonia.OpenGL.Controls; -using Avalonia.Platform.Interop; -using Avalonia.Threading; -using static Avalonia.OpenGL.GlConsts; +using ControlCatalog.Pages.OpenGl; + // ReSharper disable StringLiteralTypo namespace ControlCatalog.Pages { public class OpenGlPage : UserControl { - + public OpenGlPage() + { + AvaloniaXamlLoader.Load(this); + this.FindControl("GL") + !.Init(this.FindControl("Knobs")!); + } } public class OpenGlPageControl : OpenGlControlBase { - private float _yaw; - - public static readonly DirectProperty YawProperty = - AvaloniaProperty.RegisterDirect("Yaw", o => o.Yaw, (o, v) => o.Yaw = v); + private OpenGlContent _content = new(); + private GlPageKnobs? _knobs; - public float Yaw + public void Init(GlPageKnobs knobs) { - get => _yaw; - set => SetAndRaise(YawProperty, ref _yaw, value); + _knobs = knobs; + _knobs.PropertyChanged += KnobsPropertyChanged; } - private float _pitch; - - public static readonly DirectProperty PitchProperty = - AvaloniaProperty.RegisterDirect("Pitch", o => o.Pitch, (o, v) => o.Pitch = v); - - public float Pitch + private void KnobsPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs change) { - 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); - } - - - private float _disco; - - public static readonly DirectProperty DiscoProperty = - AvaloniaProperty.RegisterDirect("Disco", o => o.Disco, (o, v) => o.Disco = v); - - public float Disco - { - get => _disco; - set => SetAndRaise(DiscoProperty, ref _disco, value); - } - - private string _info = string.Empty; - - public static readonly DirectProperty InfoProperty = - AvaloniaProperty.RegisterDirect("Info", o => o.Info, (o, v) => o.Info = v); - - public string Info - { - get => _info; - private set => SetAndRaise(InfoProperty, ref _info, value); + if (change.Property == GlPageKnobs.YawProperty + || change.Property == GlPageKnobs.RollProperty + || change.Property == GlPageKnobs.PitchProperty + || change.Property == GlPageKnobs.DiscoProperty) + RequestNextFrameRendering(); } - private int _vertexShader; - private int _fragmentShader; - private int _shaderProgram; - private int _vertexBufferObject; - private int _indexBufferObject; - private int _vertexArrayObject; - - 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; + protected override unsafe void OnOpenGlInit(GlInterface GL) => _content.Init(GL, GlVersion); + protected override void OnOpenGlDeinit(GlInterface GL) => _content.Deinit(GL); - 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 static void CheckError(GlInterface gl) - { - int err; - while ((err = gl.GetError()) != GL_NO_ERROR) - Console.WriteLine(err); - } - - protected override unsafe void OnOpenGlInit(GlInterface GL) - { - CheckError(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(); - 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 = GL.GenVertexArray(); - GL.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) - { - // 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); - } - - 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); - GL.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) - RequestNextFrameRendering(); - } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - if (change.Property == YawProperty || change.Property == RollProperty || change.Property == PitchProperty || - change.Property == DiscoProperty) + if (_knobs == null) + return; + _content.OnOpenGlRender(gl, fb, new PixelSize((int)Bounds.Width, (int)Bounds.Height), + _knobs.Yaw, _knobs.Pitch, _knobs.Roll, _knobs.Disco); + if (_knobs.Disco > 0.01) RequestNextFrameRendering(); - base.OnPropertyChanged(change); } } } diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml index dcddcd59656..add3f976e2f 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -1,41 +1,11 @@ - A progress bar control - - - Maximum - - - - Minimum - - - - Progress Text Format - - - - - - - - - - - - - - - + - diff --git a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs b/src/Avalonia.OpenGL/GlBasicInfoInterface.cs index 38969cc9668..750b34a13f0 100644 --- a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs +++ b/src/Avalonia.OpenGL/GlBasicInfoInterface.cs @@ -15,6 +15,9 @@ public GlBasicInfoInterface(Func getProcAddress) [GetProcAddress("glGetIntegerv")] public partial void GetIntegerv(int name, out int rv); + + [GetProcAddress("glGetFloatv")] + public partial void GetFloatv(int name, out float rv); [GetProcAddress("glGetString")] public partial IntPtr GetStringNative(int v); diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs index 8f53e5a6faa..a841a195911 100644 --- a/src/Avalonia.OpenGL/GlConsts.cs +++ b/src/Avalonia.OpenGL/GlConsts.cs @@ -90,7 +90,7 @@ public static class GlConsts // public const int GL_POLYGON_SMOOTH = 0x0B41; // public const int GL_POLYGON_STIPPLE = 0x0B42; // public const int GL_EDGE_FLAG = 0x0B43; -// public const int GL_CULL_FACE = 0x0B44; + public const int GL_CULL_FACE = 0x0B44; // public const int GL_CULL_FACE_MODE = 0x0B45; // public const int GL_FRONT_FACE = 0x0B46; // public const int GL_POLYGON_OFFSET_FACTOR = 0x8038; @@ -104,10 +104,10 @@ public static class GlConsts // public const int GL_LIST_INDEX = 0x0B33; // public const int GL_LIST_MODE = 0x0B30; // public const int GL_NEVER = 0x0200; -// public const int GL_LESS = 0x0201; + public const int GL_LESS = 0x0201; // public const int GL_EQUAL = 0x0202; // public const int GL_LEQUAL = 0x0203; -// public const int GL_GREATER = 0x0204; + public const int GL_GREATER = 0x0204; // public const int GL_NOTEQUAL = 0x0205; // public const int GL_GEQUAL = 0x0206; // public const int GL_ALWAYS = 0x0207; @@ -359,7 +359,7 @@ public static class GlConsts // public const int GL_FASTEST = 0x1101; // public const int GL_NICEST = 0x1102; // public const int GL_SCISSOR_BOX = 0x0C10; -// public const int GL_SCISSOR_TEST = 0x0C11; + public const int GL_SCISSOR_TEST = 0x0C11; // public const int GL_MAP_COLOR = 0x0D10; // public const int GL_MAP_STENCIL = 0x0D11; // public const int GL_INDEX_SHIFT = 0x0D12; diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 248b20c774c..618d8a0537c 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -60,6 +60,26 @@ public GlInterface(GlVersion version, Func getProcAddress) : thi [GetProcAddress("glClearColor")] public partial void ClearColor(float r, float g, float b, float a); + + [GetProcAddress("glClearDepth", true)] + internal partial void ClearDepthDouble(double value); + + [GetProcAddress("glClearDepthf", true)] + internal partial void ClearDepthFloat(float value); + + public void ClearDepth(float value) + { + if(IsClearDepthDoubleAvailable) + ClearDepthDouble(value); + else if (IsClearDepthFloatAvailable) + _addr_ClearDepthFloat(value); + } + + [GetProcAddress("glDepthFunc")] + public partial void DepthFunc(int value); + + [GetProcAddress("glDepthMask")] + public partial void DepthMask(int value); [GetProcAddress("glClear")] public partial void Clear(int bits); @@ -319,6 +339,9 @@ public int GetUniformLocationString(int program, string name) [GetProcAddress("glEnable")] public partial void Enable(int what); + + [GetProcAddress("glDisable")] + public partial void Disable(int what); [GetProcAddress("glDeleteBuffers")] public partial void DeleteBuffers(int count, int* buffers); diff --git a/src/tools/DevGenerators/GetProcAddressInitialization.cs b/src/tools/DevGenerators/GetProcAddressInitialization.cs index e8d7c251fab..2983010b1c7 100644 --- a/src/tools/DevGenerators/GetProcAddressInitialization.cs +++ b/src/tools/DevGenerators/GetProcAddressInitialization.cs @@ -70,7 +70,12 @@ string GetContextName(string type) var isOptional = false; var first = true; var fieldName = "_addr_" + method.Name; - var delegateType = BuildDelegateType(method); + var delegateType = BuildDelegateType(classBuilder, method, fieldName); + var visibility = method.DeclaredAccessibility == Accessibility.Public + ? "public " + : method.DeclaredAccessibility == Accessibility.Internal + ? "internal " + : ""; void AppendNextAddr() { @@ -160,14 +165,8 @@ void AppendNextAddr() classBuilder .Pad(1) - .Append(delegateType); - classBuilder - .Append(fieldName) - .AppendLine(";"); - - classBuilder - .Pad(1) - .Append("public partial ") + .Append(visibility) + .Append(" partial ") .Append(method.ReturnType.GetFullyQualifiedName()) .Append(" ") .Append(method.Name) @@ -238,7 +237,8 @@ void AppendNextAddr() if (isOptional) classBuilder .Pad(1) - .Append("public bool Is") + .Append(visibility) + .Append(" bool Is") .Append(method.Name) .Append("Available => ") .Append(fieldName) @@ -313,19 +313,41 @@ static string MapToNative(ITypeSymbol type) return type.GetFullyQualifiedName(); } - static string BuildDelegateType(IMethodSymbol method) + static string BuildDelegateType(StringBuilder classBuilder, IMethodSymbol method, string fieldName) { - StringBuilder name = new("delegate* unmanaged[Stdcall]<"); - var firstArg = true; + StringBuilder functionPointer = new("delegate* unmanaged[Stdcall]<"); + // We need this one because Mono interpreter needs pre-generated trampolines for function pointers, + // but .NET WASM SDK doesn't actually scan method bodies for calli instructions and only + // looks for methods with DllImport and delegates with UnmanagedFunctionPointer + StringBuilder fakeDelegate = new( + " [global::System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute(global::System.Runtime.InteropServices.CallingConvention.Cdecl)]\n internal delegate "); + fakeDelegate + .Append(MapToNative(method.ReturnType)) + .Append(" __wasmDummy") + .Append(method.Name) + .Append("("); + + + int arg = 0; - void AppendArg(string a, RefKind kind) + void AppendArgCore(StringBuilder builder, string a, RefKind kind, bool isFirstArg) { - if (firstArg) - firstArg = false; - else - name.Append(","); - AppendRefKind(name, kind); - name.Append(a); + if (!isFirstArg) + builder.Append(","); + AppendRefKind(builder, kind); + builder.Append(a); + } + + void AppendArg(string a, RefKind kind, bool returnArg = false) + { + AppendArgCore(functionPointer, a, kind, arg == 0); + if (!returnArg) + { + AppendArgCore(fakeDelegate, a, kind, arg == 0); + fakeDelegate.Append($" a{arg}"); + } + + arg++; } foreach (var p in method.Parameters) @@ -333,9 +355,18 @@ void AppendArg(string a, RefKind kind) AppendArg(MapToNative(p.Type), p.RefKind); } - AppendArg(MapToNative(method.ReturnType), RefKind.None); - name.Append(">"); - return name.ToString(); + AppendArg(MapToNative(method.ReturnType), RefKind.None, true); + functionPointer.Append(">"); + fakeDelegate.Append(");"); + + classBuilder + .Pad(1) + .Append(functionPointer); + classBuilder + .Append(fieldName) + .AppendLine(";"); + classBuilder.AppendLine(fakeDelegate.ToString()); + return functionPointer.ToString(); } }