aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.OpenGL
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.OpenGL')
-rw-r--r--Ryujinx.Graphics.OpenGL/Buffer.cs74
-rw-r--r--Ryujinx.Graphics.OpenGL/ComputePipeline.cs95
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/AddressModeConverter.cs26
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/BlendFactorConverter.cs39
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/BlendOpConverter.cs25
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/CompareModeConverter.cs20
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/CompareOpConverter.cs28
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/DepthStencilModeConverter.cs20
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/FaceConverter.cs23
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/FrontFaceConverter.cs22
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/IndexTypeConverter.cs21
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/MagFilterConverter.cs20
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/MinFilterConverter.cs24
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/PrimitiveTopologyConverter.cs33
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/StencilOpConverter.cs27
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/SwizzleComponentConverter.cs24
-rw-r--r--Ryujinx.Graphics.OpenGL/Converters/TargetConverter.cs33
-rw-r--r--Ryujinx.Graphics.OpenGL/Counters.cs77
-rw-r--r--Ryujinx.Graphics.OpenGL/Debugger.cs43
-rw-r--r--Ryujinx.Graphics.OpenGL/Formats/FormatInfo.cs45
-rw-r--r--Ryujinx.Graphics.OpenGL/Formats/FormatTable.cs183
-rw-r--r--Ryujinx.Graphics.OpenGL/Framebuffer.cs116
-rw-r--r--Ryujinx.Graphics.OpenGL/GraphicsPipeline.cs718
-rw-r--r--Ryujinx.Graphics.OpenGL/HwCapabilities.cs27
-rw-r--r--Ryujinx.Graphics.OpenGL/Program.cs175
-rw-r--r--Ryujinx.Graphics.OpenGL/Renderer.cs84
-rw-r--r--Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj17
-rw-r--r--Ryujinx.Graphics.OpenGL/Sampler.cs60
-rw-r--r--Ryujinx.Graphics.OpenGL/Shader.cs49
-rw-r--r--Ryujinx.Graphics.OpenGL/TextureCopy.cs128
-rw-r--r--Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs85
-rw-r--r--Ryujinx.Graphics.OpenGL/TextureStorage.cs179
-rw-r--r--Ryujinx.Graphics.OpenGL/TextureView.cs425
-rw-r--r--Ryujinx.Graphics.OpenGL/VertexArray.cs135
-rw-r--r--Ryujinx.Graphics.OpenGL/VertexBuffer.cs19
-rw-r--r--Ryujinx.Graphics.OpenGL/Window.cs248
36 files changed, 3367 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.OpenGL/Buffer.cs b/Ryujinx.Graphics.OpenGL/Buffer.cs
new file mode 100644
index 00000000..b86719ce
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Buffer.cs
@@ -0,0 +1,74 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Buffer : IBuffer
+ {
+ public int Handle { get; }
+
+ public Buffer(int size)
+ {
+ Handle = GL.GenBuffer();
+
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle);
+ GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.DynamicDraw);
+ }
+
+ public void CopyTo(IBuffer destination, int srcOffset, int dstOffset, int size)
+ {
+ GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle);
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, ((Buffer)destination).Handle);
+
+ GL.CopyBufferSubData(
+ BufferTarget.CopyReadBuffer,
+ BufferTarget.CopyWriteBuffer,
+ (IntPtr)srcOffset,
+ (IntPtr)dstOffset,
+ (IntPtr)size);
+ }
+
+ public byte[] GetData(int offset, int size)
+ {
+ GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle);
+
+ byte[] data = new byte[size];
+
+ GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, data);
+
+ return data;
+ }
+
+ public void SetData(Span<byte> data)
+ {
+ unsafe
+ {
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle);
+
+ fixed (byte* ptr = data)
+ {
+ GL.BufferData(BufferTarget.CopyWriteBuffer, data.Length, (IntPtr)ptr, BufferUsageHint.DynamicDraw);
+ }
+ }
+ }
+
+ public void SetData(int offset, Span<byte> data)
+ {
+ GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle);
+
+ unsafe
+ {
+ fixed (byte* ptr = data)
+ {
+ GL.BufferSubData(BufferTarget.CopyWriteBuffer, (IntPtr)offset, data.Length, (IntPtr)ptr);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ GL.DeleteBuffer(Handle);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/ComputePipeline.cs b/Ryujinx.Graphics.OpenGL/ComputePipeline.cs
new file mode 100644
index 00000000..bee96832
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/ComputePipeline.cs
@@ -0,0 +1,95 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class ComputePipeline : IComputePipeline
+ {
+ private Renderer _renderer;
+
+ private Program _program;
+
+ public ComputePipeline(Renderer renderer)
+ {
+ _renderer = renderer;
+ }
+
+ public void Dispatch(int groupsX, int groupsY, int groupsZ)
+ {
+ BindProgram();
+
+ GL.DispatchCompute(groupsX, groupsY, groupsZ);
+
+ UnbindProgram();
+ }
+
+ public void SetProgram(IProgram program)
+ {
+ _program = (Program)program;
+ }
+
+ public void SetStorageBuffer(int index, BufferRange buffer)
+ {
+ BindProgram();
+
+ BindBuffer(index, buffer, isStorage: true);
+
+ UnbindProgram();
+ }
+
+ public void SetUniformBuffer(int index, BufferRange buffer)
+ {
+ BindProgram();
+
+ BindBuffer(index, buffer, isStorage: false);
+
+ UnbindProgram();
+ }
+
+ private void BindBuffer(int index, BufferRange buffer, bool isStorage)
+ {
+ int bindingPoint = isStorage
+ ? _program.GetStorageBufferBindingPoint(ShaderStage.Compute, index)
+ : _program.GetUniformBufferBindingPoint(ShaderStage.Compute, index);
+
+ if (bindingPoint == -1)
+ {
+ return;
+ }
+
+ BufferRangeTarget target = isStorage
+ ? BufferRangeTarget.ShaderStorageBuffer
+ : BufferRangeTarget.UniformBuffer;
+
+ if (buffer.Buffer == null)
+ {
+ GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0);
+
+ return;
+ }
+
+ int bufferHandle = ((Buffer)buffer.Buffer).Handle;
+
+ IntPtr bufferOffset = (IntPtr)buffer.Offset;
+
+ GL.BindBufferRange(
+ target,
+ bindingPoint,
+ bufferHandle,
+ bufferOffset,
+ buffer.Size);
+ }
+
+ private void BindProgram()
+ {
+ _program.Bind();
+ }
+
+ private void UnbindProgram()
+ {
+ ((GraphicsPipeline)_renderer.GraphicsPipeline).RebindProgram();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/AddressModeConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/AddressModeConverter.cs
new file mode 100644
index 00000000..8f9b5074
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/AddressModeConverter.cs
@@ -0,0 +1,26 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Sampler;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class AddressModeConverter
+ {
+ public static TextureWrapMode Convert(this AddressMode mode)
+ {
+ switch (mode)
+ {
+ case AddressMode.Clamp : return TextureWrapMode.Clamp;
+ case AddressMode.Repeat : return TextureWrapMode.Repeat;
+ case AddressMode.MirrorClamp : return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampExt;
+ case AddressMode.MirrorClampToEdge : return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToEdgeExt;
+ case AddressMode.MirrorClampToBorder : return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToBorderExt;
+ case AddressMode.ClampToBorder : return TextureWrapMode.ClampToBorder;
+ case AddressMode.MirroredRepeat : return TextureWrapMode.MirroredRepeat;
+ case AddressMode.ClampToEdge : return TextureWrapMode.ClampToEdge;
+ }
+
+ throw new ArgumentException($"Invalid address mode \"{mode}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/BlendFactorConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/BlendFactorConverter.cs
new file mode 100644
index 00000000..1dc40522
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/BlendFactorConverter.cs
@@ -0,0 +1,39 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Blend;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class BlendFactorConverter
+ {
+ public static All Convert(this BlendFactor factor)
+ {
+ switch (factor)
+ {
+ case BlendFactor.Zero: return All.Zero;
+ case BlendFactor.One: return All.One;
+ case BlendFactor.SrcColor: return All.SrcColor;
+ case BlendFactor.OneMinusSrcColor: return All.OneMinusSrcColor;
+ case BlendFactor.SrcAlpha: return All.SrcAlpha;
+ case BlendFactor.OneMinusSrcAlpha: return All.OneMinusSrcAlpha;
+ case BlendFactor.DstAlpha: return All.DstAlpha;
+ case BlendFactor.OneMinusDstAlpha: return All.OneMinusDstAlpha;
+ case BlendFactor.DstColor: return All.DstColor;
+ case BlendFactor.OneMinusDstColor: return All.OneMinusDstColor;
+ case BlendFactor.SrcAlphaSaturate: return All.SrcAlphaSaturate;
+ case BlendFactor.Src1Color: return All.Src1Color;
+ case BlendFactor.OneMinusSrc1Color: return All.OneMinusSrc1Color;
+ case BlendFactor.Src1Alpha: return All.Src1Alpha;
+ case BlendFactor.OneMinusSrc1Alpha: return All.OneMinusSrc1Alpha;
+ case BlendFactor.ConstantColor: return All.ConstantColor;
+ case BlendFactor.OneMinusConstantColor: return All.OneMinusConstantColor;
+ case BlendFactor.ConstantAlpha: return All.ConstantAlpha;
+ case BlendFactor.OneMinusConstantAlpha: return All.OneMinusConstantAlpha;
+ }
+
+ return All.Zero;
+
+ throw new ArgumentException($"Invalid blend factor \"{factor}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/BlendOpConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/BlendOpConverter.cs
new file mode 100644
index 00000000..b33a3bf8
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/BlendOpConverter.cs
@@ -0,0 +1,25 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Blend;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class BlendOpConverter
+ {
+ public static BlendEquationMode Convert(this BlendOp op)
+ {
+ switch (op)
+ {
+ case BlendOp.Add: return BlendEquationMode.FuncAdd;
+ case BlendOp.Subtract: return BlendEquationMode.FuncSubtract;
+ case BlendOp.ReverseSubtract: return BlendEquationMode.FuncReverseSubtract;
+ case BlendOp.Minimum: return BlendEquationMode.Min;
+ case BlendOp.Maximum: return BlendEquationMode.Max;
+ }
+
+ return BlendEquationMode.FuncAdd;
+
+ throw new ArgumentException($"Invalid blend operation \"{op}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/CompareModeConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/CompareModeConverter.cs
new file mode 100644
index 00000000..c0287d8a
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/CompareModeConverter.cs
@@ -0,0 +1,20 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Sampler;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class CompareModeConverter
+ {
+ public static TextureCompareMode Convert(this CompareMode mode)
+ {
+ switch (mode)
+ {
+ case CompareMode.None: return TextureCompareMode.None;
+ case CompareMode.CompareRToTexture: return TextureCompareMode.CompareRToTexture;
+ }
+
+ throw new ArgumentException($"Invalid compare mode \"{mode}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/CompareOpConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/CompareOpConverter.cs
new file mode 100644
index 00000000..f592735b
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/CompareOpConverter.cs
@@ -0,0 +1,28 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class CompareOpConverter
+ {
+ public static All Convert(this CompareOp op)
+ {
+ switch (op)
+ {
+ case CompareOp.Never: return All.Never;
+ case CompareOp.Less: return All.Less;
+ case CompareOp.Equal: return All.Equal;
+ case CompareOp.LessOrEqual: return All.Lequal;
+ case CompareOp.Greater: return All.Greater;
+ case CompareOp.NotEqual: return All.Notequal;
+ case CompareOp.GreaterOrEqual: return All.Gequal;
+ case CompareOp.Always: return All.Always;
+ }
+
+ return All.Never;
+
+ throw new ArgumentException($"Invalid compare operation \"{op}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/DepthStencilModeConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/DepthStencilModeConverter.cs
new file mode 100644
index 00000000..4d14bdea
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/DepthStencilModeConverter.cs
@@ -0,0 +1,20 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Texture;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class DepthStencilModeConverter
+ {
+ public static All Convert(this DepthStencilMode mode)
+ {
+ switch (mode)
+ {
+ case DepthStencilMode.Depth: return All.Depth;
+ case DepthStencilMode.Stencil: return All.Stencil;
+ }
+
+ throw new ArgumentException($"Invalid depth stencil mode \"{mode}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/FaceConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/FaceConverter.cs
new file mode 100644
index 00000000..5c23b68a
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/FaceConverter.cs
@@ -0,0 +1,23 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class FaceConverter
+ {
+ public static CullFaceMode Convert(this Face face)
+ {
+ switch (face)
+ {
+ case Face.Back: return CullFaceMode.Back;
+ case Face.Front: return CullFaceMode.Front;
+ case Face.FrontAndBack: return CullFaceMode.FrontAndBack;
+ }
+
+ return CullFaceMode.FrontAndBack;
+
+ throw new ArgumentException($"Invalid face \"{face}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/FrontFaceConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/FrontFaceConverter.cs
new file mode 100644
index 00000000..1cae36cd
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/FrontFaceConverter.cs
@@ -0,0 +1,22 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class FrontFaceConverter
+ {
+ public static FrontFaceDirection Convert(this FrontFace frontFace)
+ {
+ switch (frontFace)
+ {
+ case FrontFace.Clockwise: return FrontFaceDirection.Cw;
+ case FrontFace.CounterClockwise: return FrontFaceDirection.Ccw;
+ }
+
+ return FrontFaceDirection.Cw;
+
+ throw new ArgumentException($"Invalid front face \"{frontFace}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/IndexTypeConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/IndexTypeConverter.cs
new file mode 100644
index 00000000..d3100b98
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/IndexTypeConverter.cs
@@ -0,0 +1,21 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class IndexTypeConverter
+ {
+ public static DrawElementsType Convert(this IndexType type)
+ {
+ switch (type)
+ {
+ case IndexType.UByte: return DrawElementsType.UnsignedByte;
+ case IndexType.UShort: return DrawElementsType.UnsignedShort;
+ case IndexType.UInt: return DrawElementsType.UnsignedInt;
+ }
+
+ throw new ArgumentException($"Invalid index type \"{type}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/MagFilterConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/MagFilterConverter.cs
new file mode 100644
index 00000000..d86d8014
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/MagFilterConverter.cs
@@ -0,0 +1,20 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Sampler;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class MagFilterConverter
+ {
+ public static TextureMagFilter Convert(this MagFilter filter)
+ {
+ switch (filter)
+ {
+ case MagFilter.Nearest: return TextureMagFilter.Nearest;
+ case MagFilter.Linear: return TextureMagFilter.Linear;
+ }
+
+ throw new ArgumentException($"Invalid filter \"{filter}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/MinFilterConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/MinFilterConverter.cs
new file mode 100644
index 00000000..128577f8
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/MinFilterConverter.cs
@@ -0,0 +1,24 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Sampler;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class MinFilterConverter
+ {
+ public static TextureMinFilter Convert(this MinFilter filter)
+ {
+ switch (filter)
+ {
+ case MinFilter.Nearest: return TextureMinFilter.Nearest;
+ case MinFilter.Linear: return TextureMinFilter.Linear;
+ case MinFilter.NearestMipmapNearest: return TextureMinFilter.NearestMipmapNearest;
+ case MinFilter.LinearMipmapNearest: return TextureMinFilter.LinearMipmapNearest;
+ case MinFilter.NearestMipmapLinear: return TextureMinFilter.NearestMipmapLinear;
+ case MinFilter.LinearMipmapLinear: return TextureMinFilter.LinearMipmapLinear;
+ }
+
+ throw new ArgumentException($"Invalid filter \"{filter}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/PrimitiveTopologyConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/PrimitiveTopologyConverter.cs
new file mode 100644
index 00000000..fe1dfe39
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/PrimitiveTopologyConverter.cs
@@ -0,0 +1,33 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class PrimitiveTopologyConverter
+ {
+ public static PrimitiveType Convert(this PrimitiveTopology topology)
+ {
+ switch (topology)
+ {
+ case PrimitiveTopology.Points: return PrimitiveType.Points;
+ case PrimitiveTopology.Lines: return PrimitiveType.Lines;
+ case PrimitiveTopology.LineLoop: return PrimitiveType.LineLoop;
+ case PrimitiveTopology.LineStrip: return PrimitiveType.LineStrip;
+ case PrimitiveTopology.Triangles: return PrimitiveType.Triangles;
+ case PrimitiveTopology.TriangleStrip: return PrimitiveType.TriangleStrip;
+ case PrimitiveTopology.TriangleFan: return PrimitiveType.TriangleFan;
+ case PrimitiveTopology.Quads: return PrimitiveType.Quads;
+ case PrimitiveTopology.QuadStrip: return PrimitiveType.QuadStrip;
+ case PrimitiveTopology.Polygon: return PrimitiveType.Polygon;
+ case PrimitiveTopology.LinesAdjacency: return PrimitiveType.LinesAdjacency;
+ case PrimitiveTopology.LineStripAdjacency: return PrimitiveType.LineStripAdjacency;
+ case PrimitiveTopology.TrianglesAdjacency: return PrimitiveType.TrianglesAdjacency;
+ case PrimitiveTopology.TriangleStripAdjacency: return PrimitiveType.TriangleStripAdjacency;
+ case PrimitiveTopology.Patches: return PrimitiveType.Patches;
+ }
+
+ throw new ArgumentException($"Invalid primitive topology \"{topology}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/StencilOpConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/StencilOpConverter.cs
new file mode 100644
index 00000000..44fbe0a5
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/StencilOpConverter.cs
@@ -0,0 +1,27 @@
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class StencilOpConverter
+ {
+ public static StencilOp Convert(this GAL.DepthStencil.StencilOp op)
+ {
+ switch (op)
+ {
+ case GAL.DepthStencil.StencilOp.Keep: return StencilOp.Keep;
+ case GAL.DepthStencil.StencilOp.Zero: return StencilOp.Zero;
+ case GAL.DepthStencil.StencilOp.Replace: return StencilOp.Replace;
+ case GAL.DepthStencil.StencilOp.IncrementAndClamp: return StencilOp.Incr;
+ case GAL.DepthStencil.StencilOp.DecrementAndClamp: return StencilOp.Decr;
+ case GAL.DepthStencil.StencilOp.Invert: return StencilOp.Invert;
+ case GAL.DepthStencil.StencilOp.IncrementAndWrap: return StencilOp.IncrWrap;
+ case GAL.DepthStencil.StencilOp.DecrementAndWrap: return StencilOp.DecrWrap;
+ }
+
+ return StencilOp.Keep;
+
+ throw new ArgumentException($"Invalid stencil operation \"{op}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/SwizzleComponentConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/SwizzleComponentConverter.cs
new file mode 100644
index 00000000..d9ce650f
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/SwizzleComponentConverter.cs
@@ -0,0 +1,24 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Texture;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class SwizzleComponentConverter
+ {
+ public static All Convert(this SwizzleComponent swizzleComponent)
+ {
+ switch (swizzleComponent)
+ {
+ case SwizzleComponent.Zero: return All.Zero;
+ case SwizzleComponent.One: return All.One;
+ case SwizzleComponent.Red: return All.Red;
+ case SwizzleComponent.Green: return All.Green;
+ case SwizzleComponent.Blue: return All.Blue;
+ case SwizzleComponent.Alpha: return All.Alpha;
+ }
+
+ throw new ArgumentException($"Invalid swizzle component \"{swizzleComponent}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Converters/TargetConverter.cs b/Ryujinx.Graphics.OpenGL/Converters/TargetConverter.cs
new file mode 100644
index 00000000..6b524980
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Converters/TargetConverter.cs
@@ -0,0 +1,33 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.Texture;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class TargetConverter
+ {
+ public static ImageTarget ConvertToImageTarget(this Target target)
+ {
+ return (ImageTarget)target.Convert();
+ }
+
+ public static TextureTarget Convert(this Target target)
+ {
+ switch (target)
+ {
+ case Target.Texture1D: return TextureTarget.Texture1D;
+ case Target.Texture2D: return TextureTarget.Texture2D;
+ case Target.Texture3D: return TextureTarget.Texture3D;
+ case Target.Texture1DArray: return TextureTarget.Texture1DArray;
+ case Target.Texture2DArray: return TextureTarget.Texture2DArray;
+ case Target.Texture2DMultisample: return TextureTarget.Texture2DMultisample;
+ case Target.Rectangle: return TextureTarget.TextureRectangle;
+ case Target.Cubemap: return TextureTarget.TextureCubeMap;
+ case Target.CubemapArray: return TextureTarget.TextureCubeMapArray;
+ case Target.TextureBuffer: return TextureTarget.TextureBuffer;
+ }
+
+ throw new ArgumentException($"Invalid target \"{target}\".");
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Counters.cs b/Ryujinx.Graphics.OpenGL/Counters.cs
new file mode 100644
index 00000000..e82a040f
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Counters.cs
@@ -0,0 +1,77 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Counters
+ {
+ private int[] _queryObjects;
+
+ private ulong[] _accumulatedCounters;
+
+ public Counters()
+ {
+ int count = Enum.GetNames(typeof(CounterType)).Length;
+
+ _queryObjects = new int[count];
+
+ _accumulatedCounters = new ulong[count];
+ }
+
+ public void Initialize()
+ {
+ for (int index = 0; index < _queryObjects.Length; index++)
+ {
+ int handle = GL.GenQuery();
+
+ _queryObjects[index] = handle;
+
+ CounterType type = (CounterType)index;
+
+ GL.BeginQuery(GetTarget(type), handle);
+ }
+ }
+
+ public ulong GetCounter(CounterType type)
+ {
+ UpdateAccumulatedCounter(type);
+
+ return _accumulatedCounters[(int)type];
+ }
+
+ public void ResetCounter(CounterType type)
+ {
+ UpdateAccumulatedCounter(type);
+
+ _accumulatedCounters[(int)type] = 0;
+ }
+
+ private void UpdateAccumulatedCounter(CounterType type)
+ {
+ int handle = _queryObjects[(int)type];
+
+ QueryTarget target = GetTarget(type);
+
+ GL.EndQuery(target);
+
+ GL.GetQueryObject(handle, GetQueryObjectParam.QueryResult, out long result);
+
+ _accumulatedCounters[(int)type] += (ulong)result;
+
+ GL.BeginQuery(target, handle);
+ }
+
+ private static QueryTarget GetTarget(CounterType type)
+ {
+ switch (type)
+ {
+ case CounterType.SamplesPassed: return QueryTarget.SamplesPassed;
+ case CounterType.PrimitivesGenerated: return QueryTarget.PrimitivesGenerated;
+ case CounterType.TransformFeedbackPrimitivesWritten: return QueryTarget.TransformFeedbackPrimitivesWritten;
+ }
+
+ return QueryTarget.SamplesPassed;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Debugger.cs b/Ryujinx.Graphics.OpenGL/Debugger.cs
new file mode 100644
index 00000000..9da98ae1
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Debugger.cs
@@ -0,0 +1,43 @@
+using OpenTK.Graphics.OpenGL;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ public static class Debugger
+ {
+ private static DebugProc _debugCallback;
+
+ public static void Initialize()
+ {
+ GL.Enable(EnableCap.DebugOutputSynchronous);
+
+ int[] array = null;
+
+ GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, array, true);
+
+ _debugCallback = PrintDbg;
+
+ GL.DebugMessageCallback(_debugCallback, IntPtr.Zero);
+ }
+
+ private static void PrintDbg(
+ DebugSource source,
+ DebugType type,
+ int id,
+ DebugSeverity severity,
+ int length,
+ IntPtr message,
+ IntPtr userParam)
+ {
+ string msg = Marshal.PtrToStringAnsi(message);
+
+ if (type == DebugType.DebugTypeError && !msg.Contains("link"))
+ {
+ throw new Exception(msg);
+ }
+
+ System.Console.WriteLine("GL message: " + source + " " + type + " " + severity + " " + msg);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Formats/FormatInfo.cs b/Ryujinx.Graphics.OpenGL/Formats/FormatInfo.cs
new file mode 100644
index 00000000..a1fd723f
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Formats/FormatInfo.cs
@@ -0,0 +1,45 @@
+using OpenTK.Graphics.OpenGL;
+
+namespace Ryujinx.Graphics.OpenGL.Formats
+{
+ struct FormatInfo
+ {
+ public int Components { get; }
+ public bool Normalized { get; }
+ public bool Scaled { get; }
+
+ public PixelInternalFormat PixelInternalFormat { get; }
+ public PixelFormat PixelFormat { get; }
+ public PixelType PixelType { get; }
+
+ public bool IsCompressed { get; }
+
+ public FormatInfo(
+ int components,
+ bool normalized,
+ bool scaled,
+ All pixelInternalFormat,
+ PixelFormat pixelFormat,
+ PixelType pixelType)
+ {
+ Components = components;
+ Normalized = normalized;
+ Scaled = scaled;
+ PixelInternalFormat = (PixelInternalFormat)pixelInternalFormat;
+ PixelFormat = pixelFormat;
+ PixelType = pixelType;
+ IsCompressed = false;
+ }
+
+ public FormatInfo(int components, bool normalized, bool scaled, All pixelFormat)
+ {
+ Components = components;
+ Normalized = normalized;
+ Scaled = scaled;
+ PixelInternalFormat = 0;
+ PixelFormat = (PixelFormat)pixelFormat;
+ PixelType = 0;
+ IsCompressed = true;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Formats/FormatTable.cs b/Ryujinx.Graphics.OpenGL/Formats/FormatTable.cs
new file mode 100644
index 00000000..13e88c55
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Formats/FormatTable.cs
@@ -0,0 +1,183 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL.Formats
+{
+ struct FormatTable
+ {
+ private static FormatInfo[] _table;
+
+ static FormatTable()
+ {
+ _table = new FormatInfo[Enum.GetNames(typeof(Format)).Length];
+
+ Add(Format.R8Unorm, new FormatInfo(1, true, false, All.R8, PixelFormat.Red, PixelType.UnsignedByte));
+ Add(Format.R8Snorm, new FormatInfo(1, true, false, All.R8Snorm, PixelFormat.Red, PixelType.Byte));
+ Add(Format.R8Uint, new FormatInfo(1, false, false, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte));
+ Add(Format.R8Sint, new FormatInfo(1, false, false, All.R8i, PixelFormat.RedInteger, PixelType.Byte));
+ Add(Format.R16Float, new FormatInfo(1, false, false, All.R16f, PixelFormat.Red, PixelType.HalfFloat));
+ Add(Format.R16Unorm, new FormatInfo(1, true, false, All.R16, PixelFormat.Red, PixelType.UnsignedShort));
+ Add(Format.R16Snorm, new FormatInfo(1, true, false, All.R16Snorm, PixelFormat.Red, PixelType.Short));
+ Add(Format.R16Uint, new FormatInfo(1, false, false, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort));
+ Add(Format.R16Sint, new FormatInfo(1, false, false, All.R16i, PixelFormat.RedInteger, PixelType.Short));
+ Add(Format.R32Float, new FormatInfo(1, false, false, All.R32f, PixelFormat.Red, PixelType.Float));
+ Add(Format.R32Uint, new FormatInfo(1, false, false, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt));
+ Add(Format.R32Sint, new FormatInfo(1, false, false, All.R32i, PixelFormat.RedInteger, PixelType.Int));
+ Add(Format.R8G8Unorm, new FormatInfo(2, true, false, All.Rg8, PixelFormat.Rg, PixelType.UnsignedByte));
+ Add(Format.R8G8Snorm, new FormatInfo(2, true, false, All.Rg8Snorm, PixelFormat.Rg, PixelType.Byte));
+ Add(Format.R8G8Uint, new FormatInfo(2, false, false, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte));
+ Add(Format.R8G8Sint, new FormatInfo(2, false, false, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte));
+ Add(Format.R16G16Float, new FormatInfo(2, false, false, All.Rg16f, PixelFormat.Rg, PixelType.HalfFloat));
+ Add(Format.R16G16Unorm, new FormatInfo(2, true, false, All.Rg16, PixelFormat.Rg, PixelType.UnsignedShort));
+ Add(Format.R16G16Snorm, new FormatInfo(2, true, false, All.Rg16Snorm, PixelFormat.Rg, PixelType.Short));
+ Add(Format.R16G16Uint, new FormatInfo(2, false, false, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort));
+ Add(Format.R16G16Sint, new FormatInfo(2, false, false, All.Rg16i, PixelFormat.RgInteger, PixelType.Short));
+ Add(Format.R32G32Float, new FormatInfo(2, false, false, All.Rg32f, PixelFormat.Rg, PixelType.Float));
+ Add(Format.R32G32Uint, new FormatInfo(2, false, false, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt));
+ Add(Format.R32G32Sint, new FormatInfo(2, false, false, All.Rg32i, PixelFormat.RgInteger, PixelType.Int));
+ Add(Format.R8G8B8Unorm, new FormatInfo(3, true, false, All.Rgb8, PixelFormat.Rgb, PixelType.UnsignedByte));
+ Add(Format.R8G8B8Snorm, new FormatInfo(3, true, false, All.Rgb8Snorm, PixelFormat.Rgb, PixelType.Byte));
+ Add(Format.R8G8B8Uint, new FormatInfo(3, false, false, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte));
+ Add(Format.R8G8B8Sint, new FormatInfo(3, false, false, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte));
+ Add(Format.R16G16B16Float, new FormatInfo(3, false, false, All.Rgb16f, PixelFormat.Rgb, PixelType.HalfFloat));
+ Add(Format.R16G16B16Unorm, new FormatInfo(3, true, false, All.Rgb16, PixelFormat.Rgb, PixelType.UnsignedShort));
+ Add(Format.R16G16B16Snorm, new FormatInfo(3, true, false, All.Rgb16Snorm, PixelFormat.Rgb, PixelType.Short));
+ Add(Format.R16G16B16Uint, new FormatInfo(3, false, false, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort));
+ Add(Format.R16G16B16Sint, new FormatInfo(3, false, false, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short));
+ Add(Format.R32G32B32Float, new FormatInfo(3, false, false, All.Rgb32f, PixelFormat.Rgb, PixelType.Float));
+ Add(Format.R32G32B32Uint, new FormatInfo(3, false, false, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt));
+ Add(Format.R32G32B32Sint, new FormatInfo(3, false, false, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int));
+ Add(Format.R8G8B8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
+ Add(Format.R8G8B8A8Snorm, new FormatInfo(4, true, false, All.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte));
+ Add(Format.R8G8B8A8Uint, new FormatInfo(4, false, false, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte));
+ Add(Format.R8G8B8A8Sint, new FormatInfo(4, false, false, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte));
+ Add(Format.R16G16B16A16Float, new FormatInfo(4, false, false, All.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat));
+ Add(Format.R16G16B16A16Unorm, new FormatInfo(4, true, false, All.Rgba16, PixelFormat.Rgba, PixelType.UnsignedShort));
+ Add(Format.R16G16B16A16Snorm, new FormatInfo(4, true, false, All.Rgba16Snorm, PixelFormat.Rgba, PixelType.Short));
+ Add(Format.R16G16B16A16Uint, new FormatInfo(4, false, false, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort));
+ Add(Format.R16G16B16A16Sint, new FormatInfo(4, false, false, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short));
+ Add(Format.R32G32B32A32Float, new FormatInfo(4, false, false, All.Rgba32f, PixelFormat.Rgba, PixelType.Float));
+ Add(Format.R32G32B32A32Uint, new FormatInfo(4, false, false, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt));
+ Add(Format.R32G32B32A32Sint, new FormatInfo(4, false, false, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int));
+ Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte));
+ Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort));
+ Add(Format.D24X8Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt));
+ Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float));
+ Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
+ Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev));
+ Add(Format.R8G8B8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.Rgba, PixelType.UnsignedByte));
+ Add(Format.R8G8B8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte));
+ Add(Format.R4G4B4A4Unorm, new FormatInfo(4, true, false, All.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed));
+ Add(Format.R5G5B5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgb, PixelType.UnsignedShort1555Reversed));
+ Add(Format.R5G5B5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
+ Add(Format.R5G6B5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed));
+ Add(Format.R10G10B10A2Unorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed));
+ Add(Format.R10G10B10A2Uint, new FormatInfo(4, false, false, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed));
+ Add(Format.R11G11B10Float, new FormatInfo(3, false, false, All.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev));
+ Add(Format.R9G9B9E5Float, new FormatInfo(3, false, false, All.Rgb9E5, PixelFormat.Rgb, PixelType.UnsignedInt5999Rev));
+ Add(Format.Bc1RgbUnorm, new FormatInfo(2, true, false, All.CompressedRgbS3tcDxt1Ext));
+ Add(Format.Bc1RgbaUnorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt1Ext));
+ Add(Format.Bc2Unorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt3Ext));
+ Add(Format.Bc3Unorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt5Ext));
+ Add(Format.Bc1RgbSrgb, new FormatInfo(2, false, false, All.CompressedSrgbS3tcDxt1Ext));
+ Add(Format.Bc1RgbaSrgb, new FormatInfo(1, true, false, All.CompressedSrgbAlphaS3tcDxt1Ext));
+ Add(Format.Bc2Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaS3tcDxt3Ext));
+ Add(Format.Bc3Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext));
+ Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1));
+ Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1));
+ Add(Format.Bc5Unorm, new FormatInfo(1, true, false, All.CompressedRgRgtc2));
+ Add(Format.Bc5Snorm, new FormatInfo(1, true, false, All.CompressedSignedRgRgtc2));
+ Add(Format.Bc7Unorm, new FormatInfo(1, true, false, All.CompressedRgbaBptcUnorm));
+ Add(Format.Bc7Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaBptcUnorm));
+ Add(Format.Bc6HUfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcUnsignedFloat));
+ Add(Format.Bc6HSfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcSignedFloat));
+ Add(Format.R8Uscaled, new FormatInfo(1, false, true, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte));
+ Add(Format.R8Sscaled, new FormatInfo(1, false, true, All.R8i, PixelFormat.RedInteger, PixelType.Byte));
+ Add(Format.R16Uscaled, new FormatInfo(1, false, true, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort));
+ Add(Format.R16Sscaled, new FormatInfo(1, false, true, All.R16i, PixelFormat.RedInteger, PixelType.Short));
+ Add(Format.R32Uscaled, new FormatInfo(1, false, true, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt));
+ Add(Format.R32Sscaled, new FormatInfo(1, false, true, All.R32i, PixelFormat.RedInteger, PixelType.Int));
+ Add(Format.R8G8Uscaled, new FormatInfo(2, false, true, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte));
+ Add(Format.R8G8Sscaled, new FormatInfo(2, false, true, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte));
+ Add(Format.R16G16Uscaled, new FormatInfo(2, false, true, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort));
+ Add(Format.R16G16Sscaled, new FormatInfo(2, false, true, All.Rg16i, PixelFormat.RgInteger, PixelType.Short));
+ Add(Format.R32G32Uscaled, new FormatInfo(2, false, true, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt));
+ Add(Format.R32G32Sscaled, new FormatInfo(2, false, true, All.Rg32i, PixelFormat.RgInteger, PixelType.Int));
+ Add(Format.R8G8B8Uscaled, new FormatInfo(3, false, true, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte));
+ Add(Format.R8G8B8Sscaled, new FormatInfo(3, false, true, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte));
+ Add(Format.R16G16B16Uscaled, new FormatInfo(3, false, true, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort));
+ Add(Format.R16G16B16Sscaled, new FormatInfo(3, false, true, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short));
+ Add(Format.R32G32B32Uscaled, new FormatInfo(3, false, true, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt));
+ Add(Format.R32G32B32Sscaled, new FormatInfo(3, false, true, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int));
+ Add(Format.R8G8B8A8Uscaled, new FormatInfo(4, false, true, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte));
+ Add(Format.R8G8B8A8Sscaled, new FormatInfo(4, false, true, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte));
+ Add(Format.R16G16B16A16Uscaled, new FormatInfo(4, false, true, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort));
+ Add(Format.R16G16B16A16Sscaled, new FormatInfo(4, false, true, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short));
+ Add(Format.R32G32B32A32Uscaled, new FormatInfo(4, false, true, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt));
+ Add(Format.R32G32B32A32Sscaled, new FormatInfo(4, false, true, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int));
+ Add(Format.R10G10B10A2Snorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, (PixelType)All.Int2101010Rev));
+ Add(Format.R10G10B10A2Sint, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.RgbaInteger, (PixelType)All.Int2101010Rev));
+ Add(Format.R10G10B10A2Uscaled, new FormatInfo(4, false, true, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed));
+ Add(Format.R10G10B10A2Sscaled, new FormatInfo(4, false, true, All.Rgb10A2, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed));
+ Add(Format.R8G8B8X8Unorm, new FormatInfo(4, true, false, All.Rgb8, PixelFormat.Rgba, PixelType.UnsignedByte));
+ Add(Format.R8G8B8X8Snorm, new FormatInfo(4, true, false, All.Rgb8Snorm, PixelFormat.Rgba, PixelType.Byte));
+ Add(Format.R8G8B8X8Uint, new FormatInfo(4, false, false, All.Rgb8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte));
+ Add(Format.R8G8B8X8Sint, new FormatInfo(4, false, false, All.Rgb8i, PixelFormat.RgbaInteger, PixelType.Byte));
+ Add(Format.R16G16B16X16Float, new FormatInfo(4, false, false, All.Rgb16f, PixelFormat.Rgba, PixelType.HalfFloat));
+ Add(Format.R16G16B16X16Unorm, new FormatInfo(4, true, false, All.Rgb16, PixelFormat.Rgba, PixelType.UnsignedShort));
+ Add(Format.R16G16B16X16Snorm, new FormatInfo(4, true, false, All.Rgb16Snorm, PixelFormat.Rgba, PixelType.Short));
+ Add(Format.R16G16B16X16Uint, new FormatInfo(4, false, false, All.Rgb16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort));
+ Add(Format.R16G16B16X16Sint, new FormatInfo(4, false, false, All.Rgb16i, PixelFormat.RgbaInteger, PixelType.Short));
+ Add(Format.R32G32B32X32Float, new FormatInfo(4, false, false, All.Rgb32f, PixelFormat.Rgba, PixelType.Float));
+ Add(Format.R32G32B32X32Uint, new FormatInfo(4, false, false, All.Rgb32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt));
+ Add(Format.R32G32B32X32Sint, new FormatInfo(4, false, false, All.Rgb32i, PixelFormat.RgbaInteger, PixelType.Int));
+ Add(Format.Astc4x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc4X4Khr));
+ Add(Format.Astc5x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X4Khr));
+ Add(Format.Astc5x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X5Khr));
+ Add(Format.Astc6x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X5Khr));
+ Add(Format.Astc6x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X6Khr));
+ Add(Format.Astc8x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X5Khr));
+ Add(Format.Astc8x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X6Khr));
+ Add(Format.Astc8x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X8Khr));
+ Add(Format.Astc10x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X5Khr));
+ Add(Format.Astc10x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X6Khr));
+ Add(Format.Astc10x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X8Khr));
+ Add(Format.Astc10x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X10Khr));
+ Add(Format.Astc12x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X10Khr));
+ Add(Format.Astc12x12Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X12Khr));
+ Add(Format.Astc4x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
+ Add(Format.Astc5x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
+ Add(Format.Astc5x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
+ Add(Format.Astc6x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
+ Add(Format.Astc6x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
+ Add(Format.Astc8x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
+ Add(Format.Astc8x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
+ Add(Format.Astc8x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
+ Add(Format.Astc10x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
+ Add(Format.Astc10x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
+ Add(Format.Astc10x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
+ Add(Format.Astc10x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
+ Add(Format.Astc12x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
+ Add(Format.Astc12x12Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr));
+ Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Bgr, PixelType.UnsignedShort565));
+ Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Bgra, PixelType.UnsignedShort5551));
+ Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort5551));
+ Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort1555Reversed));
+ Add(Format.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte));
+ Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte));
+ Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.BgraInteger, PixelType.UnsignedByte));
+ Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.BgraInteger, PixelType.UnsignedByte));
+ }
+
+ private static void Add(Format format, FormatInfo info)
+ {
+ _table[(int)format] = info;
+ }
+
+ public static FormatInfo GetFormatInfo(Format format)
+ {
+ return _table[(int)format];
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Framebuffer.cs b/Ryujinx.Graphics.OpenGL/Framebuffer.cs
new file mode 100644
index 00000000..40913009
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Framebuffer.cs
@@ -0,0 +1,116 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Framebuffer : IDisposable
+ {
+ public int Handle { get; private set; }
+
+ private FramebufferAttachment _lastDsAttachment;
+
+ public Framebuffer()
+ {
+ Handle = GL.GenFramebuffer();
+ }
+
+ public void Bind()
+ {
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
+ }
+
+ public void AttachColor(int index, TextureView color)
+ {
+ GL.FramebufferTexture(
+ FramebufferTarget.Framebuffer,
+ FramebufferAttachment.ColorAttachment0 + index,
+ color?.Handle ?? 0,
+ 0);
+ }
+
+ public void AttachColor(int index, TextureView color, int layer)
+ {
+ GL.FramebufferTextureLayer(
+ FramebufferTarget.Framebuffer,
+ FramebufferAttachment.ColorAttachment0 + index,
+ color?.Handle ?? 0,
+ 0,
+ layer);
+ }
+
+ public void AttachDepthStencil(TextureView depthStencil)
+ {
+ // Detach the last depth/stencil buffer if there is any.
+ if (_lastDsAttachment != 0)
+ {
+ GL.FramebufferTexture(FramebufferTarget.Framebuffer, _lastDsAttachment, 0, 0);
+ }
+
+ if (depthStencil != null)
+ {
+ FramebufferAttachment attachment;
+
+ if (IsPackedDepthStencilFormat(depthStencil.Format))
+ {
+ attachment = FramebufferAttachment.DepthStencilAttachment;
+ }
+ else if (IsDepthOnlyFormat(depthStencil.Format))
+ {
+ attachment = FramebufferAttachment.DepthAttachment;
+ }
+ else
+ {
+ attachment = FramebufferAttachment.StencilAttachment;
+ }
+
+ GL.FramebufferTexture(
+ FramebufferTarget.Framebuffer,
+ attachment,
+ depthStencil.Handle,
+ 0);
+
+ _lastDsAttachment = attachment;
+ }
+ else
+ {
+ _lastDsAttachment = 0;
+ }
+ }
+
+ public void SetDrawBuffers(int colorsCount)
+ {
+ DrawBuffersEnum[] drawBuffers = new DrawBuffersEnum[colorsCount];
+
+ for (int index = 0; index < colorsCount; index++)
+ {
+ drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index;
+ }
+
+ GL.DrawBuffers(colorsCount, drawBuffers);
+ }
+
+ private static bool IsPackedDepthStencilFormat(Format format)
+ {
+ return format == Format.D24UnormS8Uint ||
+ format == Format.D32FloatS8Uint;
+ }
+
+ private static bool IsDepthOnlyFormat(Format format)
+ {
+ return format == Format.D16Unorm ||
+ format == Format.D24X8Unorm ||
+ format == Format.D32Float;
+ }
+
+ public void Dispose()
+ {
+ if (Handle != 0)
+ {
+ GL.DeleteFramebuffer(Handle);
+
+ Handle = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/GraphicsPipeline.cs b/Ryujinx.Graphics.OpenGL/GraphicsPipeline.cs
new file mode 100644
index 00000000..e9f6b2fb
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/GraphicsPipeline.cs
@@ -0,0 +1,718 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Blend;
+using Ryujinx.Graphics.GAL.Color;
+using Ryujinx.Graphics.GAL.DepthStencil;
+using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.Shader;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class GraphicsPipeline : IGraphicsPipeline
+ {
+ private Program _program;
+
+ private VertexArray _vertexArray;
+ private Framebuffer _framebuffer;
+
+ private IntPtr _indexBaseOffset;
+
+ private DrawElementsType _elementsType;
+
+ private PrimitiveType _primitiveType;
+
+ private int _stencilFrontMask;
+ private bool _depthMask;
+ private bool _depthTest;
+ private bool _hasDepthBuffer;
+
+ private TextureView _unit0Texture;
+
+ private ClipOrigin _clipOrigin;
+
+ private uint[] _componentMasks;
+
+ internal GraphicsPipeline()
+ {
+ _clipOrigin = ClipOrigin.LowerLeft;
+ }
+
+ public void BindBlendState(int index, BlendDescriptor blend)
+ {
+ if (!blend.Enable)
+ {
+ GL.Disable(IndexedEnableCap.Blend, index);
+
+ return;
+ }
+
+ GL.BlendEquationSeparate(
+ index,
+ blend.ColorOp.Convert(),
+ blend.AlphaOp.Convert());
+
+ GL.BlendFuncSeparate(
+ index,
+ (BlendingFactorSrc) blend.ColorSrcFactor.Convert(),
+ (BlendingFactorDest)blend.ColorDstFactor.Convert(),
+ (BlendingFactorSrc) blend.AlphaSrcFactor.Convert(),
+ (BlendingFactorDest)blend.AlphaDstFactor.Convert());
+
+ GL.Enable(IndexedEnableCap.Blend, index);
+ }
+
+ public void BindIndexBuffer(BufferRange buffer, IndexType type)
+ {
+ _elementsType = type.Convert();
+
+ _indexBaseOffset = (IntPtr)buffer.Offset;
+
+ EnsureVertexArray();
+
+ _vertexArray.SetIndexBuffer((Buffer)buffer.Buffer);
+ }
+
+ public void BindProgram(IProgram program)
+ {
+ _program = (Program)program;
+
+ _program.Bind();
+ }
+
+ public void BindSampler(int index, ShaderStage stage, ISampler sampler)
+ {
+ int unit = _program.GetTextureUnit(stage, index);
+
+ if (unit != -1 && sampler != null)
+ {
+ ((Sampler)sampler).Bind(unit);
+ }
+ }
+
+ public void BindTexture(int index, ShaderStage stage, ITexture texture)
+ {
+ int unit = _program.GetTextureUnit(stage, index);
+
+ if (unit != -1 && texture != null)
+ {
+ if (unit == 0)
+ {
+ _unit0Texture = ((TextureView)texture);
+ }
+ else
+ {
+ ((TextureView)texture).Bind(unit);
+ }
+ }
+ }
+
+ public void BindStorageBuffers(int index, ShaderStage stage, BufferRange[] buffers)
+ {
+ BindBuffers(index, stage, buffers, isStorage: true);
+ }
+
+ public void BindUniformBuffers(int index, ShaderStage stage, BufferRange[] buffers)
+ {
+ BindBuffers(index, stage, buffers, isStorage: false);
+ }
+
+ private void BindBuffers(int index, ShaderStage stage, BufferRange[] buffers, bool isStorage)
+ {
+ for (int bufferIndex = 0; bufferIndex < buffers.Length; bufferIndex++, index++)
+ {
+ int bindingPoint = isStorage
+ ? _program.GetStorageBufferBindingPoint(stage, index)
+ : _program.GetUniformBufferBindingPoint(stage, index);
+
+ if (bindingPoint == -1)
+ {
+ continue;
+ }
+
+ BufferRange buffer = buffers[bufferIndex];
+
+ BufferRangeTarget target = isStorage
+ ? BufferRangeTarget.ShaderStorageBuffer
+ : BufferRangeTarget.UniformBuffer;
+
+ if (buffer.Buffer == null)
+ {
+ GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0);
+
+ continue;
+ }
+
+ int bufferHandle = ((Buffer)buffer.Buffer).Handle;
+
+ IntPtr bufferOffset = (IntPtr)buffer.Offset;
+
+ GL.BindBufferRange(
+ target,
+ bindingPoint,
+ bufferHandle,
+ bufferOffset,
+ buffer.Size);
+ }
+ }
+
+ public void BindVertexAttribs(VertexAttribDescriptor[] vertexAttribs)
+ {
+ EnsureVertexArray();
+
+ _vertexArray.SetVertexAttributes(vertexAttribs);
+ }
+
+ public void BindVertexBuffers(VertexBufferDescriptor[] vertexBuffers)
+ {
+ EnsureVertexArray();
+
+ _vertexArray.SetVertexBuffers(vertexBuffers);
+ }
+
+ public void ClearRenderTargetColor(int index, uint componentMask, ColorF color)
+ {
+ GL.ColorMask(
+ index,
+ (componentMask & 1) != 0,
+ (componentMask & 2) != 0,
+ (componentMask & 4) != 0,
+ (componentMask & 8) != 0);
+
+ float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
+
+ GL.ClearBuffer(ClearBuffer.Color, index, colors);
+
+ RestoreComponentMask(index);
+ }
+
+ public void ClearRenderTargetColor(int index, uint componentMask, ColorSI color)
+ {
+ GL.ColorMask(
+ index,
+ (componentMask & 1u) != 0,
+ (componentMask & 2u) != 0,
+ (componentMask & 4u) != 0,
+ (componentMask & 8u) != 0);
+
+ int[] colors = new int[] { color.Red, color.Green, color.Blue, color.Alpha };
+
+ GL.ClearBuffer(ClearBuffer.Color, index, colors);
+
+ RestoreComponentMask(index);
+ }
+
+ public void ClearRenderTargetColor(int index, uint componentMask, ColorUI color)
+ {
+ GL.ColorMask(
+ index,
+ (componentMask & 1u) != 0,
+ (componentMask & 2u) != 0,
+ (componentMask & 4u) != 0,
+ (componentMask & 8u) != 0);
+
+ uint[] colors = new uint[] { color.Red, color.Green, color.Blue, color.Alpha };
+
+ GL.ClearBuffer(ClearBuffer.Color, index, colors);
+
+ RestoreComponentMask(index);
+ }
+
+ public void ClearRenderTargetDepthStencil(
+ float depthValue,
+ bool depthMask,
+ int stencilValue,
+ int stencilMask)
+ {
+ bool stencilMaskChanged =
+ stencilMask != 0 &&
+ stencilMask != _stencilFrontMask;
+
+ bool depthMaskChanged = depthMask && depthMask != _depthMask;
+
+ if (stencilMaskChanged)
+ {
+ GL.StencilMaskSeparate(StencilFace.Front, stencilMask);
+ }
+
+ if (depthMaskChanged)
+ {
+ GL.DepthMask(depthMask);
+ }
+
+ if (depthMask && stencilMask != 0)
+ {
+ GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue);
+ }
+ else if (depthMask)
+ {
+ GL.ClearBuffer(ClearBuffer.Depth, 0, ref depthValue);
+ }
+ else if (stencilMask != 0)
+ {
+ GL.ClearBuffer(ClearBuffer.Stencil, 0, ref stencilValue);
+ }
+
+ if (stencilMaskChanged)
+ {
+ GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
+ }
+
+ if (depthMaskChanged)
+ {
+ GL.DepthMask(_depthMask);
+ }
+ }
+
+ public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
+ {
+ if (!_program.IsLinked)
+ {
+ return;
+ }
+
+ PrepareForDraw();
+
+ if (firstInstance == 0 && instanceCount == 1)
+ {
+ if (_primitiveType == PrimitiveType.Quads)
+ {
+ for (int offset = 0; offset < vertexCount; offset += 4)
+ {
+ GL.DrawArrays(PrimitiveType.TriangleFan, firstVertex + offset, 4);
+ }
+ }
+ else if (_primitiveType == PrimitiveType.QuadStrip)
+ {
+ GL.DrawArrays(PrimitiveType.TriangleFan, firstVertex, 4);
+
+ for (int offset = 2; offset < vertexCount; offset += 2)
+ {
+ GL.DrawArrays(PrimitiveType.TriangleFan, firstVertex + offset, 4);
+ }
+ }
+ else
+ {
+ GL.DrawArrays(_primitiveType, firstVertex, vertexCount);
+ }
+
+ // GL.DrawArrays(_primitiveType, firstVertex, vertexCount);
+ }
+ else if (firstInstance == 0)
+ {
+ GL.DrawArraysInstanced(_primitiveType, firstVertex, vertexCount, instanceCount);
+ }
+ else
+ {
+ GL.DrawArraysInstancedBaseInstance(
+ _primitiveType,
+ firstVertex,
+ vertexCount,
+ instanceCount,
+ firstInstance);
+ }
+ }
+
+ public void DrawIndexed(
+ int indexCount,
+ int instanceCount,
+ int firstIndex,
+ int firstVertex,
+ int firstInstance)
+ {
+ if (!_program.IsLinked)
+ {
+ return;
+ }
+
+ PrepareForDraw();
+
+ int firstIndexOffset = firstIndex;
+
+ switch (_elementsType)
+ {
+ case DrawElementsType.UnsignedShort: firstIndexOffset *= 2; break;
+ case DrawElementsType.UnsignedInt: firstIndexOffset *= 4; break;
+ }
+
+ IntPtr indexBaseOffset = _indexBaseOffset + firstIndexOffset;
+
+ if (firstInstance == 0 && firstVertex == 0 && instanceCount == 1)
+ {
+ GL.DrawElements(_primitiveType, indexCount, _elementsType, indexBaseOffset);
+ }
+ else if (firstInstance == 0 && instanceCount == 1)
+ {
+ GL.DrawElementsBaseVertex(
+ _primitiveType,
+ indexCount,
+ _elementsType,
+ indexBaseOffset,
+ firstVertex);
+ }
+ else if (firstInstance == 0 && firstVertex == 0)
+ {
+ GL.DrawElementsInstanced(
+ _primitiveType,
+ indexCount,
+ _elementsType,
+ indexBaseOffset,
+ instanceCount);
+ }
+ else if (firstInstance == 0)
+ {
+ GL.DrawElementsInstancedBaseVertex(
+ _primitiveType,
+ indexCount,
+ _elementsType,
+ indexBaseOffset,
+ instanceCount,
+ firstVertex);
+ }
+ else if (firstVertex == 0)
+ {
+ GL.DrawElementsInstancedBaseInstance(
+ _primitiveType,
+ indexCount,
+ _elementsType,
+ indexBaseOffset,
+ instanceCount,
+ firstInstance);
+ }
+ else
+ {
+ GL.DrawElementsInstancedBaseVertexBaseInstance(
+ _primitiveType,
+ indexCount,
+ _elementsType,
+ indexBaseOffset,
+ instanceCount,
+ firstVertex,
+ firstInstance);
+ }
+ }
+
+ public void DrawIndirect(BufferRange buffer, ulong offset, int drawCount, int stride)
+ {
+
+ }
+
+ public void DrawIndexedIndirect(BufferRange buffer, ulong offset, int drawCount, int stride)
+ {
+
+ }
+
+ public void SetBlendColor(ColorF color)
+ {
+ GL.BlendColor(color.Red, color.Green, color.Blue, color.Alpha);
+ }
+
+ public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
+ {
+ if ((enables & PolygonModeMask.Point) != 0)
+ {
+ GL.Enable(EnableCap.PolygonOffsetPoint);
+ }
+ else
+ {
+ GL.Disable(EnableCap.PolygonOffsetPoint);
+ }
+
+ if ((enables & PolygonModeMask.Line) != 0)
+ {
+ GL.Enable(EnableCap.PolygonOffsetLine);
+ }
+ else
+ {
+ GL.Disable(EnableCap.PolygonOffsetLine);
+ }
+
+ if ((enables & PolygonModeMask.Fill) != 0)
+ {
+ GL.Enable(EnableCap.PolygonOffsetFill);
+ }
+ else
+ {
+ GL.Disable(EnableCap.PolygonOffsetFill);
+ }
+
+ if (enables == 0)
+ {
+ return;
+ }
+
+ GL.PolygonOffset(factor, units);
+ // GL.PolygonOffsetClamp(factor, units, clamp);
+ }
+
+ public void SetDepthTest(DepthTestDescriptor depthTest)
+ {
+ GL.DepthFunc((DepthFunction)depthTest.Func.Convert());
+
+ _depthMask = depthTest.WriteEnable;
+ _depthTest = depthTest.TestEnable;
+
+ UpdateDepthTest();
+ }
+
+ public void SetFaceCulling(bool enable, Face face)
+ {
+ if (!enable)
+ {
+ GL.Disable(EnableCap.CullFace);
+
+ return;
+ }
+
+ GL.CullFace(face.Convert());
+
+ GL.Enable(EnableCap.CullFace);
+ }
+
+ public void SetFrontFace(FrontFace frontFace)
+ {
+ GL.FrontFace(frontFace.Convert());
+ }
+
+ public void SetPrimitiveRestart(bool enable, int index)
+ {
+ if (!enable)
+ {
+ GL.Disable(EnableCap.PrimitiveRestart);
+
+ return;
+ }
+
+ GL.PrimitiveRestartIndex(index);
+
+ GL.Enable(EnableCap.PrimitiveRestart);
+ }
+
+ public void SetPrimitiveTopology(PrimitiveTopology topology)
+ {
+ _primitiveType = topology.Convert();
+ }
+
+ public void SetRenderTargetColorMasks(uint[] componentMasks)
+ {
+ _componentMasks = (uint[])componentMasks.Clone();
+
+ for (int index = 0; index < componentMasks.Length; index++)
+ {
+ RestoreComponentMask(index);
+ }
+ }
+
+ public void SetRenderTargets(ITexture color3D, ITexture depthStencil)
+ {
+ EnsureFramebuffer();
+
+ TextureView color = (TextureView)color3D;
+
+ for (int index = 0; index < color.DepthOrLayers; index++)
+ {
+ _framebuffer.AttachColor(index, color, index);
+ }
+
+ TextureView depthStencilView = (TextureView)depthStencil;
+
+ _framebuffer.AttachDepthStencil(depthStencilView);
+
+ _framebuffer.SetDrawBuffers(color.DepthOrLayers);
+
+ _hasDepthBuffer = depthStencil != null && depthStencilView.Format != Format.S8Uint;
+
+ UpdateDepthTest();
+ }
+
+ public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+ {
+ EnsureFramebuffer();
+
+ for (int index = 0; index < colors.Length; index++)
+ {
+ TextureView color = (TextureView)colors[index];
+
+ _framebuffer.AttachColor(index, color);
+ }
+
+ TextureView depthStencilView = (TextureView)depthStencil;
+
+ _framebuffer.AttachDepthStencil(depthStencilView);
+
+ _framebuffer.SetDrawBuffers(colors.Length);
+
+ _hasDepthBuffer = depthStencil != null && depthStencilView.Format != Format.S8Uint;
+
+ UpdateDepthTest();
+ }
+
+ public void SetStencilTest(StencilTestDescriptor stencilTest)
+ {
+ if (!stencilTest.TestEnable)
+ {
+ GL.Disable(EnableCap.StencilTest);
+
+ return;
+ }
+
+ GL.StencilOpSeparate(
+ StencilFace.Front,
+ stencilTest.FrontSFail.Convert(),
+ stencilTest.FrontDpFail.Convert(),
+ stencilTest.FrontDpPass.Convert());
+
+ GL.StencilFuncSeparate(
+ StencilFace.Front,
+ (StencilFunction)stencilTest.FrontFunc.Convert(),
+ stencilTest.FrontFuncRef,
+ stencilTest.FrontFuncMask);
+
+ GL.StencilMaskSeparate(StencilFace.Front, stencilTest.FrontMask);
+
+ GL.StencilOpSeparate(
+ StencilFace.Back,
+ stencilTest.BackSFail.Convert(),
+ stencilTest.BackDpFail.Convert(),
+ stencilTest.BackDpPass.Convert());
+
+ GL.StencilFuncSeparate(
+ StencilFace.Back,
+ (StencilFunction)stencilTest.BackFunc.Convert(),
+ stencilTest.BackFuncRef,
+ stencilTest.BackFuncMask);
+
+ GL.StencilMaskSeparate(StencilFace.Back, stencilTest.BackMask);
+
+ GL.Enable(EnableCap.StencilTest);
+
+ _stencilFrontMask = stencilTest.FrontMask;
+ }
+
+ public void SetViewports(int first, Viewport[] viewports)
+ {
+ bool flipY = false;
+
+ float[] viewportArray = new float[viewports.Length * 4];
+
+ double[] depthRangeArray = new double[viewports.Length * 2];
+
+ for (int index = 0; index < viewports.Length; index++)
+ {
+ int viewportElemIndex = index * 4;
+
+ Viewport viewport = viewports[index];
+
+ viewportArray[viewportElemIndex + 0] = viewport.Region.X;
+ viewportArray[viewportElemIndex + 1] = viewport.Region.Y;
+
+ // OpenGL does not support per-viewport flipping, so
+ // instead we decide that based on the viewport 0 value.
+ // It will apply to all viewports.
+ if (index == 0)
+ {
+ flipY = viewport.Region.Height < 0;
+ }
+
+ if (viewport.SwizzleY == ViewportSwizzle.NegativeY)
+ {
+ flipY = !flipY;
+ }
+
+ viewportArray[viewportElemIndex + 2] = MathF.Abs(viewport.Region.Width);
+ viewportArray[viewportElemIndex + 3] = MathF.Abs(viewport.Region.Height);
+
+ depthRangeArray[index * 2 + 0] = viewport.DepthNear;
+ depthRangeArray[index * 2 + 1] = viewport.DepthFar;
+ }
+
+ GL.ViewportArray(first, viewports.Length, viewportArray);
+
+ GL.DepthRangeArray(first, viewports.Length, depthRangeArray);
+
+ SetOrigin(flipY ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft);
+ }
+
+ private void SetOrigin(ClipOrigin origin)
+ {
+ if (_clipOrigin != origin)
+ {
+ _clipOrigin = origin;
+
+ GL.ClipControl(origin, ClipDepthMode.NegativeOneToOne);
+ }
+ }
+
+ private void EnsureVertexArray()
+ {
+ if (_vertexArray == null)
+ {
+ _vertexArray = new VertexArray();
+
+ _vertexArray.Bind();
+ }
+ }
+
+ private void EnsureFramebuffer()
+ {
+ if (_framebuffer == null)
+ {
+ _framebuffer = new Framebuffer();
+
+ _framebuffer.Bind();
+
+ GL.Enable(EnableCap.FramebufferSrgb);
+ }
+ }
+
+ private void UpdateDepthTest()
+ {
+ // Enabling depth operations is only valid when we have
+ // a depth buffer, otherwise it's not allowed.
+ if (_hasDepthBuffer)
+ {
+ if (_depthTest)
+ {
+ GL.Enable(EnableCap.DepthTest);
+ }
+ else
+ {
+ GL.Disable(EnableCap.DepthTest);
+ }
+
+ GL.DepthMask(_depthMask);
+ }
+ else
+ {
+ GL.Disable(EnableCap.DepthTest);
+
+ GL.DepthMask(false);
+ }
+ }
+
+ private void PrepareForDraw()
+ {
+ _vertexArray.Validate();
+
+ if (_unit0Texture != null)
+ {
+ _unit0Texture.Bind(0);
+ }
+ }
+
+ private void RestoreComponentMask(int index)
+ {
+ GL.ColorMask(
+ index,
+ (_componentMasks[index] & 1u) != 0,
+ (_componentMasks[index] & 2u) != 0,
+ (_componentMasks[index] & 4u) != 0,
+ (_componentMasks[index] & 8u) != 0);
+ }
+
+ public void RebindProgram()
+ {
+ _program?.Bind();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
new file mode 100644
index 00000000..f958946e
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs
@@ -0,0 +1,27 @@
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class HwCapabilities
+ {
+ private static Lazy<bool> _astcCompression = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
+
+ public static bool SupportsAstcCompression => _astcCompression.Value;
+
+ private static bool HasExtension(string name)
+ {
+ int numExtensions = GL.GetInteger(GetPName.NumExtensions);
+
+ for (int extension = 0; extension < numExtensions; extension++)
+ {
+ if (GL.GetString(StringNameIndexed.Extensions, extension) == name)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs
new file mode 100644
index 00000000..1f95b449
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Program.cs
@@ -0,0 +1,175 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using OpenTK.Graphics.OpenGL;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Program : IProgram
+ {
+ private const int StageShift = 5;
+ private const int SbStageShift = 4;
+
+ public int Handle { get; private set; }
+
+ public bool IsLinked { get; private set; }
+
+ private int[] _ubBindingPoints;
+ private int[] _sbBindingPoints;
+ private int[] _textureUnits;
+
+ public Program(IShader[] shaders)
+ {
+ _ubBindingPoints = new int[32 * 6];
+ _sbBindingPoints = new int[16 * 6];
+ _textureUnits = new int[32 * 6];
+
+ for (int index = 0; index < _ubBindingPoints.Length; index++)
+ {
+ _ubBindingPoints[index] = -1;
+ }
+
+ for (int index = 0; index < _sbBindingPoints.Length; index++)
+ {
+ _sbBindingPoints[index] = -1;
+ }
+
+ for (int index = 0; index < _textureUnits.Length; index++)
+ {
+ _textureUnits[index] = -1;
+ }
+
+ Handle = GL.CreateProgram();
+
+ for (int index = 0; index < shaders.Length; index++)
+ {
+ int shaderHandle = ((Shader)shaders[index]).Handle;
+
+ GL.AttachShader(Handle, shaderHandle);
+ }
+
+ GL.LinkProgram(Handle);
+
+ CheckProgramLink();
+
+ Bind();
+
+ int extraBlockindex = GL.GetUniformBlockIndex(Handle, "Extra");
+
+ if (extraBlockindex >= 0)
+ {
+ GL.UniformBlockBinding(Handle, extraBlockindex, 0);
+ }
+
+ int ubBindingPoint = 1;
+ int sbBindingPoint = 0;
+ int textureUnit = 0;
+
+ for (int index = 0; index < shaders.Length; index++)
+ {
+ Shader shader = (Shader)shaders[index];
+
+ foreach (BufferDescriptor descriptor in shader.Info.CBuffers)
+ {
+ int location = GL.GetUniformBlockIndex(Handle, descriptor.Name);
+
+ if (location < 0)
+ {
+ continue;
+ }
+
+ GL.UniformBlockBinding(Handle, location, ubBindingPoint);
+
+ int bpIndex = (int)shader.Stage << StageShift | descriptor.Slot;
+
+ _ubBindingPoints[bpIndex] = ubBindingPoint;
+
+ ubBindingPoint++;
+ }
+
+ foreach (BufferDescriptor descriptor in shader.Info.SBuffers)
+ {
+ int location = GL.GetProgramResourceIndex(Handle, ProgramInterface.ShaderStorageBlock, descriptor.Name);
+
+ if (location < 0)
+ {
+ continue;
+ }
+
+ GL.ShaderStorageBlockBinding(Handle, location, sbBindingPoint);
+
+ int bpIndex = (int)shader.Stage << SbStageShift | descriptor.Slot;
+
+ _sbBindingPoints[bpIndex] = sbBindingPoint;
+
+ sbBindingPoint++;
+ }
+
+ int samplerIndex = 0;
+
+ foreach (TextureDescriptor descriptor in shader.Info.Textures)
+ {
+ int location = GL.GetUniformLocation(Handle, descriptor.Name);
+
+ if (location < 0)
+ {
+ continue;
+ }
+
+ GL.Uniform1(location, textureUnit);
+
+ int uIndex = (int)shader.Stage << StageShift | samplerIndex++;
+
+ _textureUnits[uIndex] = textureUnit;
+
+ textureUnit++;
+ }
+ }
+ }
+
+ public void Bind()
+ {
+ GL.UseProgram(Handle);
+ }
+
+ public int GetUniformBufferBindingPoint(ShaderStage stage, int index)
+ {
+ return _ubBindingPoints[(int)stage << StageShift | index];
+ }
+
+ public int GetStorageBufferBindingPoint(ShaderStage stage, int index)
+ {
+ return _sbBindingPoints[(int)stage << SbStageShift | index];
+ }
+
+ public int GetTextureUnit(ShaderStage stage, int index)
+ {
+ return _textureUnits[(int)stage << StageShift | index];
+ }
+
+ private void CheckProgramLink()
+ {
+ int status = 0;
+
+ GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out status);
+
+ if (status == 0)
+ {
+ // throw new System.Exception(GL.GetProgramInfoLog(Handle));
+ }
+ else
+ {
+ IsLinked = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (Handle != 0)
+ {
+ GL.DeleteProgram(Handle);
+
+ Handle = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
new file mode 100644
index 00000000..56ba7624
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -0,0 +1,84 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Sampler;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ public class Renderer : IRenderer
+ {
+ public IComputePipeline ComputePipeline { get; }
+ public IGraphicsPipeline GraphicsPipeline { get; }
+
+ private Counters _counters;
+
+ private Window _window;
+
+ public IWindow Window => _window;
+
+ internal TextureCopy TextureCopy { get; }
+
+ public Renderer()
+ {
+ ComputePipeline = new ComputePipeline(this);
+ GraphicsPipeline = new GraphicsPipeline();
+
+ _counters = new Counters();
+
+ _window = new Window();
+
+ TextureCopy = new TextureCopy();
+ }
+
+ public IShader CompileShader(ShaderProgram shader)
+ {
+ return new Shader(shader);
+ }
+
+ public IBuffer CreateBuffer(int size)
+ {
+ return new Buffer(size);
+ }
+
+ public IProgram CreateProgram(IShader[] shaders)
+ {
+ return new Program(shaders);
+ }
+
+ public ISampler CreateSampler(SamplerCreateInfo info)
+ {
+ return new Sampler(info);
+ }
+
+ public ITexture CreateTexture(TextureCreateInfo info)
+ {
+ return new TextureStorage(this, info).CreateDefaultView();
+ }
+
+ public void FlushPipelines()
+ {
+ GL.Finish();
+ }
+
+ public Capabilities GetCapabilities()
+ {
+ return new Capabilities(HwCapabilities.SupportsAstcCompression);
+ }
+
+ public ulong GetCounter(CounterType type)
+ {
+ return _counters.GetCounter(type);
+ }
+
+ public void InitializeCounters()
+ {
+ _counters.Initialize();
+ }
+
+ public void ResetCounter(CounterType type)
+ {
+ _counters.ResetCounter(type);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
new file mode 100644
index 00000000..484e8682
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
+ <ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Ryujinx.Graphics.OpenGL/Sampler.cs b/Ryujinx.Graphics.OpenGL/Sampler.cs
new file mode 100644
index 00000000..9b9f09a1
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Sampler.cs
@@ -0,0 +1,60 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Sampler;
+using OpenTK.Graphics.OpenGL;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Sampler : ISampler
+ {
+ public int Handle { get; private set; }
+
+ public Sampler(SamplerCreateInfo info)
+ {
+ Handle = GL.GenSampler();
+
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureMinFilter, (int)info.MinFilter.Convert());
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureMagFilter, (int)info.MagFilter.Convert());
+
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapS, (int)info.AddressU.Convert());
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapT, (int)info.AddressV.Convert());
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapR, (int)info.AddressP.Convert());
+
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareMode, (int)info.CompareMode.Convert());
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareFunc, (int)info.CompareOp.Convert());
+
+ unsafe
+ {
+ float* borderColor = stackalloc float[4]
+ {
+ info.BorderColor.Red,
+ info.BorderColor.Green,
+ info.BorderColor.Blue,
+ info.BorderColor.Alpha
+ };
+
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureBorderColor, borderColor);
+ }
+
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureMinLod, info.MinLod);
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxLod, info.MaxLod);
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureLodBias, info.MipLodBias);
+
+ GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxAnisotropyExt, info.MaxAnisotropy);
+ }
+
+ public void Bind(int unit)
+ {
+ GL.BindSampler(unit, Handle);
+ }
+
+ public void Dispose()
+ {
+ if (Handle != 0)
+ {
+ GL.DeleteSampler(Handle);
+
+ Handle = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Shader.cs b/Ryujinx.Graphics.OpenGL/Shader.cs
new file mode 100644
index 00000000..f25845cf
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Shader.cs
@@ -0,0 +1,49 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Shader : IShader
+ {
+ public int Handle { get; private set; }
+
+ private ShaderProgram _program;
+
+ public ShaderProgramInfo Info => _program.Info;
+
+ public ShaderStage Stage => _program.Stage;
+
+ public Shader(ShaderProgram program)
+ {
+ _program = program;
+
+ ShaderType type = ShaderType.VertexShader;
+
+ switch (program.Stage)
+ {
+ case ShaderStage.Compute: type = ShaderType.ComputeShader; break;
+ case ShaderStage.Vertex: type = ShaderType.VertexShader; break;
+ case ShaderStage.TessellationControl: type = ShaderType.TessControlShader; break;
+ case ShaderStage.TessellationEvaluation: type = ShaderType.TessEvaluationShader; break;
+ case ShaderStage.Geometry: type = ShaderType.GeometryShader; break;
+ case ShaderStage.Fragment: type = ShaderType.FragmentShader; break;
+ }
+
+ Handle = GL.CreateShader(type);
+
+ GL.ShaderSource(Handle, program.Code);
+ GL.CompileShader(Handle);
+ }
+
+ public void Dispose()
+ {
+ if (Handle != 0)
+ {
+ GL.DeleteShader(Handle);
+
+ Handle = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/TextureCopy.cs
new file mode 100644
index 00000000..75ef07c1
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/TextureCopy.cs
@@ -0,0 +1,128 @@
+using Ryujinx.Graphics.GAL;
+using OpenTK.Graphics.OpenGL;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class TextureCopy
+ {
+ private int _srcFramebuffer;
+ private int _dstFramebuffer;
+
+ public void Copy(
+ TextureView src,
+ TextureView dst,
+ Extents2D srcRegion,
+ Extents2D dstRegion,
+ bool linearFilter)
+ {
+ GL.Disable(EnableCap.FramebufferSrgb);
+
+ int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding);
+ int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding);
+
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
+
+ Attach(FramebufferTarget.ReadFramebuffer, src.Format, src.Handle);
+ Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle);
+
+ ClearBufferMask mask = GetMask(src.Format);
+
+ BlitFramebufferFilter filter = linearFilter
+ ? BlitFramebufferFilter.Linear
+ : BlitFramebufferFilter.Nearest;
+
+ GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
+ GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+
+ GL.BlitFramebuffer(
+ srcRegion.X1,
+ srcRegion.Y1,
+ srcRegion.X2,
+ srcRegion.Y2,
+ dstRegion.X1,
+ dstRegion.Y1,
+ dstRegion.X2,
+ dstRegion.Y2,
+ mask,
+ filter);
+
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
+
+ GL.Enable(EnableCap.FramebufferSrgb);
+ }
+
+ private static void Detach(FramebufferTarget target, Format format)
+ {
+ Attach(target, format, 0);
+ }
+
+ private static void Attach(FramebufferTarget target, Format format, int handle)
+ {
+ if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)
+ {
+ GL.FramebufferTexture(target, FramebufferAttachment.DepthStencilAttachment, handle, 0);
+ }
+ else if (IsDepthOnly(format))
+ {
+ GL.FramebufferTexture(target, FramebufferAttachment.DepthAttachment, handle, 0);
+ }
+ else if (format == Format.S8Uint)
+ {
+ GL.FramebufferTexture(target, FramebufferAttachment.StencilAttachment, handle, 0);
+ }
+ else
+ {
+ GL.FramebufferTexture(target, FramebufferAttachment.ColorAttachment0, handle, 0);
+ }
+ }
+
+ private static ClearBufferMask GetMask(Format format)
+ {
+ if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)
+ {
+ return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit;
+ }
+ else if (IsDepthOnly(format))
+ {
+ return ClearBufferMask.DepthBufferBit;
+ }
+ else if (format == Format.S8Uint)
+ {
+ return ClearBufferMask.StencilBufferBit;
+ }
+ else
+ {
+ return ClearBufferMask.ColorBufferBit;
+ }
+ }
+
+ private static bool IsDepthOnly(Format format)
+ {
+ return format == Format.D16Unorm ||
+ format == Format.D24X8Unorm ||
+ format == Format.D32Float;
+ }
+
+ private int GetSrcFramebufferLazy()
+ {
+ if (_srcFramebuffer == 0)
+ {
+ _srcFramebuffer = GL.GenFramebuffer();
+ }
+
+ return _srcFramebuffer;
+ }
+
+ private int GetDstFramebufferLazy()
+ {
+ if (_dstFramebuffer == 0)
+ {
+ _dstFramebuffer = GL.GenFramebuffer();
+ }
+
+ return _dstFramebuffer;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs b/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs
new file mode 100644
index 00000000..dae492d9
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs
@@ -0,0 +1,85 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL.Texture;
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ static class TextureCopyUnscaled
+ {
+ public static void Copy(TextureView src, TextureView dst, int dstLayer, int dstLevel)
+ {
+ int srcWidth = src.Width;
+ int srcHeight = src.Height;
+ int srcDepth = src.DepthOrLayers;
+ int srcLevels = src.Levels;
+
+ srcWidth = Math.Max(1, srcWidth >> dstLevel);
+ srcHeight = Math.Max(1, srcHeight >> dstLevel);
+
+ if (src.Target == Target.Texture3D)
+ {
+ srcDepth = Math.Max(1, srcDepth >> dstLevel);
+ }
+
+ int dstWidth = dst.Width;
+ int dstHeight = dst.Height;
+ int dstDepth = dst.DepthOrLayers;
+ int dstLevels = dst.Levels;
+
+ // When copying from a compressed to a non-compressed format,
+ // the non-compressed texture will have the size of the texture
+ // in blocks (not in texels), so we must adjust that size to
+ // match the size in texels of the compressed texture.
+ if (!src.IsCompressed && dst.IsCompressed)
+ {
+ dstWidth = BitUtils.DivRoundUp(dstWidth, dst.BlockWidth);
+ dstHeight = BitUtils.DivRoundUp(dstHeight, dst.BlockHeight);
+ }
+ else if (src.IsCompressed && !dst.IsCompressed)
+ {
+ dstWidth *= dst.BlockWidth;
+ dstHeight *= dst.BlockHeight;
+ }
+
+ int width = Math.Min(srcWidth, dstWidth);
+ int height = Math.Min(srcHeight, dstHeight);
+ int depth = Math.Min(srcDepth, dstDepth);
+ int levels = Math.Min(srcLevels, dstLevels);
+
+ for (int level = 0; level < levels; level++)
+ {
+ // Stop copy if we are already out of the levels range.
+ if (level >= src.Levels || dstLevel + level >= dst.Levels)
+ {
+ break;
+ }
+
+ GL.CopyImageSubData(
+ src.Handle,
+ src.Target.ConvertToImageTarget(),
+ level,
+ 0,
+ 0,
+ 0,
+ dst.Handle,
+ dst.Target.ConvertToImageTarget(),
+ dstLevel + level,
+ 0,
+ 0,
+ dstLayer,
+ width,
+ height,
+ depth);
+
+ width = Math.Max(1, width >> 1);
+ height = Math.Max(1, height >> 1);
+
+ if (src.Target == Target.Texture3D)
+ {
+ depth = Math.Max(1, depth >> 1);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/TextureStorage.cs b/Ryujinx.Graphics.OpenGL/TextureStorage.cs
new file mode 100644
index 00000000..d74b0a8e
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/TextureStorage.cs
@@ -0,0 +1,179 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.OpenGL.Formats;
+using OpenTK.Graphics.OpenGL;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class TextureStorage
+ {
+ public int Handle { get; private set; }
+
+ private Renderer _renderer;
+
+ private TextureCreateInfo _info;
+
+ public Target Target => _info.Target;
+
+ private int _viewsCount;
+
+ public TextureStorage(Renderer renderer, TextureCreateInfo info)
+ {
+ _renderer = renderer;
+ _info = info;
+
+ Handle = GL.GenTexture();
+
+ CreateImmutableStorage();
+ }
+
+ private void CreateImmutableStorage()
+ {
+ TextureTarget target = _info.Target.Convert();
+
+ GL.ActiveTexture(TextureUnit.Texture0);
+
+ GL.BindTexture(target, Handle);
+
+ FormatInfo format = FormatTable.GetFormatInfo(_info.Format);
+
+ SizedInternalFormat internalFormat;
+
+ if (format.IsCompressed)
+ {
+ internalFormat = (SizedInternalFormat)format.PixelFormat;
+ }
+ else
+ {
+ internalFormat = (SizedInternalFormat)format.PixelInternalFormat;
+ }
+
+ switch (_info.Target)
+ {
+ case Target.Texture1D:
+ GL.TexStorage1D(
+ TextureTarget1d.Texture1D,
+ _info.Levels,
+ internalFormat,
+ _info.Width);
+ break;
+
+ case Target.Texture1DArray:
+ GL.TexStorage2D(
+ TextureTarget2d.Texture1DArray,
+ _info.Levels,
+ internalFormat,
+ _info.Width,
+ _info.Height);
+ break;
+
+ case Target.Texture2D:
+ GL.TexStorage2D(
+ TextureTarget2d.Texture2D,
+ _info.Levels,
+ internalFormat,
+ _info.Width,
+ _info.Height);
+ break;
+
+ case Target.Texture2DArray:
+ GL.TexStorage3D(
+ TextureTarget3d.Texture2DArray,
+ _info.Levels,
+ internalFormat,
+ _info.Width,
+ _info.Height,
+ _info.Depth);
+ break;
+
+ case Target.Texture2DMultisample:
+ GL.TexStorage2DMultisample(
+ TextureTargetMultisample2d.Texture2DMultisample,
+ _info.Samples,
+ internalFormat,
+ _info.Width,
+ _info.Height,
+ true);
+ break;
+
+ case Target.Texture2DMultisampleArray:
+ GL.TexStorage3DMultisample(
+ TextureTargetMultisample3d.Texture2DMultisampleArray,
+ _info.Samples,
+ internalFormat,
+ _info.Width,
+ _info.Height,
+ _info.Depth,
+ true);
+ break;
+
+ case Target.Texture3D:
+ GL.TexStorage3D(
+ TextureTarget3d.Texture3D,
+ _info.Levels,
+ internalFormat,
+ _info.Width,
+ _info.Height,
+ _info.Depth);
+ break;
+
+ case Target.Cubemap:
+ GL.TexStorage2D(
+ TextureTarget2d.TextureCubeMap,
+ _info.Levels,
+ internalFormat,
+ _info.Width,
+ _info.Height);
+ break;
+
+ case Target.CubemapArray:
+ GL.TexStorage3D(
+ (TextureTarget3d)All.TextureCubeMapArray,
+ _info.Levels,
+ internalFormat,
+ _info.Width,
+ _info.Height,
+ _info.Depth);
+ break;
+ }
+ }
+
+ public ITexture CreateDefaultView()
+ {
+ int layers = _info.GetLayers();
+
+ return CreateView(_info, 0, 0);
+ }
+
+ public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ IncrementViewsCount();
+
+ return new TextureView(_renderer, this, info, firstLayer, firstLevel);
+ }
+
+ private void IncrementViewsCount()
+ {
+ _viewsCount++;
+ }
+
+ public void DecrementViewsCount()
+ {
+ // If we don't have any views, then the storage is now useless, delete it.
+ if (--_viewsCount == 0)
+ {
+ Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ if (Handle != 0)
+ {
+ GL.DeleteTexture(Handle);
+
+ Handle = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/TextureView.cs b/Ryujinx.Graphics.OpenGL/TextureView.cs
new file mode 100644
index 00000000..8a2b50dc
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/TextureView.cs
@@ -0,0 +1,425 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.OpenGL.Formats;
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class TextureView : ITexture
+ {
+ public int Handle { get; private set; }
+
+ private Renderer _renderer;
+
+ private TextureStorage _parent;
+
+ private TextureView _emulatedViewParent;
+
+ private TextureCreateInfo _info;
+
+ private int _firstLayer;
+ private int _firstLevel;
+
+ private bool _acquired;
+ private bool _pendingDelete;
+
+ public int Width => _info.Width;
+ public int Height => _info.Height;
+ public int DepthOrLayers => _info.GetDepthOrLayers();
+ public int Levels => _info.Levels;
+
+ public Target Target => _info.Target;
+ public Format Format => _info.Format;
+
+ public int BlockWidth => _info.BlockWidth;
+ public int BlockHeight => _info.BlockHeight;
+
+ public bool IsCompressed => _info.IsCompressed;
+
+ public TextureView(
+ Renderer renderer,
+ TextureStorage parent,
+ TextureCreateInfo info,
+ int firstLayer,
+ int firstLevel)
+ {
+ _renderer = renderer;
+ _parent = parent;
+ _info = info;
+
+ _firstLayer = firstLayer;
+ _firstLevel = firstLevel;
+
+ Handle = GL.GenTexture();
+
+ CreateView();
+ }
+
+ private void CreateView()
+ {
+ TextureTarget target = Target.Convert();
+
+ FormatInfo format = FormatTable.GetFormatInfo(_info.Format);
+
+ PixelInternalFormat pixelInternalFormat;
+
+ if (format.IsCompressed)
+ {
+ pixelInternalFormat = (PixelInternalFormat)format.PixelFormat;
+ }
+ else
+ {
+ pixelInternalFormat = format.PixelInternalFormat;
+ }
+
+ GL.TextureView(
+ Handle,
+ target,
+ _parent.Handle,
+ pixelInternalFormat,
+ _firstLevel,
+ _info.Levels,
+ _firstLayer,
+ _info.GetLayers());
+
+ GL.ActiveTexture(TextureUnit.Texture0);
+
+ GL.BindTexture(target, Handle);
+
+ int[] swizzleRgba = new int[]
+ {
+ (int)_info.SwizzleR.Convert(),
+ (int)_info.SwizzleG.Convert(),
+ (int)_info.SwizzleB.Convert(),
+ (int)_info.SwizzleA.Convert()
+ };
+
+ GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba);
+
+ int maxLevel = _info.Levels - 1;
+
+ if (maxLevel < 0)
+ {
+ maxLevel = 0;
+ }
+
+ GL.TexParameter(target, TextureParameterName.TextureMaxLevel, maxLevel);
+
+ // GL.TexParameter(target, TextureParameterName.DepthStencilTextureMode, (int)_info.DepthStencilMode.Convert());
+ }
+
+ public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ if (_info.IsCompressed == info.IsCompressed)
+ {
+ firstLayer += _firstLayer;
+ firstLevel += _firstLevel;
+
+ return _parent.CreateView(info, firstLayer, firstLevel);
+ }
+ else
+ {
+ // TODO: Improve
+ TextureView emulatedView = (TextureView)_renderer.CreateTexture(info);
+
+ emulatedView._emulatedViewParent = this;
+
+ emulatedView._firstLayer = firstLayer;
+ emulatedView._firstLevel = firstLevel;
+
+ return emulatedView;
+ }
+ }
+
+ public int GetStorageDebugId()
+ {
+ return _parent.GetHashCode();
+ }
+
+ public void CopyTo(ITexture destination)
+ {
+ TextureView destinationView = (TextureView)destination;
+
+ TextureCopyUnscaled.Copy(this, destinationView, 0, 0);
+
+ int width = Math.Min(Width, destinationView.Width);
+ int height = Math.Min(Height, destinationView.Height);
+
+ int depth = Math.Min(_info.GetDepthOrLayers(), destinationView._info.GetDepthOrLayers());
+
+ int levels = Math.Min(_info.Levels, destinationView._info.Levels);
+
+ if (destinationView._emulatedViewParent != null)
+ {
+ TextureCopyUnscaled.Copy(
+ this,
+ destinationView._emulatedViewParent,
+ destinationView._firstLayer,
+ destinationView._firstLevel);
+ }
+ }
+
+ public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
+ {
+ _renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
+ }
+
+ public byte[] GetData(int face)
+ {
+ TextureTarget target = Target.Convert();
+
+ Bind(target, 0);
+
+ FormatInfo format = FormatTable.GetFormatInfo(_info.Format);
+
+ int depth = _info.GetDepthOrLayers();
+
+ if (target == TextureTarget.TextureCubeMap)
+ {
+ target = TextureTarget.TextureCubeMapPositiveX + face;
+ }
+
+ if (format.IsCompressed)
+ {
+ byte[] data = new byte[_info.Width * _info.Height * depth * 4];
+
+ GL.GetTexImage(target, 0, PixelFormat.Rgba, PixelType.UnsignedByte, data);
+
+ return data;
+ }
+ else
+ {
+ byte[] data = new byte[_info.GetMipSize(0)];
+
+ GL.GetTexImage(target, 0, format.PixelFormat, format.PixelType, data);
+
+ return data;
+ }
+ }
+
+ public void SetData(Span<byte> data)
+ {
+ unsafe
+ {
+ fixed (byte* ptr = data)
+ {
+ SetData((IntPtr)ptr, data.Length);
+ }
+ }
+ }
+
+ private void SetData(IntPtr data, int size)
+ {
+ TextureTarget target = Target.Convert();
+
+ Bind(target, 0);
+
+ FormatInfo format = FormatTable.GetFormatInfo(_info.Format);
+
+ int width = _info.Width;
+ int height = _info.Height;
+ int depth = _info.Depth;
+
+ int offset = 0;
+
+ for (int level = 0; level < _info.Levels; level++)
+ {
+ int mipSize = _info.GetMipSize(level);
+
+ int endOffset = offset + mipSize;
+
+ if ((uint)endOffset > (uint)size)
+ {
+ return;
+ }
+
+ switch (_info.Target)
+ {
+ case Target.Texture1D:
+ if (format.IsCompressed)
+ {
+ GL.CompressedTexSubImage1D(
+ target,
+ level,
+ 0,
+ width,
+ format.PixelFormat,
+ mipSize,
+ data);
+ }
+ else
+ {
+ GL.TexSubImage1D(
+ target,
+ level,
+ 0,
+ width,
+ format.PixelFormat,
+ format.PixelType,
+ data);
+ }
+ break;
+
+ case Target.Texture1DArray:
+ case Target.Texture2D:
+ if (format.IsCompressed)
+ {
+ GL.CompressedTexSubImage2D(
+ target,
+ level,
+ 0,
+ 0,
+ width,
+ height,
+ format.PixelFormat,
+ mipSize,
+ data);
+ }
+ else
+ {
+ GL.TexSubImage2D(
+ target,
+ level,
+ 0,
+ 0,
+ width,
+ height,
+ format.PixelFormat,
+ format.PixelType,
+ data);
+ }
+ break;
+
+ case Target.Texture2DArray:
+ case Target.Texture3D:
+ case Target.CubemapArray:
+ if (format.IsCompressed)
+ {
+ GL.CompressedTexSubImage3D(
+ target,
+ level,
+ 0,
+ 0,
+ 0,
+ width,
+ height,
+ depth,
+ format.PixelFormat,
+ mipSize,
+ data);
+ }
+ else
+ {
+ GL.TexSubImage3D(
+ target,
+ level,
+ 0,
+ 0,
+ 0,
+ width,
+ height,
+ depth,
+ format.PixelFormat,
+ format.PixelType,
+ data);
+ }
+ break;
+
+ case Target.Cubemap:
+ int faceOffset = 0;
+
+ for (int face = 0; face < 6; face++, faceOffset += mipSize / 6)
+ {
+ if (format.IsCompressed)
+ {
+ GL.CompressedTexSubImage2D(
+ TextureTarget.TextureCubeMapPositiveX + face,
+ level,
+ 0,
+ 0,
+ width,
+ height,
+ format.PixelFormat,
+ mipSize / 6,
+ data + faceOffset);
+ }
+ else
+ {
+ GL.TexSubImage2D(
+ TextureTarget.TextureCubeMapPositiveX + face,
+ level,
+ 0,
+ 0,
+ width,
+ height,
+ format.PixelFormat,
+ format.PixelType,
+ data + faceOffset);
+ }
+ }
+ break;
+ }
+
+ data += mipSize;
+ offset += mipSize;
+
+ width = Math.Max(1, width >> 1);
+ height = Math.Max(1, height >> 1);
+
+ if (Target == Target.Texture3D)
+ {
+ depth = Math.Max(1, depth >> 1);
+ }
+ }
+ }
+
+ public void Bind(int unit)
+ {
+ Bind(Target.Convert(), unit);
+ }
+
+ private void Bind(TextureTarget target, int unit)
+ {
+ GL.ActiveTexture(TextureUnit.Texture0 + unit);
+
+ GL.BindTexture(target, Handle);
+ }
+
+ public void Acquire()
+ {
+ _acquired = true;
+ }
+
+ public void Release()
+ {
+ _acquired = false;
+
+ if (_pendingDelete)
+ {
+ _pendingDelete = false;
+
+ Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_acquired)
+ {
+ _pendingDelete = true;
+
+ return;
+ }
+
+ if (Handle != 0)
+ {
+ GL.DeleteTexture(Handle);
+
+ _parent.DecrementViewsCount();
+
+ Handle = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/VertexArray.cs b/Ryujinx.Graphics.OpenGL/VertexArray.cs
new file mode 100644
index 00000000..cf87c953
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/VertexArray.cs
@@ -0,0 +1,135 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.OpenGL.Formats;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class VertexArray : IDisposable
+ {
+ public int Handle { get; }
+
+ private bool _needsAttribsUpdate;
+
+ private VertexBufferDescriptor[] _vertexBuffers;
+ private VertexAttribDescriptor[] _vertexAttribs;
+
+ public VertexArray()
+ {
+ Handle = GL.GenVertexArray();
+ }
+
+ public void Bind()
+ {
+ GL.BindVertexArray(Handle);
+ }
+
+ public void SetVertexBuffers(VertexBufferDescriptor[] vertexBuffers)
+ {
+ int bindingIndex = 0;
+
+ foreach (VertexBufferDescriptor vb in vertexBuffers)
+ {
+ if (vb.Buffer.Buffer != null)
+ {
+ int bufferHandle = ((Buffer)vb.Buffer.Buffer).Handle;
+
+ GL.BindVertexBuffer(bindingIndex, bufferHandle, (IntPtr)vb.Buffer.Offset, vb.Stride);
+
+ GL.VertexBindingDivisor(bindingIndex, vb.Divisor);
+ }
+ else
+ {
+ GL.BindVertexBuffer(bindingIndex, 0, IntPtr.Zero, 0);
+ }
+
+ bindingIndex++;
+ }
+
+ _vertexBuffers = vertexBuffers;
+
+ _needsAttribsUpdate = true;
+ }
+
+ public void SetVertexAttributes(VertexAttribDescriptor[] vertexAttribs)
+ {
+ int attribIndex = 0;
+
+ foreach (VertexAttribDescriptor attrib in vertexAttribs)
+ {
+ FormatInfo fmtInfo = FormatTable.GetFormatInfo(attrib.Format);
+
+ GL.EnableVertexAttribArray(attribIndex);
+
+ int offset = attrib.Offset;
+ int size = fmtInfo.Components;
+
+ bool isFloat = fmtInfo.PixelType == PixelType.Float ||
+ fmtInfo.PixelType == PixelType.HalfFloat;
+
+ if (isFloat || fmtInfo.Normalized || fmtInfo.Scaled)
+ {
+ VertexAttribType type = (VertexAttribType)fmtInfo.PixelType;
+
+ GL.VertexAttribFormat(attribIndex, size, type, fmtInfo.Normalized, offset);
+ }
+ else
+ {
+ VertexAttribIntegerType type = (VertexAttribIntegerType)fmtInfo.PixelType;
+
+ GL.VertexAttribIFormat(attribIndex, size, type, offset);
+ }
+
+ GL.VertexAttribBinding(attribIndex, attrib.BufferIndex);
+
+ attribIndex++;
+ }
+
+ for (; attribIndex < 16; attribIndex++)
+ {
+ GL.DisableVertexAttribArray(attribIndex);
+ }
+
+ _vertexAttribs = vertexAttribs;
+ }
+
+ public void SetIndexBuffer(Buffer indexBuffer)
+ {
+ GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer?.Handle ?? 0);
+ }
+
+ public void Validate()
+ {
+ for (int attribIndex = 0; attribIndex < _vertexAttribs.Length; attribIndex++)
+ {
+ VertexAttribDescriptor attrib = _vertexAttribs[attribIndex];
+
+ if ((uint)attrib.BufferIndex >= _vertexBuffers.Length)
+ {
+ GL.DisableVertexAttribArray(attribIndex);
+
+ continue;
+ }
+
+ if (_vertexBuffers[attrib.BufferIndex].Buffer.Buffer == null)
+ {
+ GL.DisableVertexAttribArray(attribIndex);
+
+ continue;
+ }
+
+ if (_needsAttribsUpdate)
+ {
+ GL.EnableVertexAttribArray(attribIndex);
+ }
+ }
+
+ _needsAttribsUpdate = false;
+ }
+
+ public void Dispose()
+ {
+ GL.DeleteVertexArray(Handle);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/VertexBuffer.cs b/Ryujinx.Graphics.OpenGL/VertexBuffer.cs
new file mode 100644
index 00000000..19a58053
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/VertexBuffer.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ struct VertexBuffer
+ {
+ public BufferRange Range { get; }
+
+ public int Divisor { get; }
+ public int Stride { get; }
+
+ public VertexBuffer(BufferRange range, int divisor, int stride)
+ {
+ Range = range;
+ Divisor = divisor;
+ Stride = stride;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs
new file mode 100644
index 00000000..a05eb1c0
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Window.cs
@@ -0,0 +1,248 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class Window : IWindow
+ {
+ private const int NativeWidth = 1280;
+ private const int NativeHeight = 720;
+
+ private int _width = 1280;
+ private int _height = 720;
+
+ private int _blitFramebufferHandle;
+ private int _copyFramebufferHandle;
+
+ private int _screenTextureHandle;
+
+ private TextureReleaseCallback _release;
+
+ private struct PresentationTexture
+ {
+ public TextureView Texture { get; }
+
+ public ImageCrop Crop { get; }
+
+ public object Context { get; }
+
+ public PresentationTexture(TextureView texture, ImageCrop crop, object context)
+ {
+ Texture = texture;
+ Crop = crop;
+ Context = context;
+ }
+ }
+
+ private Queue<PresentationTexture> _textures;
+
+ public Window()
+ {
+ _textures = new Queue<PresentationTexture>();
+ }
+
+ public void Present()
+ {
+ GL.Disable(EnableCap.FramebufferSrgb);
+
+ CopyTextureFromQueue();
+
+ int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding);
+ int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding);
+
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0);
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetCopyFramebufferHandleLazy());
+
+ GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
+
+ GL.Clear(ClearBufferMask.ColorBufferBit);
+
+ GL.BlitFramebuffer(
+ 0,
+ 0,
+ 1280,
+ 720,
+ 0,
+ 0,
+ 1280,
+ 720,
+ ClearBufferMask.ColorBufferBit,
+ BlitFramebufferFilter.Linear);
+
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
+
+ GL.Enable(EnableCap.FramebufferSrgb);
+ }
+
+ private void CopyTextureFromQueue()
+ {
+ if (!_textures.TryDequeue(out PresentationTexture presentationTexture))
+ {
+ return;
+ }
+
+ TextureView texture = presentationTexture.Texture;
+ ImageCrop crop = presentationTexture.Crop;
+ object context = presentationTexture.Context;
+
+ int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding);
+ int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding);
+
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetCopyFramebufferHandleLazy());
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetBlitFramebufferHandleLazy());
+
+ GL.FramebufferTexture(
+ FramebufferTarget.ReadFramebuffer,
+ FramebufferAttachment.ColorAttachment0,
+ texture.Handle,
+ 0);
+
+ GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
+
+ GL.Clear(ClearBufferMask.ColorBufferBit);
+
+ int srcX0, srcX1, srcY0, srcY1;
+
+ if (crop.Left == 0 && crop.Right == 0)
+ {
+ srcX0 = 0;
+ srcX1 = texture.Width;
+ }
+ else
+ {
+ srcX0 = crop.Left;
+ srcX1 = crop.Right;
+ }
+
+ if (crop.Top == 0 && crop.Bottom == 0)
+ {
+ srcY0 = 0;
+ srcY1 = texture.Height;
+ }
+ else
+ {
+ srcY0 = crop.Top;
+ srcY1 = crop.Bottom;
+ }
+
+ float ratioX = MathF.Min(1f, (_height * (float)NativeWidth) / ((float)NativeHeight * _width));
+ float ratioY = MathF.Min(1f, (_width * (float)NativeHeight) / ((float)NativeWidth * _height));
+
+ 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;
+
+ GL.BlitFramebuffer(
+ srcX0,
+ srcY0,
+ srcX1,
+ srcY1,
+ dstX0,
+ dstY0,
+ dstX1,
+ dstY1,
+ ClearBufferMask.ColorBufferBit,
+ BlitFramebufferFilter.Linear);
+
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
+
+ texture.Release();
+
+ Release(context);
+ }
+
+ public void QueueTexture(ITexture texture, ImageCrop crop, object context)
+ {
+ if (texture == null)
+ {
+ Release(context);
+
+ return;
+ }
+
+ TextureView textureView = (TextureView)texture;
+
+ textureView.Acquire();
+
+ _textures.Enqueue(new PresentationTexture(textureView, crop, context));
+ }
+
+ public void RegisterTextureReleaseCallback(TextureReleaseCallback callback)
+ {
+ _release = callback;
+ }
+
+ private void Release(object context)
+ {
+ if (_release != null)
+ {
+ _release(context);
+ }
+ }
+
+ private int GetBlitFramebufferHandleLazy()
+ {
+ int handle = _blitFramebufferHandle;
+
+ if (handle == 0)
+ {
+ handle = GL.GenFramebuffer();
+
+ _blitFramebufferHandle = handle;
+ }
+
+ return handle;
+ }
+
+ private int GetCopyFramebufferHandleLazy()
+ {
+ int handle = _copyFramebufferHandle;
+
+ if (handle == 0)
+ {
+ int textureHandle = GL.GenTexture();
+
+ GL.BindTexture(TextureTarget.Texture2D, textureHandle);
+
+ GL.TexImage2D(
+ TextureTarget.Texture2D,
+ 0,
+ PixelInternalFormat.Rgba8,
+ 1280,
+ 720,
+ 0,
+ PixelFormat.Rgba,
+ PixelType.UnsignedByte,
+ IntPtr.Zero);
+
+ handle = GL.GenFramebuffer();
+
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, handle);
+
+ GL.FramebufferTexture(
+ FramebufferTarget.Framebuffer,
+ FramebufferAttachment.ColorAttachment0,
+ textureHandle,
+ 0);
+
+ _screenTextureHandle = textureHandle;
+
+ _copyFramebufferHandle = handle;
+ }
+
+ return handle;
+ }
+ }
+}