実行環境でジオメトリシェーダが利用可能かどうかを知ることができるAPIはUnityの機能としていくつか用意されているそうです.ChatGPTに教えてもらいました.
-
SystemInfo.graphicsShaderLevel >= 40であれば大体使える(Metal等の例外あり) - 上記に加えて
SystemInfo.GraphicsDeviceTypeでMetal等の判定をする - ジオメトリシェーダを含むシェーダーを作成し,
shader.isSupportedを読む
しかし,3つともUdonにはExposeされていないので,VRChatでは使えません.残念.
方法
できないものは仕方ないので,実際に描画した結果で判定します.
VRCGraphics.Blit() はジオメトリシェーダを含むシェーダーでも描画できるので,適当なRenderTextureに描画し,VRCAsyncGpuReadbackで結果を確認します.
実装
シェーダーで緑(#00FF00FF)を書き込み,スクリプト側で読み出して緑だったらジオメトリシェーダが使える環境だとわかります.
Shader "Test/GeometryShaderSupportTest"
{
Properties
{
/* none */
}
SubShader
{
Tags { "Queue"="Overlay" "RenderType"="Transparent" }
Pass
{
Blend One Zero
ZWrite Off
ZTest Always
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma target 4.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2g
{
float4 pos : POSITION;
};
struct g2f
{
float4 pos : SV_POSITION;
};
v2g vert ( appdata v )
{
v2g o;
o.pos = UnityObjectToClipPos( v.vertex );
return o;
}
[ maxvertexcount( 1 ) ]
void geom ( triangle v2g IN[ 3 ], inout PointStream<g2f> stream )
{
g2f o;
o.pos = float4( 0, 0, 0.5, 1 );
stream.Append( o );
stream.RestartStrip();
}
float4 frag ( g2f IN ) : SV_Target
{
return float4( 0, 1, 0, 1 );
}
ENDCG
}
}
}
スクリプトの mat に上記シェーダーのマテリアルをセットします.
判定結果はとりあえずUIテキストに表示させます.
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using VRC.SDK3.Rendering;
using VRC.Udon.Common.Interfaces;
namespace Test.GeometryShaderSupportTest
{
public class GeometryShaderSupportTest : UdonSharpBehaviour
{
[SerializeField] private TMPro.TextMeshProUGUI debugText;
[SerializeField] private Material mat;
private RenderTexture rt;
private bool isChecking = false;
private bool isCheckCompleted = false;
private bool isGeometryShaderSupported = false;
void Start()
{
// 開幕0秒でReadbackをリクエストすると失敗するのでちょっと待つ.
SendCustomEventDelayedFrames(nameof(Check), 10);
}
void Update()
{
if (isCheckCompleted)
{
if (isGeometryShaderSupported)
{
debugText.text = "YES";
}
else
{
debugText.text = "NO";
}
}
else
{
debugText.text = "Checking";
}
}
public override void OnAsyncGpuReadbackComplete(VRCAsyncGPUReadbackRequest request)
{
OnReadbackComplete(request);
}
public void Check()
{
if (isCheckCompleted)
{
if (rt != null)
{
rt.Release();
rt = null;
}
return;
}
if (rt == null)
{
rt = new RenderTexture(1, 1, 0, RenderTextureFormat.ARGBFloat);
rt.Create();
}
VRCGraphics.Blit(null, rt, mat, 0);
if (!isChecking)
{
isChecking = true;
VRCAsyncGPUReadback.Request(
src: rt,
mipIndex: 0,
x: 0, width: 1,
y: 0, height: 1,
z: 0, depth: 1,
dstFormat: TextureFormat.RGBAFloat,
udonBehaviour: (IUdonEventReceiver)this
);
}
SendCustomEventDelayedSeconds(nameof(Check), 5f);
}
private void OnReadbackComplete(VRCAsyncGPUReadbackRequest request)
{
isChecking = false;
if (request == null)
{
return;
}
if (request.hasError)
{
return;
}
int dataCount = request.width * request.height;
Color[] data = new Color[dataCount];
if (request.TryGetData(data))
{
if (data[0] == Color.green)
{
isGeometryShaderSupported = true;
}
else
{
isGeometryShaderSupported = false;
}
isCheckCompleted = true;
}
}
}
}
Windowsビルドだと"YES"でiOSビルドだと"NO"が表示されます.Androidはたぶん端末によると思いますが,筆者の環境だと"YES"でした.Quest3も"YES"です.
雑記
Start() で VRCAsyncGPUReadback.Request() するとリードバックが失敗するみたいです.SendCustomEventDelayedFrames() で遅らせるか,リトライ処理を入れておくのがいいと思います.