Semantics in parameter list instead of structure?

So I banged my head against a problem for a couple days and it turns out that there’s a difference between these, at least when used with Graphics.Blit() in the context of an editor script:

#include "UnityCG.cginc"
sampler2D _source;
struct appdata_t {
    float4 pos : POSITION;
    float2 uv : TEXCOORD0; };

struct v2f {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0; };

v2f simple_VS(appdata_t v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.pos);
    o.uv = v.uv;
    return o; }

// this works...
float4 traceEdges_PS(v2f v) : SV_Target {
    return tex2D(_source, v.uv); }
#include "UnityCG.cginc"
sampler2D _source;
struct appdata_t {
    float4 pos : POSITION;
    float2 uv : TEXCOORD0; };

struct v2f {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0; };

v2f simple_VS(appdata_t v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.pos);
    o.uv = v.uv;
    return o; }

// this does not
float4 traceEdges_PS(float2 uv : TEXCOORD0) : SV_Target {
    return tex2D(_source, uv); }

The only difference is that teh working one takes the full v2f structure, while the broken one takes the UV as float2 uv : TEXCOORD0. Looking at the generated code, here’s the difference:

Which I guess would be expected, but I don’t use the position value in the pixel shader at all.

Look at your second shader more carefully. v.uv doesn’t exist in the second fragment shader.

return tex2D(_source, uv); }

Sorry; that was an editing-on-the-forum problem. I copy-pasted the top to the bottom and didn’t look well enough.

When fixed, both shaders compile. The second one just outputs a single color for all pixels (appears to be the bottom-right pixel of the input texture), but could also be the average color eg the the topmost mip level.

Curious. I don’t think I’ve ever run into this issue.

What happens if you swap the order in the v2f so that the uv is first and pos is second.

Though I’ll say when I want to be extra explicit about the values I pass, I usually use out float2 uv : TEXCOORD and have the vertex function output a float4 to SV_Position.

float4 simple_VS(appdata_t v, out float2 uv : TEXCOORD0) : SV_POSITION
{
  uv = v.uv;
  return UnityObjectToClipPos(v.vertex);
}

Sorry; I was out for a week and lazy about getting this pared down to a minimal repro.

Changes to the vertex shader do nothing.

If I change the v2f struct so that UV comes first, then both produce blank textures!

Here’s the full shader:

Shader "SemanticsBugRepro" {
    HLSLINCLUDE
        #include "UnityCG.cginc"
        void vert(
            float4 inPos : POSITION, out float4 outPos : SV_POSITION,
            float2 inUV : TEXCOORD0, out float2 outUV : TEXCOORD0)
        {
            outPos = UnityObjectToClipPos(inPos);
            outUV = inUV;
        }
    ENDHLSL
 
    SubShader {
        Pass {
            Name "SemanticsBugRepro.Working"
            ZTest Always ZWrite Off Cull Off
            HLSLPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; };
                float4 frag(v2f v) : SV_Target { return float4(v.uv, 0, 1); }
            ENDHLSL
        }
     
        Pass {
            Name "SemanticsBugRepro.Broken_JustUV"
            ZTest Always ZWrite Off Cull Off
            HLSLPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                float4 frag(float2 uv : TEXCOORD0) : SV_Target { return float4(uv, 0, 1); }
            ENDHLSL
        }
     
        Pass {
            Name "SemanticsBugRepro.Broken_UvAndPositionButUvComesFirst"
            ZTest Always ZWrite Off Cull Off
            HLSLPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                struct v2f { float2 uv : TEXCOORD0; float4 pos : SV_POSITION; };
                float4 frag(v2f v) : SV_Target { return float4(v.uv, 0, 1); }
            ENDHLSL
        }
    }
}

It may be important that it’s being invoked via an editor script like this; it might not be an issue at runtime:

using System;
using System.IO;
using UnityEditor;
using UnityEngine;
using UObject = UnityEngine.Object;

static class SemanticsBugRepro
{
    [MenuItem("==BUG REPO==/Working using struct with semantics")] private static void reproWorking() => generateTexture(0);
    [MenuItem("==BUG REPO==/Broken using semantics in param list")] private static void reproFailed() => generateTexture(1);
    [MenuItem("==BUG REPO==/Broken using struct but UV before position")] private static void reproFailed2() => generateTexture(2);
    private static void generateTexture(int shaderPass)
    {
        const int width = 512, height = 512;
        const string dstPath = "Assets/CoastlineNormals.png";
        Shader shader = Shader.Find("SemanticsBugRepro");
        if(!shader) throw new Exception("Could not find shader");
        Material material = new(shader);
        RenderTexture targetRT = RenderTexture.GetTemporary(width, height);
        RenderTexture oldRT = RenderTexture.active;
        Texture2D tempTexture = new Texture2D(width, height);
        byte[] pngBytes;
        try {
            RenderTexture.active = targetRT;
            Graphics.Blit(null, targetRT, material, shaderPass);
            tempTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0, true);
            pngBytes = tempTexture.EncodeToPNG();
        } finally {
            RenderTexture.active = oldRT;
            RenderTexture.ReleaseTemporary(targetRT);
            UObject.DestroyImmediate(tempTexture);
            UObject.DestroyImmediate(material); }
        File.WriteAllBytes(dstPath, pngBytes);
        AssetDatabase.ImportAsset(dstPath, ImportAssetOptions.ForceSynchronousImport);
        TextureImporter importer = (TextureImporter) AssetImporter.GetAtPath(dstPath);
        importer.sRGBTexture = false;
        importer.SaveAndReimport();
    }
}

Uplaoding a new bug for the order-in-struct issue.

Attached repro so anyone can play around with it. Issue may be isolated to editor scripts+URP.

7929802–1012885–semantics-bug-repro.zip (26.3 KB)