Search Unity

Geometry Shader on Mac?

Discussion in 'Shaders' started by McPeppergames, Feb 12, 2021.

  1. McPeppergames

    McPeppergames

    Joined:
    Feb 15, 2019
    Posts:
    103
    I have heard there is no way to use Geometry shaders on a Mac... is this true?

    If so, does anyone know a way to use similar effects on a Mac?

    Any help and input welcome!
     
    ina and smnbck like this.
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Geometry shaders are possible on MacOS. To use Geometry shaders you must force the game (and editor if you're developing on the Mac) to use OpenGL instead of the default Metal API.
    https://docs.unity3d.com/Manual/CommandLineArguments.html
    https://docs.unity3d.com/Manual/OpenGLCoreDetails.html
    But this comes with several huge caveats.

    MacOS's OpenGL support is limited to OpenGL 4.1. This means it does not support Compute shaders, when using OpenGL. Note: the hardware in desktop Macs from roughly the last decade have all supported Compute shaders. If you ran Windows on the Intel based Macs they would be able to use OpenGL 4.3 or better, which supports both Geometry shaders and Compute shaders. Apple explicitly chose to not support any version of OpenGL past 4.1 in MacOS.

    Not having Compute shader support means a lot of Unity's post processing effects no longer work, or run using slower versions of the effect. There are also several assets on the store that make use of Compute shaders which will also obviously not work. Likewise, there are features of both the URP and HDRP that will not work as they are also Compute shader based.

    Additionally, while you can run "OpenGL 4.1" applications on the new M1 based Macs, those will not support Geometry shaders as the hardware does not support them, at all. In part because Apple's Metal graphics API does not support Geometry shaders, and so they don't bother to support it with their own chips.


    So the question you might be wonder is, why?

    Because Geometry shaders, while convenient, are horribly inefficient. That's not to say that Geometry shaders are always the slowest option, just that there are more efficient options. Namely, Compute shaders. Which is why Apple's Metal API supports Compute, but not Geometry shaders. Metal technically doesn't even support Tessellation shaders, not directly. Those are emulated with a Compute shader.

    And that's kind of the answer, if you want to use Geometry shaders for something, you probably actually want to use a Compute shader.


    Unfortunately using Compute shaders for this requires a lot more work to implement, and Unity doesn't make things particularly easy here. If you want to take some mesh and modify it with a Compute shader you have to manually copy the mesh data into buffers that you pass to the Compute shader. You then have to write custom shaders that can take the output of the Compute shader in place of mesh's vertex data.

    See this tutorial that describes using Compute shaders to do deformation.
    https://medium.com/swlh/oculus-quest-mesh-deformation-with-compute-shaders-in-unity-9caa1b904fda
    It's not quite the same as generating new geometry from a compute shader, but unfortunately there aren't a lot of tutorials on that topic.
     
  3. McPeppergames

    McPeppergames

    Joined:
    Feb 15, 2019
    Posts:
    103
    Thank you very much for your detailed help! Much appreciated!
     
    lemapp likes this.
  4. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    13,363
    Hi, is there any update on this ?

    Can i use Geometry shaders on Mac now without any issue or downgrade of rendering ?

    If not would be strange, i have used Geometry shaders and have incredible capabilities that make other systems look years behind especially in ease of use, so not having them supported in the hardware level means Macs have suddenly become a no go for me, not even by a billion miles close. Also geometry shaders are really fast.

    Here is an example of the system i work on


    Thanks
     
    Last edited: Mar 15, 2022
  5. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    There was not and there will not be any updates. Geometry shaders will not work on Mac (or iOS). It was explicitly not part of the Metal API specification and Apple decided to not even implement it on their GPUs.

    Geometry shaders are a dead-end tech, specially after the advent of mesh shaders. They are only "very fast" on mid-range and high-end PC GPUs, and any effect using them can be made more efficiently using compute shaders and indirect instanced rendering. This is specially true on gen8 consoles (PS4/XB1/NSW).

    Geometry shaders are just still popular among Unity developers because they are easy to write using surface shaders, but other engines abandoned them several years ago.
     
    lukasynthetic and x4000 like this.
  6. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    13,363
    I use them with URP without a surface shader and are extreme easy to work with and ideal for dynamic effects. I could take time to make what is ready in 5 minutes in 2-3 months time using tricks with compute shaders, but the point is that geometry shaders are extreme easy to use are very fast even on my 5 years old laptop, where the video above was recorded (1050GTX GPU).

    So while i understand the decision to abandon it for various reasons, i find it a crazy minus for Mac personally.

    Also one big question is if this tech is abandoned by everyone, why GPUs still implement it and not use the related hardware for something else.

    Also i am not aware how you can dynamically augment the created geometry with compute shaders, i can only use them to manipulate geometry so far, but not created it on the fly.
     
    Last edited: Mar 15, 2022
  7. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    GPUs still implement it as to not break existing games which use it. Apple has no regards for backwards compatibility, so they can afford to drop it.

    Yes, you can't augment on the fly with compute shaders, but in most use cases augmentation can be replaced by procedural drawing. In the case of grass, for example, there's no need for augmentation since everything is pretty much a quad, and by changing the indirect index count or instance count you can determine how many indices will be rendered on the fly, based on data output from compute shaders.

    This article explains the drawbacks of geometry shaders:
    http://www.joshbarczak.com/blog/?p=667
     
  8. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    13,363
    Thanks for the article, is indeed interesting.

    The thing is that both augmenting the polygons are very important for my use (real time seasonal growth) and i dont see any issue with performance, maybe because i do use Intel though.

    So is still half half, as if you can get special FX, easy way and fast performing, why not want to use them and use a much harder to program and less versatile system, for maybe minor gains. I can see that some platforms do not support them well, but again that is a downside of the platform that chooses to not use well this versatile system.
     
  9. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    328
    For grass rendering, you even don't need to use geometry shader / compute shaders / instancing. Just you need Graphics.DrawProceduralNow and Vertex Shader with SV_VertexID. It allows to build geometry in runtime. For example, I used it for simple hair rendering demo based on NURBS. It works on Mac (Metal API):

    Screenshot 2022-03-16 at 11.12.28.png

    Source code:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class NurbsHair : MonoBehaviour
    4. {
    5.     public Shader NurbsHairShader;
    6.     public Color HairColor = new Color(1.0f, 0.5f, 0.5f, 1.0f);  
    7.     public Color HairEnds = new Color(0.0f, 0.0f, 0.0f, 1.0f);
    8.     [Range(1.0f, 10.0f)] public float HairPower = 5.0f;  
    9.     [Range(1, 100000)] public int HairCount = 50000;
    10.     [Range(0.0f, 0.5f)] public float HairEffect = 0.0f;  
    11.     [Range(0.1f, 1.0f)] public float HairScale = 0.75f;
    12.     [Range(2, 64)] public int HairQuality = 32;
    13.     [Range(0.0f, 10.0f)] public float HairWind = 0.0f;
    14.     public bool HairShading = true;
    15.     public enum HairMode {NURBSDerivatives = 0, VertexPositions = 1}
    16.     public HairMode HairNormalsCalculation = HairMode.VertexPositions;
    17.     public bool HairDebugNormals = false;
    18.     public Vector4 HairWeights = Vector4.one;
    19.     private Material _Material;
    20.  
    21.     void Start()
    22.     {
    23.         if (NurbsHairShader == null) NurbsHairShader = Shader.Find("Nurbs Hair");
    24.         _Material = new Material(NurbsHairShader);
    25.     }
    26.  
    27.     void OnRenderObject()
    28.     {
    29.         _Material.SetPass(0);
    30.         _Material.SetFloat("_HairScale", HairScale);
    31.         _Material.SetFloat("_HairEffect", HairEffect);
    32.         _Material.SetFloat("_HairPower", HairPower);
    33.         _Material.SetInt("_HairQuality", HairQuality * 2); // must be even number
    34.         _Material.SetColor("_HairColor", HairColor);
    35.         _Material.SetColor("_HairEnds", HairEnds);
    36.         _Material.SetVector("_HairWeights", HairWeights);
    37.         _Material.SetVector("_HairPosition", this.transform.position);
    38.         _Material.SetInt("_HairShading", System.Convert.ToInt32(HairShading));
    39.         _Material.SetFloat("_HairWind", HairWind);
    40.         _Material.SetInt("_HairNormalsMode", (int)HairNormalsCalculation);
    41.         _Material.SetInt("_HairDebugNormals", System.Convert.ToInt32(HairDebugNormals));
    42.         Graphics.DrawProceduralNow(MeshTopology.Lines, HairQuality * HairCount, 1);
    43.     }
    44. }
    Code (CSharp):
    1. Shader "Nurbs Hair"
    2. {
    3.     Subshader
    4.     {
    5.         Pass
    6.         {
    7.             Cull Off
    8.             CGPROGRAM
    9.             #pragma vertex VSMain
    10.             #pragma fragment PSMain
    11.             #pragma target 5.0
    12.  
    13.             uniform float3 _HairColor, _HairEnds;
    14.             uniform float4 _HairPosition, _HairWeights;
    15.             uniform float _HairScale, _HairEffect, _HairPower, _HairWind;
    16.             uniform int _HairQuality, _HairShading, _HairNormalsMode, _HairDebugNormals;
    17.  
    18.             // L. Piegl, W. Tiller, "The NURBS Book", Springer Verlag, 1997
    19.             // http://nurbscalculator.in/
    20.             float3 NurbsCurve (float4 cps[4], int cpsLength, float knots[8], int knotsLength, float u)
    21.             {
    22.                 const int degree = 3;
    23.                 for (int t = 0; t < cpsLength; t++) cps[t].xyz *= cps[t].w;
    24.                 int index = 0;
    25.                 float4 p = 0;
    26.                 int n = knotsLength - degree - 2;
    27.                 if (u == (knots[n + 1])) index = n;
    28.                 int low = degree;
    29.                 int high = n + 1;
    30.                 int mid = (int)floor((low + high) / 2.0);
    31.                 [unroll(16)]
    32.                 while (u < knots[mid] || u >= knots[mid + 1])
    33.                 {
    34.                     if (u < knots[mid])
    35.                         high = mid;
    36.                     else
    37.                         low = mid;
    38.                     mid = (int)floor((low + high) / 2.0);
    39.                 }
    40.                 index = mid;
    41.                 float N[degree + 1];
    42.                 float left[degree + 1];
    43.                 float right[degree + 1];
    44.                 float saved = 0.0, temp = 0.0;
    45.                 N[0] = 1.0;
    46.                 [loop] for (int j = 1; j <= degree; j++)
    47.                 {
    48.                     left[j] = (u - knots[index + 1 - j]);
    49.                     right[j] = knots[index + j] - u;
    50.                     saved = 0.0f;
    51.                     [loop] for (int r = 0; r < j; r++)
    52.                     {
    53.                         temp = N[r] / (right[r + 1] + left[j - r]);
    54.                         N[r] = saved + right[r + 1] * temp;
    55.                         saved = left[j - r] * temp;
    56.                     }
    57.                     N[j] = saved;
    58.                 }
    59.                 for (int i = 0; i <= degree; i++) p += cps[index - degree + i] * N[i];
    60.                 return (p.w != 0) ? p.xyz / p.w : p.xyz;
    61.             }
    62.  
    63.             float3 NurbsCurveTangent (float4 cps[4], int cpsLength, float knots[8], int knotsLength, float u)
    64.             {
    65.                 const int order = 1; // order of the derivative
    66.                 const int degree = 3; // curve degree
    67.                 float ders[order + 1][degree + 1];
    68.                 int span = 0;
    69.                 int n = knotsLength - degree - 2;
    70.                 if (u == (knots[n + 1])) span = n;
    71.                 int low = degree;
    72.                 int high = n + 1;
    73.                 int mid = (int)floor((low + high) / 2.0);
    74.                 [unroll(16)]
    75.                 while (u < knots[mid] || u >= knots[mid + 1])
    76.                 {
    77.                     if (u < knots[mid])
    78.                         high = mid;
    79.                     else
    80.                         low = mid;
    81.                     mid = (int)floor((low + high) / 2.0);
    82.                 }
    83.                 span = mid;
    84.                 float left[degree + 1];
    85.                 float right[degree + 1];
    86.                 float ndu[degree + 1][degree + 1];
    87.                 ndu[0][0] = 1.0;
    88.                 [loop] for (int j = 1; j <= degree; j++)
    89.                 {
    90.                     left[j] = u - knots[span + 1 - j];
    91.                     right[j] = knots[span + j] - u;
    92.                     float saved = 0.0;
    93.                     [loop] for (int r = 0; r < j; r++)
    94.                     {
    95.                         ndu[j][r] = right[r + 1] + left[j - r];
    96.                         float temp = ndu[r][j - 1] / ndu[j][ r];
    97.                         ndu[r][ j] = saved + right[r + 1] * temp;
    98.                         saved = left[j - r] * temp;
    99.                     }
    100.                     ndu[j][j] = saved;
    101.                 }
    102.                 for (int m = 0; m <= degree; m++) ders[0][m] = ndu[m][degree];
    103.                 float a[2][degree + 1];
    104.                 for (int r = 0; r <= degree; r++)
    105.                 {
    106.                     int s1 = 0;
    107.                     int s2 = 1;
    108.                     a[0][0] = 1.0;
    109.                     [unroll(order)]
    110.                     for (int k = 1; k <= order; k++)
    111.                     {
    112.                         float d = 0.0;
    113.                         int rk = r - k;
    114.                         int pk = degree - k;
    115.                         int j1 = 0;
    116.                         int j2 = 0;
    117.                         if (r >= k)
    118.                         {
    119.                             a[s2][0] = a[s1][0] / ndu[pk + 1][rk];
    120.                             d = a[s2][0] * ndu[rk][pk];
    121.                         }
    122.                         j1 = (rk >= -1) ? 1 : -rk;
    123.                         j2 = (r - 1 <= pk) ? k - 1 : degree - r;
    124.                         [unroll(order+1)] for (int j = j1; j <= j2; j++)
    125.                         {
    126.                             a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][rk + j];
    127.                             d += a[s2][j] * ndu[rk + j][pk];
    128.                         }
    129.                         if (r <= pk)
    130.                         {
    131.                             a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r];
    132.                             d += a[s2][k] * ndu[r][pk];
    133.                         }
    134.                         ders[k][r] = d;
    135.                         int s3 = s1;
    136.                         s1 = s2;
    137.                         s2 = s3;
    138.                     }
    139.                 }
    140.                 float f = degree;
    141.                 [unroll(order)]
    142.                 for (int k = 1; k <= order; k++)
    143.                 {
    144.                     for (int h = 0; h <= degree; h++) ders[k][ h] *= f;
    145.                     f *= degree - k;
    146.                 }
    147.                 int du = order < degree ? order : degree;
    148.                 float3 result[order + 1];
    149.                 for (int q = 0; q <= du; q++)
    150.                 {
    151.                     for (int j = 0; j <= degree; j++)
    152.                     {
    153.                         float4 v = cps[span - degree + j];
    154.                         result[q].xyz += v.xyz * ders[q][j];
    155.                     }
    156.                 }
    157.                 return normalize(result[1]);
    158.             }
    159.  
    160.             float Mod (float x, float y)
    161.             {
    162.                 return x - y * floor(x / y);
    163.             }
    164.  
    165.             float4 Hash(uint p) // Returns value in range -1..1
    166.             {
    167.                 p = 1103515245U*((p >> 1U)^(p));
    168.                 uint h32 = 1103515245U*((p)^(p>>3U));
    169.                 uint n = h32^(h32 >> 16);
    170.                 uint4 rz = uint4(n, n*16807U, n*48271U, n*69621U);
    171.                 return float4((rz >> 1) & (uint4)(0x7fffffffU)) / float(0x7fffffff) * 2.0 - 1.0;
    172.             }
    173.  
    174.             float2 PolarToCartesian (float2 p)
    175.             {
    176.                 return p.x * float2(cos(p.y), sin(p.y));
    177.             }
    178.  
    179.             float4 VSMain (uint vertexId : SV_VertexID, out float3 color : COLOR, out float3 normal : NORMAL) : SV_POSITION
    180.             {
    181.                 float strand = float(_HairQuality); // amount of vertices per strand, default is 64
    182.                 float instance = floor(vertexId / strand); // instance ID
    183.                 float id = Mod(vertexId, strand); // vertex ID
    184.                 float t = max((id + Mod(id, 2.0) - 1.0), 0.0) / (strand - 1.0); // interpolator
    185.                 float4 n = Hash(uint(instance + 123u)); // noise
    186.                 float2 k = PolarToCartesian (float2(n.x * 3.0, n.y * 16.0));
    187.                 float4 controlPoints[4] = {0..xxxx, 0..xxxx, 0..xxxx, 0..xxxx};
    188.                 float knotVector[8] = {0.0, 0.0, 0.0, 0.0, 1.0 - _HairEffect, 1.0, 1.0, 1.0};
    189.                 float wind = sin(_Time.g * n.x * _HairWind) * 0.1;
    190.                 controlPoints[0] = float4(0.0, 0.0, 0.0, _HairWeights.x);
    191.                 controlPoints[1] = float4(k.x / 4.0, n.z + 1, k.y / 4.0, _HairWeights.y);
    192.                 controlPoints[2] = float4(k.x / 2.0, n.w + 1, k.y / 2.0, _HairWeights.z);
    193.                 controlPoints[3] = float4(k.x, -1.0 + n.w * 0.5 + wind, k.y, _HairWeights.w);
    194.                 float3 localPos = NurbsCurve (controlPoints, 4, knotVector, 8, t) * _HairScale;
    195.                 normal = _HairNormalsMode > 0 ? normalize(localPos) : NurbsCurveTangent(controlPoints, 4, knotVector, 8, t);
    196.                 color = float4(lerp(_HairColor, _HairEnds, pow(t, _HairPower)), 1);
    197.                 return UnityObjectToClipPos(float4(localPos + _HairPosition.xyz, 1.0));
    198.             }
    199.  
    200.             float4 PSMain (float4 vertex : SV_POSITION, float3 color : COLOR, float3 normal : NORMAL) : SV_Target
    201.             {
    202.                 float angle = 1.0 - length(_WorldSpaceLightPos0.xz) / length(_WorldSpaceLightPos0.xyz);
    203.                 float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
    204.                 float3 normalDir = normalize(normal);
    205.                 float diffuse = max(dot(lightDir, normalDir), angle);
    206.                 return _HairDebugNormals > 0 ? float4(normalDir, 1.0) : (_HairShading > 0 ? float4(diffuse.xxx * color, 1.0) : float4(color, 1.0));
    207.             }
    208.             ENDCG
    209.         }
    210.     }
    211. }
    212.  
     
    JavaBob, lukasynthetic and nasos_333 like this.
  10. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    If you want to augment geometry progressively from compute you can in Unity 2020.1 and up.

    https://docs.google.com/document/d/1QC7NV7JQcvibeelORJvsaTReTyszllOlxdfEsaVL2oA/edit

    https://github.com/Unity-Technologies/MeshApiExamples

    https://forum.unity.com/threads/feedback-wanted-mesh-compute-shader-access.1096531/
     
    nasos_333 likes this.
  11. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    13,363
    Thanks a lot both, will check all out for sure :)
     
    x4000 likes this.
  12. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    13,363
    I had a look into those and seems is still nothing like the extreme fast gpu only Geometry Shaders, plus much more complex to implement and need to be on cpu side with dots and looks like the vertices are still not dynamically added in gpu side, even wjen using compute to update.

    Unless i miss something Geometry shaders still seem like alien tech comparing to stone age tech of mesh shaders that still looks a lot like compute shaders approach.
     
    Last edited: Oct 4, 2022
  13. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    No you're missing what this allows you to do. It's all GPU side. It allows you to get the GPU-side pointer to the mesh data so that you can manipulate and use that data from a Compute or fragment shader. Nothing to do with DOTS or CPU other than the CPU triggering the Compute Dispatch. "COMPUTE" shaders are GPU, not CPU.

    It also allows you to avoid having to update the data every frame if it doesn't need to change, and avoids needing to run for every single shader pass, greatly reducing GPU load.
     
  14. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    13,363
    Is there any sample of a compute shader that dynamically change the vertex counts for example inside the GPU side in the compute part ?

    I recall i had to rigidly define the matrices in C# to pass the data and that was not dynamic, if done dynamically would not be efficient (e.g. of change the arrays size in each frame when call dispatch etc)

    Of course i could define a vast array from start, with redundant array count to facilitate adding more vertices, but that is also not efficient.

    Another aspect is that Geometry shader can operate directly on existing geometry, e.g. i use if for voxelization of the scene in a replacement shader, which means this is also another use that cant be covered by compute shaders.
     
  15. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550