aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.OpenGL/Window.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.OpenGL/Window.cs')
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Window.cs420
1 files changed, 420 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.OpenGL/Window.cs b/src/Ryujinx.Graphics.OpenGL/Window.cs
new file mode 100644
index 00000000..b37ec375
--- /dev/null
+++ b/src/Ryujinx.Graphics.OpenGL/Window.cs
@@ -0,0 +1,420 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.OpenGL.Effects;
+using Ryujinx.Graphics.OpenGL.Effects.Smaa;
+using Ryujinx.Graphics.OpenGL.Image;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Window : IWindow, IDisposable
+ {
+ private readonly OpenGLRenderer _renderer;
+
+ private bool _initialized;
+
+ private int _width;
+ private int _height;
+ private bool _updateSize;
+ private int _copyFramebufferHandle;
+ private IPostProcessingEffect _antiAliasing;
+ private IScalingFilter _scalingFilter;
+ private bool _isLinear;
+ private AntiAliasing _currentAntiAliasing;
+ private bool _updateEffect;
+ private ScalingFilter _currentScalingFilter;
+ private float _scalingFilterLevel;
+ private bool _updateScalingFilter;
+ private bool _isBgra;
+ private TextureView _upscaledTexture;
+
+ internal BackgroundContextWorker BackgroundContext { get; private set; }
+
+ internal bool ScreenCaptureRequested { get; set; }
+
+ public Window(OpenGLRenderer renderer)
+ {
+ _renderer = renderer;
+ }
+
+ public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
+ {
+ GL.Disable(EnableCap.FramebufferSrgb);
+
+ (int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
+
+ CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop, swapBuffersCallback);
+
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
+
+ GL.Enable(EnableCap.FramebufferSrgb);
+
+ // Restore unpack alignment to 4, as performance overlays such as RTSS may change this to load their resources.
+ GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
+ }
+
+ public void ChangeVSyncMode(bool vsyncEnabled) { }
+
+ public void SetSize(int width, int height)
+ {
+ _width = width;
+ _height = height;
+
+ _updateSize = true;
+ }
+
+ private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback)
+ {
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
+
+ TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view;
+
+ UpdateEffect();
+
+ if (_antiAliasing != null)
+ {
+ var oldView = viewConverted;
+
+ viewConverted = _antiAliasing.Run(viewConverted, _width, _height);
+
+ if (viewConverted.Format.IsBgr())
+ {
+ var swappedView = _renderer.TextureCopy.BgraSwap(viewConverted);
+
+ viewConverted?.Dispose();
+
+ viewConverted = swappedView;
+ }
+
+ if (viewConverted != oldView && oldView != view)
+ {
+ oldView.Dispose();
+ }
+ }
+
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
+
+ GL.FramebufferTexture(
+ FramebufferTarget.ReadFramebuffer,
+ FramebufferAttachment.ColorAttachment0,
+ viewConverted.Handle,
+ 0);
+
+ GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
+
+ GL.Disable(EnableCap.RasterizerDiscard);
+ GL.Disable(IndexedEnableCap.ScissorTest, 0);
+
+ GL.Clear(ClearBufferMask.ColorBufferBit);
+
+ int srcX0, srcX1, srcY0, srcY1;
+ float scale = viewConverted.ScaleFactor;
+
+ if (crop.Left == 0 && crop.Right == 0)
+ {
+ srcX0 = 0;
+ srcX1 = (int)(viewConverted.Width / scale);
+ }
+ else
+ {
+ srcX0 = crop.Left;
+ srcX1 = crop.Right;
+ }
+
+ if (crop.Top == 0 && crop.Bottom == 0)
+ {
+ srcY0 = 0;
+ srcY1 = (int)(viewConverted.Height / scale);
+ }
+ else
+ {
+ srcY0 = crop.Top;
+ srcY1 = crop.Bottom;
+ }
+
+ if (scale != 1f)
+ {
+ srcX0 = (int)(srcX0 * scale);
+ srcY0 = (int)(srcY0 * scale);
+ srcX1 = (int)Math.Ceiling(srcX1 * scale);
+ srcY1 = (int)Math.Ceiling(srcY1 * scale);
+ }
+
+ float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
+ float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
+
+ int dstWidth = (int)(_width * ratioX);
+ int dstHeight = (int)(_height * ratioY);
+
+ int dstPaddingX = (_width - dstWidth) / 2;
+ int dstPaddingY = (_height - dstHeight) / 2;
+
+ int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
+ int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
+
+ int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
+ int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
+
+ if (ScreenCaptureRequested)
+ {
+ CaptureFrame(srcX0, srcY0, srcX1, srcY1, view.Format.IsBgr(), crop.FlipX, crop.FlipY);
+
+ ScreenCaptureRequested = false;
+ }
+
+ if (_scalingFilter != null)
+ {
+ if (viewConverted.Format.IsBgr() && !_isBgra)
+ {
+ RecreateUpscalingTexture(true);
+ }
+
+ _scalingFilter.Run(
+ viewConverted,
+ _upscaledTexture,
+ _width,
+ _height,
+ new Extents2D(
+ srcX0,
+ srcY0,
+ srcX1,
+ srcY1),
+ new Extents2D(
+ dstX0,
+ dstY0,
+ dstX1,
+ dstY1)
+ );
+
+ srcX0 = dstX0;
+ srcY0 = dstY0;
+ srcX1 = dstX1;
+ srcY1 = dstY1;
+
+ GL.FramebufferTexture(
+ FramebufferTarget.ReadFramebuffer,
+ FramebufferAttachment.ColorAttachment0,
+ _upscaledTexture.Handle,
+ 0);
+ }
+
+ GL.BlitFramebuffer(
+ srcX0,
+ srcY0,
+ srcX1,
+ srcY1,
+ dstX0,
+ dstY0,
+ dstX1,
+ dstY1,
+ ClearBufferMask.ColorBufferBit,
+ _isLinear ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest);
+
+ // Remove Alpha channel
+ GL.ColorMask(false, false, false, true);
+ GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit);
+
+ for (int i = 0; i < Constants.MaxRenderTargets; i++)
+ {
+ ((Pipeline)_renderer.Pipeline).RestoreComponentMask(i);
+ }
+
+ // Set clip control, viewport and the framebuffer to the output to placate overlays and OBS capture.
+ GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne);
+ GL.Viewport(0, 0, _width, _height);
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, drawFramebuffer);
+
+ swapBuffersCallback();
+
+ ((Pipeline)_renderer.Pipeline).RestoreClipControl();
+ ((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
+ ((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
+ ((Pipeline)_renderer.Pipeline).RestoreViewport0();
+
+ if (viewConverted != view)
+ {
+ viewConverted.Dispose();
+ }
+ }
+
+ private int GetCopyFramebufferHandleLazy()
+ {
+ int handle = _copyFramebufferHandle;
+
+ if (handle == 0)
+ {
+ handle = GL.GenFramebuffer();
+
+ _copyFramebufferHandle = handle;
+ }
+
+ return handle;
+ }
+
+ public void InitializeBackgroundContext(IOpenGLContext baseContext)
+ {
+ BackgroundContext = new BackgroundContextWorker(baseContext);
+ _initialized = true;
+ }
+
+ public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
+ {
+ long size = Math.Abs(4 * width * height);
+ byte[] bitmap = new byte[size];
+
+ GL.ReadPixels(x, y, width, height, isBgra ? PixelFormat.Bgra : PixelFormat.Rgba, PixelType.UnsignedByte, bitmap);
+
+ _renderer.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
+ }
+
+ public void Dispose()
+ {
+ if (!_initialized)
+ {
+ return;
+ }
+
+ BackgroundContext.Dispose();
+
+ if (_copyFramebufferHandle != 0)
+ {
+ GL.DeleteFramebuffer(_copyFramebufferHandle);
+
+ _copyFramebufferHandle = 0;
+ }
+
+ _antiAliasing?.Dispose();
+ _scalingFilter?.Dispose();
+ _upscaledTexture?.Dispose();
+ }
+
+ public void SetAntiAliasing(AntiAliasing effect)
+ {
+ if (_currentAntiAliasing == effect && _antiAliasing != null)
+ {
+ return;
+ }
+
+ _currentAntiAliasing = effect;
+
+ _updateEffect = true;
+ }
+
+ public void SetScalingFilter(ScalingFilter type)
+ {
+ if (_currentScalingFilter == type && _antiAliasing != null)
+ {
+ return;
+ }
+
+ _currentScalingFilter = type;
+
+ _updateScalingFilter = true;
+ }
+
+ private void UpdateEffect()
+ {
+ if (_updateEffect)
+ {
+ _updateEffect = false;
+
+ switch (_currentAntiAliasing)
+ {
+ case AntiAliasing.Fxaa:
+ _antiAliasing?.Dispose();
+ _antiAliasing = new FxaaPostProcessingEffect(_renderer);
+ break;
+ case AntiAliasing.None:
+ _antiAliasing?.Dispose();
+ _antiAliasing = null;
+ break;
+ case AntiAliasing.SmaaLow:
+ case AntiAliasing.SmaaMedium:
+ case AntiAliasing.SmaaHigh:
+ case AntiAliasing.SmaaUltra:
+ var quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
+ if (_antiAliasing is SmaaPostProcessingEffect smaa)
+ {
+ smaa.Quality = quality;
+ }
+ else
+ {
+ _antiAliasing?.Dispose();
+ _antiAliasing = new SmaaPostProcessingEffect(_renderer, quality);
+ }
+ break;
+ }
+ }
+
+ if (_updateSize && !_updateScalingFilter)
+ {
+ RecreateUpscalingTexture();
+ }
+
+ _updateSize = false;
+
+ if (_updateScalingFilter)
+ {
+ _updateScalingFilter = false;
+
+ switch (_currentScalingFilter)
+ {
+ case ScalingFilter.Bilinear:
+ case ScalingFilter.Nearest:
+ _scalingFilter?.Dispose();
+ _scalingFilter = null;
+ _isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
+ _upscaledTexture?.Dispose();
+ _upscaledTexture = null;
+ break;
+ case ScalingFilter.Fsr:
+ if (_scalingFilter is not FsrScalingFilter)
+ {
+ _scalingFilter?.Dispose();
+ _scalingFilter = new FsrScalingFilter(_renderer, _antiAliasing);
+ }
+ _isLinear = false;
+ _scalingFilter.Level = _scalingFilterLevel;
+
+ RecreateUpscalingTexture();
+ break;
+ }
+ }
+ }
+
+ private void RecreateUpscalingTexture(bool forceBgra = false)
+ {
+ _upscaledTexture?.Dispose();
+
+ var info = new TextureCreateInfo(
+ _width,
+ _height,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ Format.R8G8B8A8Unorm,
+ DepthStencilMode.Depth,
+ Target.Texture2D,
+ forceBgra ? SwizzleComponent.Blue : SwizzleComponent.Red,
+ SwizzleComponent.Green,
+ forceBgra ? SwizzleComponent.Red : SwizzleComponent.Blue,
+ SwizzleComponent.Alpha);
+
+ _isBgra = forceBgra;
+ _upscaledTexture = _renderer.CreateTexture(info, 1) as TextureView;
+ }
+
+ public void SetScalingFilterLevel(float level)
+ {
+ _scalingFilterLevel = level;
+ _updateScalingFilter = true;
+ }
+ }
+} \ No newline at end of file