diff options
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs')
-rw-r--r-- | src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs new file mode 100644 index 00000000..e1ab9327 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -0,0 +1,774 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.DiskCache; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// <summary> + /// Memory cache of shader code. + /// </summary> + class ShaderCache : IDisposable + { + /// <summary> + /// Default flags used on the shader translation process. + /// </summary> + public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode; + + private readonly struct TranslatedShader + { + public readonly CachedShaderStage Shader; + public readonly ShaderProgram Program; + + public TranslatedShader(CachedShaderStage shader, ShaderProgram program) + { + Shader = shader; + Program = program; + } + } + + private readonly struct TranslatedShaderVertexPair + { + public readonly CachedShaderStage VertexA; + public readonly CachedShaderStage VertexB; + public readonly ShaderProgram Program; + + public TranslatedShaderVertexPair(CachedShaderStage vertexA, CachedShaderStage vertexB, ShaderProgram program) + { + VertexA = vertexA; + VertexB = vertexB; + Program = program; + } + } + + private readonly GpuContext _context; + + private readonly ShaderDumper _dumper; + + private readonly Dictionary<ulong, CachedShaderProgram> _cpPrograms; + private readonly Dictionary<ShaderAddresses, CachedShaderProgram> _gpPrograms; + + private readonly struct ProgramToSave + { + public readonly CachedShaderProgram CachedProgram; + public readonly IProgram HostProgram; + public readonly byte[] BinaryCode; + + public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode) + { + CachedProgram = cachedProgram; + HostProgram = hostProgram; + BinaryCode = binaryCode; + } + } + + private Queue<ProgramToSave> _programsToSaveQueue; + + private readonly ComputeShaderCacheHashTable _computeShaderCache; + private readonly ShaderCacheHashTable _graphicsShaderCache; + private readonly DiskCacheHostStorage _diskCacheHostStorage; + private readonly BackgroundDiskCacheWriter _cacheWriter; + + /// <summary> + /// Event for signalling shader cache loading progress. + /// </summary> + public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged; + + /// <summary> + /// Creates a new instance of the shader cache. + /// </summary> + /// <param name="context">GPU context that the shader cache belongs to</param> + public ShaderCache(GpuContext context) + { + _context = context; + + _dumper = new ShaderDumper(); + + _cpPrograms = new Dictionary<ulong, CachedShaderProgram>(); + _gpPrograms = new Dictionary<ShaderAddresses, CachedShaderProgram>(); + + _programsToSaveQueue = new Queue<ProgramToSave>(); + + string diskCacheTitleId = GetDiskCachePath(); + + _computeShaderCache = new ComputeShaderCacheHashTable(); + _graphicsShaderCache = new ShaderCacheHashTable(); + _diskCacheHostStorage = new DiskCacheHostStorage(diskCacheTitleId); + + if (_diskCacheHostStorage.CacheEnabled) + { + _cacheWriter = new BackgroundDiskCacheWriter(context, _diskCacheHostStorage); + } + } + + /// <summary> + /// Gets the path where the disk cache for the current application is stored. + /// </summary> + private static string GetDiskCachePath() + { + return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null + ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader") + : null; + } + + /// <summary> + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// </summary> + public void ProcessShaderCacheQueue() + { + // Check to see if the binaries for previously compiled shaders are ready, and save them out. + + while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave)) + { + ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false); + + if (result != ProgramLinkStatus.Incomplete) + { + if (result == ProgramLinkStatus.Success) + { + _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary()); + } + + _programsToSaveQueue.Dequeue(); + } + else + { + break; + } + } + } + + /// <summary> + /// Initialize the cache. + /// </summary> + /// <param name="cancellationToken">Cancellation token to cancel the shader cache initialization process</param> + internal void Initialize(CancellationToken cancellationToken) + { + if (_diskCacheHostStorage.CacheEnabled) + { + ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader( + _context, + _graphicsShaderCache, + _computeShaderCache, + _diskCacheHostStorage, + cancellationToken, + ShaderCacheStateUpdate); + + loader.LoadShaders(); + + int errorCount = loader.ErrorCount; + if (errorCount != 0) + { + Logger.Warning?.Print(LogClass.Gpu, $"Failed to load {errorCount} shaders from the disk cache."); + } + } + } + + /// <summary> + /// Shader cache state update handler. + /// </summary> + /// <param name="state">Current state of the shader cache load process</param> + /// <param name="current">Number of the current shader being processed</param> + /// <param name="total">Total number of shaders to process</param> + private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total) + { + ShaderCacheStateChanged?.Invoke(state, current, total); + } + + /// <summary> + /// Gets a compute shader from the cache. + /// </summary> + /// <remarks> + /// This automatically translates, compiles and adds the code to the cache if not present. + /// </remarks> + /// <param name="channel">GPU channel</param> + /// <param name="poolState">Texture pool state</param> + /// <param name="computeState">Compute engine state</param> + /// <param name="gpuVa">GPU virtual address of the binary shader code</param> + /// <returns>Compiled compute shader code</returns> + public CachedShaderProgram GetComputeShader( + GpuChannel channel, + GpuChannelPoolState poolState, + GpuChannelComputeState computeState, + ulong gpuVa) + { + if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa)) + { + return cpShader; + } + + if (_computeShaderCache.TryFind(channel, poolState, computeState, gpuVa, out cpShader, out byte[] cachedGuestCode)) + { + _cpPrograms[gpuVa] = cpShader; + return cpShader; + } + + ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState); + GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState); + GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState); + + TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa); + + TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode); + + ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) }; + + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1)); + + cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader); + + _computeShaderCache.Add(cpShader); + EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray); + _cpPrograms[gpuVa] = cpShader; + + return cpShader; + } + + /// <summary> + /// Updates the shader pipeline state based on the current GPU state. + /// </summary> + /// <param name="state">Current GPU 3D engine state</param> + /// <param name="pipeline">Shader pipeline state to be updated</param> + /// <param name="graphicsState">Current graphics state</param> + /// <param name="channel">Current GPU channel</param> + private void UpdatePipelineInfo( + ref ThreedClassState state, + ref ProgramPipelineState pipeline, + GpuChannelGraphicsState graphicsState, + GpuChannel channel) + { + channel.TextureManager.UpdateRenderTargets(); + + var rtControl = state.RtControl; + var msaaMode = state.RtMsaaMode; + + pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY(); + + int count = rtControl.UnpackCount(); + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = rtControl.UnpackPermutationIndex(index); + + var colorState = state.RtColorState[rtIndex]; + + if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0) + { + pipeline.AttachmentEnable[index] = false; + pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm; + } + else + { + pipeline.AttachmentEnable[index] = true; + pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format; + } + } + + pipeline.DepthStencilEnable = state.RtDepthStencilEnable; + pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint; + + pipeline.VertexBufferCount = Constants.TotalVertexBuffers; + pipeline.Topology = graphicsState.Topology; + } + + /// <summary> + /// Gets a graphics shader program from the shader cache. + /// This includes all the specified shader stages. + /// </summary> + /// <remarks> + /// This automatically translates, compiles and adds the code to the cache if not present. + /// </remarks> + /// <param name="state">GPU state</param> + /// <param name="pipeline">Pipeline state</param> + /// <param name="channel">GPU channel</param> + /// <param name="poolState">Texture pool state</param> + /// <param name="graphicsState">3D engine state</param> + /// <param name="addresses">Addresses of the shaders for each stage</param> + /// <returns>Compiled graphics shader code</returns> + public CachedShaderProgram GetGraphicsShader( + ref ThreedClassState state, + ref ProgramPipelineState pipeline, + GpuChannel channel, + ref GpuChannelPoolState poolState, + ref GpuChannelGraphicsState graphicsState, + ShaderAddresses addresses) + { + if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses)) + { + return gpShaders; + } + + if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out var cachedGuestCode)) + { + _gpPrograms[addresses] = gpShaders; + return gpShaders; + } + + TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state); + + UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel); + + ShaderSpecializationState specState = new ShaderSpecializationState(ref graphicsState, ref pipeline, transformFeedbackDescriptors); + GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, default, graphicsState, specState, transformFeedbackDescriptors); + + ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan(); + + TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; + TranslatorContext nextStage = null; + + TargetApi api = _context.Capabilities.Api; + + for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--) + { + ulong gpuVa = addressesSpan[stageIndex + 1]; + + if (gpuVa != 0) + { + GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex); + TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa); + + if (nextStage != null) + { + currentStage.SetNextStage(nextStage); + } + + if (stageIndex == 0 && addresses.VertexA != 0) + { + translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA); + } + + translatorContexts[stageIndex + 1] = currentStage; + nextStage = currentStage; + } + } + + if (!_context.Capabilities.SupportsGeometryShader) + { + TryRemoveGeometryStage(translatorContexts); + } + + CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1]; + List<ShaderSource> shaderSources = new List<ShaderSource>(); + + TranslatorContext previousStage = null; + + for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) + { + TranslatorContext currentStage = translatorContexts[stageIndex + 1]; + + if (currentStage != null) + { + ShaderProgram program; + + if (stageIndex == 0 && translatorContexts[0] != null) + { + TranslatedShaderVertexPair translatedShader = TranslateShader( + _dumper, + channel, + currentStage, + translatorContexts[0], + cachedGuestCode.VertexACode, + cachedGuestCode.VertexBCode); + + shaders[0] = translatedShader.VertexA; + shaders[1] = translatedShader.VertexB; + program = translatedShader.Program; + } + else + { + byte[] code = cachedGuestCode.GetByIndex(stageIndex); + + TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code); + + shaders[stageIndex + 1] = translatedShader.Shader; + program = translatedShader.Program; + } + + if (program != null) + { + shaderSources.Add(CreateShaderSource(program)); + } + + previousStage = currentStage; + } + else if ( + previousStage != null && + previousStage.LayerOutputWritten && + stageIndex == 3 && + !_context.Capabilities.SupportsLayerVertexTessellation) + { + shaderSources.Add(CreateShaderSource(previousStage.GenerateGeometryPassthrough())); + } + } + + ShaderSource[] shaderSourcesArray = shaderSources.ToArray(); + + int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1; + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline)); + + gpShaders = new CachedShaderProgram(hostProgram, specState, shaders); + + _graphicsShaderCache.Add(gpShaders); + EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray); + _gpPrograms[addresses] = gpShaders; + + return gpShaders; + } + + /// <summary> + /// Tries to eliminate the geometry stage from the array of translator contexts. + /// </summary> + /// <param name="translatorContexts">Array of translator contexts</param> + public static void TryRemoveGeometryStage(TranslatorContext[] translatorContexts) + { + if (translatorContexts[4] != null) + { + // We have a geometry shader, but geometry shaders are not supported. + // Try to eliminate the geometry shader. + + ShaderProgramInfo info = translatorContexts[4].Translate().Info; + + if (info.Identification == ShaderIdentification.GeometryLayerPassthrough) + { + // We managed to identify that this geometry shader is only used to set the output Layer value, + // we can set the Layer on the previous stage instead (usually the vertex stage) and eliminate it. + + for (int i = 3; i >= 1; i--) + { + if (translatorContexts[i] != null) + { + translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute); + translatorContexts[i].SetLastInVertexPipeline(); + break; + } + } + + translatorContexts[4] = null; + } + } + } + + /// <summary> + /// Creates a shader source for use with the backend from a translated shader program. + /// </summary> + /// <param name="program">Translated shader program</param> + /// <returns>Shader source</returns> + public static ShaderSource CreateShaderSource(ShaderProgram program) + { + return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language); + } + + /// <summary> + /// Puts a program on the queue of programs to be saved on the disk cache. + /// </summary> + /// <remarks> + /// This will not do anything if disk shader cache is disabled. + /// </remarks> + /// <param name="program">Cached shader program</param> + /// <param name="hostProgram">Host program</param> + /// <param name="sources">Source for each shader stage</param> + private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources) + { + if (_diskCacheHostStorage.CacheEnabled) + { + byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null; + ProgramToSave programToSave = new ProgramToSave(program, hostProgram, binaryCode); + + _programsToSaveQueue.Enqueue(programToSave); + } + } + + /// <summary> + /// Gets transform feedback state from the current GPU state. + /// </summary> + /// <param name="state">Current GPU state</param> + /// <returns>Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled</returns> + private static TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(ref ThreedClassState state) + { + bool tfEnable = state.TfEnable; + if (!tfEnable) + { + return null; + } + + TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers]; + + for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++) + { + var tf = state.TfState[i]; + + descs[i] = new TransformFeedbackDescriptor( + tf.BufferIndex, + tf.Stride, + tf.VaryingsCount, + ref state.TfVaryingLocations[i]); + } + + return descs; + } + + /// <summary> + /// Checks if compute shader code in memory is equal to the cached shader. + /// </summary> + /// <param name="channel">GPU channel using the shader</param> + /// <param name="poolState">GPU channel state to verify shader compatibility</param> + /// <param name="computeState">GPU channel compute state to verify shader compatibility</param> + /// <param name="cpShader">Cached compute shader</param> + /// <param name="gpuVa">GPU virtual address of the shader code in memory</param> + /// <returns>True if the code is different, false otherwise</returns> + private static bool IsShaderEqual( + GpuChannel channel, + GpuChannelPoolState poolState, + GpuChannelComputeState computeState, + CachedShaderProgram cpShader, + ulong gpuVa) + { + if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa)) + { + return cpShader.SpecializationState.MatchesCompute(channel, ref poolState, computeState, true); + } + + return false; + } + + /// <summary> + /// Checks if graphics shader code from all stages in memory are equal to the cached shaders. + /// </summary> + /// <param name="channel">GPU channel using the shader</param> + /// <param name="poolState">GPU channel state to verify shader compatibility</param> + /// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param> + /// <param name="gpShaders">Cached graphics shaders</param> + /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param> + /// <returns>True if the code is different, false otherwise</returns> + private static bool IsShaderEqual( + GpuChannel channel, + ref GpuChannelPoolState poolState, + ref GpuChannelGraphicsState graphicsState, + CachedShaderProgram gpShaders, + ShaderAddresses addresses) + { + ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan(); + + for (int stageIndex = 0; stageIndex < gpShaders.Shaders.Length; stageIndex++) + { + CachedShaderStage shader = gpShaders.Shaders[stageIndex]; + + ulong gpuVa = addressesSpan[stageIndex]; + + if (!IsShaderEqual(channel.MemoryManager, shader, gpuVa)) + { + return false; + } + } + + bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false; + + return gpShaders.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true); + } + + /// <summary> + /// Checks if the code of the specified cached shader is different from the code in memory. + /// </summary> + /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param> + /// <param name="shader">Cached shader to compare with</param> + /// <param name="gpuVa">GPU virtual address of the binary shader code</param> + /// <returns>True if the code is different, false otherwise</returns> + private static bool IsShaderEqual(MemoryManager memoryManager, CachedShaderStage shader, ulong gpuVa) + { + if (shader == null) + { + return true; + } + + ReadOnlySpan<byte> memoryCode = memoryManager.GetSpan(gpuVa, shader.Code.Length); + + return memoryCode.SequenceEqual(shader.Code); + } + + /// <summary> + /// Decode the binary Maxwell shader code to a translator context. + /// </summary> + /// <param name="gpuAccessor">GPU state accessor</param> + /// <param name="api">Graphics API that will be used with the shader</param> + /// <param name="gpuVa">GPU virtual address of the binary shader code</param> + /// <returns>The generated translator context</returns> + public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa) + { + var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute); + return Translator.CreateContext(gpuVa, gpuAccessor, options); + } + + /// <summary> + /// Decode the binary Maxwell shader code to a translator context. + /// </summary> + /// <remarks> + /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader. + /// </remarks> + /// <param name="gpuAccessor">GPU state accessor</param> + /// <param name="api">Graphics API that will be used with the shader</param> + /// <param name="flags">Flags that controls shader translation</param> + /// <param name="gpuVa">GPU virtual address of the shader code</param> + /// <returns>The generated translator context</returns> + public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa) + { + var options = CreateTranslationOptions(api, flags); + return Translator.CreateContext(gpuVa, gpuAccessor, options); + } + + /// <summary> + /// Translates a previously generated translator context to something that the host API accepts. + /// </summary> + /// <param name="dumper">Optional shader code dumper</param> + /// <param name="channel">GPU channel using the shader</param> + /// <param name="currentStage">Translator context of the stage to be translated</param> + /// <param name="vertexA">Optional translator context of the shader that should be combined</param> + /// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param> + /// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param> + /// <returns>Compiled graphics shader code</returns> + private static TranslatedShaderVertexPair TranslateShader( + ShaderDumper dumper, + GpuChannel channel, + TranslatorContext currentStage, + TranslatorContext vertexA, + byte[] codeA, + byte[] codeB) + { + ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1); + + var memoryManager = channel.MemoryManager; + + codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray(); + codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray(); + byte[] cb1DataA = memoryManager.Physical.GetSpan(cb1DataAddress, vertexA.Cb1DataSize).ToArray(); + byte[] cb1DataB = memoryManager.Physical.GetSpan(cb1DataAddress, currentStage.Cb1DataSize).ToArray(); + + ShaderDumpPaths pathsA = default; + ShaderDumpPaths pathsB = default; + + if (dumper != null) + { + pathsA = dumper.Dump(codeA, compute: false); + pathsB = dumper.Dump(codeB, compute: false); + } + + ShaderProgram program = currentStage.Translate(vertexA); + + pathsB.Prepend(program); + pathsA.Prepend(program); + + CachedShaderStage vertexAStage = new CachedShaderStage(null, codeA, cb1DataA); + CachedShaderStage vertexBStage = new CachedShaderStage(program.Info, codeB, cb1DataB); + + return new TranslatedShaderVertexPair(vertexAStage, vertexBStage, program); + } + + /// <summary> + /// Translates a previously generated translator context to something that the host API accepts. + /// </summary> + /// <param name="dumper">Optional shader code dumper</param> + /// <param name="channel">GPU channel using the shader</param> + /// <param name="context">Translator context of the stage to be translated</param> + /// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param> + /// <returns>Compiled graphics shader code</returns> + private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code) + { + var memoryManager = channel.MemoryManager; + + ulong cb1DataAddress = context.Stage == ShaderStage.Compute + ? channel.BufferManager.GetComputeUniformBufferAddress(1) + : channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1); + + byte[] cb1Data = memoryManager.Physical.GetSpan(cb1DataAddress, context.Cb1DataSize).ToArray(); + code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray(); + + ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default; + ShaderProgram program = context.Translate(); + + paths.Prepend(program); + + return new TranslatedShader(new CachedShaderStage(program.Info, code, cb1Data), program); + } + + /// <summary> + /// Gets the index of a stage from a <see cref="ShaderStage"/>. + /// </summary> + /// <param name="stage">Stage to get the index from</param> + /// <returns>Stage index</returns> + private static int StageToStageIndex(ShaderStage stage) + { + return stage switch + { + ShaderStage.TessellationControl => 1, + ShaderStage.TessellationEvaluation => 2, + ShaderStage.Geometry => 3, + ShaderStage.Fragment => 4, + _ => 0 + }; + } + + /// <summary> + /// Gets information about the bindings used by a shader program. + /// </summary> + /// <param name="info">Shader program information to get the information from</param> + /// <returns>Shader bindings</returns> + public static ShaderBindings GetBindings(ShaderProgramInfo info) + { + var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray(); + var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray(); + var textureBindings = info.Textures.Select(x => x.Binding).ToArray(); + var imageBindings = info.Images.Select(x => x.Binding).ToArray(); + + return new ShaderBindings( + uniformBufferBindings, + storageBufferBindings, + textureBindings, + imageBindings); + } + + /// <summary> + /// Creates shader translation options with the requested graphics API and flags. + /// The shader language is choosen based on the current configuration and graphics API. + /// </summary> + /// <param name="api">Target graphics API</param> + /// <param name="flags">Translation flags</param> + /// <returns>Translation options</returns> + private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags) + { + TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan + ? TargetLanguage.Spirv + : TargetLanguage.Glsl; + + return new TranslationOptions(lang, api, flags); + } + + /// <summary> + /// Disposes the shader cache, deleting all the cached shaders. + /// It's an error to use the shader cache after disposal. + /// </summary> + public void Dispose() + { + foreach (CachedShaderProgram program in _graphicsShaderCache.GetPrograms()) + { + program.Dispose(); + } + + foreach (CachedShaderProgram program in _computeShaderCache.GetPrograms()) + { + program.Dispose(); + } + + _cacheWriter?.Dispose(); + } + } +} |