JinxRyu/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs
BaronKiko f5b4f6ccc4 Scissor test fix (#563)
* Handle negative viewport coordinates

* Disable scissor before framebuffer blit

* Comment to explain scissor disable will be reenabled if needed

* Comma and spelling mistake
2019-01-31 22:37:07 -03:00

527 lines
No EOL
16 KiB
C#

using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.Texture;
using System;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLRenderTarget : IGalRenderTarget
{
private const int NativeWidth = 1280;
private const int NativeHeight = 720;
private const int RenderTargetsCount = GalPipelineState.RenderTargetsCount;
private struct Rect
{
public int X { get; private set; }
public int Y { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public Rect(int X, int Y, int Width, int Height)
{
this.X = X;
this.Y = Y;
this.Width = Width;
this.Height = Height;
}
}
private class FrameBufferAttachments
{
public int MapCount { get; set; }
public DrawBuffersEnum[] Map { get; private set; }
public long[] Colors { get; private set; }
public long Zeta { get; set; }
public FrameBufferAttachments()
{
Colors = new long[RenderTargetsCount];
Map = new DrawBuffersEnum[RenderTargetsCount];
}
public void Update(FrameBufferAttachments Source)
{
for (int Index = 0; Index < RenderTargetsCount; Index++)
{
Map[Index] = Source.Map[Index];
Colors[Index] = Source.Colors[Index];
}
MapCount = Source.MapCount;
Zeta = Source.Zeta;
}
}
private int[] ColorHandles;
private int ZetaHandle;
private OGLTexture Texture;
private ImageHandler ReadTex;
private Rect Window;
private float[] Viewports;
private bool FlipX;
private bool FlipY;
private int CropTop;
private int CropLeft;
private int CropRight;
private int CropBottom;
//This framebuffer is used to attach guest rendertargets,
//think of it as a dummy OpenGL VAO
private int DummyFrameBuffer;
//These framebuffers are used to blit images
private int SrcFb;
private int DstFb;
private FrameBufferAttachments Attachments;
private FrameBufferAttachments OldAttachments;
private int CopyPBO;
public bool FramebufferSrgb { get; set; }
public OGLRenderTarget(OGLTexture Texture)
{
Attachments = new FrameBufferAttachments();
OldAttachments = new FrameBufferAttachments();
ColorHandles = new int[RenderTargetsCount];
Viewports = new float[RenderTargetsCount * 4];
this.Texture = Texture;
Texture.TextureDeleted += TextureDeletionHandler;
}
private void TextureDeletionHandler(object Sender, int Handle)
{
//Texture was deleted, the handle is no longer valid, so
//reset all uses of this handle on a render target.
for (int Attachment = 0; Attachment < RenderTargetsCount; Attachment++)
{
if (ColorHandles[Attachment] == Handle)
{
ColorHandles[Attachment] = 0;
}
}
if (ZetaHandle == Handle)
{
ZetaHandle = 0;
}
}
public void Bind()
{
if (DummyFrameBuffer == 0)
{
DummyFrameBuffer = GL.GenFramebuffer();
}
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer);
ImageHandler CachedImage;
for (int Attachment = 0; Attachment < RenderTargetsCount; Attachment++)
{
long Key = Attachments.Colors[Attachment];
int Handle = 0;
if (Key != 0 && Texture.TryGetImageHandler(Key, out CachedImage))
{
Handle = CachedImage.Handle;
}
if (Handle == ColorHandles[Attachment])
{
continue;
}
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.ColorAttachment0 + Attachment,
Handle,
0);
ColorHandles[Attachment] = Handle;
}
if (Attachments.Zeta != 0 && Texture.TryGetImageHandler(Attachments.Zeta, out CachedImage))
{
if (CachedImage.Handle != ZetaHandle)
{
if (CachedImage.HasDepth && CachedImage.HasStencil)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.DepthStencilAttachment,
CachedImage.Handle,
0);
}
else if (CachedImage.HasDepth)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.DepthAttachment,
CachedImage.Handle,
0);
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.StencilAttachment,
0,
0);
}
else
{
throw new InvalidOperationException("Invalid image format \"" + CachedImage.Format + "\" used as Zeta!");
}
ZetaHandle = CachedImage.Handle;
}
}
else if (ZetaHandle != 0)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.DepthStencilAttachment,
0,
0);
ZetaHandle = 0;
}
if (OGLExtension.ViewportArray)
{
GL.ViewportArray(0, RenderTargetsCount, Viewports);
}
else
{
GL.Viewport(
(int)Viewports[0],
(int)Viewports[1],
(int)Viewports[2],
(int)Viewports[3]);
}
if (Attachments.MapCount > 1)
{
GL.DrawBuffers(Attachments.MapCount, Attachments.Map);
}
else if (Attachments.MapCount == 1)
{
GL.DrawBuffer((DrawBufferMode)Attachments.Map[0]);
}
else
{
GL.DrawBuffer(DrawBufferMode.None);
}
OldAttachments.Update(Attachments);
}
public void BindColor(long Key, int Attachment)
{
Attachments.Colors[Attachment] = Key;
}
public void UnbindColor(int Attachment)
{
Attachments.Colors[Attachment] = 0;
}
public void BindZeta(long Key)
{
Attachments.Zeta = Key;
}
public void UnbindZeta()
{
Attachments.Zeta = 0;
}
public void Present(long Key)
{
Texture.TryGetImageHandler(Key, out ReadTex);
}
public void SetMap(int[] Map)
{
if (Map != null)
{
Attachments.MapCount = Map.Length;
for (int Attachment = 0; Attachment < Attachments.MapCount; Attachment++)
{
Attachments.Map[Attachment] = DrawBuffersEnum.ColorAttachment0 + Map[Attachment];
}
}
else
{
Attachments.MapCount = 0;
}
}
public void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom)
{
this.FlipX = FlipX;
this.FlipY = FlipY;
CropTop = Top;
CropLeft = Left;
CropRight = Right;
CropBottom = Bottom;
}
public void SetWindowSize(int Width, int Height)
{
Window = new Rect(0, 0, Width, Height);
}
public void SetViewport(int Attachment, int X, int Y, int Width, int Height)
{
int Offset = Attachment * 4;
Viewports[Offset + 0] = X;
Viewports[Offset + 1] = Y;
Viewports[Offset + 2] = Width;
Viewports[Offset + 3] = Height;
}
public void Render()
{
if (ReadTex == null)
{
return;
}
int SrcX0, SrcX1, SrcY0, SrcY1;
if (CropLeft == 0 && CropRight == 0)
{
SrcX0 = 0;
SrcX1 = ReadTex.Width;
}
else
{
SrcX0 = CropLeft;
SrcX1 = CropRight;
}
if (CropTop == 0 && CropBottom == 0)
{
SrcY0 = 0;
SrcY1 = ReadTex.Height;
}
else
{
SrcY0 = CropTop;
SrcY1 = CropBottom;
}
float RatioX = MathF.Min(1f, (Window.Height * (float)NativeWidth) / ((float)NativeHeight * Window.Width));
float RatioY = MathF.Min(1f, (Window.Width * (float)NativeHeight) / ((float)NativeWidth * Window.Height));
int DstWidth = (int)(Window.Width * RatioX);
int DstHeight = (int)(Window.Height * RatioY);
int DstPaddingX = (Window.Width - DstWidth) / 2;
int DstPaddingY = (Window.Height - DstHeight) / 2;
int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX;
int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX;
int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY;
int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY;
GL.Viewport(0, 0, Window.Width, Window.Height);
if (SrcFb == 0)
{
SrcFb = GL.GenFramebuffer();
}
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0);
GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0);
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.Disable(EnableCap.FramebufferSrgb);
// Will be re-enabled if needed while binding, called before any game GL calls
GL.Disable(EnableCap.ScissorTest);
GL.BlitFramebuffer(
SrcX0,
SrcY0,
SrcX1,
SrcY1,
DstX0,
DstY0,
DstX1,
DstY1,
ClearBufferMask.ColorBufferBit,
BlitFramebufferFilter.Linear);
if (FramebufferSrgb)
{
GL.Enable(EnableCap.FramebufferSrgb);
}
}
public void Copy(
long SrcKey,
long DstKey,
int SrcX0,
int SrcY0,
int SrcX1,
int SrcY1,
int DstX0,
int DstY0,
int DstX1,
int DstY1)
{
if (Texture.TryGetImageHandler(SrcKey, out ImageHandler SrcTex) &&
Texture.TryGetImageHandler(DstKey, out ImageHandler DstTex))
{
if (SrcTex.HasColor != DstTex.HasColor ||
SrcTex.HasDepth != DstTex.HasDepth ||
SrcTex.HasStencil != DstTex.HasStencil)
{
throw new NotImplementedException();
}
if (SrcFb == 0)
{
SrcFb = GL.GenFramebuffer();
}
if (DstFb == 0)
{
DstFb = GL.GenFramebuffer();
}
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb);
FramebufferAttachment Attachment = GetAttachment(SrcTex);
GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, Attachment, SrcTex.Handle, 0);
GL.FramebufferTexture(FramebufferTarget.DrawFramebuffer, Attachment, DstTex.Handle, 0);
BlitFramebufferFilter Filter = BlitFramebufferFilter.Nearest;
if (SrcTex.HasColor)
{
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
Filter = BlitFramebufferFilter.Linear;
}
ClearBufferMask Mask = GetClearMask(SrcTex);
GL.BlitFramebuffer(SrcX0, SrcY0, SrcX1, SrcY1, DstX0, DstY0, DstX1, DstY1, Mask, Filter);
}
}
public void Reinterpret(long Key, GalImage NewImage)
{
if (!Texture.TryGetImage(Key, out GalImage OldImage))
{
return;
}
if (NewImage.Format == OldImage.Format &&
NewImage.Width == OldImage.Width &&
NewImage.Height == OldImage.Height)
{
return;
}
if (CopyPBO == 0)
{
CopyPBO = GL.GenBuffer();
}
GL.BindBuffer(BufferTarget.PixelPackBuffer, CopyPBO);
//The buffer should be large enough to hold the largest texture.
int BufferSize = Math.Max(ImageUtils.GetSize(OldImage),
ImageUtils.GetSize(NewImage));
GL.BufferData(BufferTarget.PixelPackBuffer, BufferSize, IntPtr.Zero, BufferUsageHint.StreamCopy);
if (!Texture.TryGetImageHandler(Key, out ImageHandler CachedImage))
{
throw new InvalidOperationException();
}
(_, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(CachedImage.Format);
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
GL.GetTexImage(TextureTarget.Texture2D, 0, Format, Type, IntPtr.Zero);
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyPBO);
GL.PixelStore(PixelStoreParameter.UnpackRowLength, OldImage.Width);
Texture.Create(Key, ImageUtils.GetSize(NewImage), NewImage);
GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
}
private static FramebufferAttachment GetAttachment(ImageHandler CachedImage)
{
if (CachedImage.HasColor)
{
return FramebufferAttachment.ColorAttachment0;
}
else if (CachedImage.HasDepth && CachedImage.HasStencil)
{
return FramebufferAttachment.DepthStencilAttachment;
}
else if (CachedImage.HasDepth)
{
return FramebufferAttachment.DepthAttachment;
}
else if (CachedImage.HasStencil)
{
return FramebufferAttachment.StencilAttachment;
}
else
{
throw new InvalidOperationException();
}
}
private static ClearBufferMask GetClearMask(ImageHandler CachedImage)
{
return (CachedImage.HasColor ? ClearBufferMask.ColorBufferBit : 0) |
(CachedImage.HasDepth ? ClearBufferMask.DepthBufferBit : 0) |
(CachedImage.HasStencil ? ClearBufferMask.StencilBufferBit : 0);
}
}
}