shader-slang / slang

Making it easier to work with shaders
MIT License
1.78k stars 159 forks source link

[Metal] Hoist vertex attributes. #4375

Open csyonghe opened 2 weeks ago

csyonghe commented 2 weeks ago

Given

struct AssembledVertex
{
    float3  position;
    float3  color;
        float2  uv;
};

struct CoarseVertex
{
    float3  color;
        float2  uv;
};

struct VertexStageInput
{
    AssembledVertex assembledVertex : A;
};

float4 vertexMain(VertexStageInput input) : SV_Position
{
    VertexStageOutput output;
    return output;
}

We generate this:

struct AssembledVertex_0
{
    float3 position_0;
    float3 color_1;
    float2 uv_1;
};

struct vertexInput_0
{
    AssembledVertex_0 assembledVertex_0 [[attribute(0)]]; // metal does not allow [[attribute]] here
};

#line 49
[[vertex]] VertexStageOutput_0 vertexMain(vertexInput_0 _S1 [[stage_in]], SLANG_ParameterGroup_Uniforms_0 constant* Uniforms_0 [[buffer(1)]])
{
}

Metal does not allow defining [[attribute]] on composite types. We need to hoist all vertex attributes out to the top level of the vertex input struct.

csyonghe commented 2 weeks ago

We should be able to reuse the EntryPointVaryingParamLegalizeContext pass to first hoist, flatten and SOA-ize all the nested attributes and fragment stage_in inputs into individual top level parameters, and then pack these parameters into a struct decorated with `[[stage_in``.

We can first translate vertexMain into something like

[attribute(0)] in float3 position;
[attribute(1)] in float3 color;
[attribute(2)] in float2 uv;
float4 vertexMain() : SV_Position
{
}

The EntryPointVaryingParamLegalizeContext should have everything we need to do this transform.

Then we pack them all into a struct, and pass that struct as [stage_in] parameter:

struct StageIn_vertexMain
{
[attribute(0)] float3 position;
[attribute(1)] float3 color;
[attribute(2)] float2 uv;
};
float4 vertexMain(StageIn_vertexMain stageIn [[stage_in]]) : SV_Position
{
...
}
jkwak-work commented 2 weeks ago

I see. That make sense.

ArielG-NV commented 5 days ago

Note for implementing:

from legalizeEntryPointForMetal:

        hoistEntryPointParameterFromStruct(entryPoint);
        packStageInParameters(entryPoint);
        legalizeSystemValueParameters(entryPoint, sink);
        wrapReturnValueInStruct(sink, entryPoint);

require to be integrated (and reimplemented in some cases) into EntryPointVaryingParamLegalizeContext of slang-ir-legalize-varying-params.cpp

ArielG-NV commented 5 hours ago

Update:

Reuse of EntryPointVaryingParamLegalizeContext likely needs to be a separate "clean up" PR since the current state of EntryPointVaryingParamLegalizeContext is quite underdeveloped and will require reworks to work with the Metal backend.

Limitations of EntryPointVaryingParamLegalizeContext:

  1. Output legalization needs to be adapted to use the Metal legalization to legalize types.
  2. Output legalization currently does not modify variable semantic layouts.
  3. Output legalization of a returned variable is currently very basic and won't work with Metal.
  4. Function params are fundamentally handled differently (much simpler than Metal legalization).

Fixing the Metal code-gen issues first and then cleanup is likely the easiest course of action.

ArielG-NV commented 5 hours ago

We should be able to reuse the EntryPointVaryingParamLegalizeContext pass to first hoist, flatten and SOA-ize all the nested attributes and fragment stage_in inputs into individual top level parameters, and then pack these parameters into a struct decorated with `[[stage_in``.

We can first translate vertexMain into something like

[attribute(0)] in float3 position;
[attribute(1)] in float3 color;
[attribute(2)] in float2 uv;
float4 vertexMain() : SV_Position
{
}

The EntryPointVaryingParamLegalizeContext should have everything we need to do this transform.

Then we pack them all into a struct, and pass that struct as [stage_in] parameter:

struct StageIn_vertexMain
{
[attribute(0)] float3 position;
[attribute(1)] float3 color;
[attribute(2)] float2 uv;
};
float4 vertexMain(StageIn_vertexMain stageIn [[stage_in]]) : SV_Position
{
...
}

There are 3 suggested steps:

  1. hoist
  2. flatten
  3. SOA-ize

Since legalization of varying outputs currently requires flattening a struct (without hoisting logic), step 1 can be skipped for now (start at step 2) by reusing the struct-flattening and legalization logic varying outputs need.