Ryujinx/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs
ReinUsesLisp ce1d5be212 Move GPU emulation from Ryujinx.HLE to Ryujinx.Graphics and misc changes (#402)
* Move GPU LLE emulation from HLE to Graphics

* Graphics: Move Gal/Texture to Texture

* Remove Engines/ directory and namespace

* Use tables for image formats

* Abstract OpCode decoding

* Simplify image table

* Do not leak Read* symbols in TextureReader

* Fixups

* Rename IGalFrameBuffer -> IGalRenderTarget

* Remove MaxBpp hardcoded value

* Change yet again texture data and add G8R8 flipping

* Rename GalFrameBufferFormat to GalSurfaceFormat

* Unident EnsureSetup in ImageHandler

* Add IsCompressed

* Address some feedback
2018-09-08 14:51:50 -03:00

497 lines
15 KiB
C#

using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.Texture;
using System;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLRenderTarget : IGalRenderTarget
{
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 const int NativeWidth = 1280;
private const int NativeHeight = 720;
private const GalImageFormat RawFormat = GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm;
private OGLTexture Texture;
private ImageHandler RawTex;
private ImageHandler ReadTex;
private Rect Viewport;
private Rect Window;
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;
//Holds current attachments, used to avoid unnecesary calls to OpenGL
private int[] ColorAttachments;
private int DepthAttachment;
private int StencilAttachment;
public OGLRenderTarget(OGLTexture Texture)
{
ColorAttachments = new int[8];
this.Texture = Texture;
}
public void BindColor(long Key, int Attachment)
{
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
EnsureFrameBuffer();
Attach(ref ColorAttachments[Attachment], Tex.Handle, FramebufferAttachment.ColorAttachment0 + Attachment);
}
else
{
UnbindColor(Attachment);
}
}
public void UnbindColor(int Attachment)
{
EnsureFrameBuffer();
Attach(ref ColorAttachments[Attachment], 0, FramebufferAttachment.ColorAttachment0 + Attachment);
}
public void BindZeta(long Key)
{
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
EnsureFrameBuffer();
if (Tex.HasDepth && Tex.HasStencil)
{
if (DepthAttachment != Tex.Handle ||
StencilAttachment != Tex.Handle)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.DepthStencilAttachment,
Tex.Handle,
0);
DepthAttachment = Tex.Handle;
StencilAttachment = Tex.Handle;
}
}
else if (Tex.HasDepth)
{
Attach(ref DepthAttachment, Tex.Handle, FramebufferAttachment.DepthAttachment);
Attach(ref StencilAttachment, 0, FramebufferAttachment.StencilAttachment);
}
else if (Tex.HasStencil)
{
Attach(ref DepthAttachment, 0, FramebufferAttachment.DepthAttachment);
Attach(ref StencilAttachment, Tex.Handle, FramebufferAttachment.StencilAttachment);
}
else
{
throw new InvalidOperationException();
}
}
else
{
UnbindZeta();
}
}
public void UnbindZeta()
{
EnsureFrameBuffer();
if (DepthAttachment != 0 ||
StencilAttachment != 0)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.DepthStencilAttachment,
0,
0);
DepthAttachment = 0;
StencilAttachment = 0;
}
}
public void BindTexture(long Key, int Index)
{
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
GL.ActiveTexture(TextureUnit.Texture0 + Index);
GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
}
}
public void Set(long Key)
{
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
ReadTex = Tex;
}
}
public void Set(byte[] Data, int Width, int Height)
{
if (RawTex == null)
{
RawTex = new ImageHandler();
}
RawTex.EnsureSetup(new GalImage(Width, Height, RawFormat));
GL.BindTexture(TextureTarget.Texture2D, RawTex.Handle);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, RawTex.PixelFormat, RawTex.PixelType, Data);
ReadTex = RawTex;
}
public void SetMap(int[] Map)
{
if (Map != null && Map.Length > 0)
{
DrawBuffersEnum[] Mode = new DrawBuffersEnum[Map.Length];
for (int i = 0; i < Map.Length; i++)
{
Mode[i] = DrawBuffersEnum.ColorAttachment0 + Map[i];
}
GL.DrawBuffers(Mode.Length, Mode);
}
else
{
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
}
}
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 X, int Y, int Width, int Height)
{
Viewport = new Rect(X, Y, Width, Height);
SetViewport(Viewport);
}
private void SetViewport(Rect Viewport)
{
GL.Viewport(
Viewport.X,
Viewport.Y,
Viewport.Width,
Viewport.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;
if (SrcFb == 0) SrcFb = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0);
GL.Viewport(0, 0, Window.Width, Window.Height);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0);
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BlitFramebuffer(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear);
EnsureFrameBuffer();
}
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.TryGetImage(SrcKey, out ImageHandler SrcTex) &&
Texture.TryGetImage(DstKey, out ImageHandler DstTex))
{
if (SrcTex.HasColor != DstTex.HasColor ||
SrcTex.HasDepth != DstTex.HasDepth ||
SrcTex.HasStencil != DstTex.HasStencil)
{
throw new NotImplementedException();
}
if (SrcTex.HasColor)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.ColorAttachment0,
ClearBufferMask.ColorBufferBit,
true);
}
else if (SrcTex.HasDepth && SrcTex.HasStencil)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.DepthStencilAttachment,
ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit,
false);
}
else if (SrcTex.HasDepth)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.DepthAttachment,
ClearBufferMask.DepthBufferBit,
false);
}
else if (SrcTex.HasStencil)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.StencilAttachment,
ClearBufferMask.StencilBufferBit,
false);
}
else
{
throw new InvalidOperationException();
}
}
}
public void GetBufferData(long Key, Action<byte[]> Callback)
{
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
byte[] Data = new byte[ImageUtils.GetSize(Tex.Image)];
GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
GL.GetTexImage(
TextureTarget.Texture2D,
0,
Tex.PixelFormat,
Tex.PixelType,
Data);
Callback(Data);
}
}
public void SetBufferData(
long Key,
int Width,
int Height,
byte[] Buffer)
{
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
const int Level = 0;
const int Border = 0;
GL.TexImage2D(
TextureTarget.Texture2D,
Level,
Tex.InternalFormat,
Width,
Height,
Border,
Tex.PixelFormat,
Tex.PixelType,
Buffer);
}
}
private void EnsureFrameBuffer()
{
if (DummyFrameBuffer == 0)
{
DummyFrameBuffer = GL.GenFramebuffer();
}
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer);
}
private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment)
{
if (OldHandle != NewHandle)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FbAttachment,
NewHandle,
0);
OldHandle = NewHandle;
}
}
private void CopyTextures(
int SrcX0,
int SrcY0,
int SrcX1,
int SrcY1,
int DstX0,
int DstY0,
int DstX1,
int DstY1,
int SrcTexture,
int DstTexture,
FramebufferAttachment Attachment,
ClearBufferMask Mask,
bool Color)
{
if (SrcFb == 0) SrcFb = GL.GenFramebuffer();
if (DstFb == 0) DstFb = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb);
GL.FramebufferTexture(
FramebufferTarget.ReadFramebuffer,
Attachment,
SrcTexture,
0);
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
Attachment,
DstTexture,
0);
if (Color)
{
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
}
GL.Clear(Mask);
GL.BlitFramebuffer(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
Mask,
Color ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest);
EnsureFrameBuffer();
}
}
}