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);
}
}
}