Search Unity

Return single float from fragment shader

Discussion in 'Shaders' started by CosmoM, May 24, 2021.

  1. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    I've written a shader that writes to a single-channel RenderTexture (format RHalf). The fragment should just return a single value (half or float), but I'm having trouble finding a semantic for it that Metal accepts – SV_TARGET, DEPTH, SV_DEPTH and TEXCOORD1 all lead to complaints.

    Is it possible to have frag return just a float or half, and if so with which semantic?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You want
    SV_Target
    . Define the output as a
    fixed4
    ,
    half4
    , or
    float4
    . Though there's no reason why it can't just be a singular
    half
    or
    float
    . What's the error you're getting when you use that?

    DEPTH
    and
    SV_DEPTH
    are the same thing, one is the D3D9 and the other is the D3D10+ semantic for outputting depth values as the name alludes to. That's for modifying the depth per fragment. This isn't what you want.

    TEXCOORD# is only a valid output from the vertex shader* and can't be used as a fragment shader output.
    * or other non-fragment shader stages.
     
  3. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    For SV_Target, the error I get is: "
    Metal: Error creating pipeline state (CosmoDM/ShowCracks): fragment shader color output does not have enough components for the pixel format (MTLPixelFormatRGBA16Float)
    (null)"

    Here is the full fragment shader:
    half frag(v2f i):SV_TARGET {
    half alpha=tex2D(_MainTex,i.uv);
    alpha+=max(0.005*_Damage-10*(abs(i.uv.x-_HitPosU)+abs(i.uv.y-_HitPosV)),0);
    return saturate(alpha);
    } //frag


    The texture (_MainTex) is a single-channel RHalf rendertexture, though I suppose Unity doesn't know that when compiling the shader – still, the error seems to refer to the fragment output, and that's set as "half".

    (By the way, the only reason I've been able to write all the shaders that *do* work without errors is thanks to your answers to others, bgolus, so thanks a lot!)

    EDIT: I could in principle fix this by making the output half4 or float4 and just writing to a single channel, but I'm trying to make this efficient since I'm updating the rendertexture a lot, and I figured less bits to write...
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The format of
    _MainTex
    is irrelevant. It matters what format the render texture you're rendering to is. If you're thought is "but, I'm rendering to same render texture as
    _MainTex
    ", the answer is no, you're not. That error message is quite explicit that you're not rendering to the
    RHalf
    render texture you think you are. You can't sample from and render to the same render texture. At least not with a shader setup like above. So most likely you're rendering to a temporary
    ARGBHalf
    (aka
    MTLPixelFormatRGBA16Float
    ) render texture that Unity created for you and is then copying back to your "
    _MainTex
    " afterwards in a hidden blit.

    If that is what's happening, it could be considered a bug that the automatically created temporary render texture isn't matching the original render texture's format.

    The real solution is you need two
    RHalf
    format render textures that you ping pong between. Render to render texture "A" on even frames, and render texture "B" on odd frames, and make sure your materials are set to read from the appropriate render texture. Alternatively you should always render to the same render texture, and then use
    Graphics.CopyTexture()
    or
    CommandBuffer.CopyTexture()
    to quickly copy the data to the other one after rendering to it.


    In this case you are rendering to a 4 channel render texture, but outputting a float4 value when rendering to a single channel texture is unlikely to see a significant performance difference. Once you get the above fixed you might want to try that and actually profile the difference using Xcode to see if there is any difference.
     
  5. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    I don't *think* that's what's happening, but maybe I'm misunderstanding. Here's the piece of code that creates the rendertexture I'm using for this:
    Code (CSharp):
    1.  
    2. alphaTexture=new RenderTexture(alphaResolution,alphaResolution,0,RenderTextureFormat.RHalf);
    3. alphaTexture.enableRandomWrite=true;
    4. alphaTexture.Create();
    5. alphaTexture.wrapMode=TextureWrapMode.Repeat;
    6. alphaTexture.filterMode=FilterMode.Bilinear;
    7.  
    And whenever I want to update it (not every frame by far, only when the player hits the relevant object), I call:
    Code (CSharp):
    1.  
    2. RenderTexture temp=RenderTexture.GetTemporary(alphaResolution,alphaResolution,0,RenderTextureFormat.RHalf);
    3. Graphics.Blit(alphaTexture,temp);
    4. alphaMaterial.SetFloat("_HitPosU",uvPos.x);
    5. alphaMaterial.SetFloat("_HitPosV",uvPos.y);
    6. alphaMaterial.SetFloat("_Damage",damage);
    7. Graphics.Blit(temp,alphaTexture,alphaMaterial);
    8. RenderTexture.ReleaseTemporary(temp);
    9.  
    The "ShowCracks" shader (which has the frag I showed earlier) is on alphaMaterial. So as far as I understand, the input and output of the shader are always single-channel RHalf textures. Unless there's something going on behind the screen that I'm missing, the only way I can make sense of the error is if the shader is compiled in isolation, where Metal assumes it takes a standard four-channel input/output, not paying attention to how it's used in the C# code.

    I should note: all this code works perfectly as intended, on Metal. So even though it's an error, not a warning, it still seems to compile (not sure what the "pipeline" refers to). I'm just trying to make sense of/get rid of the error.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Yeah, there’s nothing obviously wrong with your code. The
    allowRandomWrite
    is unnecessary (you’re never doing random writes in the code you’ve shown), but otherwise seems fine. However I and a few others have noticed Unity will render to an internal temporary RT sometimes instead of the one we explicitly assigned. I don’t know the exact causes, in my case I shuffled code around at random and it eventually fixed itself, though I know others have not been that lucky and have reported the bug. Not sure if what you’re seeing is a similar bug or not.

    The error message should not be happening at compile time. Pipeline state refers to what the CPU sets up to tell the GPU how a shader is to be rendered; what blend mode to use, does it use ZWrite, any stencil settings, what the current render target is, etc. Usually this is only when a shader is used. Though if the error isn’t showing every time you call blit, only once on startup, it might be some Unity routine setup to force shaders to be loaded onto the GPU. I seem to remember something about Unity rendering every shader in one pixel on Android devices during load to force the shader to be fully compiled and loaded into the GPU’s memory. It might be doing that for Metal too, and that would explain the error if it only happens once.

    Really, I’d just make the shader output a
    half4
    and profile the difference in cost with Xcode. I’d also report it as a bug.
     
  7. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    The error indeed only happens once, whenever I make a change to the shader (i.e. when it needs to recompile). So it's probably something like that then.

    I'll test the performance of just using
    half4
    , then. Thanks a lot for your answers!