| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767 |
- using Ryujinx.Common.Logging;
- using Ryujinx.Graphics.GAL;
- using Silk.NET.Vulkan;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Threading.Tasks;
- namespace Ryujinx.Graphics.Vulkan
- {
- class ShaderCollection : IProgram
- {
- private readonly PipelineShaderStageCreateInfo[] _infos;
- private readonly Shader[] _shaders;
- private readonly PipelineLayoutCacheEntry _plce;
- public PipelineLayout PipelineLayout => _plce.PipelineLayout;
- public bool HasMinimalLayout { get; }
- public bool UsePushDescriptors { get; }
- public bool IsCompute { get; }
- public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
- public bool UpdateTexturesWithoutTemplate { get; }
- public uint Stages { get; }
- public PipelineStageFlags IncoherentBufferWriteStages { get; }
- public PipelineStageFlags IncoherentTextureWriteStages { get; }
- public ResourceBindingSegment[][] ClearSegments { get; }
- public ResourceBindingSegment[][] BindingSegments { get; }
- public DescriptorSetTemplate[] Templates { get; }
- public ProgramLinkStatus LinkStatus { get; private set; }
- public readonly SpecDescription[] SpecDescriptions;
- public bool IsLinked
- {
- get
- {
- if (LinkStatus == ProgramLinkStatus.Incomplete)
- {
- CheckProgramLink(true);
- }
- return LinkStatus == ProgramLinkStatus.Success;
- }
- }
- private HashTableSlim<PipelineUid, Auto<DisposablePipeline>> _graphicsPipelineCache;
- private HashTableSlim<SpecData, Auto<DisposablePipeline>> _computePipelineCache;
- private readonly VulkanRenderer _gd;
- private Device _device;
- private bool _initialized;
- private ProgramPipelineState _state;
- private DisposableRenderPass _dummyRenderPass;
- private readonly Task _compileTask;
- private bool _firstBackgroundUse;
- public ShaderCollection(
- VulkanRenderer gd,
- Device device,
- ShaderSource[] shaders,
- ResourceLayout resourceLayout,
- SpecDescription[] specDescription = null,
- bool isMinimal = false)
- {
- _gd = gd;
- _device = device;
- if (specDescription != null && specDescription.Length != shaders.Length)
- {
- throw new ArgumentException($"{nameof(specDescription)} array length must match {nameof(shaders)} array if provided");
- }
- gd.Shaders.Add(this);
- Shader[] internalShaders = new Shader[shaders.Length];
- _infos = new PipelineShaderStageCreateInfo[shaders.Length];
- SpecDescriptions = specDescription;
- LinkStatus = ProgramLinkStatus.Incomplete;
- uint stages = 0;
- for (int i = 0; i < shaders.Length; i++)
- {
- Shader shader = new(gd.Api, device, shaders[i]);
- stages |= 1u << shader.StageFlags switch
- {
- ShaderStageFlags.FragmentBit => 1,
- ShaderStageFlags.GeometryBit => 2,
- ShaderStageFlags.TessellationControlBit => 3,
- ShaderStageFlags.TessellationEvaluationBit => 4,
- _ => 0,
- };
- if (shader.StageFlags == ShaderStageFlags.ComputeBit)
- {
- IsCompute = true;
- }
- internalShaders[i] = shader;
- }
- _shaders = internalShaders;
- bool usePushDescriptors = !isMinimal &&
- VulkanConfiguration.UsePushDescriptors &&
- _gd.Capabilities.SupportsPushDescriptors &&
- !IsCompute &&
- !HasPushDescriptorsBug(gd) &&
- CanUsePushDescriptors(gd, resourceLayout, IsCompute);
- ReadOnlyCollection<ResourceDescriptorCollection> sets = usePushDescriptors ?
- BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets;
- _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors);
- HasMinimalLayout = isMinimal;
- UsePushDescriptors = usePushDescriptors;
- Stages = stages;
- ClearSegments = BuildClearSegments(sets);
- BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
- Templates = BuildTemplates(usePushDescriptors);
- (IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
- // Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
- UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
- _compileTask = Task.CompletedTask;
- _firstBackgroundUse = false;
- }
- public ShaderCollection(
- VulkanRenderer gd,
- Device device,
- ShaderSource[] sources,
- ResourceLayout resourceLayout,
- ProgramPipelineState state,
- bool fromCache) : this(gd, device, sources, resourceLayout)
- {
- _state = state;
- _compileTask = BackgroundCompilation();
- _firstBackgroundUse = !fromCache;
- }
- private static bool HasPushDescriptorsBug(VulkanRenderer gd)
- {
- // Those GPUs/drivers do not work properly with push descriptors, so we must force disable them.
- return gd.IsNvidiaPreTuring || (gd.IsIntelArc && gd.IsIntelWindows);
- }
- private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute)
- {
- // If binding 3 is immediately used, use an alternate set of reserved bindings.
- ReadOnlyCollection<ResourceUsage> uniformUsage = layout.SetUsages[0].Usages;
- bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3);
- int[] reserved = isCompute ? [] : gd.GetPushDescriptorReservedBindings(hasBinding3);
- // Can't use any of the reserved usages.
- for (int i = 0; i < uniformUsage.Count; i++)
- {
- int binding = uniformUsage[i].Binding;
- if (reserved.Contains(binding) ||
- binding >= Constants.MaxPushDescriptorBinding ||
- binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding))
- {
- return false;
- }
- }
-
- //Prevent the sum of descriptors from exceeding MaxPushDescriptors
- int totalDescriptors = 0;
- foreach (ResourceDescriptor desc in layout.Sets.First().Descriptors)
- {
- if (!reserved.Contains(desc.Binding))
- totalDescriptors += desc.Count;
- }
- if (totalDescriptors > gd.Capabilities.MaxPushDescriptors)
- return false;
- return true;
- }
- private static ReadOnlyCollection<ResourceDescriptorCollection> BuildPushDescriptorSets(
- VulkanRenderer gd,
- ReadOnlyCollection<ResourceDescriptorCollection> sets)
- {
- // The reserved bindings were selected when determining if push descriptors could be used.
- int[] reserved = gd.GetPushDescriptorReservedBindings(false);
- ResourceDescriptorCollection[] result = new ResourceDescriptorCollection[sets.Count];
- for (int i = 0; i < sets.Count; i++)
- {
- if (i == 0)
- {
- // Push descriptors apply here. Remove reserved bindings.
- ResourceDescriptorCollection original = sets[i];
- ResourceDescriptor[] pdUniforms = new ResourceDescriptor[original.Descriptors.Count];
- int j = 0;
- foreach (ResourceDescriptor descriptor in original.Descriptors)
- {
- if (reserved.Contains(descriptor.Binding))
- {
- // If the binding is reserved, set its descriptor count to 0.
- pdUniforms[j++] = new ResourceDescriptor(
- descriptor.Binding,
- 0,
- descriptor.Type,
- descriptor.Stages);
- }
- else
- {
- pdUniforms[j++] = descriptor;
- }
- }
- result[i] = new ResourceDescriptorCollection(new(pdUniforms));
- }
- else
- {
- result[i] = sets[i];
- }
- }
- return new(result);
- }
- private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
- {
- ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
- for (int setIndex = 0; setIndex < sets.Count; setIndex++)
- {
- List<ResourceBindingSegment> currentSegments = [];
- ResourceDescriptor currentDescriptor = default;
- int currentCount = 0;
- for (int index = 0; index < sets[setIndex].Descriptors.Count; index++)
- {
- ResourceDescriptor descriptor = sets[setIndex].Descriptors[index];
- if (currentDescriptor.Binding + currentCount != descriptor.Binding ||
- currentDescriptor.Type != descriptor.Type ||
- currentDescriptor.Stages != descriptor.Stages ||
- currentDescriptor.Count > 1 ||
- descriptor.Count > 1)
- {
- if (currentCount != 0)
- {
- currentSegments.Add(new ResourceBindingSegment(
- currentDescriptor.Binding,
- currentCount,
- currentDescriptor.Type,
- currentDescriptor.Stages,
- currentDescriptor.Count > 1));
- }
- currentDescriptor = descriptor;
- currentCount = descriptor.Count;
- }
- else
- {
- currentCount += descriptor.Count;
- }
- }
- if (currentCount != 0)
- {
- currentSegments.Add(new ResourceBindingSegment(
- currentDescriptor.Binding,
- currentCount,
- currentDescriptor.Type,
- currentDescriptor.Stages,
- currentDescriptor.Count > 1));
- }
- segments[setIndex] = currentSegments.ToArray();
- }
- return segments;
- }
- private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages, out bool usesBufferTextures)
- {
- usesBufferTextures = false;
- ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
- for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
- {
- List<ResourceBindingSegment> currentSegments = [];
- ResourceUsage currentUsage = default;
- int currentCount = 0;
- for (int index = 0; index < setUsages[setIndex].Usages.Count; index++)
- {
- ResourceUsage usage = setUsages[setIndex].Usages[index];
- if (usage.Type == ResourceType.BufferTexture)
- {
- usesBufferTextures = true;
- }
- if (currentUsage.Binding + currentCount != usage.Binding ||
- currentUsage.Type != usage.Type ||
- currentUsage.Stages != usage.Stages ||
- currentUsage.ArrayLength > 1 ||
- usage.ArrayLength > 1)
- {
- if (currentCount != 0)
- {
- currentSegments.Add(new ResourceBindingSegment(
- currentUsage.Binding,
- currentCount,
- currentUsage.Type,
- currentUsage.Stages,
- currentUsage.ArrayLength > 1));
- }
- currentUsage = usage;
- currentCount = usage.ArrayLength;
- }
- else
- {
- currentCount++;
- }
- }
- if (currentCount != 0)
- {
- currentSegments.Add(new ResourceBindingSegment(
- currentUsage.Binding,
- currentCount,
- currentUsage.Type,
- currentUsage.Stages,
- currentUsage.ArrayLength > 1));
- }
- segments[setIndex] = currentSegments.ToArray();
- }
- return segments;
- }
- private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors)
- {
- DescriptorSetTemplate[] templates = new DescriptorSetTemplate[BindingSegments.Length];
- for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++)
- {
- if (usePushDescriptors && setIndex == 0)
- {
- // Push descriptors get updated using templates owned by the pipeline layout.
- continue;
- }
- ResourceBindingSegment[] segments = BindingSegments[setIndex];
- if (segments != null && segments.Length > 0)
- {
- templates[setIndex] = new DescriptorSetTemplate(
- _gd,
- _device,
- segments,
- _plce,
- IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics,
- setIndex);
- }
- }
- return templates;
- }
- private PipelineStageFlags GetPipelineStages(ResourceStages stages)
- {
- PipelineStageFlags result = 0;
- if ((stages & ResourceStages.Compute) != 0)
- {
- result |= PipelineStageFlags.ComputeShaderBit;
- }
- if ((stages & ResourceStages.Vertex) != 0)
- {
- result |= PipelineStageFlags.VertexShaderBit;
- }
- if ((stages & ResourceStages.Fragment) != 0)
- {
- result |= PipelineStageFlags.FragmentShaderBit;
- }
- if ((stages & ResourceStages.Geometry) != 0)
- {
- result |= PipelineStageFlags.GeometryShaderBit;
- }
- if ((stages & ResourceStages.TessellationControl) != 0)
- {
- result |= PipelineStageFlags.TessellationControlShaderBit;
- }
- if ((stages & ResourceStages.TessellationEvaluation) != 0)
- {
- result |= PipelineStageFlags.TessellationEvaluationShaderBit;
- }
- return result;
- }
- private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages)
- {
- PipelineStageFlags buffer = PipelineStageFlags.None;
- PipelineStageFlags texture = PipelineStageFlags.None;
- foreach (ResourceUsageCollection set in setUsages)
- {
- foreach (ResourceUsage range in set.Usages)
- {
- if (range.Write)
- {
- PipelineStageFlags stages = GetPipelineStages(range.Stages);
- switch (range.Type)
- {
- case ResourceType.Image:
- texture |= stages;
- break;
- case ResourceType.StorageBuffer:
- case ResourceType.BufferImage:
- buffer |= stages;
- break;
- }
- }
- }
- }
- return (buffer, texture);
- }
- private async Task BackgroundCompilation()
- {
- await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
- if (Array.Exists(_shaders, shader => shader.CompileStatus == ProgramLinkStatus.Failure))
- {
- LinkStatus = ProgramLinkStatus.Failure;
- return;
- }
- try
- {
- if (IsCompute)
- {
- CreateBackgroundComputePipeline();
- }
- else
- {
- CreateBackgroundGraphicsPipeline();
- }
- }
- catch (VulkanException e)
- {
- Logger.Error?.PrintMsg(LogClass.Gpu, $"Background Compilation failed: {e.Message}");
- LinkStatus = ProgramLinkStatus.Failure;
- }
- }
- private void EnsureShadersReady()
- {
- if (!_initialized)
- {
- CheckProgramLink(true);
- ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
- for (int i = 0; i < _shaders.Length; i++)
- {
- Shader shader = _shaders[i];
- if (shader.CompileStatus != ProgramLinkStatus.Success)
- {
- resultStatus = ProgramLinkStatus.Failure;
- }
- _infos[i] = shader.GetInfo();
- }
- // If the link status was already set as failure by background compilation, prefer that decision.
- if (LinkStatus != ProgramLinkStatus.Failure)
- {
- LinkStatus = resultStatus;
- }
- _initialized = true;
- }
- }
- public PipelineShaderStageCreateInfo[] GetInfos()
- {
- EnsureShadersReady();
- return _infos;
- }
- protected DisposableRenderPass CreateDummyRenderPass()
- {
- if (_dummyRenderPass.Value.Handle != 0)
- {
- return _dummyRenderPass;
- }
- return _dummyRenderPass = _state.ToRenderPass(_gd, _device);
- }
- public void CreateBackgroundComputePipeline()
- {
- PipelineState pipeline = new();
- pipeline.Initialize();
- pipeline.Stages[0] = _shaders[0].GetInfo();
- pipeline.StagesCount = 1;
- pipeline.PipelineLayout = PipelineLayout;
- pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache);
- pipeline.Dispose();
- }
- public void CreateBackgroundGraphicsPipeline()
- {
- // To compile shaders in the background in Vulkan, we need to create valid pipelines using the shader modules.
- // The GPU provides pipeline state via the GAL that can be converted into our internal Vulkan pipeline state.
- // This should match the pipeline state at the time of the first draw. If it doesn't, then it'll likely be
- // close enough that the GPU driver will reuse the compiled shader for the different state.
- // First, we need to create a render pass object compatible with the one that will be used at runtime.
- // The active attachment formats have been provided by the abstraction layer.
- DisposableRenderPass renderPass = CreateDummyRenderPass();
- PipelineState pipeline = _state.ToVulkanPipelineState(_gd);
- // Copy the shader stage info to the pipeline.
- Span<PipelineShaderStageCreateInfo> stages = pipeline.Stages.AsSpan();
- for (int i = 0; i < _shaders.Length; i++)
- {
- stages[i] = _shaders[i].GetInfo();
- }
- pipeline.HasTessellationControlShader = HasTessellationControlShader;
- pipeline.StagesCount = (uint)_shaders.Length;
- pipeline.PipelineLayout = PipelineLayout;
- pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value, throwOnError: true);
- pipeline.Dispose();
- }
- public ProgramLinkStatus CheckProgramLink(bool blocking)
- {
- if (LinkStatus == ProgramLinkStatus.Incomplete)
- {
- ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
- foreach (Shader shader in _shaders)
- {
- if (shader.CompileStatus == ProgramLinkStatus.Incomplete)
- {
- if (blocking)
- {
- // Wait for this shader to finish compiling.
- shader.WaitForCompile();
- if (shader.CompileStatus != ProgramLinkStatus.Success)
- {
- resultStatus = ProgramLinkStatus.Failure;
- }
- }
- else
- {
- return ProgramLinkStatus.Incomplete;
- }
- }
- }
- if (!_compileTask.IsCompleted)
- {
- if (blocking)
- {
- _compileTask.Wait();
- if (LinkStatus == ProgramLinkStatus.Failure)
- {
- return ProgramLinkStatus.Failure;
- }
- }
- else
- {
- return ProgramLinkStatus.Incomplete;
- }
- }
- return resultStatus;
- }
- return LinkStatus;
- }
- public byte[] GetBinary()
- {
- return null;
- }
- public DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask)
- {
- return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask);
- }
- public void AddComputePipeline(ref SpecData key, Auto<DisposablePipeline> pipeline)
- {
- (_computePipelineCache ??= new()).Add(ref key, pipeline);
- }
- public void AddGraphicsPipeline(ref PipelineUid key, Auto<DisposablePipeline> pipeline)
- {
- (_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
- }
- public bool TryGetComputePipeline(ref SpecData key, out Auto<DisposablePipeline> pipeline)
- {
- if (_computePipelineCache == null)
- {
- pipeline = default;
- return false;
- }
- if (_computePipelineCache.TryGetValue(ref key, out pipeline))
- {
- return true;
- }
- return false;
- }
- public bool TryGetGraphicsPipeline(ref PipelineUid key, out Auto<DisposablePipeline> pipeline)
- {
- if (_graphicsPipelineCache == null)
- {
- pipeline = default;
- return false;
- }
- if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
- {
- if (_firstBackgroundUse)
- {
- Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
- _firstBackgroundUse = false;
- }
- return false;
- }
- _firstBackgroundUse = false;
- return true;
- }
- public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex)
- {
- _plce.UpdateCommandBufferIndex(commandBufferIndex);
- }
- public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
- {
- return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
- }
- public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
- {
- return _plce.GetNewManualDescriptorSetCollection(cbs, setIndex, out cacheIndex);
- }
- public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex)
- {
- _plce.UpdateManualDescriptorSetCollectionOwnership(cbs, setIndex, cacheIndex);
- }
- public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)
- {
- _plce.ReleaseManualDescriptorSetCollection(setIndex, cacheIndex);
- }
- public bool HasSameLayout(ShaderCollection other)
- {
- return other != null && _plce == other._plce;
- }
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- if (!_gd.Shaders.Remove(this))
- {
- return;
- }
- for (int i = 0; i < _shaders.Length; i++)
- {
- _shaders[i].Dispose();
- }
- if (_graphicsPipelineCache != null)
- {
- foreach (Auto<DisposablePipeline> pipeline in _graphicsPipelineCache.Values)
- {
- pipeline?.Dispose();
- }
- }
- if (_computePipelineCache != null)
- {
- foreach (Auto<DisposablePipeline> pipeline in _computePipelineCache.Values)
- {
- pipeline.Dispose();
- }
- }
- for (int i = 0; i < Templates.Length; i++)
- {
- Templates[i]?.Dispose();
- }
- if (_dummyRenderPass.Value.Handle != 0)
- {
- _dummyRenderPass.Dispose();
- }
- }
- }
- public void Dispose()
- {
- Dispose(true);
- }
- }
- }
|