using Ryujinx.Common.Memory; using Ryujinx.Graphics.Gpu.Shader.DiskCache; using Ryujinx.Graphics.Shader; using System; using System.Collections.Generic; using System.Numerics; namespace Ryujinx.Graphics.Gpu.Shader { class ShaderSpecializationState { private const uint ComsMagic = (byte)'C' | ((byte)'O' << 8) | ((byte)'M' << 16) | ((byte)'S' << 24); private const uint GfxsMagic = (byte)'G' | ((byte)'F' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24); private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24); private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24); private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24); /// /// Flags indicating GPU state that is used by the shader. /// [Flags] private enum QueriedStateFlags { EarlyZForce = 1 << 0, PrimitiveTopology = 1 << 1, TessellationMode = 1 << 2, TransformFeedback = 1 << 3 } private QueriedStateFlags _queriedState; private bool _compute; private byte _constantBufferUsePerStage; /// /// Compute engine state. /// public GpuChannelComputeState ComputeState; /// /// 3D engine state. /// public GpuChannelGraphicsState GraphicsState; /// /// Contant buffers bound at the time the shader was compiled, per stage. /// public Array5 ConstantBufferUse; /// /// Transform feedback buffers active at the time the shader was compiled. /// public TransformFeedbackDescriptor[] TransformFeedbackDescriptors; /// /// Flags indicating texture state that is used by the shader. /// [Flags] private enum QueriedTextureStateFlags { TextureFormat = 1 << 0, SamplerType = 1 << 1, CoordNormalized = 1 << 2 } /// /// Reference type wrapping a value. /// private class Box { /// /// Wrapped value. /// public T Value; } /// /// State of a texture or image that is accessed by the shader. /// private struct TextureSpecializationState { // New fields should be added to the end of the struct to keep disk shader cache compatibility. /// /// Flags indicating which state of the texture the shader depends on. /// public QueriedTextureStateFlags QueriedFlags; /// /// Encoded texture format value. /// public uint Format; /// /// True if the texture format is sRGB, false otherwise. /// public bool FormatSrgb; /// /// Texture target. /// public Image.TextureTarget TextureTarget; /// /// Indicates if the coordinates used to sample the texture are normalized or not (0.0..1.0 or 0..Width/Height). /// public bool CoordNormalized; } /// /// Texture binding information, used to identify each texture accessed by the shader. /// private struct TextureKey : IEquatable { // New fields should be added to the end of the struct to keep disk shader cache compatibility. /// /// Shader stage where the texture is used. /// public readonly int StageIndex; /// /// Texture handle offset in words on the texture buffer. /// public readonly int Handle; /// /// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register). /// public readonly int CbufSlot; /// /// Creates a new texture key. /// /// Shader stage where the texture is used /// Texture handle offset in words on the texture buffer /// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register) public TextureKey(int stageIndex, int handle, int cbufSlot) { StageIndex = stageIndex; Handle = handle; CbufSlot = cbufSlot; } public override bool Equals(object obj) { return obj is TextureKey textureKey && Equals(textureKey); } public bool Equals(TextureKey other) { return StageIndex == other.StageIndex && Handle == other.Handle && CbufSlot == other.CbufSlot; } public override int GetHashCode() { return HashCode.Combine(StageIndex, Handle, CbufSlot); } } private readonly Dictionary> _textureSpecialization; /// /// Creates a new instance of the shader specialization state. /// private ShaderSpecializationState() { _textureSpecialization = new Dictionary>(); } /// /// Creates a new instance of the shader specialization state. /// /// Current compute engine state public ShaderSpecializationState(GpuChannelComputeState state) : this() { ComputeState = state; _compute = true; } /// /// Creates a new instance of the shader specialization state. /// /// Current 3D engine state /// Optional transform feedback buffers in use, if any public ShaderSpecializationState(GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this() { GraphicsState = state; _compute = false; if (descriptors != null) { TransformFeedbackDescriptors = descriptors; _queriedState |= QueriedStateFlags.TransformFeedback; } } /// /// Indicates that the shader accesses the early Z force state. /// public void RecordEarlyZForce() { _queriedState |= QueriedStateFlags.EarlyZForce; } /// /// Indicates that the shader accesses the primitive topology state. /// public void RecordPrimitiveTopology() { _queriedState |= QueriedStateFlags.PrimitiveTopology; } /// /// Indicates that the shader accesses the tessellation mode state. /// public void RecordTessellationMode() { _queriedState |= QueriedStateFlags.TessellationMode; } /// /// Indicates that the shader accesses the constant buffer use state. /// /// Shader stage index /// Mask indicating the constant buffers bound at the time of the shader compilation public void RecordConstantBufferUse(int stageIndex, uint useMask) { ConstantBufferUse[stageIndex] = useMask; _constantBufferUsePerStage |= (byte)(1 << stageIndex); } /// /// Indicates that a given texture is accessed by the shader. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer /// Descriptor of the texture public void RegisterTexture(int stageIndex, int handle, int cbufSlot, Image.TextureDescriptor descriptor) { Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); state.Value.Format = descriptor.UnpackFormat(); state.Value.FormatSrgb = descriptor.UnpackSrgb(); state.Value.TextureTarget = descriptor.UnpackTextureTarget(); state.Value.CoordNormalized = descriptor.UnpackTextureCoordNormalized(); } /// /// Indicates that a given texture is accessed by the shader. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer /// Maxwell texture format value /// Whenever the texture format is a sRGB format /// Texture target type /// Whenever the texture coordinates used on the shader are considered normalized public void RegisterTexture( int stageIndex, int handle, int cbufSlot, uint format, bool formatSrgb, Image.TextureTarget target, bool coordNormalized) { Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); state.Value.Format = format; state.Value.FormatSrgb = formatSrgb; state.Value.TextureTarget = target; state.Value.CoordNormalized = coordNormalized; } /// /// Indicates that the format of a given texture was used during the shader translation process. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer public void RecordTextureFormat(int stageIndex, int handle, int cbufSlot) { Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); state.Value.QueriedFlags |= QueriedTextureStateFlags.TextureFormat; } /// /// Indicates that the target of a given texture was used during the shader translation process. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer public void RecordTextureSamplerType(int stageIndex, int handle, int cbufSlot) { Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); state.Value.QueriedFlags |= QueriedTextureStateFlags.SamplerType; } /// /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer public void RecordTextureCoordNormalized(int stageIndex, int handle, int cbufSlot) { Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); state.Value.QueriedFlags |= QueriedTextureStateFlags.CoordNormalized; } /// /// Checks if a given texture was registerd on this specialization state. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer public bool TextureRegistered(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot) != null; } /// /// Gets the recorded format of a given texture. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot) { TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; return (state.Format, state.FormatSrgb); } /// /// Gets the recorded target of a given texture. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer public Image.TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; } /// /// Gets the recorded coordinate normalization state of a given texture. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; } /// /// Gets texture specialization state for a given texture, or create a new one if not present. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer /// Texture specialization state private Box GetOrCreateTextureSpecState(int stageIndex, int handle, int cbufSlot) { TextureKey key = new TextureKey(stageIndex, handle, cbufSlot); if (!_textureSpecialization.TryGetValue(key, out Box state)) { _textureSpecialization.Add(key, state = new Box()); } return state; } /// /// Gets texture specialization state for a given texture. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer /// Texture specialization state private Box GetTextureSpecState(int stageIndex, int handle, int cbufSlot) { TextureKey key = new TextureKey(stageIndex, handle, cbufSlot); if (_textureSpecialization.TryGetValue(key, out Box state)) { return state; } return null; } /// /// Checks if the recorded state matches the current GPU 3D engine state. /// /// GPU channel /// Texture pool state /// Graphics state /// True if the state matches, false otherwise public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState) { if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable) { return false; } return Matches(channel, poolState, isCompute: false); } /// /// Checks if the recorded state matches the current GPU compute engine state. /// /// GPU channel /// Texture pool state /// True if the state matches, false otherwise public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState) { return Matches(channel, poolState, isCompute: true); } /// /// Checks if the recorded state matches the current GPU state. /// /// GPU channel /// Texture pool state /// Indicates whenever the check is requested by the 3D or compute engine /// True if the state matches, false otherwise private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool isCompute) { int constantBufferUsePerStageMask = _constantBufferUsePerStage; while (constantBufferUsePerStageMask != 0) { int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask); uint useMask = isCompute ? channel.BufferManager.GetComputeUniformBufferUseMask() : channel.BufferManager.GetGraphicsUniformBufferUseMask(index); if (ConstantBufferUse[index] != useMask) { return false; } constantBufferUsePerStageMask &= ~(1 << index); } foreach (var kv in _textureSpecialization) { TextureKey textureKey = kv.Key; (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex); ulong textureCbAddress; ulong samplerCbAddress; if (isCompute) { textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex); samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex); } else { textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex); samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex); } if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress)) { continue; } Image.TextureDescriptor descriptor; if (isCompute) { descriptor = channel.TextureManager.GetComputeTextureDescriptor( poolState.TexturePoolGpuVa, poolState.TextureBufferIndex, poolState.TexturePoolMaximumId, textureKey.Handle, textureKey.CbufSlot); } else { descriptor = channel.TextureManager.GetGraphicsTextureDescriptor( poolState.TexturePoolGpuVa, poolState.TextureBufferIndex, poolState.TexturePoolMaximumId, textureKey.StageIndex, textureKey.Handle, textureKey.CbufSlot); } Box specializationState = kv.Value; if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) && specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized()) { return false; } } return true; } /// /// Reads shader specialization state that has been serialized. /// /// Data reader /// Shader specialization state public static ShaderSpecializationState Read(ref BinarySerializer dataReader) { ShaderSpecializationState specState = new ShaderSpecializationState(); dataReader.Read(ref specState._queriedState); dataReader.Read(ref specState._compute); if (specState._compute) { dataReader.ReadWithMagicAndSize(ref specState.ComputeState, ComsMagic); } else { dataReader.ReadWithMagicAndSize(ref specState.GraphicsState, GfxsMagic); } dataReader.Read(ref specState._constantBufferUsePerStage); int constantBufferUsePerStageMask = specState._constantBufferUsePerStage; while (constantBufferUsePerStageMask != 0) { int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask); dataReader.Read(ref specState.ConstantBufferUse[index]); constantBufferUsePerStageMask &= ~(1 << index); } if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) { ushort tfCount = 0; dataReader.Read(ref tfCount); specState.TransformFeedbackDescriptors = new TransformFeedbackDescriptor[tfCount]; for (int index = 0; index < tfCount; index++) { dataReader.ReadWithMagicAndSize(ref specState.TransformFeedbackDescriptors[index], TfbdMagic); } } ushort count = 0; dataReader.Read(ref count); for (int index = 0; index < count; index++) { TextureKey textureKey = default; Box textureState = new Box(); dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); dataReader.ReadWithMagicAndSize(ref textureState.Value, TexsMagic); specState._textureSpecialization[textureKey] = textureState; } return specState; } /// /// Serializes the shader specialization state. /// /// Data writer public void Write(ref BinarySerializer dataWriter) { dataWriter.Write(ref _queriedState); dataWriter.Write(ref _compute); if (_compute) { dataWriter.WriteWithMagicAndSize(ref ComputeState, ComsMagic); } else { dataWriter.WriteWithMagicAndSize(ref GraphicsState, GfxsMagic); } dataWriter.Write(ref _constantBufferUsePerStage); int constantBufferUsePerStageMask = _constantBufferUsePerStage; while (constantBufferUsePerStageMask != 0) { int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask); dataWriter.Write(ref ConstantBufferUse[index]); constantBufferUsePerStageMask &= ~(1 << index); } if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) { ushort tfCount = (ushort)TransformFeedbackDescriptors.Length; dataWriter.Write(ref tfCount); for (int index = 0; index < TransformFeedbackDescriptors.Length; index++) { dataWriter.WriteWithMagicAndSize(ref TransformFeedbackDescriptors[index], TfbdMagic); } } ushort count = (ushort)_textureSpecialization.Count; dataWriter.Write(ref count); foreach (var kv in _textureSpecialization) { var textureKey = kv.Key; var textureState = kv.Value; dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); } } } }