Blit framebuffer without shaders (#229)

* Blit framebuffer without shaders

* De-hardcode native size values

* Adapt to dehardcoded framebuffers and address feedback

* Remove framebuffer rebinding
This commit is contained in:
ReinUsesLisp 2018-07-23 11:21:05 -03:00 committed by Ac_K
parent ed29982f9b
commit 1344a47c77
6 changed files with 101 additions and 288 deletions

View file

@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gal
void Set(byte[] Data, int Width, int Height); void Set(byte[] Data, int Width, int Height);
void SetTransform(float SX, float SY, float Rotate, float TX, float TY); void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom);
void SetWindowSize(int Width, int Height); void SetWindowSize(int Width, int Height);

View file

@ -1,13 +0,0 @@
#version 330 core
precision highp float;
uniform sampler2D tex;
in vec2 tex_coord;
out vec4 out_frag_color;
void main(void) {
out_frag_color = texture(tex, tex_coord);
}

View file

@ -1,28 +0,0 @@
#version 330 core
precision highp float;
uniform mat2 transform;
uniform vec2 window_size;
uniform vec2 offset;
layout(location = 0) in vec2 in_position;
layout(location = 1) in vec2 in_tex_coord;
out vec2 tex_coord;
// Have a fixed aspect ratio, fit the image within the available space.
vec2 get_scale_ratio(void) {
vec2 native_size = vec2(1280, 720);
vec2 ratio = vec2(
(window_size.y * native_size.x) / (native_size.y * window_size.x),
(window_size.x * native_size.y) / (native_size.x * window_size.y)
);
return min(ratio, 1);
}
void main(void) {
tex_coord = in_tex_coord;
vec2 t_pos = (transform * in_position) + offset;
gl_Position = vec4(t_pos * get_scale_ratio(), 0, 1);
}

View file

@ -32,48 +32,45 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public int RbHandle { get; private set; } public int RbHandle { get; private set; }
public int TexHandle { get; private set; } public int TexHandle { get; private set; }
public FrameBuffer(int Width, int Height) public FrameBuffer(int Width, int Height, bool HasRenderBuffer)
{ {
this.Width = Width; this.Width = Width;
this.Height = Height; this.Height = Height;
Handle = GL.GenFramebuffer(); Handle = GL.GenFramebuffer();
RbHandle = GL.GenRenderbuffer();
TexHandle = GL.GenTexture(); TexHandle = GL.GenTexture();
if (HasRenderBuffer)
{
RbHandle = GL.GenRenderbuffer();
}
} }
} }
private struct ShaderProgram private const int NativeWidth = 1280;
{ private const int NativeHeight = 720;
public int Handle;
public int VpHandle;
public int FpHandle;
}
private Dictionary<long, FrameBuffer> Fbs; private Dictionary<long, FrameBuffer> Fbs;
private ShaderProgram Shader;
private Rect Viewport; private Rect Viewport;
private Rect Window; private Rect Window;
private bool IsInitialized; private FrameBuffer CurrFb;
private FrameBuffer CurrReadFb;
private int RawFbTexWidth; private FrameBuffer RawFb;
private int RawFbTexHeight;
private int RawFbTexHandle;
private int CurrFbHandle; private bool FlipX;
private int CurrTexHandle; private bool FlipY;
private int VaoHandle; private int CropTop;
private int VboHandle; private int CropLeft;
private int CropRight;
private int CropBottom;
public OGLFrameBuffer() public OGLFrameBuffer()
{ {
Fbs = new Dictionary<long, FrameBuffer>(); Fbs = new Dictionary<long, FrameBuffer>();
Shader = new ShaderProgram();
} }
public void Create(long Key, int Width, int Height) public void Create(long Key, int Width, int Height)
@ -92,7 +89,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
return; return;
} }
Fb = new FrameBuffer(Width, Height); Fb = new FrameBuffer(Width, Height, true);
SetupTexture(Fb.TexHandle, Width, Height); SetupTexture(Fb.TexHandle, Width, Height);
@ -129,7 +126,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle);
CurrFbHandle = Fb.Handle; CurrFb = Fb;
} }
} }
@ -147,75 +144,50 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
{ {
CurrTexHandle = Fb.TexHandle; CurrReadFb = Fb;
} }
} }
public void Set(byte[] Data, int Width, int Height) public void Set(byte[] Data, int Width, int Height)
{ {
if (RawFbTexHandle == 0) if (RawFb == null)
{ {
RawFbTexHandle = GL.GenTexture(); CreateRawFb(Width, Height);
} }
if (RawFbTexWidth != Width || if (RawFb.Width != Width ||
RawFbTexHeight != Height) RawFb.Height != Height)
{ {
SetupTexture(RawFbTexHandle, Width, Height); SetupTexture(RawFb.TexHandle, Width, Height);
RawFbTexWidth = Width; RawFb.Width = Width;
RawFbTexHeight = Height; RawFb.Height = Height;
} }
GL.ActiveTexture(TextureUnit.Texture0); GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, RawFbTexHandle); GL.BindTexture(TextureTarget.Texture2D, RawFb.TexHandle);
(PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, Format, Type, Data); GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, Format, Type, Data);
CurrTexHandle = RawFbTexHandle; CurrReadFb = RawFb;
} }
public void SetTransform(float SX, float SY, float Rotate, float TX, float TY) public void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom)
{ {
EnsureInitialized(); this.FlipX = FlipX;
this.FlipY = FlipY;
Matrix2 Transform; CropTop = Top;
CropLeft = Left;
Transform = Matrix2.CreateScale(SX, SY); CropRight = Right;
Transform *= Matrix2.CreateRotation(Rotate); CropBottom = Bottom;
Vector2 Offs = new Vector2(TX, TY);
int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram);
GL.UseProgram(Shader.Handle);
int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform");
GL.UniformMatrix2(TransformUniformLocation, false, ref Transform);
int OffsetUniformLocation = GL.GetUniformLocation(Shader.Handle, "offset");
GL.Uniform2(OffsetUniformLocation, ref Offs);
GL.UseProgram(CurrentProgram);
} }
public void SetWindowSize(int Width, int Height) public void SetWindowSize(int Width, int Height)
{ {
int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram);
GL.UseProgram(Shader.Handle);
int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size");
GL.Uniform2(WindowSizeUniformLocation, new Vector2(Width, Height));
GL.UseProgram(CurrentProgram);
Window = new Rect(0, 0, Width, Height); Window = new Rect(0, 0, Width, Height);
} }
@ -237,72 +209,59 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void Render() public void Render()
{ {
if (CurrTexHandle != 0) if (CurrReadFb != null)
{ {
EnsureInitialized(); int SrcX0, SrcX1, SrcY0, SrcY1;
//bool CullFaceEnable = GL.IsEnabled(EnableCap.CullFace); if (CropLeft == 0 && CropRight == 0)
{
SrcX0 = 0;
SrcX1 = CurrReadFb.Width;
}
else
{
SrcX0 = CropLeft;
SrcX1 = CropRight;
}
bool DepthTestEnable = GL.IsEnabled(EnableCap.DepthTest); if (CropTop == 0 && CropBottom == 0)
{
SrcY0 = 0;
SrcY1 = CurrReadFb.Height;
}
else
{
SrcY0 = CropTop;
SrcY1 = CropBottom;
}
bool StencilTestEnable = GL.IsEnabled(EnableCap.StencilTest); 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));
bool AlphaBlendEnable = GL.IsEnabled(EnableCap.Blend); int DstWidth = (int)(Window.Width * RatioX);
int DstHeight = (int)(Window.Height * RatioY);
//GL.Disable(EnableCap.CullFace); int DstPaddingX = (Window.Width - DstWidth) / 2;
int DstPaddingY = (Window.Height - DstHeight) / 2;
GL.Disable(EnableCap.DepthTest); int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX;
int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX;
GL.Disable(EnableCap.StencilTest); int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY;
int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY;
GL.Disable(EnableCap.Blend);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, CurrTexHandle);
int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
SetViewport(Window); GL.Viewport(0, 0, Window.Width, Window.Height);
GL.Clear( GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrReadFb.Handle);
ClearBufferMask.ColorBufferBit |
ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(VaoHandle); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.UseProgram(Shader.Handle); GL.BlitFramebuffer(
SrcX0, SrcY0, SrcX1, SrcY1,
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); DstX0, DstY0, DstX1, DstY1,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear);
//Restore the original state.
GL.BindFramebuffer(FramebufferTarget.Framebuffer, CurrFbHandle);
GL.UseProgram(CurrentProgram);
//if (CullFaceEnable)
//{
// GL.Enable(EnableCap.CullFace);
//}
if (DepthTestEnable)
{
GL.Enable(EnableCap.DepthTest);
}
if (StencilTestEnable)
{
GL.Enable(EnableCap.StencilTest);
}
if (AlphaBlendEnable)
{
GL.Enable(EnableCap.Blend);
}
SetViewport(Viewport);
} }
} }
@ -354,8 +313,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
Data); Data);
Callback(Data); Callback(Data);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrFbHandle);
} }
} }
@ -390,86 +347,29 @@ namespace Ryujinx.Graphics.Gal.OpenGL
} }
} }
private void EnsureInitialized() private void CreateRawFb(int Width, int Height)
{ {
if (!IsInitialized) if (RawFb == null)
{ {
IsInitialized = true; RawFb = new FrameBuffer(Width, Height, false);
SetupShader(); SetupTexture(RawFb.TexHandle, Width, Height);
SetupVertex();
RawFb.Width = Width;
RawFb.Height = Height;
GL.BindFramebuffer(FramebufferTarget.Framebuffer, RawFb.Handle);
GL.FramebufferTexture(
FramebufferTarget.Framebuffer,
FramebufferAttachment.ColorAttachment0,
RawFb.TexHandle,
0);
GL.Viewport(0, 0, Width, Height);
} }
} }
private void SetupShader()
{
Shader.VpHandle = GL.CreateShader(ShaderType.VertexShader);
Shader.FpHandle = GL.CreateShader(ShaderType.FragmentShader);
string VpSource = EmbeddedResource.GetString("GlFbVtxShader");
string FpSource = EmbeddedResource.GetString("GlFbFragShader");
GL.ShaderSource(Shader.VpHandle, VpSource);
GL.ShaderSource(Shader.FpHandle, FpSource);
GL.CompileShader(Shader.VpHandle);
GL.CompileShader(Shader.FpHandle);
Shader.Handle = GL.CreateProgram();
GL.AttachShader(Shader.Handle, Shader.VpHandle);
GL.AttachShader(Shader.Handle, Shader.FpHandle);
GL.LinkProgram(Shader.Handle);
GL.UseProgram(Shader.Handle);
Matrix2 Transform = Matrix2.Identity;
int TexUniformLocation = GL.GetUniformLocation(Shader.Handle, "tex");
GL.Uniform1(TexUniformLocation, 0);
int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size");
GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f));
int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform");
GL.UniformMatrix2(TransformUniformLocation, false, ref Transform);
}
private void SetupVertex()
{
VaoHandle = GL.GenVertexArray();
VboHandle = GL.GenBuffer();
float[] Buffer = new float[]
{
-1, 1, 0, 0,
1, 1, 1, 0,
-1, -1, 0, 1,
1, -1, 1, 1
};
IntPtr Length = new IntPtr(Buffer.Length * 4);
GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
GL.BindVertexArray(VaoHandle);
GL.EnableVertexAttribArray(0);
GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0);
GL.EnableVertexAttribArray(1);
GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8);
}
private void SetupTexture(int Handle, int Width, int Height) private void SetupTexture(int Handle, int Width, int Height)
{ {
GL.BindTexture(TextureTarget.Texture2D, Handle); GL.BindTexture(TextureTarget.Texture2D, Handle);

View file

@ -21,13 +21,4 @@
<ProjectReference Include="..\ChocolArm64\ChocolArm64.csproj" /> <ProjectReference Include="..\ChocolArm64\ChocolArm64.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Gal\OpenGL\FbVtxShader.glsl">
<LogicalName>GlFbVtxShader</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Gal\OpenGL\FbFragShader.glsl">
<LogicalName>GlFbFragShader</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Project> </Project>

View file

@ -293,54 +293,17 @@ namespace Ryujinx.HLE.OsHle.Services.Android
Rect Crop = BufferQueue[Slot].Crop; Rect Crop = BufferQueue[Slot].Crop;
int RealWidth = FbWidth; bool FlipX = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX);
int RealHeight = FbHeight; bool FlipY = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY);
float XSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX) ? -1 : 1; //Rotation is being ignored
float YSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY) ? -1 : 1;
float ScaleX = 1; int Top = Crop.Top;
float ScaleY = 1; int Left = Crop.Left;
int Right = Crop.Right;
int Bottom = Crop.Bottom;
float OffsX = 0; Renderer.QueueAction(() => Renderer.FrameBuffer.SetTransform(FlipX, FlipY, Top, Left, Right, Bottom));
float OffsY = 0;
if (Crop.Right != 0 &&
Crop.Bottom != 0)
{
//Who knows if this is right, I was never good with math...
RealWidth = Crop.Right - Crop.Left;
RealHeight = Crop.Bottom - Crop.Top;
if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90))
{
ScaleY = (float)FbHeight / RealHeight;
ScaleX = (float)FbWidth / RealWidth;
OffsY = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * -XSign;
OffsX = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign;
}
else
{
ScaleX = (float)FbWidth / RealWidth;
ScaleY = (float)FbHeight / RealHeight;
OffsX = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * XSign;
OffsY = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign;
}
}
ScaleX *= XSign;
ScaleY *= YSign;
float Rotate = 0;
if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90))
{
Rotate = -MathF.PI * 0.5f;
}
Renderer.QueueAction(() => Renderer.FrameBuffer.SetTransform(ScaleX, ScaleY, Rotate, OffsX, OffsY));
//TODO: Support double buffering here aswell, it is broken for GPU //TODO: Support double buffering here aswell, it is broken for GPU
//frame buffers because it seems to be completely out of sync. //frame buffers because it seems to be completely out of sync.