using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CompareOp = Ryujinx.Graphics.GAL.CompareOp; using Format = Ryujinx.Graphics.GAL.Format; using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; namespace Ryujinx.Graphics.Vulkan { class DescriptorSetUpdater { private const ulong StorageBufferMaxMirrorable = 0x2000; private const int ArrayGrowthSize = 16; private record struct BufferRef { public Auto Buffer; public int Offset; public bool Write; public BufferRef(Auto buffer) { Buffer = buffer; Offset = 0; Write = true; } public BufferRef(Auto buffer, ref BufferRange range) { Buffer = buffer; Offset = range.Offset; Write = range.Write; } } private record struct TextureRef { public ShaderStage Stage; public TextureView View; public Auto ImageView; public Auto Sampler; public TextureRef(ShaderStage stage, TextureView view, Auto imageView, Auto sampler) { Stage = stage; View = view; ImageView = imageView; Sampler = sampler; } } private record struct ImageRef { public ShaderStage Stage; public TextureView View; public Auto ImageView; public ImageRef(ShaderStage stage, TextureView view, Auto imageView) { Stage = stage; View = view; ImageView = imageView; } } private readonly record struct ArrayRef(ShaderStage Stage, T Array); private readonly VulkanRenderer _gd; private readonly Device _device; private ShaderCollection _program; private readonly BufferRef[] _uniformBufferRefs; private readonly BufferRef[] _storageBufferRefs; private readonly TextureRef[] _textureRefs; private readonly ImageRef[] _imageRefs; private readonly TextureBuffer[] _bufferTextureRefs; private readonly TextureBuffer[] _bufferImageRefs; private readonly Format[] _bufferImageFormats; private ArrayRef[] _textureArrayRefs; private ArrayRef[] _imageArrayRefs; private ArrayRef[] _textureArrayExtraRefs; private ArrayRef[] _imageArrayExtraRefs; private readonly DescriptorBufferInfo[] _uniformBuffers; private readonly DescriptorBufferInfo[] _storageBuffers; private readonly DescriptorImageInfo[] _textures; private readonly DescriptorImageInfo[] _images; private readonly BufferView[] _bufferTextures; private readonly BufferView[] _bufferImages; private readonly DescriptorSetTemplateUpdater _templateUpdater; private BitMapStruct> _uniformSet; private BitMapStruct> _storageSet; private BitMapStruct> _uniformMirrored; private BitMapStruct> _storageMirrored; private readonly int[] _uniformSetPd; private int _pdSequence = 1; private bool _updateDescriptorCacheCbIndex; [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 List FeedbackLoopHazards { get; private set; } public DescriptorSetUpdater(VulkanRenderer gd, Device device) { _gd = gd; _device = device; // 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 BufferRef[Constants.MaxUniformBufferBindings]; _storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings]; _textureRefs = new TextureRef[Constants.MaxTextureBindings * 2]; _imageRefs = new ImageRef[Constants.MaxImageBindings * 2]; _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2]; _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; _bufferImageFormats = new Format[Constants.MaxImageBindings * 2]; _textureArrayRefs = Array.Empty>(); _imageArrayRefs = Array.Empty>(); _textureArrayExtraRefs = Array.Empty>(); _imageArrayExtraRefs = Array.Empty>(); _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]; _uniformSetPd = new int[Constants.MaxUniformBufferBindings]; var initialImageInfo = new DescriptorImageInfo { ImageLayout = ImageLayout.General, }; _textures.AsSpan().Fill(initialImageInfo); _images.AsSpan().Fill(initialImageInfo); 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, Format.R8G8B8A8Unorm, DepthStencilMode.Depth, Target.Texture2D, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha)); _dummySampler = (SamplerHolder)gd.CreateSampler(new SamplerCreateInfo( MinFilter.Nearest, MagFilter.Nearest, false, AddressMode.Repeat, AddressMode.Repeat, AddressMode.Repeat, CompareMode.None, CompareOp.Always, new ColorF(0, 0, 0, 0), 0, 0, 0, 1f)); _templateUpdater = new(); } public void Initialize(bool isMainPipeline) { MemoryOwner dummyTextureData = MemoryOwner.RentCleared(4); _dummyTexture.SetData(dummyTextureData); if (isMainPipeline) { FeedbackLoopHazards = new(); } } private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size) { return offset < bindingOffset + (int)info.Range && (offset + size) > bindingOffset; } internal void Rebind(Auto buffer, int offset, int size) { if (_program == null) { return; } // Check stage bindings _uniformMirrored.Union(_uniformSet).SignalSet((int binding, int count) => { for (int i = 0; i < count; i++) { ref BufferRef bufferRef = ref _uniformBufferRefs[binding]; if (bufferRef.Buffer == buffer) { ref DescriptorBufferInfo info = ref _uniformBuffers[binding]; int bindingOffset = bufferRef.Offset; if (BindingOverlaps(ref info, bindingOffset, offset, size)) { _uniformSet.Clear(binding); _uniformSetPd[binding] = 0; SignalDirty(DirtyFlags.Uniform); } } binding++; } }); _storageMirrored.Union(_storageSet).SignalSet((int binding, int count) => { for (int i = 0; i < count; i++) { ref BufferRef bufferRef = ref _storageBufferRefs[binding]; if (bufferRef.Buffer == buffer) { ref DescriptorBufferInfo info = ref _storageBuffers[binding]; int bindingOffset = bufferRef.Offset; if (BindingOverlaps(ref info, bindingOffset, offset, size)) { _storageSet.Clear(binding); SignalDirty(DirtyFlags.Storage); } } binding++; } }); } public void InsertBindingBarriers(CommandBufferScoped cbs) { if ((FeedbackLoopHazards?.Count ?? 0) > 0) { // Clear existing hazards - they will be rebuilt. foreach (TextureView hazard in FeedbackLoopHazards) { hazard.DecrementHazardUses(); } FeedbackLoopHazards.Clear(); } foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex]) { if (segment.Type == ResourceType.TextureAndSampler) { if (!segment.IsArray) { for (int i = 0; i < segment.Count; i++) { ref var texture = ref _textureRefs[segment.Binding + i]; texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); } } else { ref var arrayRef = ref _textureArrayRefs[segment.Binding]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } } } foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex]) { if (segment.Type == ResourceType.Image) { if (!segment.IsArray) { for (int i = 0; i < segment.Count; i++) { ref var image = ref _imageRefs[segment.Binding + i]; image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); } } else { ref var arrayRef = ref _imageArrayRefs[segment.Binding]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } } } for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++) { var bindingSegments = _program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { continue; } ResourceBindingSegment segment = bindingSegments[0]; if (segment.IsArray) { if (segment.Type == ResourceType.Texture || segment.Type == ResourceType.Sampler || segment.Type == ResourceType.TextureAndSampler || segment.Type == ResourceType.BufferTexture) { ref var arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage) { ref var arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } } } } public void AdvancePdSequence() { if (++_pdSequence == 0) { _pdSequence = 1; } } public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool isBound) { if (!program.HasSameLayout(_program)) { // When the pipeline layout changes, push descriptor bindings are invalidated. AdvancePdSequence(); } _program = program; _updateDescriptorCacheCbIndex = true; _dirty = DirtyFlags.All; } public void SetImage( CommandBufferScoped cbs, ShaderStage stage, int binding, ITexture image, Format imageFormat) { if (image is TextureBuffer imageBuffer) { _bufferImageRefs[binding] = imageBuffer; _bufferImageFormats[binding] = imageFormat; } else if (image is TextureView view) { ref ImageRef iRef = ref _imageRefs[binding]; iRef.View?.ClearUsage(FeedbackLoopHazards); view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView()); } else { _imageRefs[binding] = default; _bufferImageRefs[binding] = null; _bufferImageFormats[binding] = default; } SignalDirty(DirtyFlags.Image); } public void SetImage(int binding, Auto image) { _imageRefs[binding] = new(ShaderStage.Compute, null, 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 = buffer.Handle == BufferHandle.Null ? null : _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, buffer.Write, isSSBO: true); ref BufferRef currentBufferRef = ref _storageBufferRefs[index]; DescriptorBufferInfo info = new() { Offset = (ulong)buffer.Offset, Range = (ulong)buffer.Size, }; var newRef = new BufferRef(vkBuffer, ref buffer); ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range) { _storageSet.Clear(index); currentInfo = info; currentBufferRef = newRef; } } 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 BufferRef currentBufferRef = ref _storageBufferRefs[index]; DescriptorBufferInfo info = new() { Offset = 0, Range = Vk.WholeSize, }; BufferRef newRef = new(vkBuffer); ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range) { _storageSet.Clear(index); currentInfo = info; currentBufferRef = newRef; } } 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) { ref TextureRef iRef = ref _textureRefs[binding]; iRef.View?.ClearUsage(FeedbackLoopHazards); view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler()); } else { _textureRefs[binding] = default; _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.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); _textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler()); SignalDirty(DirtyFlags.Texture); } else { SetTextureAndSampler(cbs, stage, binding, texture, sampler); } } public void SetTextureArray(CommandBufferScoped cbs, ShaderStage stage, int binding, ITextureArray array) { ref ArrayRef arrayRef = ref GetArrayRef(ref _textureArrayRefs, binding, ArrayGrowthSize); if (arrayRef.Stage != stage || arrayRef.Array != array) { arrayRef.Array?.DecrementBindCount(); if (array is TextureArray textureArray) { textureArray.IncrementBindCount(); textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); } arrayRef = new ArrayRef(stage, array as TextureArray); SignalDirty(DirtyFlags.Texture); } } public void SetTextureArraySeparate(CommandBufferScoped cbs, ShaderStage stage, int setIndex, ITextureArray array) { ref ArrayRef arrayRef = ref GetArrayRef(ref _textureArrayExtraRefs, setIndex - PipelineBase.DescriptorSetLayouts); if (arrayRef.Stage != stage || arrayRef.Array != array) { arrayRef.Array?.DecrementBindCount(); if (array is TextureArray textureArray) { textureArray.IncrementBindCount(); textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); } arrayRef = new ArrayRef(stage, array as TextureArray); SignalDirty(DirtyFlags.Texture); } } public void SetImageArray(CommandBufferScoped cbs, ShaderStage stage, int binding, IImageArray array) { ref ArrayRef arrayRef = ref GetArrayRef(ref _imageArrayRefs, binding, ArrayGrowthSize); if (arrayRef.Stage != stage || arrayRef.Array != array) { arrayRef.Array?.DecrementBindCount(); if (array is ImageArray imageArray) { imageArray.IncrementBindCount(); imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); } arrayRef = new ArrayRef(stage, array as ImageArray); SignalDirty(DirtyFlags.Image); } } public void SetImageArraySeparate(CommandBufferScoped cbs, ShaderStage stage, int setIndex, IImageArray array) { ref ArrayRef arrayRef = ref GetArrayRef(ref _imageArrayExtraRefs, setIndex - PipelineBase.DescriptorSetLayouts); if (arrayRef.Stage != stage || arrayRef.Array != array) { arrayRef.Array?.DecrementBindCount(); if (array is ImageArray imageArray) { imageArray.IncrementBindCount(); imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); } arrayRef = new ArrayRef(stage, array as ImageArray); SignalDirty(DirtyFlags.Image); } } private static ref ArrayRef GetArrayRef(ref ArrayRef[] array, int index, int growthSize = 1) { ArgumentOutOfRangeException.ThrowIfNegative(index); if (array.Length <= index) { Array.Resize(ref array, index + growthSize); } return ref array[index]; } 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 = buffer.Handle == BufferHandle.Null ? null : _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false); ref BufferRef currentBufferRef = ref _uniformBufferRefs[index]; DescriptorBufferInfo info = new() { Offset = (ulong)buffer.Offset, Range = (ulong)buffer.Size, }; BufferRef newRef = new(vkBuffer, ref buffer); ref DescriptorBufferInfo currentInfo = ref _uniformBuffers[index]; if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range) { _uniformSet.Clear(index); _uniformSetPd[index] = 0; currentInfo = info; currentBufferRef = newRef; } } SignalDirty(DirtyFlags.Uniform); } private void SignalDirty(DirtyFlags flag) { _dirty |= flag; } public void UpdateAndBindDescriptorSets(CommandBufferScoped cbs, PipelineBindPoint pbp) { if ((_dirty & DirtyFlags.All) == 0) { return; } var program = _program; if (_dirty.HasFlag(DirtyFlags.Uniform)) { if (program.UsePushDescriptors) { UpdateAndBindUniformBufferPd(cbs); } else { UpdateAndBind(cbs, program, PipelineBase.UniformSetIndex, pbp); } } if (_dirty.HasFlag(DirtyFlags.Storage)) { UpdateAndBind(cbs, program, PipelineBase.StorageSetIndex, pbp); } if (_dirty.HasFlag(DirtyFlags.Texture)) { if (program.UpdateTexturesWithoutTemplate) { UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp); } else { UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); } } if (_dirty.HasFlag(DirtyFlags.Image)) { UpdateAndBind(cbs, program, PipelineBase.ImageSetIndex, pbp); } if (program.BindingSegments.Length > PipelineBase.DescriptorSetLayouts) { // Program is using extra sets, we need to bind those too. BindExtraSets(cbs, program, pbp); } _dirty = DirtyFlags.None; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool UpdateBuffer( CommandBufferScoped cbs, ref DescriptorBufferInfo info, ref BufferRef buffer, Auto dummyBuffer, bool mirrorable) { int offset = buffer.Offset; bool mirrored = false; if (mirrorable) { info.Buffer = buffer.Buffer?.GetMirrorable(cbs, ref offset, (int)info.Range, out mirrored).Value ?? default; } else { info.Buffer = buffer.Buffer?.Get(cbs, offset, (int)info.Range, buffer.Write).Value ?? default; } info.Offset = (ulong)offset; // 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; } return mirrored; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAndBind(CommandBufferScoped cbs, ShaderCollection program, int setIndex, PipelineBindPoint pbp) { var bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { return; } var dummyBuffer = _dummyBuffer?.GetBuffer(); if (_updateDescriptorCacheCbIndex) { _updateDescriptorCacheCbIndex = false; program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex); } var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs); if (!program.HasMinimalLayout) { if (isNew) { Initialize(cbs, setIndex, dsc); } } DescriptorSetTemplate template = program.Templates[setIndex]; DescriptorSetTemplateWriter tu = _templateUpdater.Begin(template); 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.Set(index)) { ref BufferRef buffer = ref _uniformBufferRefs[index]; bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true); _uniformMirrored.Set(index, mirrored); } } ReadOnlySpan uniformBuffers = _uniformBuffers; tu.Push(uniformBuffers.Slice(binding, count)); } else if (setIndex == PipelineBase.StorageSetIndex) { for (int i = 0; i < count; i++) { int index = binding + i; ref BufferRef buffer = ref _storageBufferRefs[index]; if (_storageSet.Set(index)) { ref var info = ref _storageBuffers[index]; bool mirrored = UpdateBuffer(cbs, ref info, ref _storageBufferRefs[index], dummyBuffer, !buffer.Write && info.Range <= StorageBufferMaxMirrorable); _storageMirrored.Set(index, mirrored); } } ReadOnlySpan storageBuffers = _storageBuffers; tu.Push(storageBuffers.Slice(binding, count)); } else if (setIndex == PipelineBase.TextureSetIndex) { if (!segment.IsArray) { if (segment.Type != ResourceType.BufferTexture) { Span textures = _textures; for (int i = 0; i < count; i++) { ref var texture = ref textures[i]; ref var refs = ref _textureRefs[binding + i]; texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; texture.Sampler = refs.Sampler?.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; } } tu.Push(textures[..count]); } else { Span bufferTextures = _bufferTextures; for (int i = 0; i < count; i++) { bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; } tu.Push(bufferTextures[..count]); } } else { if (segment.Type != ResourceType.BufferTexture) { tu.Push(_textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler)); } else { tu.Push(_textureArrayRefs[binding].Array.GetBufferViews(cbs)); } } } else if (setIndex == PipelineBase.ImageSetIndex) { if (!segment.IsArray) { if (segment.Type != ResourceType.BufferImage) { Span images = _images; for (int i = 0; i < count; i++) { images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default; } tu.Push(images[..count]); } else { Span bufferImages = _bufferImages; for (int i = 0; i < count; i++) { bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; } tu.Push(bufferImages[..count]); } } else { if (segment.Type != ResourceType.BufferTexture) { tu.Push(_imageArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture)); } else { tu.Push(_imageArrayRefs[binding].Array.GetBufferViews(cbs)); } } } } var sets = dsc.GetSets(); _templateUpdater.Commit(_gd, _device, sets[0]); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp) { int setIndex = PipelineBase.TextureSetIndex; var bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { return; } if (_updateDescriptorCacheCbIndex) { _updateDescriptorCacheCbIndex = false; program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex); } var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs); foreach (ResourceBindingSegment segment in bindingSegments) { int binding = segment.Binding; int count = segment.Count; if (!segment.IsArray) { if (segment.Type != ResourceType.BufferTexture) { Span textures = _textures; for (int i = 0; i < count; i++) { ref var texture = ref textures[i]; ref var refs = ref _textureRefs[binding + i]; texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; texture.Sampler = refs.Sampler?.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[..count], DescriptorType.CombinedImageSampler); } else { Span bufferTextures = _bufferTextures; for (int i = 0; i < count; i++) { bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; } dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer); } } else { if (segment.Type != ResourceType.BufferTexture) { dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler); } else { dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer); } } } var sets = dsc.GetSets(); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs) { int sequence = _pdSequence; var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex]; var dummyBuffer = _dummyBuffer?.GetBuffer(); long updatedBindings = 0; DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf()); foreach (ResourceBindingSegment segment in bindingSegments) { int binding = segment.Binding; int count = segment.Count; ReadOnlySpan uniformBuffers = _uniformBuffers; for (int i = 0; i < count; i++) { int index = binding + i; if (_uniformSet.Set(index)) { ref BufferRef buffer = ref _uniformBufferRefs[index]; bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true); _uniformMirrored.Set(index, mirrored); } if (_uniformSetPd[index] != sequence) { // Need to set this push descriptor (even if the buffer binding has not changed) _uniformSetPd[index] = sequence; updatedBindings |= 1L << index; writer.Push(MemoryMarshal.CreateReadOnlySpan(ref _uniformBuffers[index], 1)); } } } if (updatedBindings > 0) { DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings); _templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout); } } 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); } } private void BindExtraSets(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp) { for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < program.BindingSegments.Length; setIndex++) { var bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { continue; } ResourceBindingSegment segment = bindingSegments[0]; if (segment.IsArray) { DescriptorSet[] sets = null; if (segment.Type == ResourceType.Texture || segment.Type == ResourceType.Sampler || segment.Type == ResourceType.TextureAndSampler || segment.Type == ResourceType.BufferTexture) { sets = _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets( _device, cbs, _templateUpdater, program, setIndex, _dummyTexture, _dummySampler); } else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage) { sets = _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets( _device, cbs, _templateUpdater, program, setIndex, _dummyTexture); } if (sets != null) { _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } } } } public void SignalCommandBufferChange() { _updateDescriptorCacheCbIndex = true; _dirty = DirtyFlags.All; _uniformSet.Clear(); _storageSet.Clear(); AdvancePdSequence(); } public void ForceTextureDirty() { SignalDirty(DirtyFlags.Texture); } public void ForceImageDirty() { SignalDirty(DirtyFlags.Image); } private static void SwapBuffer(BufferRef[] list, Auto from, Auto to) { for (int i = 0; i < list.Length; i++) { if (list[i].Buffer == from) { list[i].Buffer = 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(); _templateUpdater.Dispose(); } } public void Dispose() { Dispose(true); } } }