using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; using System.Numerics; using System.Runtime.CompilerServices; namespace Ryujinx.Graphics.Vulkan { class DescriptorSetUpdater { private readonly VulkanRenderer _gd; private readonly PipelineBase _pipeline; private ShaderCollection _program; private Auto[] _uniformBufferRefs; private Auto[] _storageBufferRefs; private Auto[] _textureRefs; private Auto[] _samplerRefs; private Auto[] _imageRefs; private TextureBuffer[] _bufferTextureRefs; private TextureBuffer[] _bufferImageRefs; private GAL.Format[] _bufferImageFormats; private DescriptorBufferInfo[] _uniformBuffers; private DescriptorBufferInfo[] _storageBuffers; private DescriptorImageInfo[] _textures; private DescriptorImageInfo[] _images; private BufferView[] _bufferTextures; private BufferView[] _bufferImages; private bool[] _uniformSet; private bool[] _storageSet; private Silk.NET.Vulkan.Buffer _cachedSupportBuffer; [Flags] private enum DirtyFlags { None = 0, Uniform = 1 << 0, Storage = 1 << 1, Texture = 1 << 2, Image = 1 << 3, All = Uniform | Storage | Texture | Image } private DirtyFlags _dirty; private readonly BufferHolder _dummyBuffer; private readonly TextureView _dummyTexture; private readonly SamplerHolder _dummySampler; public DescriptorSetUpdater(VulkanRenderer gd, PipelineBase pipeline) { _gd = gd; _pipeline = pipeline; // Some of the bindings counts needs to be multiplied by 2 because we have buffer and // regular textures/images interleaved on the same descriptor set. _uniformBufferRefs = new Auto[Constants.MaxUniformBufferBindings]; _storageBufferRefs = new Auto[Constants.MaxStorageBufferBindings]; _textureRefs = new Auto[Constants.MaxTextureBindings * 2]; _samplerRefs = new Auto[Constants.MaxTextureBindings * 2]; _imageRefs = new Auto[Constants.MaxImageBindings * 2]; _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2]; _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; _bufferImageFormats = new GAL.Format[Constants.MaxImageBindings * 2]; _uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings]; _storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings]; _textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage]; _images = new DescriptorImageInfo[Constants.MaxImagesPerStage]; _bufferTextures = new BufferView[Constants.MaxTexturesPerStage]; _bufferImages = new BufferView[Constants.MaxImagesPerStage]; var initialImageInfo = new DescriptorImageInfo() { ImageLayout = ImageLayout.General }; _textures.AsSpan().Fill(initialImageInfo); _images.AsSpan().Fill(initialImageInfo); _uniformSet = new bool[Constants.MaxUniformBufferBindings]; _storageSet = new bool[Constants.MaxStorageBufferBindings]; if (gd.Capabilities.SupportsNullDescriptors) { // If null descriptors are supported, we can pass null as the handle. _dummyBuffer = null; } else { // If null descriptors are not supported, we need to pass the handle of a dummy buffer on unused bindings. _dummyBuffer = gd.BufferManager.Create(gd, 0x10000, forConditionalRendering: false, baseType: BufferAllocationType.DeviceLocal); } _dummyTexture = gd.CreateTextureView(new TextureCreateInfo( 1, 1, 1, 1, 1, 1, 1, 4, GAL.Format.R8G8B8A8Unorm, DepthStencilMode.Depth, Target.Texture2D, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha), 1f); _dummySampler = (SamplerHolder)gd.CreateSampler(new GAL.SamplerCreateInfo( MinFilter.Nearest, MagFilter.Nearest, false, AddressMode.Repeat, AddressMode.Repeat, AddressMode.Repeat, CompareMode.None, GAL.CompareOp.Always, new ColorF(0, 0, 0, 0), 0, 0, 0, 1f)); } public void Initialize() { Span dummyTextureData = stackalloc byte[4]; _dummyTexture.SetData(dummyTextureData); } public void SetProgram(ShaderCollection program) { _program = program; _dirty = DirtyFlags.All; } public void SetImage(int binding, ITexture image, GAL.Format imageFormat) { if (image is TextureBuffer imageBuffer) { _bufferImageRefs[binding] = imageBuffer; _bufferImageFormats[binding] = imageFormat; } else if (image is TextureView view) { _imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView(); } else { _imageRefs[binding] = null; _bufferImageRefs[binding] = null; _bufferImageFormats[binding] = default; } SignalDirty(DirtyFlags.Image); } public void SetImage(int binding, Auto image) { _imageRefs[binding] = image; SignalDirty(DirtyFlags.Image); } public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan buffers) { for (int i = 0; i < buffers.Length; i++) { var assignment = buffers[i]; var buffer = assignment.Range; int index = assignment.Binding; Auto vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false, isSSBO: true); ref Auto currentVkBuffer = ref _storageBufferRefs[index]; DescriptorBufferInfo info = new DescriptorBufferInfo() { Offset = (ulong)buffer.Offset, Range = (ulong)buffer.Size }; ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range) { _storageSet[index] = false; currentInfo = info; currentVkBuffer = vkBuffer; } } SignalDirty(DirtyFlags.Storage); } public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan> buffers) { for (int i = 0; i < buffers.Length; i++) { var vkBuffer = buffers[i]; int index = first + i; ref Auto currentVkBuffer = ref _storageBufferRefs[index]; DescriptorBufferInfo info = new DescriptorBufferInfo() { Offset = 0, Range = Vk.WholeSize }; ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range) { _storageSet[index] = false; currentInfo = info; currentVkBuffer = vkBuffer; } } SignalDirty(DirtyFlags.Storage); } public void SetTextureAndSampler( CommandBufferScoped cbs, ShaderStage stage, int binding, ITexture texture, ISampler sampler) { if (texture is TextureBuffer textureBuffer) { _bufferTextureRefs[binding] = textureBuffer; } else if (texture is TextureView view) { view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); _textureRefs[binding] = view.GetImageView(); _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler(); } else { _textureRefs[binding] = null; _samplerRefs[binding] = null; _bufferTextureRefs[binding] = null; } SignalDirty(DirtyFlags.Texture); } public void SetTextureAndSamplerIdentitySwizzle( CommandBufferScoped cbs, ShaderStage stage, int binding, ITexture texture, ISampler sampler) { if (texture is TextureView view) { view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); _textureRefs[binding] = view.GetIdentityImageView(); _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler(); SignalDirty(DirtyFlags.Texture); } else { SetTextureAndSampler(cbs, stage, binding, texture, sampler); } } public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan buffers) { for (int i = 0; i < buffers.Length; i++) { var assignment = buffers[i]; var buffer = assignment.Range; int index = assignment.Binding; Auto vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false); ref Auto currentVkBuffer = ref _uniformBufferRefs[index]; DescriptorBufferInfo info = new DescriptorBufferInfo() { Offset = (ulong)buffer.Offset, Range = (ulong)buffer.Size }; ref DescriptorBufferInfo currentInfo = ref _uniformBuffers[index]; if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range) { _uniformSet[index] = false; currentInfo = info; currentVkBuffer = vkBuffer; } } SignalDirty(DirtyFlags.Uniform); } private void SignalDirty(DirtyFlags flag) { _dirty |= flag; } public void UpdateAndBindDescriptorSets(CommandBufferScoped cbs, PipelineBindPoint pbp) { if ((_dirty & DirtyFlags.All) == 0) { return; } if (_dirty.HasFlag(DirtyFlags.Uniform)) { if (_program.UsePushDescriptors) { UpdateAndBindUniformBufferPd(cbs, pbp); } else { UpdateAndBind(cbs, PipelineBase.UniformSetIndex, pbp); } } if (_dirty.HasFlag(DirtyFlags.Storage)) { UpdateAndBind(cbs, PipelineBase.StorageSetIndex, pbp); } if (_dirty.HasFlag(DirtyFlags.Texture)) { UpdateAndBind(cbs, PipelineBase.TextureSetIndex, pbp); } if (_dirty.HasFlag(DirtyFlags.Image)) { UpdateAndBind(cbs, PipelineBase.ImageSetIndex, pbp); } _dirty = DirtyFlags.None; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UpdateBuffer( CommandBufferScoped cbs, ref DescriptorBufferInfo info, Auto buffer, Auto dummyBuffer) { info.Buffer = buffer?.Get(cbs, (int)info.Offset, (int)info.Range).Value ?? default; // The spec requires that buffers with null handle have offset as 0 and range as VK_WHOLE_SIZE. if (info.Buffer.Handle == 0) { info.Buffer = dummyBuffer?.Get(cbs).Value ?? default; info.Offset = 0; info.Range = Vk.WholeSize; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPoint pbp) { var program = _program; var bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0 && setIndex != PipelineBase.UniformSetIndex) { return; } var dummyBuffer = _dummyBuffer?.GetBuffer(); var dsc = program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs); if (!program.HasMinimalLayout) { if (isNew) { Initialize(cbs, setIndex, dsc); } if (setIndex == PipelineBase.UniformSetIndex) { Span uniformBuffer = stackalloc DescriptorBufferInfo[1]; if (!_uniformSet[0]) { _cachedSupportBuffer = _gd.BufferManager.GetBuffer(cbs.CommandBuffer, _pipeline.SupportBufferUpdater.Handle, false).Get(cbs, 0, SupportBuffer.RequiredSize).Value; _uniformSet[0] = true; } uniformBuffer[0] = new DescriptorBufferInfo() { Offset = 0, Range = (ulong)SupportBuffer.RequiredSize, Buffer = _cachedSupportBuffer }; dsc.UpdateBuffers(0, 0, uniformBuffer, DescriptorType.UniformBuffer); } } foreach (ResourceBindingSegment segment in bindingSegments) { int binding = segment.Binding; int count = segment.Count; if (setIndex == PipelineBase.UniformSetIndex) { for (int i = 0; i < count; i++) { int index = binding + i; if (!_uniformSet[index]) { UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer); _uniformSet[index] = true; } } ReadOnlySpan uniformBuffers = _uniformBuffers; dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer); } else if (setIndex == PipelineBase.StorageSetIndex) { for (int i = 0; i < count; i++) { int index = binding + i; if (!_storageSet[index]) { UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer); _storageSet[index] = true; } } ReadOnlySpan storageBuffers = _storageBuffers; dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer); } else if (setIndex == PipelineBase.TextureSetIndex) { if (segment.Type != ResourceType.BufferTexture) { Span textures = _textures; for (int i = 0; i < count; i++) { ref var texture = ref textures[i]; texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default; texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default; if (texture.ImageView.Handle == 0) { texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; } if (texture.Sampler.Handle == 0) { texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; } } dsc.UpdateImages(0, binding, textures.Slice(0, count), DescriptorType.CombinedImageSampler); } else { Span bufferTextures = _bufferTextures; for (int i = 0; i < count; i++) { bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default; } dsc.UpdateBufferImages(0, binding, bufferTextures.Slice(0, count), DescriptorType.UniformTexelBuffer); } } else if (setIndex == PipelineBase.ImageSetIndex) { if (segment.Type != ResourceType.BufferImage) { Span images = _images; for (int i = 0; i < count; i++) { images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default; } dsc.UpdateImages(0, binding, images.Slice(0, count), DescriptorType.StorageImage); } else { Span bufferImages = _bufferImages; for (int i = 0; i < count; i++) { bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default; } dsc.UpdateBufferImages(0, binding, bufferImages.Slice(0, count), DescriptorType.StorageTexelBuffer); } } } var sets = dsc.GetSets(); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } private unsafe void UpdateBuffers( CommandBufferScoped cbs, PipelineBindPoint pbp, int baseBinding, ReadOnlySpan bufferInfo, DescriptorType type) { if (bufferInfo.Length == 0) { return; } fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo) { var writeDescriptorSet = new WriteDescriptorSet { SType = StructureType.WriteDescriptorSet, DstBinding = (uint)baseBinding, DescriptorType = type, DescriptorCount = (uint)bufferInfo.Length, PBufferInfo = pBufferInfo }; _gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, _program.PipelineLayout, 0, 1, &writeDescriptorSet); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp) { if (!_uniformSet[0]) { Span uniformBuffer = stackalloc DescriptorBufferInfo[1]; uniformBuffer[0] = new DescriptorBufferInfo() { Offset = 0, Range = (ulong)SupportBuffer.RequiredSize, Buffer = _gd.BufferManager.GetBuffer(cbs.CommandBuffer, _pipeline.SupportBufferUpdater.Handle, false).Get(cbs, 0, SupportBuffer.RequiredSize).Value }; _uniformSet[0] = true; UpdateBuffers(cbs, pbp, 0, uniformBuffer, DescriptorType.UniformBuffer); } var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex]; var dummyBuffer = _dummyBuffer?.GetBuffer(); foreach (ResourceBindingSegment segment in bindingSegments) { int binding = segment.Binding; int count = segment.Count; bool doUpdate = false; for (int i = 0; i < count; i++) { int index = binding + i; if (!_uniformSet[index]) { UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer); _uniformSet[index] = true; doUpdate = true; } } if (doUpdate) { ReadOnlySpan uniformBuffers = _uniformBuffers; UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer); } } } private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc) { // We don't support clearing texture descriptors currently. if (setIndex != PipelineBase.UniformSetIndex && setIndex != PipelineBase.StorageSetIndex) { return; } var dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default; foreach (ResourceBindingSegment segment in _program.ClearSegments[setIndex]) { dsc.InitializeBuffers(0, segment.Binding, segment.Count, segment.Type.Convert(), dummyBuffer); } } public void SignalCommandBufferChange() { _dirty = DirtyFlags.All; Array.Clear(_uniformSet); Array.Clear(_storageSet); } private void SwapBuffer(Auto[] list, Auto from, Auto to) { for (int i = 0; i < list.Length; i++) { if (list[i] == from) { list[i] = to; } } } public void SwapBuffer(Auto from, Auto to) { SwapBuffer(_uniformBufferRefs, from, to); SwapBuffer(_storageBufferRefs, from, to); } protected virtual void Dispose(bool disposing) { if (disposing) { _dummyTexture.Dispose(); _dummySampler.Dispose(); } } public void Dispose() { Dispose(true); } } }