diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs index 802b73b860..be392fe0e7 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs @@ -257,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects scissors[0] = new Rectangle(0, 0, texture.Width, texture.Height); - _pipeline.SetRenderTarget(texture.GetImageViewForAttachment(), (uint)texture.Width, (uint)texture.Height, false, texture.VkFormat); + _pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height); _pipeline.SetRenderTargetColorMasks(colorMasks); _pipeline.SetScissors(scissors); _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); diff --git a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs index 5f767df167..a12e3efd0f 100644 --- a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs +++ b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.GAL; using System; +using System.Collections.Generic; using VkFormat = Silk.NET.Vulkan.Format; namespace Ryujinx.Graphics.Vulkan @@ -7,10 +8,12 @@ namespace Ryujinx.Graphics.Vulkan static class FormatTable { private static readonly VkFormat[] _table; + private static readonly Dictionary _reverseMap; static FormatTable() { _table = new VkFormat[Enum.GetNames(typeof(Format)).Length]; + _reverseMap = new Dictionary(); #pragma warning disable IDE0055 // Disable formatting Add(Format.R8Unorm, VkFormat.R8Unorm); @@ -164,6 +167,7 @@ namespace Ryujinx.Graphics.Vulkan private static void Add(Format format, VkFormat vkFormat) { _table[(int)format] = vkFormat; + _reverseMap[vkFormat] = format; } public static VkFormat GetFormat(Format format) @@ -171,6 +175,16 @@ namespace Ryujinx.Graphics.Vulkan return _table[(int)format]; } + public static Format GetFormat(VkFormat format) + { + if (!_reverseMap.TryGetValue(format, out Format result)) + { + return Format.B8G8R8A8Unorm; + } + + return result; + } + public static Format ConvertRgba8SrgbToUnorm(Format format) { return format switch diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 458a164649..af22f26560 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -12,6 +12,8 @@ namespace Ryujinx.Graphics.Vulkan private readonly Auto[] _attachments; private readonly TextureView[] _colors; private readonly TextureView _depthStencil; + private readonly TextureView[] _colorsCanonical; + private readonly TextureView _baseAttachment; private readonly uint _validColorAttachments; public uint Width { get; } @@ -28,25 +30,31 @@ namespace Ryujinx.Graphics.Vulkan public bool HasDepthStencil { get; } public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0); - public FramebufferParams( - Device device, - Auto view, - uint width, - uint height, - uint samples, - bool isDepthStencil, - VkFormat format) + public FramebufferParams(Device device, TextureView view, uint width, uint height) { + bool isDepthStencil = view.Info.Format.IsDepthOrStencil(); + _device = device; - _attachments = new[] { view }; + _attachments = new[] { view.GetImageViewForAttachment() }; _validColorAttachments = isDepthStencil ? 0u : 1u; + _baseAttachment = view; + + if (isDepthStencil) + { + _depthStencil = view; + } + else + { + _colors = new TextureView[] { view }; + _colorsCanonical = _colors; + } Width = width; Height = height; Layers = 1; - AttachmentSamples = new[] { samples }; - AttachmentFormats = new[] { format }; + AttachmentSamples = new[] { (uint)view.Info.Samples }; + AttachmentFormats = new[] { view.VkFormat }; AttachmentIndices = isDepthStencil ? Array.Empty() : new[] { 0 }; AttachmentsCount = 1; @@ -64,6 +72,7 @@ namespace Ryujinx.Graphics.Vulkan _attachments = new Auto[count]; _colors = new TextureView[colorsCount]; + _colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray(); AttachmentSamples = new uint[count]; AttachmentFormats = new VkFormat[count]; @@ -86,6 +95,7 @@ namespace Ryujinx.Graphics.Vulkan _attachments[index] = texture.GetImageViewForAttachment(); _colors[index] = texture; _validColorAttachments |= 1u << bindIndex; + _baseAttachment = texture; AttachmentSamples[index] = (uint)texture.Info.Samples; AttachmentFormats[index] = texture.VkFormat; @@ -115,6 +125,7 @@ namespace Ryujinx.Graphics.Vulkan { _attachments[count - 1] = dsTexture.GetImageViewForAttachment(); _depthStencil = dsTexture; + _baseAttachment ??= dsTexture; AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples; AttachmentFormats[count - 1] = dsTexture.VkFormat; @@ -251,19 +262,11 @@ namespace Ryujinx.Graphics.Vulkan public void InsertClearBarrier(CommandBufferScoped cbs, int index) { - if (_colors != null) - { - int realIndex = Array.IndexOf(AttachmentIndices, index); - - if (realIndex != -1) - { - _colors[realIndex].Storage?.InsertReadToWriteBarrier( - cbs, - AccessFlags.ColorAttachmentWriteBit, - PipelineStageFlags.ColorAttachmentOutputBit, - insideRenderPass: true); - } - } + _colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier( + cbs, + AccessFlags.ColorAttachmentWriteBit, + PipelineStageFlags.ColorAttachmentOutputBit, + insideRenderPass: true); } public void InsertClearBarrierDS(CommandBufferScoped cbs) @@ -274,5 +277,61 @@ namespace Ryujinx.Graphics.Vulkan PipelineStageFlags.LateFragmentTestsBit, insideRenderPass: true); } + + public TextureView[] GetAttachmentViews() + { + var result = new TextureView[_attachments.Length]; + + _colors?.CopyTo(result, 0); + + if (_depthStencil != null) + { + result[^1] = _depthStencil; + } + + return result; + } + + public RenderPassCacheKey GetRenderPassCacheKey() + { + return new RenderPassCacheKey(_depthStencil, _colorsCanonical); + } + + public void InsertLoadOpBarriers(CommandBufferScoped cbs) + { + if (_colors != null) + { + foreach (var color in _colors) + { + // If Clear or DontCare were used, this would need to be write bit. + color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit); + color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit); + } + } + + if (_depthStencil != null) + { + _depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit); + _depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit); + } + } + + public (Auto renderPass, Auto framebuffer) GetPassAndFramebuffer( + VulkanRenderer gd, + Device device, + CommandBufferScoped cbs) + { + return _baseAttachment.GetPassAndFramebuffer(gd, device, cbs, this); + } + + public TextureView GetColorView(int index) + { + return _colorsCanonical[index]; + } + + public TextureView GetDepthStencilView() + { + return _depthStencil; + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs b/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs index ff4eb78904..3796e3c524 100644 --- a/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs +++ b/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Ryujinx.Graphics.Vulkan { @@ -20,20 +21,29 @@ namespace Ryujinx.Graphics.Vulkan public TValue Value; } - private readonly Entry[][] _hashTable = new Entry[TotalBuckets][]; + private struct Bucket + { + public int Length; + public Entry[] Entries; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Span AsSpan() + { + return Entries == null ? Span.Empty : Entries.AsSpan(0, Length); + } + } + + private readonly Bucket[] _hashTable = new Bucket[TotalBuckets]; public IEnumerable Keys { get { - foreach (Entry[] bucket in _hashTable) + foreach (Bucket bucket in _hashTable) { - if (bucket != null) + for (int i = 0; i < bucket.Length; i++) { - foreach (Entry entry in bucket) - { - yield return entry.Key; - } + yield return bucket.Entries[i].Key; } } } @@ -43,14 +53,11 @@ namespace Ryujinx.Graphics.Vulkan { get { - foreach (Entry[] bucket in _hashTable) + foreach (Bucket bucket in _hashTable) { - if (bucket != null) + for (int i = 0; i < bucket.Length; i++) { - foreach (Entry entry in bucket) - { - yield return entry.Value; - } + yield return bucket.Entries[i].Value; } } } @@ -68,40 +75,64 @@ namespace Ryujinx.Graphics.Vulkan int hashCode = key.GetHashCode(); int bucketIndex = hashCode & TotalBucketsMask; - var bucket = _hashTable[bucketIndex]; - if (bucket != null) + ref var bucket = ref _hashTable[bucketIndex]; + if (bucket.Entries != null) { int index = bucket.Length; - Array.Resize(ref _hashTable[bucketIndex], index + 1); + if (index >= bucket.Entries.Length) + { + Array.Resize(ref bucket.Entries, index + 1); + } - _hashTable[bucketIndex][index] = entry; + bucket.Entries[index] = entry; } else { - _hashTable[bucketIndex] = new[] + bucket.Entries = new[] { entry, }; } + + bucket.Length++; + } + + public bool Remove(ref TKey key) + { + int hashCode = key.GetHashCode(); + + ref var bucket = ref _hashTable[hashCode & TotalBucketsMask]; + var entries = bucket.AsSpan(); + for (int i = 0; i < entries.Length; i++) + { + ref var entry = ref entries[i]; + + if (entry.Hash == hashCode && entry.Key.Equals(ref key)) + { + entries[(i + 1)..].CopyTo(entries[i..]); + bucket.Length--; + + return true; + } + } + + return false; } public bool TryGetValue(ref TKey key, out TValue value) { int hashCode = key.GetHashCode(); - var bucket = _hashTable[hashCode & TotalBucketsMask]; - if (bucket != null) + var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan(); + for (int i = 0; i < entries.Length; i++) { - for (int i = 0; i < bucket.Length; i++) - { - ref var entry = ref bucket[i]; + ref var entry = ref entries[i]; - if (entry.Hash == hashCode && entry.Key.Equals(ref key)) - { - value = entry.Value; - return true; - } + if (entry.Hash == hashCode && entry.Key.Equals(ref key)) + { + value = entry.Value; + return true; } } diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs index ce84f75218..c0ded5b3bd 100644 --- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -256,17 +256,8 @@ namespace Ryujinx.Graphics.Vulkan using var cbs = gd.CommandBufferPool.Rent(); - var dstFormat = dst.VkFormat; - var dstSamples = dst.Info.Samples; - for (int l = 0; l < levels; l++) { - int srcWidth = Math.Max(1, src.Width >> l); - int srcHeight = Math.Max(1, src.Height >> l); - - int dstWidth = Math.Max(1, dst.Width >> l); - int dstHeight = Math.Max(1, dst.Height >> l); - var mipSrcRegion = new Extents2D( srcRegion.X1 >> l, srcRegion.Y1 >> l, @@ -290,11 +281,7 @@ namespace Ryujinx.Graphics.Vulkan gd, cbs, srcView, - dst.GetImageViewForAttachment(), - dstWidth, - dstHeight, - dstSamples, - dstFormat, + dstView, mipSrcRegion, mipDstRegion); } @@ -304,12 +291,7 @@ namespace Ryujinx.Graphics.Vulkan gd, cbs, srcView, - dst.GetImageViewForAttachment(), - dstWidth, - dstHeight, - dstSamples, - dstFormat, - false, + dstView, mipSrcRegion, mipDstRegion, linearFilter, @@ -367,12 +349,7 @@ namespace Ryujinx.Graphics.Vulkan gd, cbs, srcView, - dstView.GetImageViewForAttachment(), - dstView.Width, - dstView.Height, - dstView.Info.Samples, - dstView.VkFormat, - dstView.Info.Format.IsDepthOrStencil(), + dstView, extents, extents, false); @@ -394,12 +371,7 @@ namespace Ryujinx.Graphics.Vulkan VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, - Auto dst, - int dstWidth, - int dstHeight, - int dstSamples, - VkFormat dstFormat, - bool dstIsDepthOrStencil, + TextureView dst, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter, @@ -453,6 +425,8 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); + bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil(); + if (dstIsDepthOrStencil) { _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); @@ -471,7 +445,10 @@ namespace Ryujinx.Graphics.Vulkan _pipeline.SetProgram(_programColorBlit); } - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, dstIsDepthOrStencil, dstFormat); + int dstWidth = dst.Width; + int dstHeight = dst.Height; + + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); @@ -496,11 +473,7 @@ namespace Ryujinx.Graphics.Vulkan VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, - Auto dst, - int dstWidth, - int dstHeight, - int dstSamples, - VkFormat dstFormat, + TextureView dst, Extents2D srcRegion, Extents2D dstRegion) { @@ -548,7 +521,10 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, true, dstFormat); + int dstWidth = dst.Width; + int dstHeight = dst.Height; + + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); _pipeline.SetViewports(viewports); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -660,12 +636,11 @@ namespace Ryujinx.Graphics.Vulkan public void Clear( VulkanRenderer gd, - Auto dst, + TextureView dst, ReadOnlySpan clearColor, uint componentMask, int dstWidth, int dstHeight, - VkFormat dstFormat, ComponentType type, Rectangle scissor) { @@ -710,7 +685,7 @@ namespace Ryujinx.Graphics.Vulkan } _pipeline.SetProgram(program); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetRenderTargetColorMasks(new[] { componentMask }); _pipeline.SetViewports(viewports); _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); @@ -721,7 +696,7 @@ namespace Ryujinx.Graphics.Vulkan public void Clear( VulkanRenderer gd, - Auto dst, + TextureView dst, float depthValue, bool depthMask, int stencilValue, @@ -757,7 +732,7 @@ namespace Ryujinx.Graphics.Vulkan 1f); _pipeline.SetProgram(_programDepthStencilClear); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, true, dstFormat); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetViewports(viewports); _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -1163,12 +1138,7 @@ namespace Ryujinx.Graphics.Vulkan var srcView = Create2DLayerView(src, srcLayer + z, 0); var dstView = Create2DLayerView(dst, dstLayer + z, 0); - _pipeline.SetRenderTarget( - dstView.GetImageViewForAttachment(), - (uint)dst.Width, - (uint)dst.Height, - true, - dst.VkFormat); + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); CopyMSDraw(srcView, aspectFlags, fromMS: true); @@ -1294,13 +1264,7 @@ namespace Ryujinx.Graphics.Vulkan var srcView = Create2DLayerView(src, srcLayer + z, 0); var dstView = Create2DLayerView(dst, dstLayer + z, 0); - _pipeline.SetRenderTarget( - dstView.GetImageViewForAttachment(), - (uint)dst.Width, - (uint)dst.Height, - (uint)samples, - true, - dst.VkFormat); + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); CopyMSDraw(srcView, aspectFlags, fromMS: false); @@ -1328,13 +1292,7 @@ namespace Ryujinx.Graphics.Vulkan var dstView = Create2DLayerView(dst, dstLayer + z, 0); _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, srcView, null); - _pipeline.SetRenderTarget( - dstView.GetView(format).GetImageViewForAttachment(), - (uint)dst.Width, - (uint)dst.Height, - (uint)samples, - false, - vkFormat); + _pipeline.SetRenderTarget(dstView.GetView(format), (uint)dst.Width, (uint)dst.Height); _pipeline.Draw(4, 1, 0, 0); @@ -1471,9 +1429,9 @@ namespace Ryujinx.Graphics.Vulkan }; var info = new TextureCreateInfo( - from.Info.Width, - from.Info.Height, - from.Info.Depth, + Math.Max(1, from.Info.Width >> level), + Math.Max(1, from.Info.Height >> level), + 1, 1, from.Info.Samples, from.Info.BlockWidth, diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 61215b672e..3aef1317af 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan protected FramebufferParams FramebufferParams; private Auto _framebuffer; private Auto _renderPass; + private RenderPassHolder _nullRenderPass; private int _writtenAttachmentCount; private bool _framebufferUsingColorWriteMask; @@ -1488,98 +1489,22 @@ namespace Ryujinx.Graphics.Vulkan protected unsafe void CreateRenderPass() { - const int MaxAttachments = Constants.MaxRenderTargets + 1; - - AttachmentDescription[] attachmentDescs = null; - - var subpass = new SubpassDescription - { - PipelineBindPoint = PipelineBindPoint.Graphics, - }; - - AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; - var hasFramebuffer = FramebufferParams != null; - if (hasFramebuffer && FramebufferParams.AttachmentsCount != 0) - { - attachmentDescs = new AttachmentDescription[FramebufferParams.AttachmentsCount]; - - for (int i = 0; i < FramebufferParams.AttachmentsCount; i++) - { - attachmentDescs[i] = new AttachmentDescription( - 0, - FramebufferParams.AttachmentFormats[i], - TextureStorage.ConvertToSampleCountFlags(Gd.Capabilities.SupportedSampleCounts, FramebufferParams.AttachmentSamples[i]), - AttachmentLoadOp.Load, - AttachmentStoreOp.Store, - AttachmentLoadOp.Load, - AttachmentStoreOp.Store, - ImageLayout.General, - ImageLayout.General); - } - - int colorAttachmentsCount = FramebufferParams.ColorAttachmentsCount; - - if (colorAttachmentsCount > MaxAttachments - 1) - { - colorAttachmentsCount = MaxAttachments - 1; - } - - if (colorAttachmentsCount != 0) - { - int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex; - subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1; - subpass.PColorAttachments = &attachmentReferences[0]; - - // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. - for (int i = 0; i <= maxAttachmentIndex; i++) - { - subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); - } - - for (int i = 0; i < colorAttachmentsCount; i++) - { - int bindIndex = FramebufferParams.AttachmentIndices[i]; - - subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); - } - } - - if (FramebufferParams.HasDepthStencil) - { - uint dsIndex = (uint)FramebufferParams.AttachmentsCount - 1; - - subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; - *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); - } - } - - var subpassDependency = PipelineConverter.CreateSubpassDependency(); - - fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) - { - var renderPassCreateInfo = new RenderPassCreateInfo - { - SType = StructureType.RenderPassCreateInfo, - PAttachments = pAttachmentDescs, - AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, - PSubpasses = &subpass, - SubpassCount = 1, - PDependencies = &subpassDependency, - DependencyCount = 1, - }; - - Gd.Api.CreateRenderPass(Device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); - - _renderPass?.Dispose(); - _renderPass = new Auto(new DisposableRenderPass(Gd.Api, Device, renderPass)); - } - EndRenderPass(); - _framebuffer?.Dispose(); - _framebuffer = hasFramebuffer ? FramebufferParams.Create(Gd.Api, Cbs, _renderPass) : null; + if (!hasFramebuffer || FramebufferParams.AttachmentsCount == 0) + { + // Use the null framebuffer. + _nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams); + + _renderPass = _nullRenderPass.GetRenderPass(); + _framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams); + } + else + { + (_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs); + } } protected void SignalStateChange() @@ -1770,8 +1695,7 @@ namespace Ryujinx.Graphics.Vulkan { if (disposing) { - _renderPass?.Dispose(); - _framebuffer?.Dispose(); + _nullRenderPass?.Dispose(); _newState.Dispose(); _descriptorSetUpdater.Dispose(); _vertexBufferUpdater.Dispose(); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs index a3e6818f3f..6c4419cd2f 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Vulkan { // We can't use CmdClearAttachments if not writing all components, // because on Vulkan, the pipeline state does not affect clears. - var dstTexture = FramebufferParams.GetAttachment(index); + var dstTexture = FramebufferParams.GetColorView(index); if (dstTexture == null) { return; @@ -71,7 +71,6 @@ namespace Ryujinx.Graphics.Vulkan componentMask, (int)FramebufferParams.Width, (int)FramebufferParams.Height, - FramebufferParams.AttachmentFormats[index], FramebufferParams.GetAttachmentComponentType(index), ClearScissor); } @@ -92,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan { // We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits, // because on Vulkan, the pipeline state does not affect clears. - var dstTexture = FramebufferParams.GetDepthStencilAttachment(); + var dstTexture = FramebufferParams.GetDepthStencilView(); if (dstTexture == null) { return; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs index 0a871a5c8e..dfbf19013f 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs @@ -9,21 +9,16 @@ namespace Ryujinx.Graphics.Vulkan { } - public void SetRenderTarget(Auto view, uint width, uint height, bool isDepthStencil, VkFormat format) + public void SetRenderTarget(TextureView view, uint width, uint height) { - SetRenderTarget(view, width, height, 1u, isDepthStencil, format); - } - - public void SetRenderTarget(Auto view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format) - { - CreateFramebuffer(view, width, height, samples, isDepthStencil, format); + CreateFramebuffer(view, width, height); CreateRenderPass(); SignalStateChange(); } - private void CreateFramebuffer(Auto view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format) + private void CreateFramebuffer(TextureView view, uint width, uint height) { - FramebufferParams = new FramebufferParams(Device, view, width, height, samples, isDepthStencil, format); + FramebufferParams = new FramebufferParams(Device, view, width, height); UpdatePipelineAttachmentFormats(); } diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs new file mode 100644 index 0000000000..7c57b8feb2 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + internal readonly struct RenderPassCacheKey : IRefEquatable + { + private readonly TextureView _depthStencil; + private readonly TextureView[] _colors; + + public RenderPassCacheKey(TextureView depthStencil, TextureView[] colors) + { + _depthStencil = depthStencil; + _colors = colors; + } + + public override int GetHashCode() + { + HashCode hc = new(); + + hc.Add(_depthStencil); + + if (_colors != null) + { + foreach (var color in _colors) + { + hc.Add(color); + } + } + + return hc.ToHashCode(); + } + + public bool Equals(ref RenderPassCacheKey other) + { + bool colorsNull = _colors == null; + bool otherNull = other._colors == null; + return other._depthStencil == _depthStencil && + colorsNull == otherNull && + (colorsNull || other._colors.SequenceEqual(_colors)); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs new file mode 100644 index 0000000000..3d883b2d51 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -0,0 +1,180 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class RenderPassHolder + { + private readonly struct FramebufferCacheKey : IRefEquatable + { + private readonly uint _width; + private readonly uint _height; + private readonly uint _layers; + + public FramebufferCacheKey(uint width, uint height, uint layers) + { + _width = width; + _height = height; + _layers = layers; + } + + public override int GetHashCode() + { + return HashCode.Combine(_width, _height, _layers); + } + + public bool Equals(ref FramebufferCacheKey other) + { + return other._width == _width && other._height == _height && other._layers == _layers; + } + } + + private readonly TextureView[] _textures; + private readonly Auto _renderPass; + private readonly HashTableSlim> _framebuffers; + private readonly RenderPassCacheKey _key; + + public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb) + { + // Create render pass using framebuffer params. + + const int MaxAttachments = Constants.MaxRenderTargets + 1; + + AttachmentDescription[] attachmentDescs = null; + + var subpass = new SubpassDescription + { + PipelineBindPoint = PipelineBindPoint.Graphics, + }; + + AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; + + var hasFramebuffer = fb != null; + + if (hasFramebuffer && fb.AttachmentsCount != 0) + { + attachmentDescs = new AttachmentDescription[fb.AttachmentsCount]; + + for (int i = 0; i < fb.AttachmentsCount; i++) + { + attachmentDescs[i] = new AttachmentDescription( + 0, + fb.AttachmentFormats[i], + TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, fb.AttachmentSamples[i]), + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + } + + int colorAttachmentsCount = fb.ColorAttachmentsCount; + + if (colorAttachmentsCount > MaxAttachments - 1) + { + colorAttachmentsCount = MaxAttachments - 1; + } + + if (colorAttachmentsCount != 0) + { + int maxAttachmentIndex = fb.MaxColorAttachmentIndex; + subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1; + subpass.PColorAttachments = &attachmentReferences[0]; + + // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. + for (int i = 0; i <= maxAttachmentIndex; i++) + { + subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); + } + + for (int i = 0; i < colorAttachmentsCount; i++) + { + int bindIndex = fb.AttachmentIndices[i]; + + subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); + } + } + + if (fb.HasDepthStencil) + { + uint dsIndex = (uint)fb.AttachmentsCount - 1; + + subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; + *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); + } + } + + var subpassDependency = PipelineConverter.CreateSubpassDependency(); + + fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) + { + var renderPassCreateInfo = new RenderPassCreateInfo + { + SType = StructureType.RenderPassCreateInfo, + PAttachments = pAttachmentDescs, + AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, + PSubpasses = &subpass, + SubpassCount = 1, + PDependencies = &subpassDependency, + DependencyCount = 1, + }; + + gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + + _renderPass?.Dispose(); + _renderPass = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); + } + + _framebuffers = new HashTableSlim>(); + + // Register this render pass with all render target views. + + var textures = fb.GetAttachmentViews(); + + foreach (var texture in textures) + { + texture.AddRenderPass(key, this); + } + + _textures = textures; + _key = key; + } + + public Auto GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb) + { + var key = new FramebufferCacheKey(fb.Width, fb.Height, fb.Layers); + + if (!_framebuffers.TryGetValue(ref key, out Auto result)) + { + result = fb.Create(gd.Api, cbs, _renderPass); + + _framebuffers.Add(ref key, result); + } + + return result; + } + + public Auto GetRenderPass() + { + return _renderPass; + } + + public void Dispose() + { + // Dispose all framebuffers + + foreach (var fb in _framebuffers.Values) + { + fb.Dispose(); + } + + // Notify all texture views that this render pass has been disposed. + + foreach (var texture in _textures) + { + texture.RemoveRenderPass(_key); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index f5b80f9484..393db26116 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; using System.Collections.Generic; +using System.Linq; using Format = Ryujinx.Graphics.GAL.Format; using VkBuffer = Silk.NET.Vulkan.Buffer; using VkFormat = Silk.NET.Vulkan.Format; @@ -23,6 +24,8 @@ namespace Ryujinx.Graphics.Vulkan private readonly TextureCreateInfo _info; + private HashTableSlim _renderPasses; + public TextureCreateInfo Info => _info; public TextureStorage Storage { get; } @@ -158,6 +161,26 @@ namespace Ryujinx.Graphics.Vulkan Valid = true; } + /// + /// Create a texture view for an existing swapchain image view. + /// Does not set storage, so only appropriate for swapchain use. + /// + /// Do not use this for normal textures, and make sure uses do not try to read storage. + public TextureView(VulkanRenderer gd, Device device, DisposableImageView view, TextureCreateInfo info, VkFormat format) + { + _gd = gd; + _device = device; + + _imageView = new Auto(view); + _imageViewDraw = _imageView; + _imageViewIdentity = _imageView; + _info = info; + + VkFormat = format; + + Valid = true; + } + public Auto GetImage() { return Storage.GetImage(); @@ -939,6 +962,34 @@ namespace Ryujinx.Graphics.Vulkan throw new NotImplementedException(); } + public (Auto renderPass, Auto framebuffer) GetPassAndFramebuffer( + VulkanRenderer gd, + Device device, + CommandBufferScoped cbs, + FramebufferParams fb) + { + var key = fb.GetRenderPassCacheKey(); + + if (_renderPasses == null || !_renderPasses.TryGetValue(ref key, out RenderPassHolder rpHolder)) + { + rpHolder = new RenderPassHolder(gd, device, key, fb); + } + + return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb)); + } + + public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass) + { + _renderPasses ??= new HashTableSlim(); + + _renderPasses.Add(ref key, renderPass); + } + + public void RemoveRenderPass(RenderPassCacheKey key) + { + _renderPasses.Remove(ref key); + } + protected virtual void Dispose(bool disposing) { if (disposing) @@ -948,15 +999,29 @@ namespace Ryujinx.Graphics.Vulkan if (_gd.Textures.Remove(this)) { _imageView.Dispose(); - _imageViewIdentity.Dispose(); _imageView2dArray?.Dispose(); + if (_imageViewIdentity != _imageView) + { + _imageViewIdentity.Dispose(); + } + if (_imageViewDraw != _imageViewIdentity) { _imageViewDraw.Dispose(); } Storage.DecrementViewsCount(); + + if (_renderPasses != null) + { + var renderPasses = _renderPasses.Values.ToArray(); + + foreach (var pass in renderPasses) + { + pass.Dispose(); + } + } } } } diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index 2c5764a990..5ddb6eedae 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan private SwapchainKHR _swapchain; private Image[] _swapchainImages; - private Auto[] _swapchainImageViews; + private TextureView[] _swapchainImageViews; private Semaphore[] _imageAvailableSemaphores; private Semaphore[] _renderFinishedSemaphores; @@ -143,6 +143,23 @@ namespace Ryujinx.Graphics.Vulkan Clipped = true, }; + var textureCreateInfo = new TextureCreateInfo( + _width, + _height, + 1, + 1, + 1, + 1, + 1, + 1, + FormatTable.GetFormat(surfaceFormat.Format), + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha); + _gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError(); _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null); @@ -154,11 +171,11 @@ namespace Ryujinx.Graphics.Vulkan _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages); } - _swapchainImageViews = new Auto[imageCount]; + _swapchainImageViews = new TextureView[imageCount]; for (int i = 0; i < _swapchainImageViews.Length; i++) { - _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format); + _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo); } var semaphoreCreateInfo = new SemaphoreCreateInfo @@ -181,7 +198,7 @@ namespace Ryujinx.Graphics.Vulkan } } - private unsafe Auto CreateSwapchainImageView(Image swapchainImage, VkFormat format) + private unsafe TextureView CreateSwapchainImageView(Image swapchainImage, VkFormat format, TextureCreateInfo info) { var componentMapping = new ComponentMapping( ComponentSwizzle.R, @@ -204,7 +221,8 @@ namespace Ryujinx.Graphics.Vulkan }; _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError(); - return new Auto(new DisposableImageView(_gd.Api, _device, imageView)); + + return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format); } private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled) @@ -406,7 +424,7 @@ namespace Ryujinx.Graphics.Vulkan _scalingFilter.Run( view, cbs, - _swapchainImageViews[nextImage], + _swapchainImageViews[nextImage].GetImageViewForAttachment(), _format, _width, _height, @@ -421,11 +439,6 @@ namespace Ryujinx.Graphics.Vulkan cbs, view, _swapchainImageViews[nextImage], - _width, - _height, - 1, - _format, - false, new Extents2D(srcX0, srcY0, srcX1, srcY1), new Extents2D(dstX0, dstY1, dstX1, dstY0), _isLinear,