diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 2037b85695..819072db18 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -8,8 +8,10 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Headless; +using Avalonia.LinuxFramebuffer; using Avalonia.LinuxFramebuffer.Output; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.Vulkan; @@ -19,16 +21,9 @@ namespace ControlCatalog.Desktop { static class Program { - private static bool s_useFramebuffer; - [STAThread] static int Main(string[] args) { - if (args.Contains("--fbdev")) - { - s_useFramebuffer = true; - } - if (args.Contains("--wait-for-attach")) { Console.WriteLine("Attach debugger and use 'Set next statement'"); @@ -50,7 +45,22 @@ namespace ControlCatalog.Desktop return scaling; return 1; } - if (s_useFramebuffer) + SurfaceOrientation GetOrientation() + { + var idx = Array.IndexOf(args, "--orientation"); + if (idx >= 0 && args.Length > idx + 1 && + Enum.TryParse(args[idx + 1], true, out var orientation)) + return orientation; + return SurfaceOrientation.Rotation0; + } + string? GetCard() + { + var idx = Array.IndexOf(args, "--card"); + if (idx >= 0 && args.Length > idx + 1) + return args[idx + 1]; + return null; + } + if (args.Contains("--fbdev")) { SilenceConsole(); return builder.StartLinuxFbDev(args, new FbDevOutputOptions() @@ -108,7 +118,11 @@ namespace ControlCatalog.Desktop else if (args.Contains("--drm")) { SilenceConsole(); - return builder.StartLinuxDrm(args, scaling: GetScaling()); + return builder.StartLinuxDrm(args, card: GetCard(), options: new DrmOutputOptions() + { + Scaling = GetScaling(), + Orientation = GetOrientation(), + }); } else if (args.Contains("--dxgi")) { diff --git a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs index 3f1f9bf44e..9b0f1bee38 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs @@ -10,6 +10,7 @@ using AndroidX.Window.Layout; using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Platform; using AndroidOrientation = global::Android.Content.Res.Orientation; +using AndroidRotation = global::Android.Views.SurfaceOrientation; namespace Avalonia.Android.Platform; @@ -53,7 +54,7 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl var orientation = displayContext.Resources?.Configuration?.Orientation; if (orientation == AndroidOrientation.Square) naturalOrientation = ScreenOrientation.None; - else if (rotation is SurfaceOrientation.Rotation0 or SurfaceOrientation.Rotation180) + else if (rotation is AndroidRotation.Rotation0 or AndroidRotation.Rotation180) naturalOrientation = orientation == AndroidOrientation.Landscape ? ScreenOrientation.Landscape : ScreenOrientation.Portrait; @@ -73,14 +74,14 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl CurrentOrientation = (display.Rotation, naturalOrientation) switch { (_, ScreenOrientation.None) => ScreenOrientation.None, - (SurfaceOrientation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape, - (SurfaceOrientation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait, - (SurfaceOrientation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped, - (SurfaceOrientation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped, - (SurfaceOrientation.Rotation0, _) => ScreenOrientation.Portrait, - (SurfaceOrientation.Rotation90, _) => ScreenOrientation.Landscape, - (SurfaceOrientation.Rotation180, _) => ScreenOrientation.PortraitFlipped, - (SurfaceOrientation.Rotation270, _) => ScreenOrientation.LandscapeFlipped, + (AndroidRotation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape, + (AndroidRotation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait, + (AndroidRotation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped, + (AndroidRotation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped, + (AndroidRotation.Rotation0, _) => ScreenOrientation.Portrait, + (AndroidRotation.Rotation90, _) => ScreenOrientation.Landscape, + (AndroidRotation.Rotation180, _) => ScreenOrientation.PortraitFlipped, + (AndroidRotation.Rotation270, _) => ScreenOrientation.LandscapeFlipped, _ => ScreenOrientation.Portrait }; } diff --git a/src/Avalonia.Base/Platform/ISurfaceOrientation.cs b/src/Avalonia.Base/Platform/ISurfaceOrientation.cs new file mode 100644 index 0000000000..2f4f2d3241 --- /dev/null +++ b/src/Avalonia.Base/Platform/ISurfaceOrientation.cs @@ -0,0 +1,6 @@ +namespace Avalonia.Platform; + +internal interface ISurfaceOrientation +{ + SurfaceOrientation Orientation { get; } +} diff --git a/src/Avalonia.Base/Platform/SurfaceOrientation.cs b/src/Avalonia.Base/Platform/SurfaceOrientation.cs new file mode 100644 index 0000000000..71835fb6ff --- /dev/null +++ b/src/Avalonia.Base/Platform/SurfaceOrientation.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Platform; + +public enum SurfaceOrientation +{ + Rotation0, + Rotation90, + Rotation180, + Rotation270, +} diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs index 55be3da5f0..c1e1df0639 100644 --- a/src/Avalonia.OpenGL/GlConsts.cs +++ b/src/Avalonia.OpenGL/GlConsts.cs @@ -20,7 +20,7 @@ namespace Avalonia.OpenGL // public const int GL_LINE_STRIP = 0x0003; public const int GL_TRIANGLES = 0x0004; // public const int GL_TRIANGLE_STRIP = 0x0005; -// public const int GL_TRIANGLE_FAN = 0x0006; + public const int GL_TRIANGLE_FAN = 0x0006; // public const int GL_QUADS = 0x0007; // public const int GL_QUAD_STRIP = 0x0008; // public const int GL_POLYGON = 0x0009; diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 12a5ef733e..63d265c41e 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -343,6 +343,8 @@ namespace Avalonia.OpenGL [GetProcAddress("glUniform1f")] public partial void Uniform1f(int location, float falue); + [GetProcAddress("glUniform1i")] + public partial void Uniform1i(int location, int value); [GetProcAddress("glUniformMatrix4fv")] public partial void UniformMatrix4fv(int location, int count, bool transpose, void* value); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index f733fe4d72..42a8ed4f0d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using Avalonia.Platform; namespace Avalonia.LinuxFramebuffer { @@ -12,7 +13,13 @@ namespace Avalonia.LinuxFramebuffer /// Default: 1.0 /// public double Scaling { get; set; } = 1.0; - + + /// + /// The orientation of the screen relative to the frame buffer memory orientation + /// Default: Normal + /// + public SurfaceOrientation Orientation { get; set; } = SurfaceOrientation.Rotation0; + /// /// If true an two cycle buffer swapping is processed at init. /// Default: True diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 2a086b353b..edd0b1ef72 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -11,7 +11,7 @@ using Avalonia.Rendering.Composition; namespace Avalonia.LinuxFramebuffer { - class FramebufferToplevelImpl : ITopLevelImpl, IScreenInfoProvider + class FramebufferToplevelImpl : ITopLevelImpl, IScreenInfoProvider, ISurfaceOrientation { private readonly IOutputBackend _outputBackend; private readonly IInputBackend _inputBackend; @@ -81,5 +81,7 @@ using Avalonia.Rendering.Composition; public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public object? TryGetFeature(Type featureType) => null; + + SurfaceOrientation ISurfaceOrientation.Orientation => _outputBackend is ISurfaceOrientation surfaceOrientation ? surfaceOrientation.Orientation : SurfaceOrientation.Rotation0; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index abff7f0936..671508a943 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,8 +1,10 @@ using System; using System.IO; +using System.Linq; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Platform; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { @@ -30,9 +32,22 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput private unsafe void InputThread(IntPtr ctx, LibInputBackendOptions options) { var fd = libinput_get_fd(ctx); + IntPtr[] devices = [.. options.Events!.Select(f => libinput_path_add_device(ctx, f)).Where(d => d != IntPtr.Zero)]; + var screenOrientation = _screen is ISurfaceOrientation surfaceOrientation ? surfaceOrientation.Orientation : SurfaceOrientation.Rotation0; + + float[] matrix = screenOrientation switch + { + SurfaceOrientation.Rotation90 => [0, 1, 0, -1, 0, 1], + SurfaceOrientation.Rotation180 => [-1, 0, 1, 0, -1, 1], + SurfaceOrientation.Rotation270 => [0, -1, 1, 1, 0, 0], + _ => [1, 0, 0, 0, 1, 0], // Normal + }; + + foreach (var device in devices) + { + libinput_device_config_calibration_set_matrix(device, matrix); + } - foreach (var f in options.Events!) - libinput_path_add_device(ctx, f); while (true) { IntPtr ev; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs index c027440708..fbb11905f9 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs @@ -55,6 +55,9 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput [DllImport(LibInput)] public extern static IntPtr libinput_path_remove_device(IntPtr device); + [DllImport(LibInput)] + public extern static int libinput_device_config_calibration_set_matrix(IntPtr device, float[] matrix); + [DllImport(LibInput)] public extern static int libinput_get_fd(IntPtr ctx); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 92ec8b475d..0d7d74e01c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -142,7 +142,10 @@ namespace Avalonia.LinuxFramebuffer { get { - EnsureTopLevel(); + if (_topLevel == null) + { + EnsureTopLevel(); + } return _topLevel; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 12ab5ad821..ddd3b6e49f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -13,11 +13,13 @@ using static Avalonia.LinuxFramebuffer.Output.LibDrm; namespace Avalonia.LinuxFramebuffer.Output { - public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface + public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface, ISurfaceOrientation { private DrmOutputOptions _outputOptions = new(); private DrmCard _card; - public PixelSize PixelSize => _mode.Resolution; + public PixelSize PixelSize => Orientation == SurfaceOrientation.Rotation0 || Orientation == SurfaceOrientation.Rotation180 + ? new PixelSize(_mode.Resolution.Width, _mode.Resolution.Height) + : new PixelSize(_mode.Resolution.Height, _mode.Resolution.Width); public double Scaling { @@ -25,6 +27,12 @@ namespace Avalonia.LinuxFramebuffer.Output set => _outputOptions.Scaling = value; } + public SurfaceOrientation Orientation + { + get => _outputOptions.Orientation; + set => _outputOptions.Orientation = value; + } + class SharedContextGraphics : IPlatformGraphics { private readonly IPlatformGraphicsContext _context; @@ -113,6 +121,12 @@ namespace Avalonia.LinuxFramebuffer.Output private IntPtr _currentBo; private IntPtr _gbmTargetSurface; private uint _crtcId; + private int _rotationFbo; + private int _rotationTexture; + private PixelSize _rotatedSize; + private int _rotationProgram; + private int _rotationVbo; + private int _rotationVao; void FbDestroyCallback(IntPtr bo, IntPtr userData) { @@ -157,7 +171,6 @@ namespace Avalonia.LinuxFramebuffer.Output return fbHandle; } - [MemberNotNull(nameof(_card))] [MemberNotNull(nameof(PlatformGraphics))] [MemberNotNull(nameof(FbDestroyDelegate))] @@ -236,6 +249,129 @@ namespace Avalonia.LinuxFramebuffer.Output _mode = mode; _currentBo = bo; + + // Initialize FBO for rotation if needed + var needsRotation = _outputOptions.Orientation != SurfaceOrientation.Rotation0; + if (needsRotation) + { + // For 90/270 rotation, swap width and height + _rotatedSize = (_outputOptions.Orientation == SurfaceOrientation.Rotation90 || + _outputOptions.Orientation == SurfaceOrientation.Rotation270) + ? new PixelSize(modeInfo.Resolution.Height, modeInfo.Resolution.Width) + : modeInfo.Resolution; + + using (_deferredContext.MakeCurrent(_eglSurface)) + { + var gl = _deferredContext.GlInterface; + _rotationFbo = gl.GenFramebuffer(); + _rotationTexture = gl.GenTexture(); + + gl.BindTexture(GlConsts.GL_TEXTURE_2D, _rotationTexture); + gl.TexImage2D(GlConsts.GL_TEXTURE_2D, 0, GlConsts.GL_RGBA, _rotatedSize.Width, _rotatedSize.Height, 0, + GlConsts.GL_RGBA, GlConsts.GL_UNSIGNED_BYTE, IntPtr.Zero); + gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MIN_FILTER, GlConsts.GL_LINEAR); + gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MAG_FILTER, GlConsts.GL_LINEAR); + + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _rotationFbo); + gl.FramebufferTexture2D(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, + GlConsts.GL_TEXTURE_2D, _rotationTexture, 0); + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + + // Create shader program for textured quad + const string vertexShader = @" + attribute vec2 aPos; + attribute vec2 aTexCoord; + varying vec2 vTexCoord; + void main() { + gl_Position = vec4(aPos, 0.0, 1.0); + vTexCoord = aTexCoord; + }"; + + const string fragmentShader = @" + precision mediump float; + varying vec2 vTexCoord; + uniform sampler2D uTexture; + void main() { + gl_FragColor = texture2D(uTexture, vTexCoord); + }"; + + var vs = gl.CreateShader(GlConsts.GL_VERTEX_SHADER); + gl.ShaderSourceString(vs, vertexShader); + gl.CompileShader(vs); + + var fs = gl.CreateShader(GlConsts.GL_FRAGMENT_SHADER); + gl.ShaderSourceString(fs, fragmentShader); + gl.CompileShader(fs); + + _rotationProgram = gl.CreateProgram(); + gl.AttachShader(_rotationProgram, vs); + gl.AttachShader(_rotationProgram, fs); + gl.LinkProgram(_rotationProgram); + gl.DeleteShader(vs); + gl.DeleteShader(fs); + + // Create VBO with quad vertices - texture coords depend on rotation + // Format: x, y, u, v + float[] vertices = _outputOptions.Orientation switch + { + SurfaceOrientation.Rotation90 => new float[] { + // 90° clockwise rotation + -1.0f, -1.0f, 1.0f, 0.0f, // Bottom-left -> Bottom-right of texture + 1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right -> Top-right of texture + 1.0f, 1.0f, 0.0f, 1.0f, // Top-right -> Top-left of texture + -1.0f, 1.0f, 0.0f, 0.0f // Top-left -> Bottom-left of texture + }, + SurfaceOrientation.Rotation180 => new float[] { + // 180° rotation + -1.0f, -1.0f, 1.0f, 1.0f, // Bottom-left -> Top-right of texture + 1.0f, -1.0f, 0.0f, 1.0f, // Bottom-right -> Top-left of texture + 1.0f, 1.0f, 0.0f, 0.0f, // Top-right -> Bottom-left of texture + -1.0f, 1.0f, 1.0f, 0.0f // Top-left -> Bottom-right of texture + }, + SurfaceOrientation.Rotation270 => new float[] { + // 270° clockwise (90° counter-clockwise) rotation + -1.0f, -1.0f, 0.0f, 1.0f, // Bottom-left -> Top-left of texture + 1.0f, -1.0f, 0.0f, 0.0f, // Bottom-right -> Bottom-left of texture + 1.0f, 1.0f, 1.0f, 0.0f, // Top-right -> Bottom-right of texture + -1.0f, 1.0f, 1.0f, 1.0f // Top-left -> Top-right of texture + }, + _ => new float[] { + // No rotation (shouldn't reach here but fallback) + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f + } + }; + + _rotationVbo = gl.GenBuffer(); + _rotationVao = gl.GenVertexArray(); + + gl.BindVertexArray(_rotationVao); + gl.BindBuffer(GlConsts.GL_ARRAY_BUFFER, _rotationVbo); + + fixed (float* ptr = vertices) + { + gl.BufferData(GlConsts.GL_ARRAY_BUFFER, new IntPtr(vertices.Length * sizeof(float)), + new IntPtr(ptr), GlConsts.GL_STATIC_DRAW); + } + + var posAttrib = gl.GetAttribLocationString(_rotationProgram, "aPos"); + gl.EnableVertexAttribArray(posAttrib); + gl.VertexAttribPointer(posAttrib, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), IntPtr.Zero); + + var texAttrib = gl.GetAttribLocationString(_rotationProgram, "aTexCoord"); + gl.EnableVertexAttribArray(texAttrib); + gl.VertexAttribPointer(texAttrib, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), new IntPtr(2 * sizeof(float))); + + gl.BindVertexArray(0); + } + } + else + { + // No rotation needed + _rotatedSize = modeInfo.Resolution; + } if (_outputOptions.EnableInitialBufferSwapping) { @@ -288,7 +424,39 @@ namespace Avalonia.LinuxFramebuffer.Output public void Dispose() { - _parent._deferredContext.GlInterface.Flush(); + var gl = _parent._deferredContext.GlInterface; + + if (_parent._outputOptions.Orientation != SurfaceOrientation.Rotation0) + { + // Rotation enabled - blit from FBO to screen + // Unbind FBO to render to default framebuffer + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + gl.Viewport(0, 0, _parent._mode.Resolution.Width, _parent._mode.Resolution.Height); + + // Clear the screen + gl.ClearColor(0, 0, 0, 1); + gl.Clear(GlConsts.GL_COLOR_BUFFER_BIT); + + // Use the shader program + gl.UseProgram(_parent._rotationProgram); + + // Bind the FBO texture + gl.ActiveTexture(GlConsts.GL_TEXTURE0); + gl.BindTexture(GlConsts.GL_TEXTURE_2D, _parent._rotationTexture); + + // Set texture uniform (texture unit 0) + var texLoc = gl.GetUniformLocationString(_parent._rotationProgram, "uTexture"); + gl.Uniform1i(texLoc, 0); + + // Draw the rotated quad + gl.BindVertexArray(_parent._rotationVao); + gl.DrawArrays(GlConsts.GL_TRIANGLE_FAN, 0, 4); + gl.BindVertexArray(0); + + gl.UseProgram(0); + } + + gl.Flush(); _parent._eglSurface.SwapBuffers(); var nextBo = gbm_surface_lock_front_buffer(_parent._gbmTargetSurface); @@ -333,7 +501,7 @@ namespace Avalonia.LinuxFramebuffer.Output public IGlContext Context => _parent._deferredContext; - public PixelSize Size => _parent._mode.Resolution; + public PixelSize Size => _parent._rotatedSize; public double Scaling => _parent.Scaling; @@ -342,7 +510,23 @@ namespace Avalonia.LinuxFramebuffer.Output public IGlPlatformSurfaceRenderingSession BeginDraw() { - return new RenderSession(_parent, _parent._deferredContext.MakeCurrent(_parent._eglSurface)); + var clearContext = _parent._deferredContext.MakeCurrent(_parent._eglSurface); + var gl = _parent._deferredContext.GlInterface; + + if (_parent._outputOptions.Orientation != SurfaceOrientation.Rotation0) + { + // Bind FBO for rendering when rotation is enabled + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _parent._rotationFbo); + gl.Viewport(0, 0, _parent._rotatedSize.Width, _parent._rotatedSize.Height); + } + else + { + // Render directly to screen when no rotation + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + gl.Viewport(0, 0, _parent._mode.Resolution.Width, _parent._mode.Resolution.Height); + } + + return new RenderSession(_parent, clearContext); } }