Move solution and projects to src

This commit is contained in:
TSR Berry 2023-04-08 01:22:00 +02:00 committed by Mary
parent cd124bda58
commit cee7121058
3466 changed files with 55 additions and 55 deletions

View file

@ -0,0 +1,91 @@
using Ryujinx.Common;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.OpenGL
{
unsafe class BackgroundContextWorker : IDisposable
{
[ThreadStatic]
public static bool InBackground;
private Thread _thread;
private bool _running;
private AutoResetEvent _signal;
private Queue<Action> _work;
private ObjectPool<ManualResetEventSlim> _invokePool;
private readonly IOpenGLContext _backgroundContext;
public BackgroundContextWorker(IOpenGLContext backgroundContext)
{
_backgroundContext = backgroundContext;
_running = true;
_signal = new AutoResetEvent(false);
_work = new Queue<Action>();
_invokePool = new ObjectPool<ManualResetEventSlim>(() => new ManualResetEventSlim(), 10);
_thread = new Thread(Run);
_thread.Start();
}
private void Run()
{
InBackground = true;
_backgroundContext.MakeCurrent();
while (_running)
{
Action action;
lock (_work)
{
_work.TryDequeue(out action);
}
if (action != null)
{
action();
}
else
{
_signal.WaitOne();
}
}
_backgroundContext.Dispose();
}
public void Invoke(Action action)
{
ManualResetEventSlim actionComplete = _invokePool.Allocate();
lock (_work)
{
_work.Enqueue(() =>
{
action();
actionComplete.Set();
});
}
_signal.Set();
actionComplete.Wait();
actionComplete.Reset();
_invokePool.Release(actionComplete);
}
public void Dispose()
{
_running = false;
_signal.Set();
_thread.Join();
_signal.Dispose();
}
}
}

View file

@ -0,0 +1,103 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL
{
static class Buffer
{
public static void Clear(BufferHandle destination, int offset, int size, uint value)
{
GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination.ToInt32());
unsafe
{
uint* valueArr = stackalloc uint[1];
valueArr[0] = value;
GL.ClearBufferSubData(
BufferTarget.CopyWriteBuffer,
PixelInternalFormat.Rgba8ui,
(IntPtr)offset,
(IntPtr)size,
PixelFormat.RgbaInteger,
PixelType.UnsignedByte,
(IntPtr)valueArr);
}
}
public static BufferHandle Create()
{
return Handle.FromInt32<BufferHandle>(GL.GenBuffer());
}
public static BufferHandle Create(int size)
{
int handle = GL.GenBuffer();
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.DynamicDraw);
return Handle.FromInt32<BufferHandle>(handle);
}
public static void Copy(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
{
GL.BindBuffer(BufferTarget.CopyReadBuffer, source.ToInt32());
GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination.ToInt32());
GL.CopyBufferSubData(
BufferTarget.CopyReadBuffer,
BufferTarget.CopyWriteBuffer,
(IntPtr)srcOffset,
(IntPtr)dstOffset,
(IntPtr)size);
}
public static unsafe PinnedSpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
{
// Data in the persistent buffer and host array is guaranteed to be available
// until the next time the host thread requests data.
if (HwCapabilities.UsePersistentBufferForFlush)
{
return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
}
else
{
IntPtr target = renderer.PersistentBuffers.Default.GetHostArray(size);
GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32());
GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target);
return new PinnedSpan<byte>(target.ToPointer(), size);
}
}
public static void Resize(BufferHandle handle, int size)
{
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.StreamCopy);
}
public static void SetData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
{
GL.BindBuffer(BufferTarget.CopyWriteBuffer, buffer.ToInt32());
unsafe
{
fixed (byte* ptr = data)
{
GL.BufferSubData(BufferTarget.CopyWriteBuffer, (IntPtr)offset, data.Length, (IntPtr)ptr);
}
}
}
public static void Delete(BufferHandle buffer)
{
GL.DeleteBuffer(buffer.ToInt32());
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.OpenGL
{
static class Constants
{
public const int MaxRenderTargets = 8;
public const int MaxViewports = 16;
public const int MaxVertexAttribs = 16;
public const int MaxVertexBuffers = 16;
public const int MaxTransformFeedbackBuffers = 4;
}
}

View file

@ -0,0 +1,101 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Ryujinx.Graphics.OpenGL
{
public static class Debugger
{
private static DebugProc _debugCallback;
private static int _counter;
public static void Initialize(GraphicsDebugLevel logLevel)
{
// Disable everything
GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, false);
if (logLevel == GraphicsDebugLevel.None)
{
GL.Disable(EnableCap.DebugOutputSynchronous);
GL.DebugMessageCallback(null, IntPtr.Zero);
return;
}
GL.Enable(EnableCap.DebugOutputSynchronous);
if (logLevel == GraphicsDebugLevel.Error)
{
GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypeError, DebugSeverityControl.DontCare, 0, (int[])null, true);
}
else if (logLevel == GraphicsDebugLevel.Slowdowns)
{
GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypeError, DebugSeverityControl.DontCare, 0, (int[])null, true);
GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypePerformance, DebugSeverityControl.DontCare, 0, (int[])null, true);
}
else
{
GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, true);
}
_counter = 0;
_debugCallback = GLDebugHandler;
GL.DebugMessageCallback(_debugCallback, IntPtr.Zero);
Logger.Warning?.Print(LogClass.Gpu, "OpenGL Debugging is enabled. Performance will be negatively impacted.");
}
private static void GLDebugHandler(
DebugSource source,
DebugType type,
int id,
DebugSeverity severity,
int length,
IntPtr message,
IntPtr userParam)
{
string msg = Marshal.PtrToStringUTF8(message).Replace('\n', ' ');
switch (type)
{
case DebugType.DebugTypeError : Logger.Error?.Print(LogClass.Gpu, $"{severity}: {msg}\nCallStack={Environment.StackTrace}", "GLERROR"); break;
case DebugType.DebugTypePerformance: Logger.Warning?.Print(LogClass.Gpu, $"{severity}: {msg}", "GLPERF"); break;
case DebugType.DebugTypePushGroup : Logger.Info?.Print(LogClass.Gpu, $"{{ ({id}) {severity}: {msg}", "GLINFO"); break;
case DebugType.DebugTypePopGroup : Logger.Info?.Print(LogClass.Gpu, $"}} ({id}) {severity}: {msg}", "GLINFO"); break;
default:
if (source == DebugSource.DebugSourceApplication)
{
Logger.Info?.Print(LogClass.Gpu, $"{type} {severity}: {msg}", "GLINFO");
}
else
{
Logger.Debug?.Print(LogClass.Gpu, $"{type} {severity}: {msg}", "GLDEBUG");
}
break;
}
}
// Useful debug helpers
public static void PushGroup(string dbgMsg)
{
int counter = Interlocked.Increment(ref _counter);
GL.PushDebugGroup(DebugSourceExternal.DebugSourceApplication, counter, dbgMsg.Length, dbgMsg);
}
public static void PopGroup()
{
GL.PopDebugGroup();
}
public static void Print(string dbgMsg, DebugType type = DebugType.DebugTypeMarker, DebugSeverity severity = DebugSeverity.DebugSeverityNotification, int id = 999999)
{
GL.DebugMessageInsert(DebugSourceExternal.DebugSourceApplication, type, id, severity, dbgMsg.Length, dbgMsg);
}
}
}

View file

@ -0,0 +1,138 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
namespace Ryujinx.Graphics.OpenGL
{
class DrawTextureEmulation
{
private const string VertexShader = @"#version 430 core
uniform float srcX0;
uniform float srcY0;
uniform float srcX1;
uniform float srcY1;
layout (location = 0) out vec2 texcoord;
void main()
{
bool x1 = (gl_VertexID & 1) != 0;
bool y1 = (gl_VertexID & 2) != 0;
gl_Position = vec4(x1 ? 1 : -1, y1 ? -1 : 1, 0, 1);
texcoord = vec2(x1 ? srcX1 : srcX0, y1 ? srcY1 : srcY0);
}";
private const string FragmentShader = @"#version 430 core
layout (location = 0) uniform sampler2D tex;
layout (location = 0) in vec2 texcoord;
layout (location = 0) out vec4 colour;
void main()
{
colour = texture(tex, texcoord);
}";
private int _vsHandle;
private int _fsHandle;
private int _programHandle;
private int _uniformSrcX0Location;
private int _uniformSrcY0Location;
private int _uniformSrcX1Location;
private int _uniformSrcY1Location;
private bool _initialized;
public void Draw(
TextureView texture,
Sampler sampler,
float x0,
float y0,
float x1,
float y1,
float s0,
float t0,
float s1,
float t1)
{
EnsureInitialized();
GL.UseProgram(_programHandle);
texture.Bind(0);
sampler.Bind(0);
if (x0 > x1)
{
float temp = s0;
s0 = s1;
s1 = temp;
}
if (y0 > y1)
{
float temp = t0;
t0 = t1;
t1 = temp;
}
GL.Uniform1(_uniformSrcX0Location, s0);
GL.Uniform1(_uniformSrcY0Location, t0);
GL.Uniform1(_uniformSrcX1Location, s1);
GL.Uniform1(_uniformSrcY1Location, t1);
GL.ViewportIndexed(0, MathF.Min(x0, x1), MathF.Min(y0, y1), MathF.Abs(x1 - x0), MathF.Abs(y1 - y0));
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
private void EnsureInitialized()
{
if (_initialized)
{
return;
}
_initialized = true;
_vsHandle = GL.CreateShader(ShaderType.VertexShader);
_fsHandle = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(_vsHandle, VertexShader);
GL.ShaderSource(_fsHandle, FragmentShader);
GL.CompileShader(_vsHandle);
GL.CompileShader(_fsHandle);
_programHandle = GL.CreateProgram();
GL.AttachShader(_programHandle, _vsHandle);
GL.AttachShader(_programHandle, _fsHandle);
GL.LinkProgram(_programHandle);
GL.DetachShader(_programHandle, _vsHandle);
GL.DetachShader(_programHandle, _fsHandle);
_uniformSrcX0Location = GL.GetUniformLocation(_programHandle, "srcX0");
_uniformSrcY0Location = GL.GetUniformLocation(_programHandle, "srcY0");
_uniformSrcX1Location = GL.GetUniformLocation(_programHandle, "srcX1");
_uniformSrcY1Location = GL.GetUniformLocation(_programHandle, "srcY1");
}
public void Dispose()
{
if (!_initialized)
{
return;
}
GL.DeleteShader(_vsHandle);
GL.DeleteShader(_fsHandle);
GL.DeleteProgram(_programHandle);
_initialized = false;
}
}
}

View file

@ -0,0 +1,177 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal class FsrScalingFilter : IScalingFilter
{
private readonly OpenGLRenderer _renderer;
private int _inputUniform;
private int _outputUniform;
private int _sharpeningUniform;
private int _srcX0Uniform;
private int _srcX1Uniform;
private int _srcY0Uniform;
private int _scalingShaderProgram;
private int _sharpeningShaderProgram;
private float _scale = 1;
private int _srcY1Uniform;
private int _dstX0Uniform;
private int _dstX1Uniform;
private int _dstY0Uniform;
private int _dstY1Uniform;
private int _scaleXUniform;
private int _scaleYUniform;
private TextureStorage _intermediaryTexture;
public float Level
{
get => _scale;
set
{
_scale = MathF.Max(0.01f, value);
}
}
public FsrScalingFilter(OpenGLRenderer renderer, IPostProcessingEffect filter)
{
Initialize();
_renderer = renderer;
}
public void Dispose()
{
if (_scalingShaderProgram != 0)
{
GL.DeleteProgram(_scalingShaderProgram);
GL.DeleteProgram(_sharpeningShaderProgram);
}
_intermediaryTexture?.Dispose();
}
private void Initialize()
{
var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl");
var sharpeningShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl");
var fsrA = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h");
var fsr1 = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h");
scalingShader = scalingShader.Replace("#include \"ffx_a.h\"", fsrA);
scalingShader = scalingShader.Replace("#include \"ffx_fsr1.h\"", fsr1);
sharpeningShader = sharpeningShader.Replace("#include \"ffx_a.h\"", fsrA);
sharpeningShader = sharpeningShader.Replace("#include \"ffx_fsr1.h\"", fsr1);
_scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader);
_sharpeningShaderProgram = CompileProgram(sharpeningShader, ShaderType.ComputeShader);
_inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source");
_outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput");
_sharpeningUniform = GL.GetUniformLocation(_sharpeningShaderProgram, "sharpening");
_srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0");
_srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1");
_srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0");
_srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1");
_dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0");
_dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1");
_dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0");
_dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1");
_scaleXUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleX");
_scaleYUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleY");
}
public void Run(
TextureView view,
TextureView destinationTexture,
int width,
int height,
Extents2D source,
Extents2D destination)
{
if (_intermediaryTexture == null || _intermediaryTexture.Info.Width != width || _intermediaryTexture.Info.Height != height)
{
_intermediaryTexture?.Dispose();
var originalInfo = view.Info;
var info = new TextureCreateInfo(width,
height,
originalInfo.Depth,
originalInfo.Levels,
originalInfo.Samples,
originalInfo.BlockWidth,
originalInfo.BlockHeight,
originalInfo.BytesPerPixel,
originalInfo.Format,
originalInfo.DepthStencilMode,
originalInfo.Target,
originalInfo.SwizzleR,
originalInfo.SwizzleG,
originalInfo.SwizzleB,
originalInfo.SwizzleA);
_intermediaryTexture = new TextureStorage(_renderer, info, view.ScaleFactor);
_intermediaryTexture.CreateDefaultView();
}
var textureView = _intermediaryTexture.CreateView(_intermediaryTexture.Info, 0, 0) as TextureView;
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
int threadGroupWorkRegionDim = 16;
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
// Scaling pass
float srcWidth = Math.Abs(source.X2 - source.X1);
float srcHeight = Math.Abs(source.Y2 - source.Y1);
float scaleX = srcWidth / view.Width;
float scaleY = srcHeight / view.Height;
GL.UseProgram(_scalingShaderProgram);
view.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_srcX0Uniform, (float)source.X1);
GL.Uniform1(_srcX1Uniform, (float)source.X2);
GL.Uniform1(_srcY0Uniform, (float)source.Y1);
GL.Uniform1(_srcY1Uniform, (float)source.Y2);
GL.Uniform1(_dstX0Uniform, (float)destination.X1);
GL.Uniform1(_dstX1Uniform, (float)destination.X2);
GL.Uniform1(_dstY0Uniform, (float)destination.Y1);
GL.Uniform1(_dstY1Uniform, (float)destination.Y2);
GL.Uniform1(_scaleXUniform, scaleX);
GL.Uniform1(_scaleYUniform, scaleY);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
// Sharpening Pass
GL.UseProgram(_sharpeningShaderProgram);
GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
textureView.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_sharpeningUniform, 1.5f - (Level * 0.01f * 1.5f));
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.UseProgram(previousProgram);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
GL.ActiveTexture((TextureUnit)previousUnit);
}
}
}

View file

@ -0,0 +1,81 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.OpenGL.Image;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal class FxaaPostProcessingEffect : IPostProcessingEffect
{
private readonly OpenGLRenderer _renderer;
private int _resolutionUniform;
private int _inputUniform;
private int _outputUniform;
private int _shaderProgram;
private TextureStorage _textureStorage;
public FxaaPostProcessingEffect(OpenGLRenderer renderer)
{
Initialize();
_renderer = renderer;
}
public void Dispose()
{
if (_shaderProgram != 0)
{
GL.DeleteProgram(_shaderProgram);
_textureStorage?.Dispose();
}
}
private void Initialize()
{
_shaderProgram = ShaderHelper.CompileProgram(EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl"), ShaderType.ComputeShader);
_resolutionUniform = GL.GetUniformLocation(_shaderProgram, "invResolution");
_inputUniform = GL.GetUniformLocation(_shaderProgram, "inputTexture");
_outputUniform = GL.GetUniformLocation(_shaderProgram, "imgOutput");
}
public TextureView Run(TextureView view, int width, int height)
{
if (_textureStorage == null || _textureStorage.Info.Width != view.Width || _textureStorage.Info.Height != view.Height)
{
_textureStorage?.Dispose();
_textureStorage = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_textureStorage.CreateDefaultView();
}
var textureView = _textureStorage.CreateView(view.Info, 0, 0) as TextureView;
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_shaderProgram);
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
view.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.UseProgram(previousProgram);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
GL.ActiveTexture((TextureUnit)previousUnit);
return textureView;
}
}
}

View file

@ -0,0 +1,11 @@
using Ryujinx.Graphics.OpenGL.Image;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal interface IPostProcessingEffect : IDisposable
{
const int LocalGroupSize = 64;
TextureView Run(TextureView view, int width, int height);
}
}

View file

@ -0,0 +1,18 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal interface IScalingFilter : IDisposable
{
float Level { get; set; }
void Run(
TextureView view,
TextureView destinationTexture,
int width,
int height,
Extents2D source,
Extents2D destination);
}
}

View file

@ -0,0 +1,40 @@
using OpenTK.Graphics.OpenGL;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal static class ShaderHelper
{
public static int CompileProgram(string shaderCode, ShaderType shaderType)
{
var shader = GL.CreateShader(shaderType);
GL.ShaderSource(shader, shaderCode);
GL.CompileShader(shader);
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
GL.LinkProgram(program);
GL.DetachShader(program, shader);
GL.DeleteShader(shader);
return program;
}
public static int CompileProgram(string[] shaders, ShaderType shaderType)
{
var shader = GL.CreateShader(shaderType);
GL.ShaderSource(shader, shaders.Length, shaders, (int[])null);
GL.CompileShader(shader);
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
GL.LinkProgram(program);
GL.DetachShader(program, shader);
GL.DeleteShader(shader);
return program;
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,88 @@
#version 430 core
precision mediump float;
layout (local_size_x = 64) in;
layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
layout( location=1 ) uniform sampler2D Source;
layout( location=2 ) uniform float srcX0;
layout( location=3 ) uniform float srcX1;
layout( location=4 ) uniform float srcY0;
layout( location=5 ) uniform float srcY1;
layout( location=6 ) uniform float dstX0;
layout( location=7 ) uniform float dstX1;
layout( location=8 ) uniform float dstY0;
layout( location=9 ) uniform float dstY1;
layout( location=10 ) uniform float scaleX;
layout( location=11 ) uniform float scaleY;
#define A_GPU 1
#define A_GLSL 1
#include "ffx_a.h"
#define FSR_EASU_F 1
AU4 con0, con1, con2, con3;
float srcW, srcH, dstW, dstH;
vec2 bLeft, tRight;
AF2 translate(AF2 pos) {
return AF2(pos.x * scaleX, pos.y * scaleY);
}
void setBounds(vec2 bottomLeft, vec2 topRight) {
bLeft = bottomLeft;
tRight = topRight;
}
AF2 translateDest(AF2 pos) {
AF2 translatedPos = AF2(pos.x, pos.y);
translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1: translatedPos.y;
return translatedPos;
}
AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(Source, translate(p), 0); return res; }
AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(Source, translate(p), 1); return res; }
AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(Source, translate(p), 2); return res; }
#include "ffx_fsr1.h"
float insideBox(vec2 v) {
vec2 s = step(bLeft, v) - step(tRight, v);
return s.x * s.y;
}
void CurrFilter(AU2 pos)
{
if((insideBox(vec2(pos.x, pos.y))) == 0) {
imageStore(imgOutput, ASU2(pos.x, pos.y), AF4(0,0,0,1));
return;
}
AF3 c;
FsrEasuF(c, AU2(pos.x - bLeft.x, pos.y - bLeft.y), con0, con1, con2, con3);
imageStore(imgOutput, ASU2(translateDest(pos)), AF4(c, 1));
}
void main() {
srcW = abs(srcX1 - srcX0);
srcH = abs(srcY1 - srcY0);
dstW = abs(dstX1 - dstX0);
dstH = abs(dstY1 - dstY0);
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
setBounds(vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1),
vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0));
// Upscaling
FsrEasuCon(con0, con1, con2, con3,
srcW, srcH, // Viewport size (top left aligned) in the input image which is to be scaled.
srcW, srcH, // The size of the input image.
dstW, dstH); // The output resolution.
CurrFilter(gxy);
gxy.x += 8u;
CurrFilter(gxy);
gxy.y += 8u;
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
}

View file

@ -0,0 +1,37 @@
#version 430 core
precision mediump float;
layout (local_size_x = 64) in;
layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
layout( location=1 ) uniform sampler2D source;
layout( location=2 ) uniform float sharpening;
#define A_GPU 1
#define A_GLSL 1
#include "ffx_a.h"
#define FSR_RCAS_F 1
AU4 con0;
AF4 FsrRcasLoadF(ASU2 p) { return AF4(texelFetch(source, p, 0)); }
void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {}
#include "ffx_fsr1.h"
void CurrFilter(AU2 pos)
{
AF3 c;
FsrRcasF(c.r, c.g, c.b, pos, con0);
imageStore(imgOutput, ASU2(pos), AF4(c, 1));
}
void main() {
FsrRcasCon(con0, sharpening);
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
CurrFilter(gxy);
gxy.x += 8u;
CurrFilter(gxy);
gxy.y += 8u;
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
layout(rgba8, binding = 0) uniform image2D imgOutput;
uniform sampler2D inputTexture;
layout( location=0 ) uniform vec2 invResolution;
uniform sampler2D samplerArea;
uniform sampler2D samplerSearch;
void main() {
ivec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
vec2 pixCoord;
vec4 offset[3];
SMAABlendingWeightCalculationVS(coord, pixCoord, offset);
vec4 oColor = SMAABlendingWeightCalculationPS(coord, pixCoord, offset, inputTexture, samplerArea, samplerSearch, ivec4(0));
imageStore(imgOutput, texelCoord, oColor);
}
}
}

View file

@ -0,0 +1,24 @@
layout(rgba8, binding = 0) uniform image2D imgOutput;
uniform sampler2D inputTexture;
layout( location=0 ) uniform vec2 invResolution;
void main()
{
vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
vec4 offset[3];
SMAAEdgeDetectionVS(coord, offset);
vec2 oColor = SMAAColorEdgeDetectionPS(coord, offset, inputTexture);
if (oColor != float2(-2.0, -2.0))
{
imageStore(imgOutput, texelCoord, vec4(oColor, 0.0, 1.0));
}
}
}
}

View file

@ -0,0 +1,26 @@
layout(rgba8, binding = 0) uniform image2D imgOutput;
uniform sampler2D inputTexture;
layout( location=0 ) uniform vec2 invResolution;
uniform sampler2D samplerBlend;
void main() {
vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
vec2 pixCoord;
vec4 offset;
SMAANeighborhoodBlendingVS(coord, offset);
vec4 oColor = SMAANeighborhoodBlendingPS(coord, offset, inputTexture, samplerBlend);
imageStore(imgOutput, texelCoord, oColor);
}
}
}

View file

@ -0,0 +1,261 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
{
internal partial class SmaaPostProcessingEffect : IPostProcessingEffect
{
public const int AreaWidth = 160;
public const int AreaHeight = 560;
public const int SearchWidth = 64;
public const int SearchHeight = 16;
private readonly OpenGLRenderer _renderer;
private TextureStorage _outputTexture;
private TextureStorage _searchTexture;
private TextureStorage _areaTexture;
private int[] _edgeShaderPrograms;
private int[] _blendShaderPrograms;
private int[] _neighbourShaderPrograms;
private TextureStorage _edgeOutputTexture;
private TextureStorage _blendOutputTexture;
private string[] _qualities;
private int _inputUniform;
private int _outputUniform;
private int _samplerAreaUniform;
private int _samplerSearchUniform;
private int _samplerBlendUniform;
private int _resolutionUniform;
private int _quality = 1;
public int Quality
{
get => _quality; set
{
_quality = Math.Clamp(value, 0, _qualities.Length - 1);
}
}
public SmaaPostProcessingEffect(OpenGLRenderer renderer, int quality)
{
_renderer = renderer;
_edgeShaderPrograms = Array.Empty<int>();
_blendShaderPrograms = Array.Empty<int>();
_neighbourShaderPrograms = Array.Empty<int>();
_qualities = new string[] { "SMAA_PRESET_LOW", "SMAA_PRESET_MEDIUM", "SMAA_PRESET_HIGH", "SMAA_PRESET_ULTRA" };
Quality = quality;
Initialize();
}
public void Dispose()
{
_searchTexture?.Dispose();
_areaTexture?.Dispose();
_outputTexture?.Dispose();
_edgeOutputTexture?.Dispose();
_blendOutputTexture?.Dispose();
DeleteShaders();
}
private void DeleteShaders()
{
for (int i = 0; i < _edgeShaderPrograms.Length; i++)
{
GL.DeleteProgram(_edgeShaderPrograms[i]);
GL.DeleteProgram(_blendShaderPrograms[i]);
GL.DeleteProgram(_neighbourShaderPrograms[i]);
}
}
private unsafe void RecreateShaders(int width, int height)
{
string baseShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl");
var pixelSizeDefine = $"#define SMAA_RT_METRICS float4(1.0 / {width}.0, 1.0 / {height}.0, {width}, {height}) \n";
_edgeShaderPrograms = new int[_qualities.Length];
_blendShaderPrograms = new int[_qualities.Length];
_neighbourShaderPrograms = new int[_qualities.Length];
for (int i = 0; i < +_edgeShaderPrograms.Length; i++)
{
var presets = $"#version 430 core \n#define {_qualities[i]} 1 \n{pixelSizeDefine}#define SMAA_GLSL_4 1 \nlayout (local_size_x = 16, local_size_y = 16) in;\n{baseShader}";
var edgeShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl");
var blendShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl");
var neighbourShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl");
var shaders = new string[] { presets, edgeShaderData };
var edgeProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
shaders[1] = blendShaderData;
var blendProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
shaders[1] = neighbourShaderData;
var neighbourProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
_edgeShaderPrograms[i] = edgeProgram;
_blendShaderPrograms[i] = blendProgram;
_neighbourShaderPrograms[i] = neighbourProgram;
}
_inputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "inputTexture");
_outputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "imgOutput");
_samplerAreaUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerArea");
_samplerSearchUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerSearch");
_samplerBlendUniform = GL.GetUniformLocation(_neighbourShaderPrograms[0], "samplerBlend");
_resolutionUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "invResolution");
}
private void Initialize()
{
var areaInfo = new TextureCreateInfo(AreaWidth,
AreaHeight,
1,
1,
1,
1,
1,
1,
Format.R8G8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
var searchInfo = new TextureCreateInfo(SearchWidth,
SearchHeight,
1,
1,
1,
1,
1,
1,
Format.R8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
_areaTexture = new TextureStorage(_renderer, areaInfo, 1);
_searchTexture = new TextureStorage(_renderer, searchInfo, 1);
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin");
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin");
var areaView = _areaTexture.CreateDefaultView();
var searchView = _searchTexture.CreateDefaultView();
areaView.SetData(areaTexture);
searchView.SetData(searchTexture);
}
public TextureView Run(TextureView view, int width, int height)
{
if (_outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height)
{
_outputTexture?.Dispose();
_outputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_outputTexture.CreateDefaultView();
_edgeOutputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_edgeOutputTexture.CreateDefaultView();
_blendOutputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_blendOutputTexture.CreateDefaultView();
DeleteShaders();
RecreateShaders(view.Width, view.Height);
}
var textureView = _outputTexture.CreateView(view.Info, 0, 0) as TextureView;
var edgeOutput = _edgeOutputTexture.DefaultView as TextureView;
var blendOutput = _blendOutputTexture.DefaultView as TextureView;
var areaTexture = _areaTexture.DefaultView as TextureView;
var searchTexture = _searchTexture.DefaultView as TextureView;
var previousFramebuffer = GL.GetInteger(GetPName.FramebufferBinding);
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int previousTextureBinding0 = GL.GetInteger(GetPName.TextureBinding2D);
GL.ActiveTexture(TextureUnit.Texture1);
int previousTextureBinding1 = GL.GetInteger(GetPName.TextureBinding2D);
GL.ActiveTexture(TextureUnit.Texture2);
int previousTextureBinding2 = GL.GetInteger(GetPName.TextureBinding2D);
var framebuffer = new Framebuffer();
framebuffer.Bind();
framebuffer.AttachColor(0, edgeOutput);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.ClearColor(0, 0, 0, 0);
framebuffer.AttachColor(0, blendOutput);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.ClearColor(0, 0, 0, 0);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, previousFramebuffer);
framebuffer.Dispose();
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
GL.BindImageTexture(0, edgeOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_edgeShaderPrograms[Quality]);
view.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
GL.BindImageTexture(0, blendOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_blendShaderPrograms[Quality]);
edgeOutput.Bind(0);
areaTexture.Bind(1);
searchTexture.Bind(2);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_samplerAreaUniform, 1);
GL.Uniform1(_samplerSearchUniform, 2);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_neighbourShaderPrograms[Quality]);
view.Bind(0);
blendOutput.Bind(1);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_samplerBlendUniform, 1);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
GL.UseProgram(previousProgram);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding0);
GL.ActiveTexture(TextureUnit.Texture1);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding1);
GL.ActiveTexture(TextureUnit.Texture2);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding2);
GL.ActiveTexture((TextureUnit)previousUnit);
return textureView;
}
}
}

View file

@ -0,0 +1,675 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.OpenGL
{
static class EnumConversion
{
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;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(AddressMode)} enum value: {mode}.");
return TextureWrapMode.Clamp;
}
public static NvBlendEquationAdvanced Convert(this AdvancedBlendOp op)
{
switch (op)
{
case AdvancedBlendOp.Zero:
return NvBlendEquationAdvanced.Zero;
case AdvancedBlendOp.Src:
return NvBlendEquationAdvanced.SrcNv;
case AdvancedBlendOp.Dst:
return NvBlendEquationAdvanced.DstNv;
case AdvancedBlendOp.SrcOver:
return NvBlendEquationAdvanced.SrcOverNv;
case AdvancedBlendOp.DstOver:
return NvBlendEquationAdvanced.DstOverNv;
case AdvancedBlendOp.SrcIn:
return NvBlendEquationAdvanced.SrcInNv;
case AdvancedBlendOp.DstIn:
return NvBlendEquationAdvanced.DstInNv;
case AdvancedBlendOp.SrcOut:
return NvBlendEquationAdvanced.SrcOutNv;
case AdvancedBlendOp.DstOut:
return NvBlendEquationAdvanced.DstOutNv;
case AdvancedBlendOp.SrcAtop:
return NvBlendEquationAdvanced.SrcAtopNv;
case AdvancedBlendOp.DstAtop:
return NvBlendEquationAdvanced.DstAtopNv;
case AdvancedBlendOp.Xor:
return NvBlendEquationAdvanced.XorNv;
case AdvancedBlendOp.Plus:
return NvBlendEquationAdvanced.PlusNv;
case AdvancedBlendOp.PlusClamped:
return NvBlendEquationAdvanced.PlusClampedNv;
case AdvancedBlendOp.PlusClampedAlpha:
return NvBlendEquationAdvanced.PlusClampedAlphaNv;
case AdvancedBlendOp.PlusDarker:
return NvBlendEquationAdvanced.PlusDarkerNv;
case AdvancedBlendOp.Multiply:
return NvBlendEquationAdvanced.MultiplyNv;
case AdvancedBlendOp.Screen:
return NvBlendEquationAdvanced.ScreenNv;
case AdvancedBlendOp.Overlay:
return NvBlendEquationAdvanced.OverlayNv;
case AdvancedBlendOp.Darken:
return NvBlendEquationAdvanced.DarkenNv;
case AdvancedBlendOp.Lighten:
return NvBlendEquationAdvanced.LightenNv;
case AdvancedBlendOp.ColorDodge:
return NvBlendEquationAdvanced.ColordodgeNv;
case AdvancedBlendOp.ColorBurn:
return NvBlendEquationAdvanced.ColorburnNv;
case AdvancedBlendOp.HardLight:
return NvBlendEquationAdvanced.HardlightNv;
case AdvancedBlendOp.SoftLight:
return NvBlendEquationAdvanced.SoftlightNv;
case AdvancedBlendOp.Difference:
return NvBlendEquationAdvanced.DifferenceNv;
case AdvancedBlendOp.Minus:
return NvBlendEquationAdvanced.MinusNv;
case AdvancedBlendOp.MinusClamped:
return NvBlendEquationAdvanced.MinusClampedNv;
case AdvancedBlendOp.Exclusion:
return NvBlendEquationAdvanced.ExclusionNv;
case AdvancedBlendOp.Contrast:
return NvBlendEquationAdvanced.ContrastNv;
case AdvancedBlendOp.Invert:
return NvBlendEquationAdvanced.Invert;
case AdvancedBlendOp.InvertRGB:
return NvBlendEquationAdvanced.InvertRgbNv;
case AdvancedBlendOp.InvertOvg:
return NvBlendEquationAdvanced.InvertOvgNv;
case AdvancedBlendOp.LinearDodge:
return NvBlendEquationAdvanced.LineardodgeNv;
case AdvancedBlendOp.LinearBurn:
return NvBlendEquationAdvanced.LinearburnNv;
case AdvancedBlendOp.VividLight:
return NvBlendEquationAdvanced.VividlightNv;
case AdvancedBlendOp.LinearLight:
return NvBlendEquationAdvanced.LinearlightNv;
case AdvancedBlendOp.PinLight:
return NvBlendEquationAdvanced.PinlightNv;
case AdvancedBlendOp.HardMix:
return NvBlendEquationAdvanced.HardmixNv;
case AdvancedBlendOp.Red:
return NvBlendEquationAdvanced.RedNv;
case AdvancedBlendOp.Green:
return NvBlendEquationAdvanced.GreenNv;
case AdvancedBlendOp.Blue:
return NvBlendEquationAdvanced.BlueNv;
case AdvancedBlendOp.HslHue:
return NvBlendEquationAdvanced.HslHueNv;
case AdvancedBlendOp.HslSaturation:
return NvBlendEquationAdvanced.HslSaturationNv;
case AdvancedBlendOp.HslColor:
return NvBlendEquationAdvanced.HslColorNv;
case AdvancedBlendOp.HslLuminosity:
return NvBlendEquationAdvanced.HslLuminosityNv;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(AdvancedBlendOp)} enum value: {op}.");
return NvBlendEquationAdvanced.Zero;
}
public static All Convert(this AdvancedBlendOverlap overlap)
{
switch (overlap)
{
case AdvancedBlendOverlap.Uncorrelated:
return All.UncorrelatedNv;
case AdvancedBlendOverlap.Disjoint:
return All.DisjointNv;
case AdvancedBlendOverlap.Conjoint:
return All.ConjointNv;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(AdvancedBlendOverlap)} enum value: {overlap}.");
return All.UncorrelatedNv;
}
public static All Convert(this BlendFactor factor)
{
switch (factor)
{
case BlendFactor.Zero:
case BlendFactor.ZeroGl:
return All.Zero;
case BlendFactor.One:
case BlendFactor.OneGl:
return All.One;
case BlendFactor.SrcColor:
case BlendFactor.SrcColorGl:
return All.SrcColor;
case BlendFactor.OneMinusSrcColor:
case BlendFactor.OneMinusSrcColorGl:
return All.OneMinusSrcColor;
case BlendFactor.SrcAlpha:
case BlendFactor.SrcAlphaGl:
return All.SrcAlpha;
case BlendFactor.OneMinusSrcAlpha:
case BlendFactor.OneMinusSrcAlphaGl:
return All.OneMinusSrcAlpha;
case BlendFactor.DstAlpha:
case BlendFactor.DstAlphaGl:
return All.DstAlpha;
case BlendFactor.OneMinusDstAlpha:
case BlendFactor.OneMinusDstAlphaGl:
return All.OneMinusDstAlpha;
case BlendFactor.DstColor:
case BlendFactor.DstColorGl:
return All.DstColor;
case BlendFactor.OneMinusDstColor:
case BlendFactor.OneMinusDstColorGl:
return All.OneMinusDstColor;
case BlendFactor.SrcAlphaSaturate:
case BlendFactor.SrcAlphaSaturateGl:
return All.SrcAlphaSaturate;
case BlendFactor.Src1Color:
case BlendFactor.Src1ColorGl:
return All.Src1Color;
case BlendFactor.OneMinusSrc1Color:
case BlendFactor.OneMinusSrc1ColorGl:
return All.OneMinusSrc1Color;
case BlendFactor.Src1Alpha:
case BlendFactor.Src1AlphaGl:
return All.Src1Alpha;
case BlendFactor.OneMinusSrc1Alpha:
case BlendFactor.OneMinusSrc1AlphaGl:
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;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(BlendFactor)} enum value: {factor}.");
return All.Zero;
}
public static BlendEquationMode Convert(this BlendOp op)
{
switch (op)
{
case BlendOp.Add:
case BlendOp.AddGl:
return BlendEquationMode.FuncAdd;
case BlendOp.Minimum:
case BlendOp.MinimumGl:
return BlendEquationMode.Min;
case BlendOp.Maximum:
case BlendOp.MaximumGl:
return BlendEquationMode.Max;
case BlendOp.Subtract:
case BlendOp.SubtractGl:
return BlendEquationMode.FuncSubtract;
case BlendOp.ReverseSubtract:
case BlendOp.ReverseSubtractGl:
return BlendEquationMode.FuncReverseSubtract;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(BlendOp)} enum value: {op}.");
return BlendEquationMode.FuncAdd;
}
public static TextureCompareMode Convert(this CompareMode mode)
{
switch (mode)
{
case CompareMode.None:
return TextureCompareMode.None;
case CompareMode.CompareRToTexture:
return TextureCompareMode.CompareRToTexture;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(CompareMode)} enum value: {mode}.");
return TextureCompareMode.None;
}
public static All Convert(this CompareOp op)
{
switch (op)
{
case CompareOp.Never:
case CompareOp.NeverGl:
return All.Never;
case CompareOp.Less:
case CompareOp.LessGl:
return All.Less;
case CompareOp.Equal:
case CompareOp.EqualGl:
return All.Equal;
case CompareOp.LessOrEqual:
case CompareOp.LessOrEqualGl:
return All.Lequal;
case CompareOp.Greater:
case CompareOp.GreaterGl:
return All.Greater;
case CompareOp.NotEqual:
case CompareOp.NotEqualGl:
return All.Notequal;
case CompareOp.GreaterOrEqual:
case CompareOp.GreaterOrEqualGl:
return All.Gequal;
case CompareOp.Always:
case CompareOp.AlwaysGl:
return All.Always;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(CompareOp)} enum value: {op}.");
return All.Never;
}
public static ClipDepthMode Convert(this DepthMode mode)
{
switch (mode)
{
case DepthMode.MinusOneToOne:
return ClipDepthMode.NegativeOneToOne;
case DepthMode.ZeroToOne:
return ClipDepthMode.ZeroToOne;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(DepthMode)} enum value: {mode}.");
return ClipDepthMode.NegativeOneToOne;
}
public static All Convert(this DepthStencilMode mode)
{
switch (mode)
{
case DepthStencilMode.Depth:
return All.DepthComponent;
case DepthStencilMode.Stencil:
return All.StencilIndex;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(DepthStencilMode)} enum value: {mode}.");
return All.Depth;
}
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;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(Face)} enum value: {face}.");
return CullFaceMode.Back;
}
public static FrontFaceDirection Convert(this FrontFace frontFace)
{
switch (frontFace)
{
case FrontFace.Clockwise:
return FrontFaceDirection.Cw;
case FrontFace.CounterClockwise:
return FrontFaceDirection.Ccw;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(FrontFace)} enum value: {frontFace}.");
return FrontFaceDirection.Cw;
}
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;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(IndexType)} enum value: {type}.");
return DrawElementsType.UnsignedByte;
}
public static TextureMagFilter Convert(this MagFilter filter)
{
switch (filter)
{
case MagFilter.Nearest:
return TextureMagFilter.Nearest;
case MagFilter.Linear:
return TextureMagFilter.Linear;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(MagFilter)} enum value: {filter}.");
return TextureMagFilter.Nearest;
}
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;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(MinFilter)} enum value: {filter}.");
return TextureMinFilter.Nearest;
}
public static OpenTK.Graphics.OpenGL.PolygonMode Convert(this GAL.PolygonMode mode)
{
switch (mode)
{
case GAL.PolygonMode.Point:
return OpenTK.Graphics.OpenGL.PolygonMode.Point;
case GAL.PolygonMode.Line:
return OpenTK.Graphics.OpenGL.PolygonMode.Line;
case GAL.PolygonMode.Fill:
return OpenTK.Graphics.OpenGL.PolygonMode.Fill;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.PolygonMode)} enum value: {mode}.");
return OpenTK.Graphics.OpenGL.PolygonMode.Fill;
}
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.TriangleFan;
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;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(PrimitiveTopology)} enum value: {topology}.");
return PrimitiveType.Points;
}
public static TransformFeedbackPrimitiveType ConvertToTfType(this PrimitiveTopology topology)
{
switch (topology)
{
case PrimitiveTopology.Points:
return TransformFeedbackPrimitiveType.Points;
case PrimitiveTopology.Lines:
case PrimitiveTopology.LineLoop:
case PrimitiveTopology.LineStrip:
case PrimitiveTopology.LinesAdjacency:
case PrimitiveTopology.LineStripAdjacency:
return TransformFeedbackPrimitiveType.Lines;
case PrimitiveTopology.Triangles:
case PrimitiveTopology.TriangleStrip:
case PrimitiveTopology.TriangleFan:
case PrimitiveTopology.TrianglesAdjacency:
case PrimitiveTopology.TriangleStripAdjacency:
return TransformFeedbackPrimitiveType.Triangles;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(PrimitiveTopology)} enum value: {topology}.");
return TransformFeedbackPrimitiveType.Points;
}
public static OpenTK.Graphics.OpenGL.StencilOp Convert(this GAL.StencilOp op)
{
switch (op)
{
case GAL.StencilOp.Keep:
case GAL.StencilOp.KeepGl:
return OpenTK.Graphics.OpenGL.StencilOp.Keep;
case GAL.StencilOp.Zero:
case GAL.StencilOp.ZeroGl:
return OpenTK.Graphics.OpenGL.StencilOp.Zero;
case GAL.StencilOp.Replace:
case GAL.StencilOp.ReplaceGl:
return OpenTK.Graphics.OpenGL.StencilOp.Replace;
case GAL.StencilOp.IncrementAndClamp:
case GAL.StencilOp.IncrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Incr;
case GAL.StencilOp.DecrementAndClamp:
case GAL.StencilOp.DecrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Decr;
case GAL.StencilOp.Invert:
case GAL.StencilOp.InvertGl:
return OpenTK.Graphics.OpenGL.StencilOp.Invert;
case GAL.StencilOp.IncrementAndWrap:
case GAL.StencilOp.IncrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap;
case GAL.StencilOp.DecrementAndWrap:
case GAL.StencilOp.DecrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.StencilOp)} enum value: {op}.");
return OpenTK.Graphics.OpenGL.StencilOp.Keep;
}
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;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(SwizzleComponent)} enum value: {swizzleComponent}.");
return All.Zero;
}
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.Texture2DMultisampleArray:
return TextureTarget.Texture2DMultisampleArray;
case Target.Cubemap:
return TextureTarget.TextureCubeMap;
case Target.CubemapArray:
return TextureTarget.TextureCubeMapArray;
case Target.TextureBuffer:
return TextureTarget.TextureBuffer;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(Target)} enum value: {target}.");
return TextureTarget.Texture2D;
}
public static NvViewportSwizzle Convert(this ViewportSwizzle swizzle)
{
switch (swizzle)
{
case ViewportSwizzle.PositiveX:
return NvViewportSwizzle.ViewportSwizzlePositiveXNv;
case ViewportSwizzle.PositiveY:
return NvViewportSwizzle.ViewportSwizzlePositiveYNv;
case ViewportSwizzle.PositiveZ:
return NvViewportSwizzle.ViewportSwizzlePositiveZNv;
case ViewportSwizzle.PositiveW:
return NvViewportSwizzle.ViewportSwizzlePositiveWNv;
case ViewportSwizzle.NegativeX:
return NvViewportSwizzle.ViewportSwizzleNegativeXNv;
case ViewportSwizzle.NegativeY:
return NvViewportSwizzle.ViewportSwizzleNegativeYNv;
case ViewportSwizzle.NegativeZ:
return NvViewportSwizzle.ViewportSwizzleNegativeZNv;
case ViewportSwizzle.NegativeW:
return NvViewportSwizzle.ViewportSwizzleNegativeWNv;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(ViewportSwizzle)} enum value: {swizzle}.");
return NvViewportSwizzle.ViewportSwizzlePositiveXNv;
}
public static All Convert(this LogicalOp op)
{
switch (op)
{
case LogicalOp.Clear:
return All.Clear;
case LogicalOp.And:
return All.And;
case LogicalOp.AndReverse:
return All.AndReverse;
case LogicalOp.Copy:
return All.Copy;
case LogicalOp.AndInverted:
return All.AndInverted;
case LogicalOp.Noop:
return All.Noop;
case LogicalOp.Xor:
return All.Xor;
case LogicalOp.Or:
return All.Or;
case LogicalOp.Nor:
return All.Nor;
case LogicalOp.Equiv:
return All.Equiv;
case LogicalOp.Invert:
return All.Invert;
case LogicalOp.OrReverse:
return All.OrReverse;
case LogicalOp.CopyInverted:
return All.CopyInverted;
case LogicalOp.OrInverted:
return All.OrInverted;
case LogicalOp.Nand:
return All.Nand;
case LogicalOp.Set:
return All.Set;
}
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(LogicalOp)} enum value: {op}.");
return All.Never;
}
public static ShaderType Convert(this ShaderStage stage)
{
return stage switch
{
ShaderStage.Compute => ShaderType.ComputeShader,
ShaderStage.Vertex => ShaderType.VertexShader,
ShaderStage.TessellationControl => ShaderType.TessControlShader,
ShaderStage.TessellationEvaluation => ShaderType.TessEvaluationShader,
ShaderStage.Geometry => ShaderType.GeometryShader,
ShaderStage.Fragment => ShaderType.FragmentShader,
_ => ShaderType.VertexShader
};
}
}
}

View file

@ -0,0 +1,45 @@
using OpenTK.Graphics.OpenGL;
namespace Ryujinx.Graphics.OpenGL
{
readonly 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;
}
}
}

View file

@ -0,0 +1,225 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL
{
struct FormatTable
{
private static FormatInfo[] _table;
private static SizedInternalFormat[] _tableImage;
static FormatTable()
{
int tableSize = Enum.GetNames<Format>().Length;
_table = new FormatInfo[tableSize];
_tableImage = new SizedInternalFormat[tableSize];
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.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
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.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.Bc1RgbaUnorm, new FormatInfo(4, true, false, All.CompressedRgbaS3tcDxt1Ext));
Add(Format.Bc2Unorm, new FormatInfo(4, true, false, All.CompressedRgbaS3tcDxt3Ext));
Add(Format.Bc3Unorm, new FormatInfo(4, true, false, All.CompressedRgbaS3tcDxt5Ext));
Add(Format.Bc1RgbaSrgb, new FormatInfo(4, true, false, All.CompressedSrgbAlphaS3tcDxt1Ext));
Add(Format.Bc2Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaS3tcDxt3Ext));
Add(Format.Bc3Srgb, new FormatInfo(4, 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(2, true, false, All.CompressedRgRgtc2));
Add(Format.Bc5Snorm, new FormatInfo(2, true, false, All.CompressedSignedRgRgtc2));
Add(Format.Bc7Unorm, new FormatInfo(4, true, false, All.CompressedRgbaBptcUnorm));
Add(Format.Bc7Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaBptcUnorm));
Add(Format.Bc6HSfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcSignedFloat));
Add(Format.Bc6HUfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcUnsignedFloat));
Add(Format.Etc2RgbUnorm, new FormatInfo(4, false, false, All.CompressedRgb8Etc2));
Add(Format.Etc2RgbaUnorm, new FormatInfo(4, false, false, All.CompressedRgba8Etc2Eac));
Add(Format.Etc2RgbPtaUnorm, new FormatInfo(4, false, false, All.CompressedRgb8PunchthroughAlpha1Etc2));
Add(Format.Etc2RgbSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8Etc2));
Add(Format.Etc2RgbaSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Etc2Eac));
Add(Format.Etc2RgbPtaSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8PunchthroughAlpha1Etc2));
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.Astc4x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc4X4Khr));
Add(Format.Astc5x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X4Khr));
Add(Format.Astc5x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X5Khr));
Add(Format.Astc6x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X5Khr));
Add(Format.Astc6x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X6Khr));
Add(Format.Astc8x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X5Khr));
Add(Format.Astc8x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X6Khr));
Add(Format.Astc8x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X8Khr));
Add(Format.Astc10x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X5Khr));
Add(Format.Astc10x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X6Khr));
Add(Format.Astc10x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X8Khr));
Add(Format.Astc10x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X10Khr));
Add(Format.Astc12x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X10Khr));
Add(Format.Astc12x12Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X12Khr));
Add(Format.Astc4x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
Add(Format.Astc5x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
Add(Format.Astc5x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
Add(Format.Astc6x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
Add(Format.Astc6x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
Add(Format.Astc8x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
Add(Format.Astc8x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
Add(Format.Astc8x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
Add(Format.Astc10x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
Add(Format.Astc10x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
Add(Format.Astc10x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
Add(Format.Astc10x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
Add(Format.Astc12x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
Add(Format.Astc12x12Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr));
Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed));
Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551));
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte));
Add(Format.R8Unorm, SizedInternalFormat.R8);
Add(Format.R8Uint, SizedInternalFormat.R8ui);
Add(Format.R8Sint, SizedInternalFormat.R8i);
Add(Format.R16Float, SizedInternalFormat.R16f);
Add(Format.R16Unorm, SizedInternalFormat.R16);
Add(Format.R16Snorm, (SizedInternalFormat)All.R16Snorm);
Add(Format.R16Uint, SizedInternalFormat.R16ui);
Add(Format.R16Sint, SizedInternalFormat.R16i);
Add(Format.R32Float, SizedInternalFormat.R32f);
Add(Format.R32Uint, SizedInternalFormat.R32ui);
Add(Format.R32Sint, SizedInternalFormat.R32i);
Add(Format.R8G8Unorm, SizedInternalFormat.Rg8);
Add(Format.R8G8Snorm, (SizedInternalFormat)All.Rg8Snorm);
Add(Format.R8G8Uint, SizedInternalFormat.Rg8ui);
Add(Format.R8G8Sint, SizedInternalFormat.Rg8i);
Add(Format.R16G16Float, SizedInternalFormat.Rg16f);
Add(Format.R16G16Unorm, SizedInternalFormat.Rg16);
Add(Format.R16G16Snorm, (SizedInternalFormat)All.Rg16Snorm);
Add(Format.R16G16Uint, SizedInternalFormat.Rg16ui);
Add(Format.R16G16Sint, SizedInternalFormat.Rg16i);
Add(Format.R32G32Float, SizedInternalFormat.Rg32f);
Add(Format.R32G32Uint, SizedInternalFormat.Rg32ui);
Add(Format.R32G32Sint, SizedInternalFormat.Rg32i);
Add(Format.R8G8B8A8Unorm, SizedInternalFormat.Rgba8);
Add(Format.R8G8B8A8Snorm, (SizedInternalFormat)All.Rgba8Snorm);
Add(Format.R8G8B8A8Uint, SizedInternalFormat.Rgba8ui);
Add(Format.R8G8B8A8Sint, SizedInternalFormat.Rgba8i);
Add(Format.R16G16B16A16Float, SizedInternalFormat.Rgba16f);
Add(Format.R16G16B16A16Unorm, SizedInternalFormat.Rgba16);
Add(Format.R16G16B16A16Snorm, (SizedInternalFormat)All.Rgba16Snorm);
Add(Format.R16G16B16A16Uint, SizedInternalFormat.Rgba16ui);
Add(Format.R16G16B16A16Sint, SizedInternalFormat.Rgba16i);
Add(Format.R32G32B32A32Float, SizedInternalFormat.Rgba32f);
Add(Format.R32G32B32A32Uint, SizedInternalFormat.Rgba32ui);
Add(Format.R32G32B32A32Sint, SizedInternalFormat.Rgba32i);
Add(Format.R8G8B8A8Srgb, SizedInternalFormat.Rgba8);
Add(Format.R10G10B10A2Unorm, (SizedInternalFormat)All.Rgb10A2);
Add(Format.R10G10B10A2Uint, (SizedInternalFormat)All.Rgb10A2ui);
Add(Format.R11G11B10Float, (SizedInternalFormat)All.R11fG11fB10f);
}
private static void Add(Format format, FormatInfo info)
{
_table[(int)format] = info;
}
private static void Add(Format format, SizedInternalFormat sif)
{
_tableImage[(int)format] = sif;
}
public static FormatInfo GetFormatInfo(Format format)
{
return _table[(int)format];
}
public static SizedInternalFormat GetImageFormat(Format format)
{
return _tableImage[(int)format];
}
}
}

View file

@ -0,0 +1,247 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.OpenGL
{
class Framebuffer : IDisposable
{
public int Handle { get; private set; }
private int _clearFbHandle;
private bool _clearFbInitialized;
private FramebufferAttachment _lastDsAttachment;
private readonly TextureView[] _colors;
private TextureView _depthStencil;
private int _colorsCount;
private bool _dualSourceBlend;
public Framebuffer()
{
Handle = GL.GenFramebuffer();
_clearFbHandle = GL.GenFramebuffer();
_colors = new TextureView[8];
}
public int Bind()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
return Handle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AttachColor(int index, TextureView color)
{
if (_colors[index] == color)
{
return;
}
FramebufferAttachment attachment = FramebufferAttachment.ColorAttachment0 + index;
GL.FramebufferTexture(FramebufferTarget.Framebuffer, attachment, color?.Handle ?? 0, 0);
_colors[index] = color;
}
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 = GetAttachment(depthStencil.Format);
GL.FramebufferTexture(
FramebufferTarget.Framebuffer,
attachment,
depthStencil.Handle,
0);
_lastDsAttachment = attachment;
}
else
{
_lastDsAttachment = 0;
}
_depthStencil = depthStencil;
}
public void SetDualSourceBlend(bool enable)
{
bool oldEnable = _dualSourceBlend;
_dualSourceBlend = enable;
// When dual source blend is used,
// we can only have one draw buffer.
if (enable)
{
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
}
else if (oldEnable)
{
SetDrawBuffersImpl(_colorsCount);
}
}
public void SetDrawBuffers(int colorsCount)
{
if (_colorsCount != colorsCount && !_dualSourceBlend)
{
SetDrawBuffersImpl(colorsCount);
}
_colorsCount = colorsCount;
}
private void SetDrawBuffersImpl(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 FramebufferAttachment GetAttachment(Format format)
{
if (IsPackedDepthStencilFormat(format))
{
return FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnlyFormat(format))
{
return FramebufferAttachment.DepthAttachment;
}
else
{
return FramebufferAttachment.StencilAttachment;
}
}
private static bool IsPackedDepthStencilFormat(Format format)
{
return format == Format.D24UnormS8Uint ||
format == Format.D32FloatS8Uint ||
format == Format.S8UintD24Unorm;
}
private static bool IsDepthOnlyFormat(Format format)
{
return format == Format.D16Unorm || format == Format.D32Float;
}
public int GetColorLayerCount(int index)
{
return _colors[index]?.Info.GetDepthOrLayers() ?? 0;
}
public int GetDepthStencilLayerCount()
{
return _depthStencil?.Info.GetDepthOrLayers() ?? 0;
}
public void AttachColorLayerForClear(int index, int layer)
{
TextureView color = _colors[index];
if (!IsLayered(color))
{
return;
}
BindClearFb();
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, color.Handle, 0, layer);
}
public void DetachColorLayerForClear(int index)
{
TextureView color = _colors[index];
if (!IsLayered(color))
{
return;
}
GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, 0, 0);
Bind();
}
public void AttachDepthStencilLayerForClear(int layer)
{
TextureView depthStencil = _depthStencil;
if (!IsLayered(depthStencil))
{
return;
}
BindClearFb();
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), depthStencil.Handle, 0, layer);
}
public void DetachDepthStencilLayerForClear()
{
TextureView depthStencil = _depthStencil;
if (!IsLayered(depthStencil))
{
return;
}
GL.FramebufferTexture(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), 0, 0);
Bind();
}
private void BindClearFb()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _clearFbHandle);
if (!_clearFbInitialized)
{
SetDrawBuffersImpl(Constants.MaxRenderTargets);
_clearFbInitialized = true;
}
}
private static bool IsLayered(TextureView view)
{
return view != null &&
view.Target != Target.Texture1D &&
view.Target != Target.Texture2D &&
view.Target != Target.Texture2DMultisample &&
view.Target != Target.TextureBuffer;
}
public void Dispose()
{
if (Handle != 0)
{
GL.DeleteFramebuffer(Handle);
Handle = 0;
}
if (_clearFbHandle != 0)
{
GL.DeleteFramebuffer(_clearFbHandle);
_clearFbHandle = 0;
}
}
}
}

View file

@ -0,0 +1,23 @@
using Ryujinx.Graphics.GAL;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.OpenGL
{
static class Handle
{
public static T FromInt32<T>(int handle) where T : unmanaged
{
Debug.Assert(Unsafe.SizeOf<T>() == sizeof(ulong));
ulong handle64 = (uint)handle;
return Unsafe.As<ulong, T>(ref handle64);
}
public static int ToInt32(this BufferHandle handle)
{
return (int)Unsafe.As<BufferHandle, ulong>(ref handle);
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.OpenGL.Helper
{
[SupportedOSPlatform("linux")]
internal static partial class GLXHelper
{
private const string LibraryName = "glx.dll";
static GLXHelper()
{
NativeLibrary.SetDllImportResolver(typeof(GLXHelper).Assembly, (name, assembly, path) =>
{
if (name != LibraryName)
{
return IntPtr.Zero;
}
if (!NativeLibrary.TryLoad("libGL.so.1", assembly, path, out IntPtr result))
{
if (!NativeLibrary.TryLoad("libGL.so", assembly, path, out result))
{
return IntPtr.Zero;
}
}
return result;
});
}
[LibraryImport(LibraryName, EntryPoint = "glXGetCurrentContext")]
public static partial IntPtr GetCurrentContext();
}
}

View file

@ -0,0 +1,15 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.OpenGL.Helper
{
[SupportedOSPlatform("windows")]
internal static partial class WGLHelper
{
private const string LibraryName = "OPENGL32.DLL";
[LibraryImport(LibraryName, EntryPoint = "wglGetCurrentContext")]
public static partial IntPtr GetCurrentContext();
}
}

View file

@ -0,0 +1,142 @@
using OpenTK.Graphics.OpenGL;
using System;
namespace Ryujinx.Graphics.OpenGL
{
static class HwCapabilities
{
private static readonly Lazy<bool> _supportsAlphaToCoverageDitherControl = new Lazy<bool>(() => HasExtension("GL_NV_alpha_to_coverage_dither_control"));
private static readonly Lazy<bool> _supportsAstcCompression = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
private static readonly Lazy<bool> _supportsBlendEquationAdvanced = new Lazy<bool>(() => HasExtension("GL_NV_blend_equation_advanced"));
private static readonly Lazy<bool> _supportsDrawTexture = new Lazy<bool>(() => HasExtension("GL_NV_draw_texture"));
private static readonly Lazy<bool> _supportsFragmentShaderInterlock = new Lazy<bool>(() => HasExtension("GL_ARB_fragment_shader_interlock"));
private static readonly Lazy<bool> _supportsFragmentShaderOrdering = new Lazy<bool>(() => HasExtension("GL_INTEL_fragment_shader_ordering"));
private static readonly Lazy<bool> _supportsGeometryShaderPassthrough = new Lazy<bool>(() => HasExtension("GL_NV_geometry_shader_passthrough"));
private static readonly Lazy<bool> _supportsImageLoadFormatted = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
private static readonly Lazy<bool> _supportsIndirectParameters = new Lazy<bool>(() => HasExtension("GL_ARB_indirect_parameters"));
private static readonly Lazy<bool> _supportsParallelShaderCompile = new Lazy<bool>(() => HasExtension("GL_ARB_parallel_shader_compile"));
private static readonly Lazy<bool> _supportsPolygonOffsetClamp = new Lazy<bool>(() => HasExtension("GL_EXT_polygon_offset_clamp"));
private static readonly Lazy<bool> _supportsQuads = new Lazy<bool>(SupportsQuadsCheck);
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
private static readonly Lazy<bool> _supportsShaderViewportLayerArray = new Lazy<bool>(() => HasExtension("GL_ARB_shader_viewport_layer_array"));
private static readonly Lazy<bool> _supportsViewportArray2 = new Lazy<bool>(() => HasExtension("GL_NV_viewport_array2"));
private static readonly Lazy<bool> _supportsTextureCompressionBptc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_bptc"));
private static readonly Lazy<bool> _supportsTextureCompressionRgtc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_rgtc"));
private static readonly Lazy<bool> _supportsTextureCompressionS3tc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_s3tc"));
private static readonly Lazy<bool> _supportsTextureShadowLod = new Lazy<bool>(() => HasExtension("GL_EXT_texture_shadow_lod"));
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new Lazy<int>(() => GetLimit(All.MaxComputeSharedMemorySize));
private static readonly Lazy<int> _storageBufferOffsetAlignment = new Lazy<int>(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
public enum GpuVendor
{
Unknown,
AmdWindows,
AmdUnix,
IntelWindows,
IntelUnix,
Nvidia
}
private static readonly Lazy<GpuVendor> _gpuVendor = new Lazy<GpuVendor>(GetGpuVendor);
private static bool _isAMD => _gpuVendor.Value == GpuVendor.AmdWindows || _gpuVendor.Value == GpuVendor.AmdUnix;
private static bool _isIntel => _gpuVendor.Value == GpuVendor.IntelWindows || _gpuVendor.Value == GpuVendor.IntelUnix;
public static GpuVendor Vendor => _gpuVendor.Value;
private static Lazy<float> _maxSupportedAnisotropy = new Lazy<float>(GL.GetFloat((GetPName)All.MaxTextureMaxAnisotropy));
public static bool UsePersistentBufferForFlush => _gpuVendor.Value == GpuVendor.AmdWindows || _gpuVendor.Value == GpuVendor.Nvidia;
public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value;
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
public static bool SupportsBlendEquationAdvanced => _supportsBlendEquationAdvanced.Value;
public static bool SupportsDrawTexture => _supportsDrawTexture.Value;
public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value;
public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value;
public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value;
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
public static bool SupportsQuads => _supportsQuads.Value;
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
public static bool SupportsShaderViewportLayerArray => _supportsShaderViewportLayerArray.Value;
public static bool SupportsViewportArray2 => _supportsViewportArray2.Value;
public static bool SupportsTextureCompressionBptc => _supportsTextureCompressionBptc.Value;
public static bool SupportsTextureCompressionRgtc => _supportsTextureCompressionRgtc.Value;
public static bool SupportsTextureCompressionS3tc => _supportsTextureCompressionS3tc.Value;
public static bool SupportsTextureShadowLod => _supportsTextureShadowLod.Value;
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
public static bool SupportsMismatchingViewFormat => _gpuVendor.Value != GpuVendor.AmdWindows && _gpuVendor.Value != GpuVendor.IntelWindows;
public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia;
public static bool RequiresSyncFlush => _gpuVendor.Value == GpuVendor.AmdWindows || _isIntel;
public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value;
public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value;
public static float MaximumSupportedAnisotropy => _maxSupportedAnisotropy.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;
}
private static int GetLimit(All name)
{
return GL.GetInteger((GetPName)name);
}
private static GpuVendor GetGpuVendor()
{
string vendor = GL.GetString(StringName.Vendor).ToLower();
if (vendor == "nvidia corporation")
{
return GpuVendor.Nvidia;
}
else if (vendor == "intel")
{
string renderer = GL.GetString(StringName.Renderer).ToLower();
return renderer.Contains("mesa") ? GpuVendor.IntelUnix : GpuVendor.IntelWindows;
}
else if (vendor == "ati technologies inc." || vendor == "advanced micro devices, inc.")
{
return GpuVendor.AmdWindows;
}
else if (vendor == "amd" || vendor == "x.org")
{
return GpuVendor.AmdUnix;
}
else
{
return GpuVendor.Unknown;
}
}
private static bool SupportsQuadsCheck()
{
GL.GetError(); // Clear any existing error.
GL.Begin(PrimitiveType.Quads);
GL.End();
return GL.GetError() == ErrorCode.NoError;
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.Graphics.OpenGL.Helper;
using System;
namespace Ryujinx.Graphics.OpenGL
{
public interface IOpenGLContext : IDisposable
{
void MakeCurrent();
// TODO: Support more APIs per platform.
static bool HasContext()
{
if (OperatingSystem.IsWindows())
{
return WGLHelper.GetCurrentContext() != IntPtr.Zero;
}
else if (OperatingSystem.IsLinux())
{
return GLXHelper.GetCurrentContext() != IntPtr.Zero;
}
else
{
return false;
}
}
}
}

View file

@ -0,0 +1,149 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Ryujinx.Graphics.OpenGL.Image
{
static class FormatConverter
{
public unsafe static byte[] ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
{
byte[] output = new byte[data.Length];
int start = 0;
if (Avx2.IsSupported)
{
var mask = Vector256.Create(
(byte)3, (byte)0, (byte)1, (byte)2,
(byte)7, (byte)4, (byte)5, (byte)6,
(byte)11, (byte)8, (byte)9, (byte)10,
(byte)15, (byte)12, (byte)13, (byte)14,
(byte)19, (byte)16, (byte)17, (byte)18,
(byte)23, (byte)20, (byte)21, (byte)22,
(byte)27, (byte)24, (byte)25, (byte)26,
(byte)31, (byte)28, (byte)29, (byte)30);
int sizeAligned = data.Length & ~31;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 32)
{
var dataVec = Avx.LoadVector256(pInput + i);
dataVec = Avx2.Shuffle(dataVec, mask);
Avx.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
else if (Ssse3.IsSupported)
{
var mask = Vector128.Create(
(byte)3, (byte)0, (byte)1, (byte)2,
(byte)7, (byte)4, (byte)5, (byte)6,
(byte)11, (byte)8, (byte)9, (byte)10,
(byte)15, (byte)12, (byte)13, (byte)14);
int sizeAligned = data.Length & ~15;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 16)
{
var dataVec = Sse2.LoadVector128(pInput + i);
dataVec = Ssse3.Shuffle(dataVec, mask);
Sse2.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
var outSpan = MemoryMarshal.Cast<byte, uint>(output);
var dataSpan = MemoryMarshal.Cast<byte, uint>(data);
for (int i = start / sizeof(uint); i < dataSpan.Length; i++)
{
outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8);
}
return output;
}
public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data)
{
byte[] output = new byte[data.Length];
int start = 0;
if (Avx2.IsSupported)
{
var mask = Vector256.Create(
(byte)1, (byte)2, (byte)3, (byte)0,
(byte)5, (byte)6, (byte)7, (byte)4,
(byte)9, (byte)10, (byte)11, (byte)8,
(byte)13, (byte)14, (byte)15, (byte)12,
(byte)17, (byte)18, (byte)19, (byte)16,
(byte)21, (byte)22, (byte)23, (byte)20,
(byte)25, (byte)26, (byte)27, (byte)24,
(byte)29, (byte)30, (byte)31, (byte)28);
int sizeAligned = data.Length & ~31;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 32)
{
var dataVec = Avx.LoadVector256(pInput + i);
dataVec = Avx2.Shuffle(dataVec, mask);
Avx.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
else if (Ssse3.IsSupported)
{
var mask = Vector128.Create(
(byte)1, (byte)2, (byte)3, (byte)0,
(byte)5, (byte)6, (byte)7, (byte)4,
(byte)9, (byte)10, (byte)11, (byte)8,
(byte)13, (byte)14, (byte)15, (byte)12);
int sizeAligned = data.Length & ~15;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 16)
{
var dataVec = Sse2.LoadVector128(pInput + i);
dataVec = Ssse3.Shuffle(dataVec, mask);
Sse2.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
var outSpan = MemoryMarshal.Cast<byte, uint>(output);
var dataSpan = MemoryMarshal.Cast<byte, uint>(data);
for (int i = start / sizeof(uint); i < dataSpan.Length; i++)
{
outSpan[i] = BitOperations.RotateRight(dataSpan[i], 8);
}
return output;
}
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.OpenGL.Image
{
interface ITextureInfo
{
ITextureInfo Storage { get; }
int Handle { get; }
int FirstLayer => 0;
int FirstLevel => 0;
TextureCreateInfo Info { get; }
}
}

View file

@ -0,0 +1,103 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.OpenGL.Image
{
class IntermediatePool : IDisposable
{
private readonly OpenGLRenderer _renderer;
private readonly List<TextureView> _entries;
public IntermediatePool(OpenGLRenderer renderer)
{
_renderer = renderer;
_entries = new List<TextureView>();
}
public TextureView GetOrCreateWithAtLeast(
Target target,
int blockWidth,
int blockHeight,
int bytesPerPixel,
Format format,
int width,
int height,
int depth,
int levels,
int samples)
{
TextureView entry;
for (int i = 0; i < _entries.Count; i++)
{
entry = _entries[i];
if (entry.Target == target && entry.Format == format && entry.Info.Samples == samples)
{
if (entry.Width < width ||
entry.Height < height ||
entry.Info.Depth < depth ||
entry.Info.Levels < levels)
{
width = Math.Max(width, entry.Width);
height = Math.Max(height, entry.Height);
depth = Math.Max(depth, entry.Info.Depth);
levels = Math.Max(levels, entry.Info.Levels);
entry.Dispose();
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels, samples);
_entries[i] = entry;
}
return entry;
}
}
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels, samples);
_entries.Add(entry);
return entry;
}
private TextureView CreateNew(
Target target,
int blockWidth,
int blockHeight,
int bytesPerPixel,
Format format,
int width,
int height,
int depth,
int levels,
int samples)
{
return (TextureView)_renderer.CreateTexture(new TextureCreateInfo(
width,
height,
depth,
levels,
samples,
blockWidth,
blockHeight,
bytesPerPixel,
format,
DepthStencilMode.Depth,
target,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha), 1f);
}
public void Dispose()
{
foreach (TextureView entry in _entries)
{
entry.Dispose();
}
_entries.Clear();
}
}
}

View file

@ -0,0 +1,64 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.OpenGL.Image
{
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());
if (HwCapabilities.SupportsSeamlessCubemapPerTexture)
{
GL.SamplerParameter(Handle, (SamplerParameterName)ArbSeamlessCubemapPerTexture.TextureCubeMapSeamless, info.SeamlessCubemap ? 1 : 0);
}
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;
}
}
}
}

View file

@ -0,0 +1,44 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureBase
{
public int Handle { get; protected set; }
public TextureCreateInfo Info { get; }
public int Width => Info.Width;
public int Height => Info.Height;
public float ScaleFactor { get; }
public Target Target => Info.Target;
public Format Format => Info.Format;
public TextureBase(TextureCreateInfo info, float scaleFactor = 1f)
{
Info = info;
ScaleFactor = scaleFactor;
Handle = GL.GenTexture();
}
public void Bind(int unit)
{
Bind(Target.Convert(), unit);
}
protected void Bind(TextureTarget target, int unit)
{
GL.ActiveTexture(TextureUnit.Texture0 + unit);
GL.BindTexture(target, Handle);
}
public static void ClearBinding(int unit)
{
GL.ActiveTexture(TextureUnit.Texture0 + unit);
GL.BindTextureUnit(unit, 0);
}
}
}

View file

@ -0,0 +1,108 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureBuffer : TextureBase, ITexture
{
private OpenGLRenderer _renderer;
private int _bufferOffset;
private int _bufferSize;
private int _bufferCount;
private BufferHandle _buffer;
public TextureBuffer(OpenGLRenderer renderer, TextureCreateInfo info) : base(info)
{
_renderer = renderer;
}
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
{
throw new NotSupportedException();
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{
throw new NotSupportedException();
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
throw new NotSupportedException();
}
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
{
throw new NotSupportedException();
}
public PinnedSpan<byte> GetData()
{
return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize);
}
public PinnedSpan<byte> GetData(int layer, int level)
{
return GetData();
}
public void SetData(SpanOrArray<byte> data)
{
var dataSpan = data.AsSpan();
Buffer.SetData(_buffer, _bufferOffset, dataSpan.Slice(0, Math.Min(dataSpan.Length, _bufferSize)));
}
public void SetData(SpanOrArray<byte> data, int layer, int level)
{
throw new NotSupportedException();
}
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
{
throw new NotSupportedException();
}
public void SetStorage(BufferRange buffer)
{
if (_buffer != BufferHandle.Null &&
_buffer == buffer.Handle &&
buffer.Offset == _bufferOffset &&
buffer.Size == _bufferSize &&
_renderer.BufferCount == _bufferCount)
{
// Only rebind the buffer when more have been created.
return;
}
_buffer = buffer.Handle;
_bufferOffset = buffer.Offset;
_bufferSize = buffer.Size;
_bufferCount = _renderer.BufferCount;
Bind(0);
SizedInternalFormat format = (SizedInternalFormat)FormatTable.GetFormatInfo(Info.Format).PixelInternalFormat;
GL.TexBufferRange(TextureBufferTarget.TextureBuffer, format, _buffer.ToInt32(), (IntPtr)buffer.Offset, buffer.Size);
}
public void Dispose()
{
if (Handle != 0)
{
GL.DeleteTexture(Handle);
Handle = 0;
}
}
public void Release()
{
Dispose();
}
}
}

View file

@ -0,0 +1,524 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureCopy : IDisposable
{
private readonly OpenGLRenderer _renderer;
private int _srcFramebuffer;
private int _dstFramebuffer;
private int _copyPboHandle;
private int _copyPboSize;
public IntermediatePool IntermediatePool { get; }
public TextureCopy(OpenGLRenderer renderer)
{
_renderer = renderer;
IntermediatePool = new IntermediatePool(renderer);
}
public void Copy(
TextureView src,
TextureView dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter,
int srcLayer = 0,
int dstLayer = 0,
int srcLevel = 0,
int dstLevel = 0)
{
int levels = Math.Min(src.Info.Levels - srcLevel, dst.Info.Levels - dstLevel);
int layers = Math.Min(src.Info.GetLayers() - srcLayer, dst.Info.GetLayers() - dstLayer);
Copy(src, dst, srcRegion, dstRegion, linearFilter, srcLayer, dstLayer, srcLevel, dstLevel, layers, levels);
}
public void Copy(
TextureView src,
TextureView dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter,
int srcLayer,
int dstLayer,
int srcLevel,
int dstLevel,
int layers,
int levels)
{
TextureView srcConverted = src.Format.IsBgr() != dst.Format.IsBgr() ? BgraSwap(src) : src;
(int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
if (srcLevel != 0)
{
srcRegion = srcRegion.Reduce(srcLevel);
}
if (dstLevel != 0)
{
dstRegion = dstRegion.Reduce(dstLevel);
}
for (int level = 0; level < levels; level++)
{
for (int layer = 0; layer < layers; layer++)
{
if ((srcLayer | dstLayer) != 0 || layers > 1)
{
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level, srcLayer + layer);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level, dstLayer + layer);
}
else
{
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level);
}
ClearBufferMask mask = GetMask(src.Format);
if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger())
{
linearFilter = false;
}
BlitFramebufferFilter filter = linearFilter
? BlitFramebufferFilter.Linear
: BlitFramebufferFilter.Nearest;
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.Disable(EnableCap.RasterizerDiscard);
GL.Disable(IndexedEnableCap.ScissorTest, 0);
GL.BlitFramebuffer(
srcRegion.X1,
srcRegion.Y1,
srcRegion.X2,
srcRegion.Y2,
dstRegion.X1,
dstRegion.Y1,
dstRegion.X2,
dstRegion.Y2,
mask,
filter);
}
if (level < levels - 1)
{
srcRegion = srcRegion.Reduce(1);
dstRegion = dstRegion.Reduce(1);
}
}
Attach(FramebufferTarget.ReadFramebuffer, src.Format, 0);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, 0);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
if (srcConverted != src)
{
srcConverted.Dispose();
}
}
public void CopyUnscaled(
ITextureInfo src,
ITextureInfo dst,
int srcLayer,
int dstLayer,
int srcLevel,
int dstLevel)
{
TextureCreateInfo srcInfo = src.Info;
TextureCreateInfo dstInfo = dst.Info;
int srcDepth = srcInfo.GetDepthOrLayers();
int srcLevels = srcInfo.Levels;
int dstDepth = dstInfo.GetDepthOrLayers();
int dstLevels = dstInfo.Levels;
if (dstInfo.Target == Target.Texture3D)
{
dstDepth = Math.Max(1, dstDepth >> dstLevel);
}
int depth = Math.Min(srcDepth, dstDepth);
int levels = Math.Min(srcLevels, dstLevels);
CopyUnscaled(src, dst, srcLayer, dstLayer, srcLevel, dstLevel, depth, levels);
}
public void CopyUnscaled(
ITextureInfo src,
ITextureInfo dst,
int srcLayer,
int dstLayer,
int srcLevel,
int dstLevel,
int depth,
int levels)
{
TextureCreateInfo srcInfo = src.Info;
TextureCreateInfo dstInfo = dst.Info;
int srcHandle = src.Handle;
int dstHandle = dst.Handle;
int srcWidth = srcInfo.Width;
int srcHeight = srcInfo.Height;
int dstWidth = dstInfo.Width;
int dstHeight = dstInfo.Height;
srcWidth = Math.Max(1, srcWidth >> srcLevel);
srcHeight = Math.Max(1, srcHeight >> srcLevel);
dstWidth = Math.Max(1, dstWidth >> dstLevel);
dstHeight = Math.Max(1, dstHeight >> dstLevel);
int blockWidth = 1;
int blockHeight = 1;
bool sizeInBlocks = false;
// 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 (!srcInfo.IsCompressed && dstInfo.IsCompressed)
{
srcWidth *= dstInfo.BlockWidth;
srcHeight *= dstInfo.BlockHeight;
blockWidth = dstInfo.BlockWidth;
blockHeight = dstInfo.BlockHeight;
sizeInBlocks = true;
}
else if (srcInfo.IsCompressed && !dstInfo.IsCompressed)
{
dstWidth *= srcInfo.BlockWidth;
dstHeight *= srcInfo.BlockHeight;
blockWidth = srcInfo.BlockWidth;
blockHeight = srcInfo.BlockHeight;
}
int width = Math.Min(srcWidth, dstWidth);
int height = Math.Min(srcHeight, dstHeight);
for (int level = 0; level < levels; level++)
{
// Stop copy if we are already out of the levels range.
if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels)
{
break;
}
if ((width % blockWidth != 0 || height % blockHeight != 0) && src is TextureView srcView && dst is TextureView dstView)
{
PboCopy(srcView, dstView, srcLayer, dstLayer, srcLevel + level, dstLevel + level, width, height);
}
else
{
int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width;
int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height;
if (HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows)
{
GL.CopyImageSubData(
src.Storage.Handle,
src.Storage.Info.Target.ConvertToImageTarget(),
src.FirstLevel + srcLevel + level,
0,
0,
src.FirstLayer + srcLayer,
dst.Storage.Handle,
dst.Storage.Info.Target.ConvertToImageTarget(),
dst.FirstLevel + dstLevel + level,
0,
0,
dst.FirstLayer + dstLayer,
copyWidth,
copyHeight,
depth);
}
else
{
GL.CopyImageSubData(
srcHandle,
srcInfo.Target.ConvertToImageTarget(),
srcLevel + level,
0,
0,
srcLayer,
dstHandle,
dstInfo.Target.ConvertToImageTarget(),
dstLevel + level,
0,
0,
dstLayer,
copyWidth,
copyHeight,
depth);
}
}
width = Math.Max(1, width >> 1);
height = Math.Max(1, height >> 1);
if (srcInfo.Target == Target.Texture3D)
{
depth = Math.Max(1, depth >> 1);
}
}
}
private static FramebufferAttachment AttachmentForFormat(Format format)
{
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)
{
return FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnly(format))
{
return FramebufferAttachment.DepthAttachment;
}
else if (format == Format.S8Uint)
{
return FramebufferAttachment.StencilAttachment;
}
else
{
return FramebufferAttachment.ColorAttachment0;
}
}
private static void Attach(FramebufferTarget target, Format format, int handle, int level = 0)
{
FramebufferAttachment attachment = AttachmentForFormat(format);
GL.FramebufferTexture(target, attachment, handle, level);
}
private static void Attach(FramebufferTarget target, Format format, int handle, int level, int layer)
{
FramebufferAttachment attachment = AttachmentForFormat(format);
GL.FramebufferTextureLayer(target, attachment, handle, level, layer);
}
private static ClearBufferMask GetMask(Format format)
{
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint || format == Format.S8UintD24Unorm)
{
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.D32Float;
}
public TextureView BgraSwap(TextureView from)
{
TextureView to = (TextureView)_renderer.CreateTexture(from.Info, from.ScaleFactor);
EnsurePbo(from);
GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle);
from.WriteToPbo(0, forceBgra: true);
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, _copyPboHandle);
to.ReadFromPbo(0, _copyPboSize);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
return to;
}
private TextureView PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
{
int dstWidth = width;
int dstHeight = height;
// The size of the source texture.
int unpackWidth = from.Width;
int unpackHeight = from.Height;
if (from.Info.IsCompressed != to.Info.IsCompressed)
{
if (from.Info.IsCompressed)
{
// Dest size is in pixels, but should be in blocks
dstWidth = BitUtils.DivRoundUp(width, from.Info.BlockWidth);
dstHeight = BitUtils.DivRoundUp(height, from.Info.BlockHeight);
// When copying from a compressed texture, the source size must be taken in blocks for unpacking to the uncompressed block texture.
unpackWidth = BitUtils.DivRoundUp(from.Info.Width, from.Info.BlockWidth);
unpackHeight = BitUtils.DivRoundUp(from.Info.Height, from.Info.BlockHeight);
}
else
{
// When copying to a compressed texture, the source size must be scaled by the block width for unpacking on the compressed target.
unpackWidth = from.Info.Width * to.Info.BlockWidth;
unpackHeight = from.Info.Height * to.Info.BlockHeight;
}
}
EnsurePbo(from);
GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle);
// The source texture is written out in full, then the destination is taken as a slice from the data using unpack params.
// The offset points to the base at which the requested layer is at.
int offset = from.WriteToPbo2D(0, srcLayer, srcLevel);
// If the destination size is not an exact match for the source unpack parameters, we need to set them to slice the data correctly.
bool slice = (unpackWidth != dstWidth || unpackHeight != dstHeight);
if (slice)
{
// Set unpack parameters to take a slice of width/height:
GL.PixelStore(PixelStoreParameter.UnpackRowLength, unpackWidth);
GL.PixelStore(PixelStoreParameter.UnpackImageHeight, unpackHeight);
if (to.Info.IsCompressed)
{
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockWidth, to.Info.BlockWidth);
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockHeight, to.Info.BlockHeight);
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockDepth, 1);
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockSize, to.Info.BytesPerPixel);
}
}
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, _copyPboHandle);
to.ReadFromPbo2D(offset, dstLayer, dstLevel, dstWidth, dstHeight);
if (slice)
{
// Reset unpack parameters
GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0);
GL.PixelStore(PixelStoreParameter.UnpackImageHeight, 0);
if (to.Info.IsCompressed)
{
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockWidth, 0);
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockHeight, 0);
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockDepth, 0);
GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockSize, 0);
}
}
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
return to;
}
private void EnsurePbo(TextureView view)
{
int requiredSize = 0;
for (int level = 0; level < view.Info.Levels; level++)
{
requiredSize += view.Info.GetMipSize(level);
}
if (_copyPboSize < requiredSize && _copyPboHandle != 0)
{
GL.DeleteBuffer(_copyPboHandle);
_copyPboHandle = 0;
}
if (_copyPboHandle == 0)
{
_copyPboHandle = GL.GenBuffer();
_copyPboSize = requiredSize;
GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle);
GL.BufferData(BufferTarget.PixelPackBuffer, requiredSize, IntPtr.Zero, BufferUsageHint.DynamicCopy);
}
}
private int GetSrcFramebufferLazy()
{
if (_srcFramebuffer == 0)
{
_srcFramebuffer = GL.GenFramebuffer();
}
return _srcFramebuffer;
}
private int GetDstFramebufferLazy()
{
if (_dstFramebuffer == 0)
{
_dstFramebuffer = GL.GenFramebuffer();
}
return _dstFramebuffer;
}
public void Dispose()
{
if (_srcFramebuffer != 0)
{
GL.DeleteFramebuffer(_srcFramebuffer);
_srcFramebuffer = 0;
}
if (_dstFramebuffer != 0)
{
GL.DeleteFramebuffer(_dstFramebuffer);
_dstFramebuffer = 0;
}
if (_copyPboHandle != 0)
{
GL.DeleteBuffer(_copyPboHandle);
_copyPboHandle = 0;
}
IntermediatePool.Dispose();
}
}
}

View file

@ -0,0 +1,252 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureCopyIncompatible
{
private const string ComputeShaderShortening = @"#version 450 core
layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src;
layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst;
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
void main()
{
uvec2 coords = gl_GlobalInvocationID.xy;
ivec2 imageSz = imageSize(src);
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
{
return;
}
uint coordsShifted = coords.x << $RATIO_LOG2$;
uvec2 dstCoords0 = uvec2(coordsShifted, coords.y);
uvec2 dstCoords1 = uvec2(coordsShifted + 1, coords.y);
uvec2 dstCoords2 = uvec2(coordsShifted + 2, coords.y);
uvec2 dstCoords3 = uvec2(coordsShifted + 3, coords.y);
uvec4 rgba = imageLoad(src, ivec2(coords));
imageStore(dst, ivec2(dstCoords0), rgba.rrrr);
imageStore(dst, ivec2(dstCoords1), rgba.gggg);
imageStore(dst, ivec2(dstCoords2), rgba.bbbb);
imageStore(dst, ivec2(dstCoords3), rgba.aaaa);
}";
private const string ComputeShaderWidening = @"#version 450 core
layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src;
layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst;
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
void main()
{
uvec2 coords = gl_GlobalInvocationID.xy;
ivec2 imageSz = imageSize(dst);
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
{
return;
}
uvec2 srcCoords = uvec2(coords.x << $RATIO_LOG2$, coords.y);
uint r = imageLoad(src, ivec2(srcCoords) + ivec2(0, 0)).r;
uint g = imageLoad(src, ivec2(srcCoords) + ivec2(1, 0)).r;
uint b = imageLoad(src, ivec2(srcCoords) + ivec2(2, 0)).r;
uint a = imageLoad(src, ivec2(srcCoords) + ivec2(3, 0)).r;
imageStore(dst, ivec2(coords), uvec4(r, g, b, a));
}";
private readonly OpenGLRenderer _renderer;
private readonly Dictionary<int, int> _shorteningProgramHandles;
private readonly Dictionary<int, int> _wideningProgramHandles;
public TextureCopyIncompatible(OpenGLRenderer renderer)
{
_renderer = renderer;
_shorteningProgramHandles = new Dictionary<int, int>();
_wideningProgramHandles = new Dictionary<int, int>();
}
public void CopyIncompatibleFormats(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int depth, int levels)
{
TextureCreateInfo srcInfo = src.Info;
TextureCreateInfo dstInfo = dst.Info;
int srcBpp = src.Info.BytesPerPixel;
int dstBpp = dst.Info.BytesPerPixel;
// Calculate ideal component size, given our constraints:
// - Component size must not exceed bytes per pixel of source and destination image formats.
// - Maximum component size is 4 (R32).
int componentSize = Math.Min(Math.Min(srcBpp, dstBpp), 4);
int srcComponentsCount = srcBpp / componentSize;
int dstComponentsCount = dstBpp / componentSize;
var srcFormat = GetFormat(componentSize, srcComponentsCount);
var dstFormat = GetFormat(componentSize, dstComponentsCount);
GL.UseProgram(srcBpp < dstBpp
? GetWideningShader(componentSize, srcComponentsCount, dstComponentsCount)
: GetShorteningShader(componentSize, srcComponentsCount, dstComponentsCount));
for (int l = 0; l < levels; l++)
{
int srcWidth = Math.Max(1, src.Info.Width >> l);
int srcHeight = Math.Max(1, src.Info.Height >> l);
int dstWidth = Math.Max(1, dst.Info.Width >> l);
int dstHeight = Math.Max(1, dst.Info.Height >> l);
int width = Math.Min(srcWidth, dstWidth);
int height = Math.Min(srcHeight, dstHeight);
for (int z = 0; z < depth; z++)
{
GL.BindImageTexture(0, src.Handle, srcLevel + l, false, srcLayer + z, TextureAccess.ReadOnly, srcFormat);
GL.BindImageTexture(1, dst.Handle, dstLevel + l, false, dstLayer + z, TextureAccess.WriteOnly, dstFormat);
GL.DispatchCompute((width + 31) / 32, (height + 31) / 32, 1);
}
}
Pipeline pipeline = (Pipeline)_renderer.Pipeline;
pipeline.RestoreProgram();
pipeline.RestoreImages1And2();
}
private static SizedInternalFormat GetFormat(int componentSize, int componentsCount)
{
if (componentSize == 1)
{
return componentsCount switch
{
1 => SizedInternalFormat.R8ui,
2 => SizedInternalFormat.Rg8ui,
4 => SizedInternalFormat.Rgba8ui,
_ => throw new ArgumentException($"Invalid components count {componentsCount}.")
};
}
else if (componentSize == 2)
{
return componentsCount switch
{
1 => SizedInternalFormat.R16ui,
2 => SizedInternalFormat.Rg16ui,
4 => SizedInternalFormat.Rgba16ui,
_ => throw new ArgumentException($"Invalid components count {componentsCount}.")
};
}
else if (componentSize == 4)
{
return componentsCount switch
{
1 => SizedInternalFormat.R32ui,
2 => SizedInternalFormat.Rg32ui,
4 => SizedInternalFormat.Rgba32ui,
_ => throw new ArgumentException($"Invalid components count {componentsCount}.")
};
}
else
{
throw new ArgumentException($"Invalid component size {componentSize}.");
}
}
private int GetShorteningShader(int componentSize, int srcComponentsCount, int dstComponentsCount)
{
return GetShader(ComputeShaderShortening, _shorteningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount);
}
private int GetWideningShader(int componentSize, int srcComponentsCount, int dstComponentsCount)
{
return GetShader(ComputeShaderWidening, _wideningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount);
}
private int GetShader(
string code,
Dictionary<int, int> programHandles,
int componentSize,
int srcComponentsCount,
int dstComponentsCount)
{
int componentSizeLog2 = BitOperations.Log2((uint)componentSize);
int srcIndex = componentSizeLog2 + BitOperations.Log2((uint)srcComponentsCount) * 3;
int dstIndex = componentSizeLog2 + BitOperations.Log2((uint)dstComponentsCount) * 3;
int key = srcIndex | (dstIndex << 8);
if (!programHandles.TryGetValue(key, out int programHandle))
{
int csHandle = GL.CreateShader(ShaderType.ComputeShader);
string[] formatTable = new[] { "r8ui", "r16ui", "r32ui", "rg8ui", "rg16ui", "rg32ui", "rgba8ui", "rgba16ui", "rgba32ui" };
string srcFormat = formatTable[srcIndex];
string dstFormat = formatTable[dstIndex];
int srcBpp = srcComponentsCount * componentSize;
int dstBpp = dstComponentsCount * componentSize;
int ratio = srcBpp < dstBpp ? dstBpp / srcBpp : srcBpp / dstBpp;
int ratioLog2 = BitOperations.Log2((uint)ratio);
GL.ShaderSource(csHandle, code
.Replace("$SRC_FORMAT$", srcFormat)
.Replace("$DST_FORMAT$", dstFormat)
.Replace("$RATIO_LOG2$", ratioLog2.ToString(CultureInfo.InvariantCulture)));
GL.CompileShader(csHandle);
programHandle = GL.CreateProgram();
GL.AttachShader(programHandle, csHandle);
GL.LinkProgram(programHandle);
GL.DetachShader(programHandle, csHandle);
GL.DeleteShader(csHandle);
GL.GetProgram(programHandle, GetProgramParameterName.LinkStatus, out int status);
if (status == 0)
{
throw new Exception(GL.GetProgramInfoLog(programHandle));
}
programHandles.Add(key, programHandle);
}
return programHandle;
}
public void Dispose()
{
foreach (int handle in _shorteningProgramHandles.Values)
{
GL.DeleteProgram(handle);
}
_shorteningProgramHandles.Clear();
foreach (int handle in _wideningProgramHandles.Values)
{
GL.DeleteProgram(handle);
}
_wideningProgramHandles.Clear();
}
}
}

View file

@ -0,0 +1,276 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
using System.Numerics;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureCopyMS
{
private const string ComputeShaderMSToNonMS = @"#version 450 core
layout (binding = 0, $FORMAT$) uniform uimage2DMS imgIn;
layout (binding = 1, $FORMAT$) uniform uimage2D imgOut;
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
void main()
{
uvec2 coords = gl_GlobalInvocationID.xy;
ivec2 imageSz = imageSize(imgOut);
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
{
return;
}
int inSamples = imageSamples(imgIn);
int samplesInXLog2 = 0;
int samplesInYLog2 = 0;
switch (inSamples)
{
case 2:
samplesInXLog2 = 1;
break;
case 4:
samplesInXLog2 = 1;
samplesInYLog2 = 1;
break;
case 8:
samplesInXLog2 = 2;
samplesInYLog2 = 1;
break;
case 16:
samplesInXLog2 = 2;
samplesInYLog2 = 2;
break;
}
int samplesInX = 1 << samplesInXLog2;
int samplesInY = 1 << samplesInYLog2;
int sampleIdx = (int(coords.x) & (samplesInX - 1)) | ((int(coords.y) & (samplesInY - 1)) << samplesInXLog2);
uvec4 value = imageLoad(imgIn, ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2), sampleIdx);
imageStore(imgOut, ivec2(coords), value);
}";
private const string ComputeShaderNonMSToMS = @"#version 450 core
layout (binding = 0, $FORMAT$) uniform uimage2D imgIn;
layout (binding = 1, $FORMAT$) uniform uimage2DMS imgOut;
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
void main()
{
uvec2 coords = gl_GlobalInvocationID.xy;
ivec2 imageSz = imageSize(imgIn);
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
{
return;
}
int outSamples = imageSamples(imgOut);
int samplesInXLog2 = 0;
int samplesInYLog2 = 0;
switch (outSamples)
{
case 2:
samplesInXLog2 = 1;
break;
case 4:
samplesInXLog2 = 1;
samplesInYLog2 = 1;
break;
case 8:
samplesInXLog2 = 2;
samplesInYLog2 = 1;
break;
case 16:
samplesInXLog2 = 2;
samplesInYLog2 = 2;
break;
}
int samplesInX = 1 << samplesInXLog2;
int samplesInY = 1 << samplesInYLog2;
int sampleIdx = (int(coords.x) & (samplesInX - 1)) | ((int(coords.y) & (samplesInY - 1)) << samplesInXLog2);
uvec4 value = imageLoad(imgIn, ivec2(coords));
imageStore(imgOut, ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2), sampleIdx, value);
}";
private readonly OpenGLRenderer _renderer;
private int[] _msToNonMSProgramHandles;
private int[] _nonMSToMSProgramHandles;
public TextureCopyMS(OpenGLRenderer renderer)
{
_renderer = renderer;
_msToNonMSProgramHandles = new int[5];
_nonMSToMSProgramHandles = new int[5];
}
public void CopyMSToNonMS(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int depth)
{
TextureCreateInfo srcInfo = src.Info;
TextureCreateInfo dstInfo = dst.Info;
int srcHandle = CreateViewIfNeeded(src);
int dstHandle = CreateViewIfNeeded(dst);
int dstWidth = dstInfo.Width;
int dstHeight = dstInfo.Height;
GL.UseProgram(GetMSToNonMSShader(srcInfo.BytesPerPixel));
for (int z = 0; z < depth; z++)
{
GL.BindImageTexture(0, srcHandle, 0, false, srcLayer + z, TextureAccess.ReadOnly, GetFormat(srcInfo.BytesPerPixel));
GL.BindImageTexture(1, dstHandle, 0, false, dstLayer + z, TextureAccess.WriteOnly, GetFormat(dstInfo.BytesPerPixel));
GL.DispatchCompute((dstWidth + 31) / 32, (dstHeight + 31) / 32, 1);
}
Pipeline pipeline = (Pipeline)_renderer.Pipeline;
pipeline.RestoreProgram();
pipeline.RestoreImages1And2();
DestroyViewIfNeeded(src, srcHandle);
DestroyViewIfNeeded(dst, dstHandle);
}
public void CopyNonMSToMS(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int depth)
{
TextureCreateInfo srcInfo = src.Info;
TextureCreateInfo dstInfo = dst.Info;
int srcHandle = CreateViewIfNeeded(src);
int dstHandle = CreateViewIfNeeded(dst);
int srcWidth = srcInfo.Width;
int srcHeight = srcInfo.Height;
GL.UseProgram(GetNonMSToMSShader(srcInfo.BytesPerPixel));
for (int z = 0; z < depth; z++)
{
GL.BindImageTexture(0, srcHandle, 0, false, srcLayer + z, TextureAccess.ReadOnly, GetFormat(srcInfo.BytesPerPixel));
GL.BindImageTexture(1, dstHandle, 0, false, dstLayer + z, TextureAccess.WriteOnly, GetFormat(dstInfo.BytesPerPixel));
GL.DispatchCompute((srcWidth + 31) / 32, (srcHeight + 31) / 32, 1);
}
Pipeline pipeline = (Pipeline)_renderer.Pipeline;
pipeline.RestoreProgram();
pipeline.RestoreImages1And2();
DestroyViewIfNeeded(src, srcHandle);
DestroyViewIfNeeded(dst, dstHandle);
}
private static SizedInternalFormat GetFormat(int bytesPerPixel)
{
return bytesPerPixel switch
{
1 => SizedInternalFormat.R8ui,
2 => SizedInternalFormat.R16ui,
4 => SizedInternalFormat.R32ui,
8 => SizedInternalFormat.Rg32ui,
16 => SizedInternalFormat.Rgba32ui,
_ => throw new ArgumentException($"Invalid bytes per pixel {bytesPerPixel}.")
};
}
private static int CreateViewIfNeeded(ITextureInfo texture)
{
// Binding sRGB textures as images doesn't work on NVIDIA,
// we need to create and bind a RGBA view for it to work.
if (texture.Info.Format == Format.R8G8B8A8Srgb)
{
int handle = GL.GenTexture();
GL.TextureView(
handle,
texture.Info.Target.Convert(),
texture.Storage.Handle,
PixelInternalFormat.Rgba8,
texture.FirstLevel,
1,
texture.FirstLayer,
texture.Info.GetLayers());
return handle;
}
return texture.Handle;
}
private static void DestroyViewIfNeeded(ITextureInfo info, int handle)
{
if (info.Handle != handle)
{
GL.DeleteTexture(handle);
}
}
private int GetMSToNonMSShader(int bytesPerPixel)
{
return GetShader(ComputeShaderMSToNonMS, _msToNonMSProgramHandles, bytesPerPixel);
}
private int GetNonMSToMSShader(int bytesPerPixel)
{
return GetShader(ComputeShaderNonMSToMS, _nonMSToMSProgramHandles, bytesPerPixel);
}
private int GetShader(string code, int[] programHandles, int bytesPerPixel)
{
int index = BitOperations.Log2((uint)bytesPerPixel);
if (programHandles[index] == 0)
{
int csHandle = GL.CreateShader(ShaderType.ComputeShader);
string format = new[] { "r8ui", "r16ui", "r32ui", "rg32ui", "rgba32ui" }[index];
GL.ShaderSource(csHandle, code.Replace("$FORMAT$", format));
GL.CompileShader(csHandle);
int programHandle = GL.CreateProgram();
GL.AttachShader(programHandle, csHandle);
GL.LinkProgram(programHandle);
GL.DetachShader(programHandle, csHandle);
GL.DeleteShader(csHandle);
GL.GetProgram(programHandle, GetProgramParameterName.LinkStatus, out int status);
if (status == 0)
{
throw new Exception(GL.GetProgramInfoLog(programHandle));
}
programHandles[index] = programHandle;
}
return programHandles[index];
}
public void Dispose()
{
for (int i = 0; i < _msToNonMSProgramHandles.Length; i++)
{
if (_msToNonMSProgramHandles[i] != 0)
{
GL.DeleteProgram(_msToNonMSProgramHandles[i]);
_msToNonMSProgramHandles[i] = 0;
}
}
for (int i = 0; i < _nonMSToMSProgramHandles.Length; i++)
{
if (_nonMSToMSProgramHandles[i] != 0)
{
GL.DeleteProgram(_nonMSToMSProgramHandles[i]);
_nonMSToMSProgramHandles[i] = 0;
}
}
}
}
}

View file

@ -0,0 +1,212 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureStorage : ITextureInfo
{
public ITextureInfo Storage => this;
public int Handle { get; private set; }
public float ScaleFactor { get; private set; }
public TextureCreateInfo Info { get; }
private readonly OpenGLRenderer _renderer;
private int _viewsCount;
internal ITexture DefaultView { get; private set; }
public TextureStorage(OpenGLRenderer renderer, TextureCreateInfo info, float scaleFactor)
{
_renderer = renderer;
Info = info;
Handle = GL.GenTexture();
ScaleFactor = scaleFactor;
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;
}
int levels = Info.GetLevelsClamped();
switch (Info.Target)
{
case Target.Texture1D:
GL.TexStorage1D(
TextureTarget1d.Texture1D,
levels,
internalFormat,
Info.Width);
break;
case Target.Texture1DArray:
GL.TexStorage2D(
TextureTarget2d.Texture1DArray,
levels,
internalFormat,
Info.Width,
Info.Height);
break;
case Target.Texture2D:
GL.TexStorage2D(
TextureTarget2d.Texture2D,
levels,
internalFormat,
Info.Width,
Info.Height);
break;
case Target.Texture2DArray:
GL.TexStorage3D(
TextureTarget3d.Texture2DArray,
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,
levels,
internalFormat,
Info.Width,
Info.Height,
Info.Depth);
break;
case Target.Cubemap:
GL.TexStorage2D(
TextureTarget2d.TextureCubeMap,
levels,
internalFormat,
Info.Width,
Info.Height);
break;
case Target.CubemapArray:
GL.TexStorage3D(
(TextureTarget3d)All.TextureCubeMapArray,
levels,
internalFormat,
Info.Width,
Info.Height,
Info.Depth);
break;
default:
Logger.Debug?.Print(LogClass.Gpu, $"Invalid or unsupported texture target: {target}.");
break;
}
}
public ITexture CreateDefaultView()
{
DefaultView = CreateView(Info, 0, 0);
return DefaultView;
}
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)
{
if (DefaultView == null)
{
Dispose();
}
else
{
// If the default view still exists, we can put it into the resource pool.
Release();
}
}
}
/// <summary>
/// Release the TextureStorage to the resource pool without disposing its handle.
/// </summary>
public void Release()
{
_viewsCount = 1; // When we are used again, we will have the default view.
_renderer.ResourcePool.AddTexture((TextureView)DefaultView);
}
public void DeleteDefault()
{
DefaultView = null;
}
public void Dispose()
{
DefaultView = null;
if (Handle != 0)
{
GL.DeleteTexture(Handle);
Handle = 0;
}
}
}
}

View file

@ -0,0 +1,867 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureView : TextureBase, ITexture, ITextureInfo
{
private readonly OpenGLRenderer _renderer;
private readonly TextureStorage _parent;
public ITextureInfo Storage => _parent;
public int FirstLayer { get; private set; }
public int FirstLevel { get; private set; }
public TextureView(
OpenGLRenderer renderer,
TextureStorage parent,
TextureCreateInfo info,
int firstLayer,
int firstLevel) : base(info, parent.ScaleFactor)
{
_renderer = renderer;
_parent = parent;
FirstLayer = firstLayer;
FirstLevel = firstLevel;
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;
}
int levels = Info.GetLevelsClamped();
GL.TextureView(
Handle,
target,
_parent.Handle,
pixelInternalFormat,
FirstLevel,
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()
};
if (Info.Format == Format.A1B5G5R5Unorm)
{
int temp = swizzleRgba[0];
int temp2 = swizzleRgba[1];
swizzleRgba[0] = swizzleRgba[3];
swizzleRgba[1] = swizzleRgba[2];
swizzleRgba[2] = temp2;
swizzleRgba[3] = temp;
}
else if (Info.Format.IsBgr())
{
// Swap B <-> R for BGRA formats, as OpenGL has no support for them
// and we need to manually swap the components on read/write on the GPU.
int temp = swizzleRgba[0];
swizzleRgba[0] = swizzleRgba[2];
swizzleRgba[2] = temp;
}
GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba);
int maxLevel = 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)
{
firstLayer += FirstLayer;
firstLevel += FirstLevel;
return _parent.CreateView(info, firstLayer, firstLevel);
}
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
{
TextureView destinationView = (TextureView)destination;
bool srcIsMultisample = Target.IsMultisample();
bool dstIsMultisample = destinationView.Target.IsMultisample();
if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
CopyWithBlitForDepthMS(destinationView, 0, firstLayer, layers);
}
else if (!dstIsMultisample && srcIsMultisample)
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
_renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, 0, firstLayer, layers);
}
else if (dstIsMultisample && !srcIsMultisample)
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, 0, firstLayer, layers);
}
else if (destinationView.Info.BytesPerPixel != Info.BytesPerPixel)
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
}
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
}
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{
TextureView destinationView = (TextureView)destination;
bool srcIsMultisample = Target.IsMultisample();
bool dstIsMultisample = destinationView.Target.IsMultisample();
if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
{
CopyWithBlitForDepthMS(destinationView, srcLayer, dstLayer, 1);
}
else if (!dstIsMultisample && srcIsMultisample)
{
_renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, srcLayer, dstLayer, 1);
}
else if (dstIsMultisample && !srcIsMultisample)
{
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, srcLayer, dstLayer, 1);
}
else if (destinationView.Info.BytesPerPixel != Info.BytesPerPixel)
{
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
}
private void CopyWithBlitForDepthMS(TextureView destinationView, int srcLayer, int dstLayer, int layers)
{
// This is currently used for multisample <-> non-multisample copies.
// We can't do that with compute because it's not possible to write depth textures on compute.
// It can be done with draws, but we don't have support for saving and restoring the OpenGL state
// for a draw with different state right now.
// This approach uses blit, which causes a resolution loss since some samples will be lost
// in the process.
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
if (destinationView.Target.IsMultisample())
{
TextureView intermmediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
Info.Target,
Info.BlockWidth,
Info.BlockHeight,
Info.BytesPerPixel,
Format,
destinationView.Width,
destinationView.Height,
Info.Depth,
1,
1);
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, dstRegion, false);
_renderer.TextureCopy.Copy(intermmediate, destinationView, dstRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
}
else
{
Target target = Target switch
{
Target.Texture2DMultisample => Target.Texture2D,
Target.Texture2DMultisampleArray => Target.Texture2DArray,
_ => Target
};
TextureView intermmediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
target,
Info.BlockWidth,
Info.BlockHeight,
Info.BytesPerPixel,
Format,
Width,
Height,
Info.Depth,
1,
1);
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, false);
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
}
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
}
public unsafe PinnedSpan<byte> GetData()
{
int size = 0;
int levels = Info.GetLevelsClamped();
for (int level = 0; level < levels; level++)
{
size += Info.GetMipSize(level);
}
ReadOnlySpan<byte> data;
if (HwCapabilities.UsePersistentBufferForFlush)
{
data = _renderer.PersistentBuffers.Default.GetTextureData(this, size);
}
else
{
IntPtr target = _renderer.PersistentBuffers.Default.GetHostArray(size);
WriteTo(target);
data = new ReadOnlySpan<byte>(target.ToPointer(), size);
}
if (Format == Format.S8UintD24Unorm)
{
data = FormatConverter.ConvertD24S8ToS8D24(data);
}
return PinnedSpan<byte>.UnsafeFromSpan(data);
}
public unsafe PinnedSpan<byte> GetData(int layer, int level)
{
int size = Info.GetMipSize(level);
if (HwCapabilities.UsePersistentBufferForFlush)
{
return PinnedSpan<byte>.UnsafeFromSpan(_renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level));
}
else
{
IntPtr target = _renderer.PersistentBuffers.Default.GetHostArray(size);
int offset = WriteTo2D(target, layer, level);
return new PinnedSpan<byte>((byte*)target.ToPointer() + offset, size);
}
}
public void WriteToPbo(int offset, bool forceBgra)
{
WriteTo(IntPtr.Zero + offset, forceBgra);
}
public int WriteToPbo2D(int offset, int layer, int level)
{
return WriteTo2D(IntPtr.Zero + offset, layer, level);
}
private int WriteTo2D(IntPtr data, int layer, int level)
{
TextureTarget target = Target.Convert();
Bind(target, 0);
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
PixelFormat pixelFormat = format.PixelFormat;
PixelType pixelType = format.PixelType;
if (target == TextureTarget.TextureCubeMap || target == TextureTarget.TextureCubeMapArray)
{
target = TextureTarget.TextureCubeMapPositiveX + (layer % 6);
}
int mipSize = Info.GetMipSize2D(level);
if (format.IsCompressed)
{
GL.GetCompressedTextureSubImage(Handle, level, 0, 0, layer, Math.Max(1, Info.Width >> level), Math.Max(1, Info.Height >> level), 1, mipSize, data);
}
else if (format.PixelFormat != PixelFormat.DepthStencil)
{
GL.GetTextureSubImage(Handle, level, 0, 0, layer, Math.Max(1, Info.Width >> level), Math.Max(1, Info.Height >> level), 1, pixelFormat, pixelType, mipSize, data);
}
else
{
GL.GetTexImage(target, level, pixelFormat, pixelType, data);
// The GL function returns all layers. Must return the offset of the layer we're interested in.
return target switch
{
TextureTarget.TextureCubeMapArray => (layer / 6) * mipSize,
TextureTarget.Texture1DArray => layer * mipSize,
TextureTarget.Texture2DArray => layer * mipSize,
_ => 0
};
}
return 0;
}
private void WriteTo(IntPtr data, bool forceBgra = false)
{
TextureTarget target = Target.Convert();
Bind(target, 0);
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
PixelFormat pixelFormat = format.PixelFormat;
PixelType pixelType = format.PixelType;
if (forceBgra)
{
if (pixelType == PixelType.UnsignedShort565)
{
pixelType = PixelType.UnsignedShort565Reversed;
}
else if (pixelType == PixelType.UnsignedShort565Reversed)
{
pixelType = PixelType.UnsignedShort565;
}
else
{
pixelFormat = PixelFormat.Bgra;
}
}
int faces = 1;
if (target == TextureTarget.TextureCubeMap)
{
target = TextureTarget.TextureCubeMapPositiveX;
faces = 6;
}
int levels = Info.GetLevelsClamped();
for (int level = 0; level < levels; level++)
{
for (int face = 0; face < faces; face++)
{
int faceOffset = face * Info.GetMipSize2D(level);
if (format.IsCompressed)
{
GL.GetCompressedTexImage(target + face, level, data + faceOffset);
}
else
{
GL.GetTexImage(target + face, level, pixelFormat, pixelType, data + faceOffset);
}
}
data += Info.GetMipSize(level);
}
}
public void SetData(SpanOrArray<byte> data)
{
var dataSpan = data.AsSpan();
if (Format == Format.S8UintD24Unorm)
{
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
}
unsafe
{
fixed (byte* ptr = dataSpan)
{
ReadFrom((IntPtr)ptr, dataSpan.Length);
}
}
}
public void SetData(SpanOrArray<byte> data, int layer, int level)
{
var dataSpan = data.AsSpan();
if (Format == Format.S8UintD24Unorm)
{
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
}
unsafe
{
fixed (byte* ptr = dataSpan)
{
int width = Math.Max(Info.Width >> level, 1);
int height = Math.Max(Info.Height >> level, 1);
ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height);
}
}
}
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
{
var dataSpan = data.AsSpan();
if (Format == Format.S8UintD24Unorm)
{
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
}
int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth);
int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight);
unsafe
{
fixed (byte* ptr = dataSpan)
{
ReadFrom2D(
(IntPtr)ptr,
layer,
level,
region.X,
region.Y,
region.Width,
region.Height,
BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks);
}
}
}
public void ReadFromPbo(int offset, int size)
{
ReadFrom(IntPtr.Zero + offset, size);
}
public void ReadFromPbo2D(int offset, int layer, int level, int width, int height)
{
ReadFrom2D(IntPtr.Zero + offset, layer, level, 0, 0, width, height);
}
private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height)
{
int mipSize = Info.GetMipSize2D(level);
ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
}
private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize)
{
TextureTarget target = Target.Convert();
Bind(target, 0);
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
switch (Target)
{
case Target.Texture1D:
if (format.IsCompressed)
{
GL.CompressedTexSubImage1D(
target,
level,
x,
width,
format.PixelFormat,
mipSize,
data);
}
else
{
GL.TexSubImage1D(
target,
level,
x,
width,
format.PixelFormat,
format.PixelType,
data);
}
break;
case Target.Texture1DArray:
if (format.IsCompressed)
{
GL.CompressedTexSubImage2D(
target,
level,
x,
layer,
width,
1,
format.PixelFormat,
mipSize,
data);
}
else
{
GL.TexSubImage2D(
target,
level,
x,
layer,
width,
1,
format.PixelFormat,
format.PixelType,
data);
}
break;
case Target.Texture2D:
if (format.IsCompressed)
{
GL.CompressedTexSubImage2D(
target,
level,
x,
y,
width,
height,
format.PixelFormat,
mipSize,
data);
}
else
{
GL.TexSubImage2D(
target,
level,
x,
y,
width,
height,
format.PixelFormat,
format.PixelType,
data);
}
break;
case Target.Texture2DArray:
case Target.Texture3D:
case Target.CubemapArray:
if (format.IsCompressed)
{
GL.CompressedTexSubImage3D(
target,
level,
x,
y,
layer,
width,
height,
1,
format.PixelFormat,
mipSize,
data);
}
else
{
GL.TexSubImage3D(
target,
level,
x,
y,
layer,
width,
height,
1,
format.PixelFormat,
format.PixelType,
data);
}
break;
case Target.Cubemap:
if (format.IsCompressed)
{
GL.CompressedTexSubImage2D(
TextureTarget.TextureCubeMapPositiveX + layer,
level,
x,
y,
width,
height,
format.PixelFormat,
mipSize,
data);
}
else
{
GL.TexSubImage2D(
TextureTarget.TextureCubeMapPositiveX + layer,
level,
x,
y,
width,
height,
format.PixelFormat,
format.PixelType,
data);
}
break;
}
}
private void ReadFrom(IntPtr data, int size)
{
TextureTarget target = Target.Convert();
int baseLevel = 0;
// glTexSubImage on cubemap views is broken on Intel, we have to use the storage instead.
if (Target == Target.Cubemap && HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows)
{
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(target, Storage.Handle);
baseLevel = FirstLevel;
}
else
{
Bind(target, 0);
}
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
int width = Info.Width;
int height = Info.Height;
int depth = Info.Depth;
int levels = Info.GetLevelsClamped();
int offset = 0;
for (int level = 0; level < levels; level++)
{
int mipSize = Info.GetMipSize(level);
int endOffset = offset + mipSize;
if ((uint)endOffset > (uint)size)
{
return;
}
switch (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,
baseLevel + level,
0,
0,
width,
height,
format.PixelFormat,
mipSize / 6,
data + faceOffset);
}
else
{
GL.TexSubImage2D(
TextureTarget.TextureCubeMapPositiveX + face,
baseLevel + 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 SetStorage(BufferRange buffer)
{
throw new NotSupportedException();
}
private void DisposeHandles()
{
if (Handle != 0)
{
GL.DeleteTexture(Handle);
Handle = 0;
}
}
/// <summary>
/// Release the view without necessarily disposing the parent if we are the default view.
/// This allows it to be added to the resource pool and reused later.
/// </summary>
public void Release()
{
bool hadHandle = Handle != 0;
if (_parent.DefaultView != this)
{
DisposeHandles();
}
if (hadHandle)
{
_parent.DecrementViewsCount();
}
}
public void Dispose()
{
if (_parent.DefaultView == this)
{
// Remove the default view (us), so that the texture cannot be released to the cache.
_parent.DeleteDefault();
}
Release();
}
}
}

View file

@ -0,0 +1,276 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using Ryujinx.Graphics.OpenGL.Queries;
using Ryujinx.Graphics.Shader.Translation;
using System;
namespace Ryujinx.Graphics.OpenGL
{
public sealed class OpenGLRenderer : IRenderer
{
private readonly Pipeline _pipeline;
public IPipeline Pipeline => _pipeline;
private readonly Counters _counters;
private readonly Window _window;
public IWindow Window => _window;
private TextureCopy _textureCopy;
private TextureCopy _backgroundTextureCopy;
internal TextureCopy TextureCopy => BackgroundContextWorker.InBackground ? _backgroundTextureCopy : _textureCopy;
internal TextureCopyIncompatible TextureCopyIncompatible { get; }
internal TextureCopyMS TextureCopyMS { get; }
private Sync _sync;
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
internal PersistentBuffers PersistentBuffers { get; }
internal ResourcePool ResourcePool { get; }
internal int BufferCount { get; private set; }
public string GpuVendor { get; private set; }
public string GpuRenderer { get; private set; }
public string GpuVersion { get; private set; }
public bool PreferThreading => true;
public OpenGLRenderer()
{
_pipeline = new Pipeline();
_counters = new Counters();
_window = new Window(this);
_textureCopy = new TextureCopy(this);
_backgroundTextureCopy = new TextureCopy(this);
TextureCopyIncompatible = new TextureCopyIncompatible(this);
TextureCopyMS = new TextureCopyMS(this);
_sync = new Sync();
PersistentBuffers = new PersistentBuffers();
ResourcePool = new ResourcePool();
}
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
{
BufferCount++;
return Buffer.Create(size);
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
return new Program(shaders, info.FragmentOutputMap);
}
public ISampler CreateSampler(SamplerCreateInfo info)
{
return new Sampler(info);
}
public ITexture CreateTexture(TextureCreateInfo info, float scaleFactor)
{
if (info.Target == Target.TextureBuffer)
{
return new TextureBuffer(this, info);
}
else
{
return ResourcePool.GetTextureOrNull(info, scaleFactor) ?? new TextureStorage(this, info, scaleFactor).CreateDefaultView();
}
}
public void DeleteBuffer(BufferHandle buffer)
{
Buffer.Delete(buffer);
}
public HardwareInfo GetHardwareInfo()
{
return new HardwareInfo(GpuVendor, GpuRenderer);
}
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{
return Buffer.GetData(this, buffer, offset, size);
}
public Capabilities GetCapabilities()
{
bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows;
bool amdWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows;
return new Capabilities(
api: TargetApi.OpenGL,
vendorName: GpuVendor,
hasFrontFacingBug: intelWindows,
hasVectorIndexingBug: amdWindows,
needsFragmentOutputSpecialization: false,
reduceShaderPrecision: false,
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
supportsBc67Compression: true, // Should check BPTC extension, but for some reason NVIDIA is not exposing the extension.
supportsEtc2Compression: true,
supports3DTextureCompression: false,
supportsBgraFormat: false,
supportsR4G4Format: false,
supportsR4G4B4A4Format: true,
supportsSnormBufferTextureFormat: false,
supports5BitComponentFormat: true,
supportsBlendEquationAdvanced: HwCapabilities.SupportsBlendEquationAdvanced,
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
supportsGeometryShader: true,
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
supportsCubemapView: true,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
maximumStorageBuffersPerStage: 16,
maximumTexturesPerStage: 32,
maximumImagesPerStage: 8,
maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
}
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
{
Buffer.SetData(buffer, offset, data);
}
public void UpdateCounters()
{
_counters.Update();
}
public void PreFrame()
{
_sync.Cleanup();
ResourcePool.Tick();
}
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, bool hostReserved)
{
return _counters.QueueReport(type, resultHandler, _pipeline.DrawCount, hostReserved);
}
public void Initialize(GraphicsDebugLevel glLogLevel)
{
Debugger.Initialize(glLogLevel);
PrintGpuInformation();
if (HwCapabilities.SupportsParallelShaderCompile)
{
GL.Arb.MaxShaderCompilerThreads(Math.Min(Environment.ProcessorCount, 8));
}
_pipeline.Initialize(this);
_counters.Initialize(_pipeline);
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
// This call is expected to fail if we're running with a core profile,
// as this clamp target was deprecated, but that's fine as a core profile
// should already have the desired behaviour were outputs are not clamped.
GL.ClampColor(ClampColorTarget.ClampFragmentColor, ClampColorMode.False);
}
private void PrintGpuInformation()
{
GpuVendor = GL.GetString(StringName.Vendor);
GpuRenderer = GL.GetString(StringName.Renderer);
GpuVersion = GL.GetString(StringName.Version);
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
}
public void ResetCounter(CounterType type)
{
_counters.QueueReset(type);
}
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
{
// alwaysBackground is ignored, since we cannot switch from the current context.
if (IOpenGLContext.HasContext())
{
action(); // We have a context already - use that (assuming it is the main one).
}
else
{
_window.BackgroundContext.Invoke(action);
}
}
public void InitializeBackgroundContext(IOpenGLContext baseContext)
{
_window.InitializeBackgroundContext(baseContext);
}
public void Dispose()
{
_textureCopy.Dispose();
_backgroundTextureCopy.Dispose();
TextureCopyMS.Dispose();
PersistentBuffers.Dispose();
ResourcePool.Dispose();
_pipeline.Dispose();
_window.Dispose();
_counters.Dispose();
_sync.Dispose();
}
public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
{
return new Program(programBinary, hasFragmentShader, info.FragmentOutputMap);
}
public void CreateSync(ulong id, bool strict)
{
_sync.Create(id);
}
public void WaitSync(ulong id)
{
_sync.Wait(id);
}
public ulong GetCurrentSync()
{
return _sync.GetCurrent();
}
public void SetInterruptAction(Action<Action> interruptAction)
{
// Currently no need for an interrupt action.
}
public void Screenshot()
{
_window.ScreenCaptureRequested = true;
}
public void OnScreenCaptured(ScreenCaptureImageInfo bitmap)
{
ScreenCaptured?.Invoke(this, bitmap);
}
}
}

View file

@ -0,0 +1,136 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.OpenGL
{
class PersistentBuffers : IDisposable
{
private PersistentBuffer _main = new PersistentBuffer();
private PersistentBuffer _background = new PersistentBuffer();
public PersistentBuffer Default => BackgroundContextWorker.InBackground ? _background : _main;
public void Dispose()
{
_main?.Dispose();
_background?.Dispose();
}
}
class PersistentBuffer : IDisposable
{
private IntPtr _bufferMap;
private int _copyBufferHandle;
private int _copyBufferSize;
private byte[] _data;
private IntPtr _dataMap;
private void EnsureBuffer(int requiredSize)
{
if (_copyBufferSize < requiredSize && _copyBufferHandle != 0)
{
GL.DeleteBuffer(_copyBufferHandle);
_copyBufferHandle = 0;
}
if (_copyBufferHandle == 0)
{
_copyBufferHandle = GL.GenBuffer();
_copyBufferSize = requiredSize;
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, IntPtr.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit);
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, IntPtr.Zero, requiredSize, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
}
}
public unsafe IntPtr GetHostArray(int requiredSize)
{
if (_data == null || _data.Length < requiredSize)
{
_data = GC.AllocateUninitializedArray<byte>(requiredSize, true);
_dataMap = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(_data));
}
return _dataMap;
}
private void Sync()
{
GL.MemoryBarrier(MemoryBarrierFlags.ClientMappedBufferBarrierBit);
IntPtr sync = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
WaitSyncStatus syncResult = GL.ClientWaitSync(sync, ClientWaitSyncFlags.SyncFlushCommandsBit, 1000000000);
if (syncResult == WaitSyncStatus.TimeoutExpired)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to sync persistent buffer state within 1000ms. Continuing...");
}
GL.DeleteSync(sync);
}
public unsafe ReadOnlySpan<byte> GetTextureData(TextureView view, int size)
{
EnsureBuffer(size);
GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyBufferHandle);
view.WriteToPbo(0, false);
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
Sync();
return new ReadOnlySpan<byte>(_bufferMap.ToPointer(), size);
}
public unsafe ReadOnlySpan<byte> GetTextureData(TextureView view, int size, int layer, int level)
{
EnsureBuffer(size);
GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyBufferHandle);
int offset = view.WriteToPbo2D(0, layer, level);
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
Sync();
return new ReadOnlySpan<byte>(_bufferMap.ToPointer(), size).Slice(offset);
}
public unsafe ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{
EnsureBuffer(size);
GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32());
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
GL.CopyBufferSubData(BufferTarget.CopyReadBuffer, BufferTarget.CopyWriteBuffer, (IntPtr)offset, IntPtr.Zero, size);
GL.BindBuffer(BufferTarget.CopyWriteBuffer, 0);
Sync();
return new ReadOnlySpan<byte>(_bufferMap.ToPointer(), size);
}
public void Dispose()
{
if (_copyBufferHandle != 0)
{
GL.DeleteBuffer(_copyBufferHandle);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,177 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Buffers.Binary;
namespace Ryujinx.Graphics.OpenGL
{
class Program : IProgram
{
private const int MaxShaderLogLength = 2048;
public int Handle { get; private set; }
public bool IsLinked
{
get
{
if (_status == ProgramLinkStatus.Incomplete)
{
CheckProgramLink(true);
}
return _status == ProgramLinkStatus.Success;
}
}
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
private int[] _shaderHandles;
public bool HasFragmentShader;
public int FragmentOutputMap { get; }
public Program(ShaderSource[] shaders, int fragmentOutputMap)
{
Handle = GL.CreateProgram();
GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1);
_shaderHandles = new int[shaders.Length];
for (int index = 0; index < shaders.Length; index++)
{
ShaderSource shader = shaders[index];
if (shader.Stage == ShaderStage.Fragment)
{
HasFragmentShader = true;
}
int shaderHandle = GL.CreateShader(shader.Stage.Convert());
switch (shader.Language)
{
case TargetLanguage.Glsl:
GL.ShaderSource(shaderHandle, shader.Code);
GL.CompileShader(shaderHandle);
break;
case TargetLanguage.Spirv:
GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
break;
}
GL.AttachShader(Handle, shaderHandle);
_shaderHandles[index] = shaderHandle;
}
GL.LinkProgram(Handle);
FragmentOutputMap = fragmentOutputMap;
}
public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap)
{
Handle = GL.CreateProgram();
if (code.Length >= 4)
{
BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
unsafe
{
fixed (byte* ptr = code)
{
GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
}
}
}
HasFragmentShader = hasFragmentShader;
FragmentOutputMap = fragmentOutputMap;
}
public void Bind()
{
GL.UseProgram(Handle);
}
public ProgramLinkStatus CheckProgramLink(bool blocking)
{
if (!blocking && HwCapabilities.SupportsParallelShaderCompile)
{
GL.GetProgram(Handle, (GetProgramParameterName)ArbParallelShaderCompile.CompletionStatusArb, out int completed);
if (completed == 0)
{
return ProgramLinkStatus.Incomplete;
}
}
GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status);
DeleteShaders();
if (status == 0)
{
_status = ProgramLinkStatus.Failure;
string log = GL.GetProgramInfoLog(Handle);
if (log.Length > MaxShaderLogLength)
{
log = log.Substring(0, MaxShaderLogLength) + "...";
}
Logger.Warning?.Print(LogClass.Gpu, $"Shader linking failed: \n{log}");
}
else
{
_status = ProgramLinkStatus.Success;
}
return _status;
}
public byte[] GetBinary()
{
GL.GetProgram(Handle, (GetProgramParameterName)All.ProgramBinaryLength, out int size);
byte[] data = new byte[size + 4];
GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data);
BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(size, 4), (int)binFormat);
return data;
}
private void DeleteShaders()
{
if (_shaderHandles != null)
{
foreach (int shaderHandle in _shaderHandles)
{
GL.DetachShader(Handle, shaderHandle);
GL.DeleteShader(shaderHandle);
}
_shaderHandles = null;
}
}
public void Dispose()
{
if (Handle != 0)
{
DeleteShaders();
GL.DeleteProgram(Handle);
Handle = 0;
}
}
}
}

View file

@ -0,0 +1,120 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Ryujinx.Graphics.OpenGL.Queries
{
class BufferedQuery : IDisposable
{
private const int MaxQueryRetries = 5000;
private const long DefaultValue = -1;
private const ulong HighMask = 0xFFFFFFFF00000000;
public int Query { get; }
private int _buffer;
private IntPtr _bufferMap;
private QueryTarget _type;
public BufferedQuery(QueryTarget type)
{
_buffer = GL.GenBuffer();
Query = GL.GenQuery();
_type = type;
GL.BindBuffer(BufferTarget.QueryBuffer, _buffer);
unsafe
{
long defaultValue = DefaultValue;
GL.BufferStorage(BufferTarget.QueryBuffer, sizeof(long), (IntPtr)(&defaultValue), BufferStorageFlags.MapReadBit | BufferStorageFlags.MapWriteBit | BufferStorageFlags.MapPersistentBit);
}
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, IntPtr.Zero, sizeof(long), BufferAccessMask.MapReadBit | BufferAccessMask.MapWriteBit | BufferAccessMask.MapPersistentBit);
}
public void Reset()
{
GL.EndQuery(_type);
GL.BeginQuery(_type, Query);
}
public void Begin()
{
GL.BeginQuery(_type, Query);
}
public unsafe void End(bool withResult)
{
GL.EndQuery(_type);
if (withResult)
{
GL.BindBuffer(BufferTarget.QueryBuffer, _buffer);
Marshal.WriteInt64(_bufferMap, -1L);
GL.GetQueryObject(Query, GetQueryObjectParam.QueryResult, (long*)0);
GL.MemoryBarrier(MemoryBarrierFlags.QueryBufferBarrierBit | MemoryBarrierFlags.ClientMappedBufferBarrierBit);
}
else
{
// Dummy result, just return 0.
Marshal.WriteInt64(_bufferMap, 0L);
}
}
private bool WaitingForValue(long data)
{
return data == DefaultValue ||
((ulong)data & HighMask) == (unchecked((ulong)DefaultValue) & HighMask);
}
public bool TryGetResult(out long result)
{
result = Marshal.ReadInt64(_bufferMap);
return !WaitingForValue(result);
}
public long AwaitResult(AutoResetEvent wakeSignal = null)
{
long data = DefaultValue;
if (wakeSignal == null)
{
while (WaitingForValue(data))
{
data = Marshal.ReadInt64(_bufferMap);
}
}
else
{
int iterations = 0;
while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
{
data = Marshal.ReadInt64(_bufferMap);
if (WaitingForValue(data))
{
wakeSignal.WaitOne(1);
}
}
if (iterations >= MaxQueryRetries)
{
Logger.Error?.Print(LogClass.Gpu, $"Error: Query result timed out. Took more than {MaxQueryRetries} tries.");
}
}
return data;
}
public void Dispose()
{
GL.BindBuffer(BufferTarget.QueryBuffer, _buffer);
GL.UnmapBuffer(BufferTarget.QueryBuffer);
GL.DeleteBuffer(_buffer);
GL.DeleteQuery(Query);
}
}
}

View file

@ -0,0 +1,229 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.OpenGL.Queries
{
class CounterQueue : IDisposable
{
private const int QueryPoolInitialSize = 100;
public CounterType Type { get; }
public bool Disposed { get; private set; }
private readonly Pipeline _pipeline;
private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>();
private CounterQueueEvent _current;
private ulong _accumulatedCounter;
private int _waiterCount;
private object _lock = new object();
private Queue<BufferedQuery> _queryPool;
private AutoResetEvent _queuedEvent = new AutoResetEvent(false);
private AutoResetEvent _wakeSignal = new AutoResetEvent(false);
private AutoResetEvent _eventConsumed = new AutoResetEvent(false);
private Thread _consumerThread;
internal CounterQueue(Pipeline pipeline, CounterType type)
{
Type = type;
_pipeline = pipeline;
QueryTarget glType = GetTarget(Type);
_queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize);
for (int i = 0; i < QueryPoolInitialSize; i++)
{
_queryPool.Enqueue(new BufferedQuery(glType));
}
_current = new CounterQueueEvent(this, glType, 0);
_consumerThread = new Thread(EventConsumer);
_consumerThread.Start();
}
private void EventConsumer()
{
while (!Disposed)
{
CounterQueueEvent evt = null;
lock (_lock)
{
if (_events.Count > 0)
{
evt = _events.Dequeue();
}
}
if (evt == null)
{
_queuedEvent.WaitOne(); // No more events to go through, wait for more.
}
else
{
// Spin-wait rather than sleeping if there are any waiters, by passing null instead of the wake signal.
evt.TryConsume(ref _accumulatedCounter, true, _waiterCount == 0 ? _wakeSignal : null);
}
if (_waiterCount > 0)
{
_eventConsumed.Set();
}
}
}
internal BufferedQuery GetQueryObject()
{
// Creating/disposing query objects on a context we're sharing with will cause issues.
// So instead, make a lot of query objects on the main thread and reuse them.
lock (_lock)
{
if (_queryPool.Count > 0)
{
BufferedQuery result = _queryPool.Dequeue();
return result;
}
else
{
return new BufferedQuery(GetTarget(Type));
}
}
}
internal void ReturnQueryObject(BufferedQuery query)
{
lock (_lock)
{
_queryPool.Enqueue(query);
}
}
public CounterQueueEvent QueueReport(EventHandler<ulong> resultHandler, ulong lastDrawIndex, bool hostReserved)
{
CounterQueueEvent result;
ulong draws = lastDrawIndex - _current.DrawIndex;
lock (_lock)
{
// A query's result only matters if more than one draw was performed during it.
// Otherwise, dummy it out and return 0 immediately.
if (hostReserved)
{
// This counter event is guaranteed to be available for host conditional rendering.
_current.ReserveForHostAccess();
}
_current.Complete(draws > 0, _pipeline.GetCounterDivisor(Type));
_events.Enqueue(_current);
_current.OnResult += resultHandler;
result = _current;
_current = new CounterQueueEvent(this, GetTarget(Type), lastDrawIndex);
}
_queuedEvent.Set();
return result;
}
public void QueueReset()
{
lock (_lock)
{
_current.Clear();
}
}
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;
}
public void Flush(bool blocking)
{
if (!blocking)
{
// Just wake the consumer thread - it will update the queries.
_wakeSignal.Set();
return;
}
lock (_lock)
{
// Tell the queue to process all events.
while (_events.Count > 0)
{
CounterQueueEvent flush = _events.Peek();
if (!flush.TryConsume(ref _accumulatedCounter, true))
{
return; // If not blocking, then return when we encounter an event that is not ready yet.
}
_events.Dequeue();
}
}
}
public void FlushTo(CounterQueueEvent evt)
{
// Flush the counter queue on the main thread.
Interlocked.Increment(ref _waiterCount);
_wakeSignal.Set();
while (!evt.Disposed)
{
_eventConsumed.WaitOne(1);
}
Interlocked.Decrement(ref _waiterCount);
}
public void Dispose()
{
lock (_lock)
{
while (_events.Count > 0)
{
CounterQueueEvent evt = _events.Dequeue();
evt.Dispose();
}
Disposed = true;
}
_queuedEvent.Set();
_consumerThread.Join();
foreach (BufferedQuery query in _queryPool)
{
query.Dispose();
}
_queuedEvent.Dispose();
_wakeSignal.Dispose();
_eventConsumed.Dispose();
}
}
}

View file

@ -0,0 +1,163 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
using System.Threading;
namespace Ryujinx.Graphics.OpenGL.Queries
{
class CounterQueueEvent : ICounterEvent
{
public event EventHandler<ulong> OnResult;
public QueryTarget Type { get; }
public bool ClearCounter { get; private set; }
public int Query => _counter.Query;
public bool Disposed { get; private set; }
public bool Invalid { get; set; }
public ulong DrawIndex { get; }
private CounterQueue _queue;
private BufferedQuery _counter;
private bool _hostAccessReserved = false;
private int _refCount = 1; // Starts with a reference from the counter queue.
private object _lock = new object();
private ulong _result = ulong.MaxValue;
private double _divisor = 1f;
public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex)
{
_queue = queue;
_counter = queue.GetQueryObject();
Type = type;
DrawIndex = drawIndex;
_counter.Begin();
}
internal void Clear()
{
_counter.Reset();
ClearCounter = true;
}
internal void Complete(bool withResult, double divisor)
{
_counter.End(withResult);
_divisor = divisor;
}
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
{
lock (_lock)
{
if (Disposed)
{
return true;
}
if (ClearCounter || Type == QueryTarget.Timestamp)
{
result = 0;
}
long queryResult;
if (block)
{
queryResult = _counter.AwaitResult(wakeSignal);
}
else
{
if (!_counter.TryGetResult(out queryResult))
{
return false;
}
}
result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
_result = result;
OnResult?.Invoke(this, result);
Dispose(); // Return the our resources to the pool.
return true;
}
}
public void Flush()
{
if (Disposed)
{
return;
}
// Tell the queue to process all events up to this one.
_queue.FlushTo(this);
}
public void DecrementRefCount()
{
if (Interlocked.Decrement(ref _refCount) == 0)
{
DisposeInternal();
}
}
public bool ReserveForHostAccess()
{
if (_hostAccessReserved)
{
return true;
}
if (IsValueAvailable())
{
return false;
}
if (Interlocked.Increment(ref _refCount) == 1)
{
Interlocked.Decrement(ref _refCount);
return false;
}
_hostAccessReserved = true;
return true;
}
public void ReleaseHostAccess()
{
_hostAccessReserved = false;
DecrementRefCount();
}
private void DisposeInternal()
{
_queue.ReturnQueryObject(_counter);
}
private bool IsValueAvailable()
{
return _result != ulong.MaxValue || _counter.TryGetResult(out _);
}
public void Dispose()
{
Disposed = true;
DecrementRefCount();
}
}
}

View file

@ -0,0 +1,57 @@
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL.Queries
{
class Counters : IDisposable
{
private CounterQueue[] _counterQueues;
public Counters()
{
int count = Enum.GetNames<CounterType>().Length;
_counterQueues = new CounterQueue[count];
}
public void Initialize(Pipeline pipeline)
{
for (int index = 0; index < _counterQueues.Length; index++)
{
CounterType type = (CounterType)index;
_counterQueues[index] = new CounterQueue(pipeline, type);
}
}
public CounterQueueEvent QueueReport(CounterType type, EventHandler<ulong> resultHandler, ulong lastDrawIndex, bool hostReserved)
{
return _counterQueues[(int)type].QueueReport(resultHandler, lastDrawIndex, hostReserved);
}
public void QueueReset(CounterType type)
{
_counterQueues[(int)type].QueueReset();
}
public void Update()
{
foreach (var queue in _counterQueues)
{
queue.Flush(false);
}
}
public void Flush(CounterType type)
{
_counterQueues[(int)type].Flush(true);
}
public void Dispose()
{
foreach (var queue in _counterQueues)
{
queue.Dispose();
}
}
}
}

View file

@ -0,0 +1,122 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.OpenGL
{
class DisposedTexture
{
public TextureCreateInfo Info;
public TextureView View;
public float ScaleFactor;
public int RemainingFrames;
}
/// <summary>
/// A structure for pooling resources that can be reused without recreation, such as textures.
/// </summary>
class ResourcePool : IDisposable
{
private const int DisposedLiveFrames = 2;
private readonly object _lock = new object();
private readonly Dictionary<TextureCreateInfo, List<DisposedTexture>> _textures = new Dictionary<TextureCreateInfo, List<DisposedTexture>>();
/// <summary>
/// Add a texture that is not being used anymore to the resource pool to be used later.
/// Both the texture's view and storage should be completely unused.
/// </summary>
/// <param name="view">The texture's view</param>
public void AddTexture(TextureView view)
{
lock (_lock)
{
List<DisposedTexture> list;
if (!_textures.TryGetValue(view.Info, out list))
{
list = new List<DisposedTexture>();
_textures.Add(view.Info, list);
}
list.Add(new DisposedTexture()
{
Info = view.Info,
View = view,
ScaleFactor = view.ScaleFactor,
RemainingFrames = DisposedLiveFrames
});
}
}
/// <summary>
/// Attempt to obtain a texture from the resource cache with the desired parameters.
/// </summary>
/// <param name="info">The creation info for the desired texture</param>
/// <param name="scaleFactor">The scale factor for the desired texture</param>
/// <returns>A TextureView with the description specified, or null if one was not found.</returns>
public TextureView GetTextureOrNull(TextureCreateInfo info, float scaleFactor)
{
lock (_lock)
{
List<DisposedTexture> list;
if (!_textures.TryGetValue(info, out list))
{
return null;
}
foreach (DisposedTexture texture in list)
{
if (scaleFactor == texture.ScaleFactor)
{
list.Remove(texture);
return texture.View;
}
}
return null;
}
}
/// <summary>
/// Update the pool, removing any resources that have expired.
/// </summary>
public void Tick()
{
lock (_lock)
{
foreach (List<DisposedTexture> list in _textures.Values)
{
for (int i = 0; i < list.Count; i++)
{
DisposedTexture tex = list[i];
if (--tex.RemainingFrames < 0)
{
tex.View.Dispose();
list.RemoveAt(i--);
}
}
}
}
}
/// <summary>
/// Disposes the resource pool.
/// </summary>
public void Dispose()
{
lock (_lock)
{
foreach (List<DisposedTexture> list in _textures.Values)
{
foreach (DisposedTexture texture in list)
{
texture.View.Dispose();
}
}
_textures.Clear();
}
}
}
}

View file

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Graphics" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
<EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
<EmbeddedResource Include="Effects\Shaders\fsr_sharpening.glsl" />
<EmbeddedResource Include="Effects\Shaders\fxaa.glsl" />
<EmbeddedResource Include="Effects\Shaders\smaa.hlsl" />
<EmbeddedResource Include="Effects\Shaders\smaa_blend.glsl" />
<EmbeddedResource Include="Effects\Shaders\smaa_edge.glsl" />
<EmbeddedResource Include="Effects\Shaders\smaa_neighbour.glsl" />
<EmbeddedResource Include="Effects\Shaders\ffx_fsr1.h" />
<EmbeddedResource Include="Effects\Shaders\ffx_a.h" />
<EmbeddedResource Include="Effects\Shaders\fsr_scaling.glsl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,168 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.OpenGL
{
class Sync : IDisposable
{
private class SyncHandle
{
public ulong ID;
public IntPtr Handle;
}
private ulong _firstHandle = 0;
private ClientWaitSyncFlags _syncFlags => HwCapabilities.RequiresSyncFlush ? ClientWaitSyncFlags.None : ClientWaitSyncFlags.SyncFlushCommandsBit;
private List<SyncHandle> _handles = new List<SyncHandle>();
public void Create(ulong id)
{
SyncHandle handle = new SyncHandle
{
ID = id,
Handle = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None)
};
if (HwCapabilities.RequiresSyncFlush)
{
// Force commands to flush up to the syncpoint.
GL.ClientWaitSync(handle.Handle, ClientWaitSyncFlags.SyncFlushCommandsBit, 0);
}
lock (_handles)
{
_handles.Add(handle);
}
}
public ulong GetCurrent()
{
lock (_handles)
{
ulong lastHandle = _firstHandle;
foreach (SyncHandle handle in _handles)
{
lock (handle)
{
if (handle.Handle == IntPtr.Zero)
{
continue;
}
if (handle.ID > lastHandle)
{
WaitSyncStatus syncResult = GL.ClientWaitSync(handle.Handle, _syncFlags, 0);
if (syncResult == WaitSyncStatus.AlreadySignaled)
{
lastHandle = handle.ID;
}
}
}
}
return lastHandle;
}
}
public void Wait(ulong id)
{
SyncHandle result = null;
lock (_handles)
{
if ((long)(_firstHandle - id) > 0)
{
return; // The handle has already been signalled or deleted.
}
foreach (SyncHandle handle in _handles)
{
if (handle.ID == id)
{
result = handle;
break;
}
}
}
if (result != null)
{
lock (result)
{
if (result.Handle == IntPtr.Zero)
{
return;
}
WaitSyncStatus syncResult = GL.ClientWaitSync(result.Handle, _syncFlags, 1000000000);
if (syncResult == WaitSyncStatus.TimeoutExpired)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"GL Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
}
}
}
}
public void Cleanup()
{
// Iterate through handles and remove any that have already been signalled.
while (true)
{
SyncHandle first = null;
lock (_handles)
{
first = _handles.FirstOrDefault();
}
if (first == null) break;
WaitSyncStatus syncResult = GL.ClientWaitSync(first.Handle, _syncFlags, 0);
if (syncResult == WaitSyncStatus.AlreadySignaled)
{
// Delete the sync object.
lock (_handles)
{
lock (first)
{
_firstHandle = first.ID + 1;
_handles.RemoveAt(0);
GL.DeleteSync(first.Handle);
first.Handle = IntPtr.Zero;
}
}
} else
{
// This sync handle and any following have not been reached yet.
break;
}
}
}
public void Dispose()
{
lock (_handles)
{
foreach (SyncHandle handle in _handles)
{
lock (handle)
{
GL.DeleteSync(handle.Handle);
handle.Handle = IntPtr.Zero;
}
}
_handles.Clear();
}
}
}
}

View file

@ -0,0 +1,280 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.OpenGL
{
class VertexArray : IDisposable
{
public int Handle { get; private set; }
private readonly VertexAttribDescriptor[] _vertexAttribs;
private readonly VertexBufferDescriptor[] _vertexBuffers;
private int _minVertexCount;
private uint _vertexAttribsInUse;
private uint _vertexBuffersInUse;
private uint _vertexBuffersLimited;
private BufferRange _indexBuffer;
private BufferHandle _tempIndexBuffer;
private BufferHandle _tempVertexBuffer;
private int _tempVertexBufferSize;
public VertexArray()
{
Handle = GL.GenVertexArray();
_vertexAttribs = new VertexAttribDescriptor[Constants.MaxVertexAttribs];
_vertexBuffers = new VertexBufferDescriptor[Constants.MaxVertexBuffers];
_tempIndexBuffer = Buffer.Create();
}
public void Bind()
{
GL.BindVertexArray(Handle);
}
public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
{
int minVertexCount = int.MaxValue;
int bindingIndex;
for (bindingIndex = 0; bindingIndex < vertexBuffers.Length; bindingIndex++)
{
VertexBufferDescriptor vb = vertexBuffers[bindingIndex];
if (vb.Buffer.Handle != BufferHandle.Null)
{
int vertexCount = vb.Stride <= 0 ? 0 : vb.Buffer.Size / vb.Stride;
if (minVertexCount > vertexCount)
{
minVertexCount = vertexCount;
}
GL.BindVertexBuffer(bindingIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride);
GL.VertexBindingDivisor(bindingIndex, vb.Divisor);
_vertexBuffersInUse |= 1u << bindingIndex;
}
else
{
if ((_vertexBuffersInUse & (1u << bindingIndex)) != 0)
{
GL.BindVertexBuffer(bindingIndex, 0, IntPtr.Zero, 0);
_vertexBuffersInUse &= ~(1u << bindingIndex);
}
}
_vertexBuffers[bindingIndex] = vb;
}
_minVertexCount = minVertexCount;
}
public void SetVertexAttributes(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
{
int index = 0;
for (; index < vertexAttribs.Length; index++)
{
VertexAttribDescriptor attrib = vertexAttribs[index];
if (attrib.Equals(_vertexAttribs[index]))
{
continue;
}
FormatInfo fmtInfo = FormatTable.GetFormatInfo(attrib.Format);
if (attrib.IsZero)
{
// Disabling the attribute causes the shader to read a constant value.
// We currently set the constant to (0, 0, 0, 0).
DisableVertexAttrib(index);
}
else
{
EnableVertexAttrib(index);
}
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(index, size, type, fmtInfo.Normalized, offset);
}
else
{
VertexAttribIntegerType type = (VertexAttribIntegerType)fmtInfo.PixelType;
GL.VertexAttribIFormat(index, size, type, offset);
}
GL.VertexAttribBinding(index, attrib.BufferIndex);
_vertexAttribs[index] = attrib;
}
for (; index < Constants.MaxVertexAttribs; index++)
{
DisableVertexAttrib(index);
}
}
public void SetIndexBuffer(BufferRange range)
{
_indexBuffer = range;
GL.BindBuffer(BufferTarget.ElementArrayBuffer, range.Handle.ToInt32());
}
public void SetRangeOfIndexBuffer()
{
Buffer.Resize(_tempIndexBuffer, _indexBuffer.Size);
Buffer.Copy(_indexBuffer.Handle, _tempIndexBuffer, _indexBuffer.Offset, 0, _indexBuffer.Size);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _tempIndexBuffer.ToInt32());
}
public void RestoreIndexBuffer()
{
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer.Handle.ToInt32());
}
public void PreDraw(int vertexCount)
{
LimitVertexBuffers(vertexCount);
}
public void PreDrawVbUnbounded()
{
UnlimitVertexBuffers();
}
public void LimitVertexBuffers(int vertexCount)
{
// Is it possible for the draw to fetch outside the bounds of any vertex buffer currently bound?
if (vertexCount <= _minVertexCount)
{
return;
}
// If the draw can fetch out of bounds, let's ensure that it will only fetch zeros rather than memory garbage.
int currentTempVbOffset = 0;
uint buffersInUse = _vertexBuffersInUse;
while (buffersInUse != 0)
{
int vbIndex = BitOperations.TrailingZeroCount(buffersInUse);
ref var vb = ref _vertexBuffers[vbIndex];
int requiredSize = vertexCount * vb.Stride;
if (vb.Buffer.Size < requiredSize)
{
BufferHandle tempVertexBuffer = EnsureTempVertexBufferSize(currentTempVbOffset + requiredSize);
Buffer.Copy(vb.Buffer.Handle, tempVertexBuffer, vb.Buffer.Offset, currentTempVbOffset, vb.Buffer.Size);
Buffer.Clear(tempVertexBuffer, currentTempVbOffset + vb.Buffer.Size, requiredSize - vb.Buffer.Size, 0);
GL.BindVertexBuffer(vbIndex, tempVertexBuffer.ToInt32(), (IntPtr)currentTempVbOffset, vb.Stride);
currentTempVbOffset += requiredSize;
_vertexBuffersLimited |= 1u << vbIndex;
}
buffersInUse &= ~(1u << vbIndex);
}
}
private BufferHandle EnsureTempVertexBufferSize(int size)
{
BufferHandle tempVertexBuffer = _tempVertexBuffer;
if (_tempVertexBufferSize < size)
{
_tempVertexBufferSize = size;
if (tempVertexBuffer == BufferHandle.Null)
{
tempVertexBuffer = Buffer.Create(size);
_tempVertexBuffer = tempVertexBuffer;
return tempVertexBuffer;
}
Buffer.Resize(_tempVertexBuffer, size);
}
return tempVertexBuffer;
}
public void UnlimitVertexBuffers()
{
uint buffersLimited = _vertexBuffersLimited;
if (buffersLimited == 0)
{
return;
}
while (buffersLimited != 0)
{
int vbIndex = BitOperations.TrailingZeroCount(buffersLimited);
ref var vb = ref _vertexBuffers[vbIndex];
GL.BindVertexBuffer(vbIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride);
buffersLimited &= ~(1u << vbIndex);
}
_vertexBuffersLimited = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnableVertexAttrib(int index)
{
uint mask = 1u << index;
if ((_vertexAttribsInUse & mask) == 0)
{
_vertexAttribsInUse |= mask;
GL.EnableVertexAttribArray(index);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DisableVertexAttrib(int index)
{
uint mask = 1u << index;
if ((_vertexAttribsInUse & mask) != 0)
{
_vertexAttribsInUse &= ~mask;
GL.DisableVertexAttribArray(index);
GL.VertexAttrib4(index, 0f, 0f, 0f, 1f);
}
}
public void Dispose()
{
if (Handle != 0)
{
GL.DeleteVertexArray(Handle);
Handle = 0;
}
}
}
}

View file

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