Resumen#
Los cinco ejemplos de abajo son el mismo shader: una etapa de vertex y una de fragment, además de una etapa de compute que escribe en un storage buffer. Solo cambian los nombres de los atributos. Cambia entre ellos con las pestañas o con el menú Sintaxis en la parte superior de la página; tu elección se recuerda en toda la documentación.
Un paquete por sintaxis#
Cada sintaxis tiene su propio proyecto y namespace: YotsubaShader.HlslSyntax, YotsubaShader.GlslSyntax, YotsubaShader.MslSyntax, YotsubaShader.SpirvSyntax y YotsubaShader.WgslSyntax. Sus atributos de recursos están en el namespace .Resources, por ejemplo YotsubaShader.HlslSyntax.Resources. Referencian YotsubaShader.Language para anotaciones de stages y tipos nativos y YotsubaShader.Concepts para conceptos compartidos. Las locations y los atributos de recursos pertenecen a namespaces de sintaxis. Los motores y frameworks utilizan ese núcleo; añade la sintaxis con la que escribas.
El mismo shader en cinco sintaxis#
using YotsubaShader.Language;
using YotsubaShader.HlslSyntax;
using YotsubaShader.HlslSyntax.Resources;
public struct VertexOutput
{
[SV_Position]
public Vector4 Position;
}
public class Triangle
{
[register(RegisterPrefix.u, 0)]
public RWStructuredBuffer<float> Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([SV_VertexID] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
[return: SV_Target(0)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
[ShaderStage(Stage.Compute)]
[numthreads(8, 8, 1)]
public void Compute([SV_DispatchThreadID] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.GlslSyntax;
using YotsubaShader.GlslSyntax.Resources;
public struct VertexOutput
{
[gl_Position]
public Vector4 Position;
}
public class Triangle
{
[buffer, layout(0, 0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([gl_VertexIndex] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
[ShaderStage(Stage.Compute)]
[layout_local_size(8, 8, 1)]
public void Compute([gl_GlobalInvocationID] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.MslSyntax;
using YotsubaShader.MslSyntax.Resources;
public struct VertexOutput
{
[position]
public Vector4 Position;
}
public class Triangle
{
[device, buffer(0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([vertex_id] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
// El tamaño del workgroup lo define el host
[ShaderStage(Stage.Compute)]
public void Compute([thread_position_in_grid] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.SpirvSyntax;
using YotsubaShader.SpirvSyntax.Resources;
public struct VertexOutput
{
[BuiltIn_Position]
public Vector4 Position;
}
public class Triangle
{
[StorageBuffer, DescriptorSet(0), @Binding(0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([BuiltIn_VertexIndex] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
// El tamaño del workgroup lo define el host
[ShaderStage(Stage.Compute)]
public void Compute([BuiltIn_GlobalInvocationId] UInt3 id)
{
}
}
using YotsubaShader.Language;
using YotsubaShader.WgslSyntax;
using YotsubaShader.WgslSyntax.Resources;
public struct VertexOutput
{
[@builtin_position]
public Vector4 Position;
}
public class Triangle
{
[storage(storage_use.read_write), @group(0), @binding(0)]
public float[] Output;
[ShaderStage(Stage.Vertex)]
public VertexOutput Vertex([@builtin_vertex_index] uint id)
{
VertexOutput output = new VertexOutput();
output.Position = new Vector4(0, 0, 0, 1);
return output;
}
[ShaderStage(Stage.Fragment)]
public Vector4 Fragment(VertexOutput input)
{
return new Vector4(1, 0.4f, 0.7f, 1);
}
[ShaderStage(Stage.Compute)]
[@workgroup_size(8, 8, 1)]
public void Compute([@builtin_global_invocation_id] UInt3 id)
{
}
}
Estos nombres se parecen deliberadamente a los de sus lenguajes de origen. En C#, los atributos inspirados en WGSL usan @ en el identificador, como en [@builtin_position], y siguen siendo C# válido.
Recursos en cinco sintaxis#
Los uniform buffers y los slots de binding siguen el mismo patrón.
using YotsubaShader.HlslSyntax;
using YotsubaShader.HlslSyntax.Resources;
public class Lighting
{
[cbuffer]
[register(RegisterPrefix.b, 0)]
public Material Material;
}
using YotsubaShader.GlslSyntax;
using YotsubaShader.GlslSyntax.Resources;
public class Lighting
{
[uniform]
[layout(set: 0, binding: 0)]
public Material Material;
}
using YotsubaShader.MslSyntax;
using YotsubaShader.MslSyntax.Resources;
public class Lighting
{
[constant]
[buffer(0)]
public Material Material;
}
using YotsubaShader.SpirvSyntax;
using YotsubaShader.SpirvSyntax.Resources;
public class Lighting
{
[Uniform]
[DescriptorSet(0)]
[Binding(0)]
public Material Material;
}
using YotsubaShader.WgslSyntax;
using YotsubaShader.WgslSyntax.Resources;
public class Lighting
{
[uniform]
[@group(0)]
[@binding(0)]
public Material Material;
}
Los atributos compartidos [Uniform], [StorageBuffer] y [Binding] de YotsubaShader.Language también funcionan en todas las sintaxis.
¿Cuál debería elegir?#
- Tu equipo ya conoce un lenguaje de shaders: usa su sintaxis. Nada en la IR depende de la sintaxis.
- Apuntas a una sola API: la sintaxis equivalente se lee de forma más natural (MSL para Metal, SPIR-V para Vulkan), pero es una comodidad, no un requisito.
- Empiezas de cero: la sintaxis HLSL es una buena primera opción. La relación entre etapas, entradas, posición y color del fragmento es fácil de ver.
Equivalencia#
Cada sintaxis está pensada para ser semánticamente equivalente a las demás. El frontend ya asocia los atributos de posición y de índice de vértice de las cinco sintaxis a los mismos valores integrados de la IR. Consulta la tabla completa en Valores integrados en todas las sintaxis.
