Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4c0eb91d7e | ||
|
da75a9a6ea |
@@ -1,98 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
class IntermmediatePool : IDisposable
|
||||
{
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
private readonly List<TextureView> _entries;
|
||||
|
||||
public IntermmediatePool(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)
|
||||
{
|
||||
TextureView entry;
|
||||
|
||||
for (int i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
entry = _entries[i];
|
||||
|
||||
if (entry.Target == target && entry.Format == format)
|
||||
{
|
||||
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);
|
||||
_entries[i] = entry;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
|
||||
_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)
|
||||
{
|
||||
return (TextureView)_renderer.CreateTexture(new TextureCreateInfo(
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
levels,
|
||||
1,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,8 +9,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
|
||||
public IntermmediatePool IntermmediatePool { get; }
|
||||
|
||||
private int _srcFramebuffer;
|
||||
private int _dstFramebuffer;
|
||||
|
||||
@@ -20,7 +18,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
public TextureCopy(OpenGLRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
IntermmediatePool = new IntermmediatePool(renderer);
|
||||
}
|
||||
|
||||
public void Copy(
|
||||
@@ -517,8 +514,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
_copyPboHandle = 0;
|
||||
}
|
||||
|
||||
IntermmediatePool.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
276
Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs
Normal file
276
Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -116,28 +116,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
|
||||
if (destinationView.Target.IsMultisample() || Target.IsMultisample())
|
||||
if (!destinationView.Target.IsMultisample() && Target.IsMultisample())
|
||||
{
|
||||
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
|
||||
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
|
||||
|
||||
TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
|
||||
GetIntermmediateTarget(Target),
|
||||
Info.BlockWidth,
|
||||
Info.BlockHeight,
|
||||
Info.BytesPerPixel,
|
||||
Format,
|
||||
Width,
|
||||
Height,
|
||||
Info.Depth,
|
||||
Info.Levels);
|
||||
|
||||
GL.Disable(EnableCap.FramebufferSrgb);
|
||||
|
||||
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true);
|
||||
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, firstLayer, 0, firstLevel);
|
||||
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
|
||||
_renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, 0, firstLayer, layers);
|
||||
}
|
||||
else if (destinationView.Target.IsMultisample() && !Target.IsMultisample())
|
||||
{
|
||||
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
|
||||
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, 0, firstLayer, layers);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -149,28 +136,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
|
||||
if (destinationView.Target.IsMultisample() || Target.IsMultisample())
|
||||
if (!destinationView.Target.IsMultisample() && Target.IsMultisample())
|
||||
{
|
||||
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
|
||||
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
|
||||
|
||||
TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
|
||||
GetIntermmediateTarget(Target),
|
||||
Info.BlockWidth,
|
||||
Info.BlockHeight,
|
||||
Info.BytesPerPixel,
|
||||
Format,
|
||||
Math.Max(1, Width >> srcLevel),
|
||||
Math.Max(1, Height >> srcLevel),
|
||||
1,
|
||||
1);
|
||||
|
||||
GL.Disable(EnableCap.FramebufferSrgb);
|
||||
|
||||
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true, srcLayer, 0, srcLevel, 0, 1, 1);
|
||||
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, dstLayer, 0, dstLevel, 1, 1);
|
||||
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
_renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, srcLayer, dstLayer,1);
|
||||
}
|
||||
else if (destinationView.Target.IsMultisample() && !Target.IsMultisample())
|
||||
{
|
||||
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, srcLayer, dstLayer, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -178,17 +150,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
}
|
||||
}
|
||||
|
||||
private static Target GetIntermmediateTarget(Target srcTarget)
|
||||
{
|
||||
return srcTarget switch
|
||||
{
|
||||
Target.Texture2D => Target.Texture2DMultisample,
|
||||
Target.Texture2DArray => Target.Texture2DMultisampleArray,
|
||||
Target.Texture2DMultisampleArray => Target.Texture2DArray,
|
||||
_ => Target.Texture2D
|
||||
};
|
||||
}
|
||||
|
||||
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
|
||||
{
|
||||
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
|
||||
|
@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
private TextureCopy _textureCopy;
|
||||
private TextureCopy _backgroundTextureCopy;
|
||||
internal TextureCopy TextureCopy => BackgroundContextWorker.InBackground ? _backgroundTextureCopy : _textureCopy;
|
||||
internal TextureCopyMS TextureCopyMS { get; }
|
||||
|
||||
private Sync _sync;
|
||||
|
||||
@@ -48,6 +49,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_window = new Window(this);
|
||||
_textureCopy = new TextureCopy(this);
|
||||
_backgroundTextureCopy = new TextureCopy(this);
|
||||
TextureCopyMS = new TextureCopyMS(this);
|
||||
_sync = new Sync();
|
||||
PersistentBuffers = new PersistentBuffers();
|
||||
ResourcePool = new ResourcePool();
|
||||
@@ -211,6 +213,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
_textureCopy.Dispose();
|
||||
_backgroundTextureCopy.Dispose();
|
||||
TextureCopyMS.Dispose();
|
||||
PersistentBuffers.Dispose();
|
||||
ResourcePool.Dispose();
|
||||
_pipeline.Dispose();
|
||||
|
@@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
class Pipeline : IPipeline, IDisposable
|
||||
{
|
||||
private const int SavedImages = 2;
|
||||
|
||||
private readonly DrawTextureEmulation _drawTexture;
|
||||
|
||||
internal ulong DrawCount { get; private set; }
|
||||
@@ -46,6 +48,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
private Vector4<float>[] _renderScale = new Vector4<float>[73];
|
||||
private int _fragmentScaleCount;
|
||||
|
||||
private (TextureBase, Format)[] _images;
|
||||
private TextureBase _unit0Texture;
|
||||
private Sampler _unit0Sampler;
|
||||
|
||||
@@ -78,6 +81,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_fragmentOutputMap = uint.MaxValue;
|
||||
_componentMasks = uint.MaxValue;
|
||||
|
||||
_images = new (TextureBase, Format)[SavedImages];
|
||||
|
||||
var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
|
||||
new Span<Vector4<float>>(_renderScale).Fill(defaultScale);
|
||||
|
||||
@@ -907,6 +912,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
public void SetImage(int binding, ITexture texture, Format imageFormat)
|
||||
{
|
||||
if ((uint)binding < SavedImages)
|
||||
{
|
||||
_images[binding] = (texture as TextureBase, imageFormat);
|
||||
}
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
return;
|
||||
@@ -1608,6 +1618,32 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
public void RestoreProgram()
|
||||
{
|
||||
_program?.Bind();
|
||||
}
|
||||
|
||||
public void RestoreImages1And2()
|
||||
{
|
||||
for (int i = 0; i < SavedImages; i++)
|
||||
{
|
||||
(TextureBase texBase, Format imageFormat) = _images[i];
|
||||
|
||||
if (texBase != null)
|
||||
{
|
||||
SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat);
|
||||
|
||||
if (format != 0)
|
||||
{
|
||||
GL.BindImageTexture(i, texBase.Handle, 0, true, 0, TextureAccess.ReadWrite, format);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
GL.BindImageTexture(i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
|
||||
{
|
||||
if (value is CounterQueueEvent)
|
||||
|
@@ -370,6 +370,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
|
||||
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
@@ -388,6 +389,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride);
|
||||
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
@@ -398,6 +400,29 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return holder.GetBuffer();
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
var key = new TopologyConversionCacheKey(_gd, pattern, indexSize);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
|
||||
{
|
||||
// The destination index size is always I32.
|
||||
|
||||
int indexCount = size / indexSize;
|
||||
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
|
||||
holder = _gd.BufferManager.Create(_gd, convertedCount * 4);
|
||||
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount);
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
return holder.GetBuffer();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||
|
@@ -140,6 +140,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBufferTopologyConversion(cbs, offset, size, pattern, indexSize);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, out int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -9,38 +8,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
private readonly IndexType _type;
|
||||
|
||||
private readonly Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size, IndexType type)
|
||||
{
|
||||
_buffer = buffer;
|
||||
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_type = type;
|
||||
buffer?.IncrementReferenceCount();
|
||||
}
|
||||
|
||||
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
_buffer = buffer;
|
||||
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_type = IndexType.Uint16;
|
||||
buffer?.IncrementReferenceCount();
|
||||
}
|
||||
|
||||
public void BindIndexBuffer(Vk api, CommandBufferScoped cbs)
|
||||
{
|
||||
if (_buffer != null)
|
||||
{
|
||||
api.CmdBindIndexBuffer(cbs.CommandBuffer, _buffer.Get(cbs, _offset, _size).Value, (ulong)_offset, _type);
|
||||
}
|
||||
}
|
||||
|
||||
public void BindTransformFeedbackBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding)
|
||||
{
|
||||
if (_buffer != null)
|
||||
|
@@ -10,14 +10,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
struct I8ToI16CacheKey : ICacheKey
|
||||
{
|
||||
public I8ToI16CacheKey() { }
|
||||
// Used to notify the pipeline that bindings have invalidated on dispose.
|
||||
private readonly VulkanRenderer _gd;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public I8ToI16CacheKey(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is I8ToI16CacheKey;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
public void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal.DirtyIndexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
struct AlignedVertexBufferCacheKey : ICacheKey
|
||||
@@ -55,6 +66,41 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
struct TopologyConversionCacheKey : ICacheKey
|
||||
{
|
||||
private IndexBufferPattern _pattern;
|
||||
private int _indexSize;
|
||||
|
||||
// Used to notify the pipeline that bindings have invalidated on dispose.
|
||||
private readonly VulkanRenderer _gd;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public TopologyConversionCacheKey(VulkanRenderer gd, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
_gd = gd;
|
||||
_pattern = pattern;
|
||||
_indexSize = indexSize;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is TopologyConversionCacheKey entry &&
|
||||
entry._pattern == _pattern &&
|
||||
entry._indexSize == _indexSize;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal.DirtyIndexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
struct CacheByRange<T> where T : IDisposable
|
||||
{
|
||||
private struct Entry
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -179,8 +180,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency,
|
||||
GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency,
|
||||
GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList,
|
||||
GAL.PrimitiveTopology.Quads => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan, // Emulated with triangle fans
|
||||
GAL.PrimitiveTopology.QuadStrip => Silk.NET.Vulkan.PrimitiveTopology.TriangleStrip, // Emulated with triangle strips
|
||||
GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."),
|
||||
GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."),
|
||||
_ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList)
|
||||
};
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||
using Ryujinx.Graphics.Vulkan.Shaders;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
@@ -399,6 +400,86 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
newSize);
|
||||
}
|
||||
|
||||
public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
|
||||
CommandBufferScoped cbs,
|
||||
BufferHolder src,
|
||||
BufferHolder dst,
|
||||
IndexBufferPattern pattern,
|
||||
int indexSize,
|
||||
int srcOffset,
|
||||
int indexCount)
|
||||
{
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
int outputIndexSize = 4;
|
||||
|
||||
// TODO: Do this with a compute shader?
|
||||
var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, indexCount * indexSize).Value;
|
||||
var dstBuffer = dst.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
||||
|
||||
gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
|
||||
|
||||
var bufferCopy = new List<BufferCopy>();
|
||||
int outputOffset = 0;
|
||||
|
||||
// Try to merge copies of adjacent indices to reduce copy count.
|
||||
int sequenceStart = 0;
|
||||
int sequenceLength = 0;
|
||||
|
||||
foreach (var index in pattern.GetIndexMapping(indexCount))
|
||||
{
|
||||
if (sequenceLength > 0)
|
||||
{
|
||||
if (index == sequenceStart + sequenceLength && indexSize == outputIndexSize)
|
||||
{
|
||||
sequenceLength++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Commit the copy so far.
|
||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
||||
outputOffset += outputIndexSize * sequenceLength;
|
||||
}
|
||||
|
||||
sequenceStart = index;
|
||||
sequenceLength = 1;
|
||||
}
|
||||
|
||||
if (sequenceLength > 0)
|
||||
{
|
||||
// Commit final pending copy.
|
||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
||||
}
|
||||
|
||||
var bufferCopyArray = bufferCopy.ToArray();
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
|
||||
fixed (BufferCopy* pBufferCopy = bufferCopyArray)
|
||||
{
|
||||
gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)bufferCopyArray.Length, pBufferCopy);
|
||||
}
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
139
Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
Normal file
139
Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class IndexBufferPattern : IDisposable
|
||||
{
|
||||
public int PrimitiveVertices { get; }
|
||||
public int PrimitiveVerticesOut { get; }
|
||||
public int BaseIndex { get; }
|
||||
public int[] OffsetIndex { get; }
|
||||
public int IndexStride { get; }
|
||||
public bool RepeatStart { get; }
|
||||
|
||||
private VulkanRenderer _gd;
|
||||
private int _currentSize;
|
||||
private BufferHandle _repeatingBuffer;
|
||||
|
||||
public IndexBufferPattern(VulkanRenderer gd,
|
||||
int primitiveVertices,
|
||||
int primitiveVerticesOut,
|
||||
int baseIndex,
|
||||
int[] offsetIndex,
|
||||
int indexStride,
|
||||
bool repeatStart)
|
||||
{
|
||||
PrimitiveVertices = primitiveVertices;
|
||||
PrimitiveVerticesOut = primitiveVerticesOut;
|
||||
BaseIndex = baseIndex;
|
||||
OffsetIndex = offsetIndex;
|
||||
IndexStride = indexStride;
|
||||
RepeatStart = repeatStart;
|
||||
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
public int GetPrimitiveCount(int vertexCount)
|
||||
{
|
||||
return Math.Max(0, ((vertexCount - BaseIndex) + IndexStride - 1) / IndexStride);
|
||||
}
|
||||
|
||||
public int GetConvertedCount(int indexCount)
|
||||
{
|
||||
int primitiveCount = GetPrimitiveCount(indexCount);
|
||||
return primitiveCount * OffsetIndex.Length;
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetIndexMapping(int indexCount)
|
||||
{
|
||||
int primitiveCount = GetPrimitiveCount(indexCount);
|
||||
int index = BaseIndex;
|
||||
|
||||
for (int i = 0; i < primitiveCount; i++)
|
||||
{
|
||||
if (RepeatStart)
|
||||
{
|
||||
// Used for triangle fan
|
||||
yield return 0;
|
||||
}
|
||||
|
||||
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
|
||||
{
|
||||
yield return index + OffsetIndex[j];
|
||||
}
|
||||
|
||||
index += IndexStride;
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
|
||||
{
|
||||
int primitiveCount = GetPrimitiveCount(vertexCount);
|
||||
indexCount = primitiveCount * PrimitiveVerticesOut;
|
||||
|
||||
int expectedSize = primitiveCount * OffsetIndex.Length;
|
||||
|
||||
if (expectedSize <= _currentSize && _repeatingBuffer != BufferHandle.Null)
|
||||
{
|
||||
return _repeatingBuffer;
|
||||
}
|
||||
|
||||
// Expand the repeating pattern to the number of requested primitives.
|
||||
BufferHandle newBuffer = _gd.CreateBuffer(expectedSize * sizeof(int));
|
||||
|
||||
// Copy the old data to the new one.
|
||||
if (_repeatingBuffer != BufferHandle.Null)
|
||||
{
|
||||
_gd.Pipeline.CopyBuffer(_repeatingBuffer, newBuffer, 0, 0, _currentSize * sizeof(int));
|
||||
_gd.DeleteBuffer(_repeatingBuffer);
|
||||
}
|
||||
|
||||
_repeatingBuffer = newBuffer;
|
||||
|
||||
// Add the additional repeats on top.
|
||||
int newPrimitives = primitiveCount;
|
||||
int oldPrimitives = (_currentSize) / OffsetIndex.Length;
|
||||
|
||||
int[] newData;
|
||||
|
||||
newPrimitives -= oldPrimitives;
|
||||
newData = new int[expectedSize - _currentSize];
|
||||
|
||||
int outOffset = 0;
|
||||
int index = oldPrimitives * IndexStride + BaseIndex;
|
||||
|
||||
for (int i = 0; i < newPrimitives; i++)
|
||||
{
|
||||
if (RepeatStart)
|
||||
{
|
||||
// Used for triangle fan
|
||||
newData[outOffset++] = 0;
|
||||
}
|
||||
|
||||
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
|
||||
{
|
||||
newData[outOffset++] = index + OffsetIndex[j];
|
||||
}
|
||||
|
||||
index += IndexStride;
|
||||
}
|
||||
|
||||
_gd.SetBufferData(newBuffer, _currentSize * sizeof(int), MemoryMarshal.Cast<int, byte>(newData));
|
||||
_currentSize = expectedSize;
|
||||
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_repeatingBuffer != BufferHandle.Null)
|
||||
{
|
||||
_gd.DeleteBuffer(_repeatingBuffer);
|
||||
_repeatingBuffer = BufferHandle.Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
97
Ryujinx.Graphics.Vulkan/IndexBufferState.cs
Normal file
97
Ryujinx.Graphics.Vulkan/IndexBufferState.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal struct IndexBufferState
|
||||
{
|
||||
public static IndexBufferState Null => new IndexBufferState(GAL.BufferHandle.Null, 0, 0);
|
||||
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
private readonly IndexType _type;
|
||||
|
||||
private readonly GAL.BufferHandle _handle;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public IndexBufferState(GAL.BufferHandle handle, int offset, int size, IndexType type)
|
||||
{
|
||||
_handle = handle;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_type = type;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public IndexBufferState(GAL.BufferHandle handle, int offset, int size)
|
||||
{
|
||||
_handle = handle;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_type = IndexType.Uint16;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public void BindIndexBuffer(VulkanRenderer gd, CommandBufferScoped cbs)
|
||||
{
|
||||
Auto<DisposableBuffer> autoBuffer;
|
||||
int offset, size;
|
||||
IndexType type = _type;
|
||||
|
||||
if (_type == IndexType.Uint8Ext && !gd.Capabilities.SupportsIndexTypeUint8)
|
||||
{
|
||||
// Index type is not supported. Convert to I16.
|
||||
autoBuffer = gd.BufferManager.GetBufferI8ToI16(cbs, _handle, _offset, _size);
|
||||
|
||||
type = IndexType.Uint16;
|
||||
offset = 0;
|
||||
size = _size * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int _);
|
||||
|
||||
offset = _offset;
|
||||
size = _size;
|
||||
}
|
||||
|
||||
_buffer = autoBuffer;
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, offset, size).Value, (ulong)offset, type);
|
||||
}
|
||||
}
|
||||
|
||||
public void BindConvertedIndexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, int firstIndex, int indexCount, int convertedCount, IndexBufferPattern pattern)
|
||||
{
|
||||
Auto<DisposableBuffer> autoBuffer;
|
||||
|
||||
// Convert the index buffer using the given pattern.
|
||||
int indexSize = _type switch
|
||||
{
|
||||
IndexType.Uint32 => 4,
|
||||
IndexType.Uint16 => 2,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
int firstIndexOffset = firstIndex * indexSize;
|
||||
|
||||
autoBuffer = gd.BufferManager.GetBufferTopologyConversion(cbs, _handle, _offset + firstIndexOffset, indexCount * indexSize, pattern, indexSize);
|
||||
|
||||
int size = convertedCount * 4;
|
||||
|
||||
_buffer = autoBuffer;
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, 0, size).Value, 0, IndexType.Uint32);
|
||||
}
|
||||
}
|
||||
|
||||
public bool BoundEquals(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
return _buffer == buffer;
|
||||
}
|
||||
}
|
||||
}
|
@@ -51,13 +51,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly DescriptorSetUpdater _descriptorSetUpdater;
|
||||
|
||||
private BufferState _indexBuffer;
|
||||
private IndexBufferState _indexBuffer;
|
||||
private IndexBufferPattern _indexBufferPattern;
|
||||
private readonly BufferState[] _transformFeedbackBuffers;
|
||||
private readonly VertexBufferState[] _vertexBuffers;
|
||||
private ulong _vertexBuffersDirty;
|
||||
protected Rectangle<int> ClearScissor;
|
||||
|
||||
public SupportBufferUpdater SupportBufferUpdater;
|
||||
public IndexBufferPattern QuadsToTrisPattern;
|
||||
public IndexBufferPattern TriFanToTrisPattern;
|
||||
|
||||
private bool _needsIndexBufferRebind;
|
||||
private bool _needsTransformFeedbackBuffersRebind;
|
||||
@@ -107,6 +110,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SupportBufferUpdater = new SupportBufferUpdater(Gd);
|
||||
SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount);
|
||||
|
||||
QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false);
|
||||
TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true);
|
||||
}
|
||||
|
||||
public unsafe void Barrier()
|
||||
@@ -245,6 +251,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public void DirtyIndexBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
if (_indexBuffer.BoundEquals(buffer))
|
||||
{
|
||||
_needsIndexBufferRebind = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
|
||||
{
|
||||
if (!_program.IsLinked)
|
||||
@@ -267,24 +281,59 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||
BeginRenderPass();
|
||||
ResumeTransformFeedbackInternal();
|
||||
DrawCount++;
|
||||
|
||||
if (_topology == GAL.PrimitiveTopology.Quads)
|
||||
if (Gd.TopologyUnsupported(_topology))
|
||||
{
|
||||
int quadsCount = vertexCount / 4;
|
||||
// Temporarily bind a conversion pattern as an index buffer.
|
||||
_needsIndexBufferRebind = true;
|
||||
|
||||
for (int i = 0; i < quadsCount; i++)
|
||||
IndexBufferPattern pattern = _topology switch
|
||||
{
|
||||
Gd.Api.CmdDraw(CommandBuffer, 4, (uint)instanceCount, (uint)(firstVertex + i * 4), (uint)firstInstance);
|
||||
}
|
||||
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
|
||||
GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern,
|
||||
_ => throw new NotSupportedException($"Unsupported topology: {_topology}")
|
||||
};
|
||||
|
||||
BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount);
|
||||
var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, handle, false);
|
||||
|
||||
Gd.Api.CmdBindIndexBuffer(CommandBuffer, buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value, 0, Silk.NET.Vulkan.IndexType.Uint32);
|
||||
|
||||
BeginRenderPass(); // May have been interrupted to set buffer data.
|
||||
ResumeTransformFeedbackInternal();
|
||||
|
||||
Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResumeTransformFeedbackInternal();
|
||||
|
||||
Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIndexBufferPattern()
|
||||
{
|
||||
IndexBufferPattern pattern = null;
|
||||
|
||||
if (Gd.TopologyUnsupported(_topology))
|
||||
{
|
||||
pattern = _topology switch
|
||||
{
|
||||
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
|
||||
GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern,
|
||||
_ => throw new NotSupportedException($"Unsupported topology: {_topology}")
|
||||
};
|
||||
}
|
||||
|
||||
if (_indexBufferPattern != pattern)
|
||||
{
|
||||
_indexBufferPattern = pattern;
|
||||
_needsIndexBufferRebind = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
|
||||
{
|
||||
if (!_program.IsLinked)
|
||||
@@ -292,22 +341,34 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateIndexBufferPattern();
|
||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||
BeginRenderPass();
|
||||
ResumeTransformFeedbackInternal();
|
||||
DrawCount++;
|
||||
|
||||
if (_topology == GAL.PrimitiveTopology.Quads)
|
||||
if (_indexBufferPattern != null)
|
||||
{
|
||||
int quadsCount = indexCount / 4;
|
||||
// Convert the index buffer into a supported topology.
|
||||
IndexBufferPattern pattern = _indexBufferPattern;
|
||||
|
||||
for (int i = 0; i < quadsCount; i++)
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
|
||||
if (_needsIndexBufferRebind)
|
||||
{
|
||||
Gd.Api.CmdDrawIndexed(CommandBuffer, 4, (uint)instanceCount, (uint)(firstIndex + i * 4), firstVertex, (uint)firstInstance);
|
||||
_indexBuffer.BindConvertedIndexBuffer(Gd, Cbs, firstIndex, indexCount, convertedCount, pattern);
|
||||
|
||||
_needsIndexBufferRebind = false;
|
||||
}
|
||||
|
||||
BeginRenderPass(); // May have been interrupted to set buffer data.
|
||||
ResumeTransformFeedbackInternal();
|
||||
|
||||
Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)convertedCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResumeTransformFeedbackInternal();
|
||||
|
||||
Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance);
|
||||
}
|
||||
}
|
||||
@@ -500,34 +561,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type)
|
||||
{
|
||||
_indexBuffer.Dispose();
|
||||
|
||||
if (buffer.Handle != BufferHandle.Null)
|
||||
{
|
||||
Auto<DisposableBuffer> ib = null;
|
||||
int offset = buffer.Offset;
|
||||
int size = buffer.Size;
|
||||
|
||||
if (type == GAL.IndexType.UByte && !Gd.Capabilities.SupportsIndexTypeUint8)
|
||||
{
|
||||
ib = Gd.BufferManager.GetBufferI8ToI16(Cbs, buffer.Handle, offset, size);
|
||||
offset = 0;
|
||||
size *= 2;
|
||||
type = GAL.IndexType.UShort;
|
||||
}
|
||||
else
|
||||
{
|
||||
ib = Gd.BufferManager.GetBuffer(CommandBuffer, buffer.Handle, false);
|
||||
}
|
||||
|
||||
_indexBuffer = new BufferState(ib, offset, size, type.Convert());
|
||||
_indexBuffer = new IndexBufferState(buffer.Handle, buffer.Offset, buffer.Size, type.Convert());
|
||||
}
|
||||
else
|
||||
{
|
||||
_indexBuffer = BufferState.Null;
|
||||
_indexBuffer = IndexBufferState.Null;
|
||||
}
|
||||
|
||||
_indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
|
||||
_needsIndexBufferRebind = true;
|
||||
}
|
||||
|
||||
public void SetLineParameters(float width, bool smooth)
|
||||
@@ -584,7 +627,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_topology = topology;
|
||||
|
||||
var vkTopology = topology.Convert();
|
||||
var vkTopology = Gd.TopologyRemap(topology).Convert();
|
||||
|
||||
_newState.Topology = vkTopology;
|
||||
|
||||
@@ -1127,9 +1170,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Commit changes to the support buffer before drawing.
|
||||
SupportBufferUpdater.Commit();
|
||||
|
||||
if (_needsIndexBufferRebind)
|
||||
if (_needsIndexBufferRebind && _indexBufferPattern == null)
|
||||
{
|
||||
_indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
|
||||
_indexBuffer.BindIndexBuffer(Gd, Cbs);
|
||||
_needsIndexBufferRebind = false;
|
||||
}
|
||||
|
||||
@@ -1265,7 +1308,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_renderPass?.Dispose();
|
||||
_framebuffer?.Dispose();
|
||||
_indexBuffer.Dispose();
|
||||
_newState.Dispose();
|
||||
_descriptorSetUpdater.Dispose();
|
||||
|
||||
|
@@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
|
||||
|
||||
pipeline.Topology = state.Topology.Convert();
|
||||
pipeline.Topology = gd.TopologyRemap(state.Topology).Convert();
|
||||
|
||||
int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
|
||||
|
@@ -471,6 +471,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
|
||||
}
|
||||
|
||||
public GAL.PrimitiveTopology TopologyRemap(GAL.PrimitiveTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
GAL.PrimitiveTopology.Quads => GAL.PrimitiveTopology.Triangles,
|
||||
GAL.PrimitiveTopology.QuadStrip => GAL.PrimitiveTopology.TriangleStrip,
|
||||
_ => topology
|
||||
};
|
||||
}
|
||||
|
||||
public bool TopologyUnsupported(GAL.PrimitiveTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
GAL.PrimitiveTopology.Quads => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public void Initialize(GraphicsDebugLevel logLevel)
|
||||
{
|
||||
SetupContext(logLevel);
|
||||
|
Reference in New Issue
Block a user