using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Shader { /// /// Shader info structure builder. /// class ShaderInfoBuilder { private const ResourceStages SupportBufferStages = ResourceStages.Compute | ResourceStages.Vertex | ResourceStages.Fragment; private const ResourceStages VtgStages = ResourceStages.Vertex | ResourceStages.TessellationControl | ResourceStages.TessellationEvaluation | ResourceStages.Geometry; private readonly GpuContext _context; private int _fragmentOutputMap; private readonly int _reservedConstantBuffers; private readonly int _reservedStorageBuffers; private readonly int _reservedTextures; private readonly int _reservedImages; private List[] _resourceDescriptors; private List[] _resourceUsages; /// /// Creates a new shader info builder. /// /// GPU context that owns the shaders that will be added to the builder /// Indicates if the graphics shader is used with transform feedback enabled /// Indicates that the vertex shader will be emulated on a compute shader public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false) { _context = context; _fragmentOutputMap = -1; int uniformSetIndex = context.Capabilities.UniformBufferSetIndex; int storageSetIndex = context.Capabilities.StorageBufferSetIndex; int textureSetIndex = context.Capabilities.TextureSetIndex; int imageSetIndex = context.Capabilities.ImageSetIndex; int totalSets = Math.Max(uniformSetIndex, storageSetIndex); totalSets = Math.Max(totalSets, textureSetIndex); totalSets = Math.Max(totalSets, imageSetIndex); totalSets++; _resourceDescriptors = new List[totalSets]; _resourceUsages = new List[totalSets]; for (int index = 0; index < totalSets; index++) { _resourceDescriptors[index] = new(); _resourceUsages[index] = new(); } AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1); AddUsage(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1); ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); _reservedConstantBuffers = rrc.ReservedConstantBuffers; _reservedStorageBuffers = rrc.ReservedStorageBuffers; _reservedTextures = rrc.ReservedTextures; _reservedImages = rrc.ReservedImages; // TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader. ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages; PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1); PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true); PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures); PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true); } /// /// Populates descriptors and usages for vertex as compute and transform feedback emulation reserved resources. /// /// Shader stages where the resources are used /// Resource type /// Resource set index where the resources are used /// First binding number /// Amount of bindings /// True if the binding is written from the shader, false otherwise private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false) { AddDescriptor(stages, type, setIndex, start, count); AddUsage(stages, type, setIndex, start, count, write); } /// /// Adds information from a given shader stage. /// /// Shader stage information /// True if the shader stage has been converted into a compute shader public void AddStageInfo(ShaderProgramInfo info, bool vertexAsCompute = false) { if (info.Stage == ShaderStage.Fragment) { _fragmentOutputMap = info.FragmentOutputMap; } int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch { ShaderStage.TessellationControl => 1, ShaderStage.TessellationEvaluation => 2, ShaderStage.Geometry => 3, ShaderStage.Fragment => 4, _ => 0, }); ResourceStages stages = vertexAsCompute ? ResourceStages.Compute : info.Stage switch { ShaderStage.Compute => ResourceStages.Compute, ShaderStage.Vertex => ResourceStages.Vertex, ShaderStage.TessellationControl => ResourceStages.TessellationControl, ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation, ShaderStage.Geometry => ResourceStages.Geometry, ShaderStage.Fragment => ResourceStages.Fragment, _ => ResourceStages.None, }; int uniformsPerStage = (int)_context.Capabilities.MaximumUniformBuffersPerStage; int storagesPerStage = (int)_context.Capabilities.MaximumStorageBuffersPerStage; int texturesPerStage = (int)_context.Capabilities.MaximumTexturesPerStage; int imagesPerStage = (int)_context.Capabilities.MaximumImagesPerStage; int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage; int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage; int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2; int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2; int uniformSetIndex = _context.Capabilities.UniformBufferSetIndex; int storageSetIndex = _context.Capabilities.StorageBufferSetIndex; int textureSetIndex = _context.Capabilities.TextureSetIndex; int imageSetIndex = _context.Capabilities.ImageSetIndex; AddDescriptor(stages, ResourceType.UniformBuffer, uniformSetIndex, uniformBinding, uniformsPerStage); AddDescriptor(stages, ResourceType.StorageBuffer, storageSetIndex, storageBinding, storagesPerStage); AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, textureSetIndex, textureBinding, texturesPerStage); AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, imageSetIndex, imageBinding, imagesPerStage); AddArrayDescriptors(info.Textures, stages, isImage: false); AddArrayDescriptors(info.Images, stages, isImage: true); AddUsage(info.CBuffers, stages, isStorage: false); AddUsage(info.SBuffers, stages, isStorage: true); AddUsage(info.Textures, stages, isImage: false); AddUsage(info.Images, stages, isImage: true); } /// /// Adds a resource descriptor to the list of descriptors. /// /// Shader stages where the resource is used /// Type of the resource /// Descriptor set number where the resource will be bound /// Binding number where the resource will be bound /// Number of resources bound at the binding location private void AddDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count) { for (int index = 0; index < count; index++) { _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding + index, 1, type, stages)); } } /// /// Adds two interleaved groups of resources to the list of descriptors. /// /// Shader stages where the resource is used /// Type of the first interleaved resource /// Type of the second interleaved resource /// Descriptor set number where the resource will be bound /// Binding number where the resource will be bound /// Number of resources bound at the binding location private void AddDualDescriptor(ResourceStages stages, ResourceType type, ResourceType type2, int setIndex, int binding, int count) { AddDescriptor(stages, type, setIndex, binding, count); AddDescriptor(stages, type2, setIndex, binding + count, count); } /// /// Adds all array descriptors (those with an array length greater than one). /// /// Textures to be added /// Stages where the textures are used /// True for images, false for textures private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, bool isImage) { foreach (TextureDescriptor texture in textures) { if (texture.ArrayLength > 1) { ResourceType type = GetTextureResourceType(texture, isImage); GetDescriptors(texture.Set).Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); } } } /// /// Adds buffer usage information to the list of usages. /// /// Shader stages where the resource is used /// Type of the resource /// Descriptor set number where the resource will be bound /// Binding number where the resource will be bound /// Number of resources bound at the binding location /// True if the binding is written from the shader, false otherwise private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count, bool write = false) { for (int index = 0; index < count; index++) { _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages, write)); } } /// /// Adds buffer usage information to the list of usages. /// /// Buffers to be added /// Stages where the buffers are used /// True for storage buffers, false for uniform buffers private void AddUsage(IEnumerable buffers, ResourceStages stages, bool isStorage) { foreach (BufferDescriptor buffer in buffers) { GetUsages(buffer.Set).Add(new ResourceUsage( buffer.Binding, 1, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, stages, buffer.Flags.HasFlag(BufferUsageFlags.Write))); } } /// /// Adds texture usage information to the list of usages. /// /// Textures to be added /// Stages where the textures are used /// True for images, false for textures private void AddUsage(IEnumerable textures, ResourceStages stages, bool isImage) { foreach (TextureDescriptor texture in textures) { ResourceType type = GetTextureResourceType(texture, isImage); GetUsages(texture.Set).Add(new ResourceUsage( texture.Binding, texture.ArrayLength, type, stages, texture.Flags.HasFlag(TextureUsageFlags.ImageStore))); } } /// /// Gets the list of resource descriptors for a given set index. A new list will be created if needed. /// /// Resource set index /// List of resource descriptors private List GetDescriptors(int setIndex) { if (_resourceDescriptors.Length <= setIndex) { int oldLength = _resourceDescriptors.Length; Array.Resize(ref _resourceDescriptors, setIndex + 1); for (int index = oldLength; index <= setIndex; index++) { _resourceDescriptors[index] = new(); } } return _resourceDescriptors[setIndex]; } /// /// Gets the list of resource usages for a given set index. A new list will be created if needed. /// /// Resource set index /// List of resource usages private List GetUsages(int setIndex) { if (_resourceUsages.Length <= setIndex) { int oldLength = _resourceUsages.Length; Array.Resize(ref _resourceUsages, setIndex + 1); for (int index = oldLength; index <= setIndex; index++) { _resourceUsages[index] = new(); } } return _resourceUsages[setIndex]; } /// /// Gets a resource type from a texture descriptor. /// /// Texture descriptor /// Whether the texture is a image texture (writable) or not (sampled) /// Resource type private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage) { bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; if (isBuffer) { return isImage ? ResourceType.BufferImage : ResourceType.BufferTexture; } else if (isImage) { return ResourceType.Image; } else if (texture.Type == SamplerType.None) { return ResourceType.Sampler; } else if (texture.Separate) { return ResourceType.Texture; } else { return ResourceType.TextureAndSampler; } } /// /// Creates a new shader information structure from the added information. /// /// Optional pipeline state for background shader compilation /// Indicates if the shader comes from a disk cache /// Shader information public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false) { int totalSets = _resourceDescriptors.Length; var descriptors = new ResourceDescriptorCollection[totalSets]; var usages = new ResourceUsageCollection[totalSets]; for (int index = 0; index < totalSets; index++) { descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly()); usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly()); } ResourceLayout resourceLayout = new(descriptors.AsReadOnly(), usages.AsReadOnly()); if (pipeline.HasValue) { return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache); } else { return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache); } } /// /// Builds shader information for shaders from the disk cache. /// /// GPU context that owns the shaders /// Shaders from the disk cache /// Optional pipeline for background compilation /// Indicates if the graphics shader is used with transform feedback enabled /// Shader information public static ShaderInfo BuildForCache( GpuContext context, IEnumerable programs, ProgramPipelineState? pipeline, bool tfEnabled) { ShaderInfoBuilder builder = new(context, tfEnabled); foreach (CachedShaderStage program in programs) { if (program?.Info != null) { builder.AddStageInfo(program.Info); } } return builder.Build(pipeline, fromCache: true); } /// /// Builds shader information for a compute shader. /// /// GPU context that owns the shader /// Compute shader information /// True if the compute shader comes from a disk cache, false otherwise /// Shader information public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false) { ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false); builder.AddStageInfo(info); return builder.Build(null, fromCache); } /// /// Builds shader information for a vertex or geometry shader thas was converted to compute shader. /// /// GPU context that owns the shader /// Compute shader information /// Indicates if the graphics shader is used with transform feedback enabled /// True if the compute shader comes from a disk cache, false otherwise /// Shader information public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false) { ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true); builder.AddStageInfo(info, vertexAsCompute: true); return builder.Build(null, fromCache); } } }