|
@@ -3,44 +3,83 @@ 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
|
|
|
+namespace Avalonia.OpenGL.Controls
|
|
|
{
|
|
|
public abstract class OpenGlControlBase : Control
|
|
|
{
|
|
|
private IGlContext _context;
|
|
|
- private int _fb, _texture, _renderBuffer;
|
|
|
- private OpenGlTextureBitmap _bitmap;
|
|
|
- private PixelSize _oldSize;
|
|
|
+ private int _fb, _depthBuffer;
|
|
|
+ private OpenGlBitmap _bitmap;
|
|
|
+ private IOpenGlBitmapAttachment _attachment;
|
|
|
+ private PixelSize _depthBufferSize;
|
|
|
private bool _glFailed;
|
|
|
+ private bool _initialized;
|
|
|
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.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
|
|
|
+ EnsureTextureAttachment();
|
|
|
+ EnsureDepthBufferAttachment(_context.GlInterface);
|
|
|
+ if(!CheckFramebufferStatus(_context.GlInterface))
|
|
|
+ return;
|
|
|
+
|
|
|
+ OnOpenGlRender(_context.GlInterface, _fb);
|
|
|
+ _attachment.Present();
|
|
|
}
|
|
|
|
|
|
context.DrawImage(_bitmap, new Rect(_bitmap.Size), Bounds);
|
|
|
base.Render(context);
|
|
|
}
|
|
|
+
|
|
|
+ private void CheckError(GlInterface gl)
|
|
|
+ {
|
|
|
+ int err;
|
|
|
+ while ((err = gl.GetError()) != GL_NO_ERROR)
|
|
|
+ Console.WriteLine(err);
|
|
|
+ }
|
|
|
+
|
|
|
+ void EnsureTextureAttachment()
|
|
|
+ {
|
|
|
+ _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
|
|
|
+ if (_bitmap == null || _attachment == null || _bitmap.PixelSize != GetPixelSize())
|
|
|
+ {
|
|
|
+ _attachment?.Dispose();
|
|
|
+ _attachment = null;
|
|
|
+ _bitmap?.Dispose();
|
|
|
+ _bitmap = null;
|
|
|
+ _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96));
|
|
|
+ _attachment = _bitmap.CreateFramebufferAttachment(_context);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void EnsureDepthBufferAttachment(GlInterface gl)
|
|
|
+ {
|
|
|
+ var size = GetPixelSize();
|
|
|
+ if (size == _depthBufferSize && _depthBuffer != 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
|
|
|
+ if (_depthBuffer != 0) gl.DeleteRenderbuffers(1, new[] { _depthBuffer });
|
|
|
+
|
|
|
+ var oneArr = new int[1];
|
|
|
+ gl.GenRenderbuffers(1, oneArr);
|
|
|
+ _depthBuffer = oneArr[0];
|
|
|
+ gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
|
|
|
+ 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, _depthBuffer);
|
|
|
+ gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
|
|
|
+ }
|
|
|
|
|
|
- void DoCleanup(bool callUserDeinit)
|
|
|
+ void DoCleanup()
|
|
|
{
|
|
|
if (_context != null)
|
|
|
{
|
|
@@ -50,16 +89,19 @@ namespace Avalonia.OpenGL
|
|
|
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();
|
|
|
+ gl.DeleteRenderbuffers(1, new[] { _depthBuffer });
|
|
|
+ _attachment?.Dispose();
|
|
|
+ _attachment = null;
|
|
|
+ _bitmap?.Dispose();
|
|
|
+ _bitmap = null;
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- if (callUserDeinit)
|
|
|
+ if (_initialized)
|
|
|
+ {
|
|
|
+ _initialized = false;
|
|
|
OnOpenGlDeinit(_context.GlInterface, _fb);
|
|
|
+ }
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
@@ -72,11 +114,11 @@ namespace Avalonia.OpenGL
|
|
|
|
|
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
{
|
|
|
- DoCleanup(true);
|
|
|
+ DoCleanup();
|
|
|
base.OnDetachedFromVisualTree(e);
|
|
|
}
|
|
|
|
|
|
- bool EnsureInitialized()
|
|
|
+ private bool EnsureInitializedCore()
|
|
|
{
|
|
|
if (_context != null)
|
|
|
return true;
|
|
@@ -84,34 +126,43 @@ namespace Avalonia.OpenGL
|
|
|
if (_glFailed)
|
|
|
return false;
|
|
|
|
|
|
- var feature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
|
|
|
+ var feature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
|
|
|
if (feature == null)
|
|
|
return false;
|
|
|
+ if (!feature.CanShareContexts)
|
|
|
+ {
|
|
|
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
|
|
|
+ "Unable to initialize OpenGL: current platform does not support multithreaded context sharing");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
try
|
|
|
{
|
|
|
- _context = feature.CreateContext();
|
|
|
-
|
|
|
+ _context = feature.CreateSharedContext();
|
|
|
}
|
|
|
catch (Exception e)
|
|
|
{
|
|
|
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
|
|
|
"Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e);
|
|
|
- _glFailed = true;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
GlVersion = _context.Version;
|
|
|
try
|
|
|
{
|
|
|
- _bitmap = new OpenGlTextureBitmap();
|
|
|
+ _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96));
|
|
|
+ if (!_bitmap.SupportsContext(_context))
|
|
|
+ {
|
|
|
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
|
|
|
+ "Unable to initialize OpenGL: unable to create OpenGlBitmap: OpenGL context is not compatible");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
}
|
|
|
catch (Exception e)
|
|
|
{
|
|
|
_context.Dispose();
|
|
|
_context = null;
|
|
|
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
|
|
|
- "Unable to initialize OpenGL: unable to create OpenGlTextureBitmap: {exception}", e);
|
|
|
- _glFailed = true;
|
|
|
+ "Unable to initialize OpenGL: unable to create OpenGlBitmap: {exception}", e);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
@@ -119,80 +170,55 @@ namespace Avalonia.OpenGL
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
- _oldSize = GetPixelSize();
|
|
|
+ _depthBufferSize = 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);
|
|
|
+ EnsureDepthBufferAttachment(gl);
|
|
|
+ EnsureTextureAttachment();
|
|
|
|
|
|
- var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
- if (status != GL_FRAMEBUFFER_COMPLETE)
|
|
|
- {
|
|
|
- int code;
|
|
|
- while ((code = gl.GetError()) != 0)
|
|
|
- Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
|
|
|
- "Unable to initialize OpenGL FBO: {code}", code);
|
|
|
-
|
|
|
- _glFailed = true;
|
|
|
- return false;
|
|
|
- }
|
|
|
+ return CheckFramebufferStatus(gl);
|
|
|
}
|
|
|
catch(Exception e)
|
|
|
{
|
|
|
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
|
|
|
"Unable to initialize OpenGL FBO: {exception}", e);
|
|
|
- _glFailed = true;
|
|
|
+ return false;
|
|
|
}
|
|
|
-
|
|
|
- if (!_glFailed)
|
|
|
- OnOpenGlInit(_context.GlInterface, _fb);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (_glFailed)
|
|
|
+ private bool CheckFramebufferStatus(GlInterface gl)
|
|
|
+ {
|
|
|
+ var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
+ if (status != GL_FRAMEBUFFER_COMPLETE)
|
|
|
{
|
|
|
- DoCleanup(false);
|
|
|
+ int code;
|
|
|
+ while ((code = gl.GetError()) != 0)
|
|
|
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
|
|
|
+ "Unable to initialize OpenGL FBO: {code}", code);
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- void ResizeTexture(GlInterface gl)
|
|
|
+ private bool EnsureInitialized()
|
|
|
{
|
|
|
- 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);
|
|
|
+ if (_initialized)
|
|
|
+ return true;
|
|
|
+ _glFailed = !(_initialized = EnsureInitializedCore());
|
|
|
+ if (_glFailed)
|
|
|
+ return false;
|
|
|
+ using (_context.MakeCurrent())
|
|
|
+ OnOpenGlInit(_context.GlInterface, _fb);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
- PixelSize GetPixelSize()
|
|
|
+ private PixelSize GetPixelSize()
|
|
|
{
|
|
|
var scaling = VisualRoot.RenderScaling;
|
|
|
return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)),
|