Skip to content

Instantly share code, notes, and snippets.

@wotakuro
Created April 13, 2026 07:24
Show Gist options
  • Select an option

  • Save wotakuro/a6c207e36fa522feac90786ae8370ffa to your computer and use it in GitHub Desktop.

Select an option

Save wotakuro/a6c207e36fa522feac90786ae8370ffa to your computer and use it in GitHub Desktop.
Unity CBUFFER ANLYZER
using UnityEngine;
using UnityEditor;
using UnityEngine.Rendering;
using UnityEditor.Rendering;
using System.Collections.Generic;
namespace UTJ.Sample
{
public class ShaderConstantBufferAnalyzer : EditorWindow
{
private Shader targetShader;
private Dictionary<string, Dictionary<int, ShaderData.ConstantInfo>> cbFieldsByIndex;
private Dictionary<string, ShaderData.ConstantBufferInfo> cbInfo;
private ShaderCompilerPlatform targetShaderCompilerPlatform = ShaderCompilerPlatform.D3D;
private BuildTarget buildTarget = BuildTarget.StandaloneWindows64;
private Vector2 scroll;
private string dataStr = "";
[MenuItem("Tools/Shader CBuffer Analyzer")]
public static void ShowWindow()
{
GetWindow<ShaderConstantBufferAnalyzer>("CBuffer Analyzer");
}
private void OnGUI()
{
GUILayout.Label("Shader Constant Buffer Analyzer", EditorStyles.boldLabel);
this.targetShader = (Shader)EditorGUILayout.ObjectField("Target Shader", this.targetShader, typeof(Shader), false);
this.targetShaderCompilerPlatform = (ShaderCompilerPlatform)EditorGUILayout.EnumPopup("ShaderCompilerPlatform", this.targetShaderCompilerPlatform);
this.buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("BuildTarget", this.buildTarget);
if (GUILayout.Button("Analyze Constant Buffers") && targetShader != null)
{
AnalyzeShader(targetShader);
}
scroll = EditorGUILayout.BeginScrollView(scroll);
EditorGUILayout.TextArea(dataStr);
EditorGUILayout.EndScrollView();
}
private void AnalyzeShader(Shader shader)
{
ShaderData shaderData = ShaderUtil.GetShaderData(shader);
if (shaderData == null)
{
Debug.LogError("ShaderDataの取得に失敗しました。");
return;
}
this.cbFieldsByIndex = new Dictionary<string, Dictionary<int, ShaderData.ConstantInfo>>();
this.cbInfo = new Dictionary<string, ShaderData.ConstantBufferInfo>();
// 1. Shaderからキーワード一覧を取得する (Unity 2021.2以降)
LocalKeyword[] keywords = shader.keywordSpace.keywords;
for (int i = 0; i < 1; i++)
{
ShaderData.Subshader subshader = shaderData.GetSubshader(i);
for (int j = 0; j < subshader.PassCount; j++)
{
ShaderData.Pass pass = subshader.GetPass(j);
// 2. キーワードなし(ベース)のバリアントをコンパイル
CompileAndLogCBufferInfo(pass, ShaderType.Vertex, new string[0]);
CompileAndLogCBufferInfo(pass, ShaderType.Fragment, new string[0]);
// 3. 取得したキーワードごとにループしてコンパイル
foreach (LocalKeyword keyword in keywords)
{
// 単一のキーワードを有効にして解析
CompileAndLogCBufferInfo(pass, ShaderType.Vertex, new string[] { keyword.name });
CompileAndLogCBufferInfo(pass, ShaderType.Fragment, new string[] { keyword.name });
}
}
}
dataStr = "========================CBuffer=====================\n";
foreach (var constantKvs in this.cbFieldsByIndex)
{
var cbInfos = new List<ShaderData.ConstantInfo>(constantKvs.Value.Values);
cbInfos.Sort((a, b) => { return a.Index - b.Index; });
dataStr += $"===== {constantKvs.Key} ( {this.cbInfo[constantKvs.Key].Size} Byte) ====\n";
foreach (var field in cbInfos)
{
int size = CalculateShaderConstantSize(field);
dataStr += ($" Index:{field.Index}-{field.Index + size} ({size} Byte), Field Name: {field.Name}, DataType: {field.DataType},ConstantType:{field.ConstantType} , Rows:{field.Rows} , Columns:{field.Columns},StructSize: {field.StructSize} ,ArraySize: {field.ArraySize}\n");
}
}
}
/// <summary>
/// 指定されたキーワードの配列でシェーダーバリアントをコンパイルし、CBufferの情報を出力します
/// </summary>
private void CompileAndLogCBufferInfo(ShaderData.Pass pass, ShaderType type, string[] keywords)
{
// ログ出力用のキーワード文字列を生成
string keywordString = keywords.Length == 0 ? "<No Keywords>" : string.Join(" ", keywords);
// 指定したキーワードでバリアントをコンパイル
ShaderData.VariantCompileInfo variantInfo = pass.CompileVariant(
type, // 解析したいシェーダーステージ(Vertex, Fragment等)
keywords, // キーワードの配列
targetShaderCompilerPlatform, // ターゲットプラットフォーム
this.buildTarget, // ビルドターゲット
GraphicsTier.Tier3 // グラフィックスタリア
);
if (!variantInfo.Success)
{
// Shaderの記述によっては、特定のパスでサポートされていないキーワードを渡すと失敗することがあります
return;
}
ShaderData.ConstantBufferInfo[] cbInfos = variantInfo.ConstantBuffers;
// CBufferが存在する場合のみログを出力
if (cbInfos.Length > 0)
{
foreach (var cb in cbInfos)
{
// Skip global
if (cb.Name.Contains("Globals"))
{
continue;
}
if (!this.cbInfo.TryAdd(cb.Name, cb))
{
if (cb.Size != this.cbInfo[cb.Name].Size)
{
Debug.LogError("ConstantBufferSize Different " + cb.Name + " " + cb.Size + "-" + this.cbInfo[cb.Name].Size);
}
if (cb.Size > this.cbInfo[cb.Name].Size)
{
this.cbInfo[cb.Name] = cb;
}
}
CollectCBInfo(cb, cb.Fields);
}
}
}
private int CalculateShaderConstantSize(in ShaderData.ConstantInfo info)
{
int arraySize = Mathf.Max(1, info.ArraySize);
int rows = Mathf.Max(1, info.Rows);
int columns = Mathf.Max(1, info.Columns);
int size = info.StructSize;
if (info.StructSize <= 0)
{
switch (info.DataType)
{
case ShaderParamType.Float:
case ShaderParamType.Int:
case ShaderParamType.UInt:
size = 4;
break;
case ShaderParamType.Short:
case ShaderParamType.Half:
size = 2;
break;
case ShaderParamType.Bool:
size = 1;
break;
}
}
return size * arraySize * rows * columns;
}
private void CollectCBInfo(ShaderData.ConstantBufferInfo cbInfo, ShaderData.ConstantInfo[] fieldInfos)
{
if (fieldInfos == null) { return; }
Dictionary<int, ShaderData.ConstantInfo> fieldDict;
if (!this.cbFieldsByIndex.TryGetValue(cbInfo.Name, out fieldDict))
{
fieldDict = new Dictionary<int, ShaderData.ConstantInfo>();
this.cbFieldsByIndex.Add(cbInfo.Name, fieldDict);
}
foreach (var info in fieldInfos)
{
if (!fieldDict.TryAdd(info.Index, info))
{
if (fieldDict[info.Index].Name != info.Name)
{
Debug.LogError("Different Name in " + cbInfo.Name + " " + info.Index + "::" + fieldDict[info.Index].Name + "-" + info.Name);
}
}
// CollectCBInfo(cbInfo, info.StructFields);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment