大きなメッシュをテッセレーションする際にパフォーマンスを向上させるには、ジョブシステムを使用します。これにより、メインスレッドが長期間ブロックされるのを防ぎます。テッセレーションジョブを作成するときは、MeshGenerationContext.AddMeshGenerationJob API を使用して、UI Toolkit にジョブハンドルを提供します。これにより UI Toolkit は、ジョブが終了してからメッシュにアクセスすることが保証されます。
頂点またはインデックスの数を簡単に計算できる場合は、メインスレッドを使用してメッシュを割り当て、描画シーケンスに追加します。この処理は、以下の API を使用して実行できます。
ノート: メッシュは、generateVisualContent の呼び出し中であれば、任意の時点で描画シーケンスに追加できます。
以下の例は、メインスレッドでの割り当てによりジョブ化されたテッセレーションを示しています。ここでは、ジョブが実際のテッセレーションを実行します。
using System;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.UIElements;
class CheckerboardElement : VisualElement
{
struct CheckerboardJob : IJob
{
[WriteOnly] public NativeSlice<Vertex> vertices;
[WriteOnly] public NativeSlice<ushort> indices;
public int horizontalCount;
public int verticalCount;
public float divisions;
public Color32 color1;
public Color32 color2;
public void Execute()
{
Span<Color32> colors = stackalloc Color32[2];
colors[0] = color1;
colors[1] = color2;
int colorIndex = 0;
int vCount = 0;
int iCount = 0;
float left = 0;
float right = divisions;
for (int i = 0; i < horizontalCount; ++i)
{
float top = 0;
float bottom = divisions;
for (int j = 0; j < verticalCount; ++j)
{
colorIndex = (i ^ j) & 1;
Color32 color = colors[colorIndex];
vertices[vCount + 0] = new Vertex { position = new Vector3(left, bottom), tint = color };
vertices[vCount + 1] = new Vertex { position = new Vector3(left, top), tint = color };
vertices[vCount + 2] = new Vertex { position = new Vector3(right, top), tint = color };
vertices[vCount + 3] = new Vertex { position = new Vector3(right, bottom), tint = color };
indices[iCount + 0] = (ushort)(vCount + 0);
indices[iCount + 1] = (ushort)(vCount + 1);
indices[iCount + 2] = (ushort)(vCount + 2);
indices[iCount + 3] = (ushort)(vCount + 2);
indices[iCount + 4] = (ushort)(vCount + 3);
indices[iCount + 5] = (ushort)(vCount + 0);
vCount += 4;
iCount += 6;
top += divisions;
bottom += divisions;
}
left += divisions;
right += divisions;
}
}
}
int m_Divisions;
Color32 m_Color1;
Color32 m_Color2;
public CheckerboardElement(int divisions, Color32 color1, Color32 color2)
{
generateVisualContent = OnGenerateVisualContent;
m_Divisions = divisions;
m_Color1 = color1;
m_Color2 = color2;
}
void OnGenerateVisualContent(MeshGenerationContext mgc)
{
int horizontalCount = Mathf.FloorToInt(layout.width / m_Divisions);
int verticalCount = Mathf.FloorToInt(layout.height / m_Divisions);
if (horizontalCount == 0 || verticalCount == 0)
return;
var job = new CheckerboardJob
{
horizontalCount = horizontalCount,
verticalCount = verticalCount,
divisions = m_Divisions,
color1 = m_Color1,
color2 = m_Color2
};
int quads = horizontalCount * verticalCount;
mgc.AllocateTempMesh(quads * 4, quads * 6, out job.vertices, out job.indices);
mgc.DrawMesh(job.vertices, job.indices);
JobHandle jobHandle = job.Schedule();
mgc.AddMeshGenerationJob(jobHandle);
}
}
頂点またはインデックスの数を計算するための計算コストが高い場合は、描画シーケンスに MeshGenerationNode を挿入します。次に、TempMeshAllocator とともにノードをジョブに提供します。この処理は、以下の API を使用して実行できます。
この方法では、ジョブから割り当てを実行します。TempMeshAllocator.AllocateTempMesh を使用して、ジョブから一時的なメッシュを安全に割り当てます。MeshGenerationNode.DrawMesh は、ジョブによって生成されるネスト状の描画シーケンスのコンテナのように動作します。
上記の例を基に、ジョブからの割り当てとテッセレーションの両方を実行する方法を以下に示します。
using System;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.UIElements;
class CheckerboardElement_AllocFromJob : VisualElement
{
struct CheckerboardJob : IJob
{
[ReadOnly]
public TempMeshAllocator allocator;
public MeshGenerationNode node;
public int horizontalCount;
public int verticalCount;
public float divisions;
public Color32 color1;
public Color32 color2;
public void Execute()
{
// Let's pretend that the number of vertices/indices is difficult to compute...
int quads = horizontalCount * verticalCount;
int totalVertexCount = quads * 4;
int totalIndexCount = quads * 6;
// Allocate the mesh from the job
allocator.AllocateTempMesh(totalVertexCount, totalIndexCount, out NativeSlice<Vertex> vertices, out NativeSlice<ushort> indices);
Span<Color32> colors = stackalloc Color32[2];
colors[0] = color1;
colors[1] = color2;
int colorIndex = 0;
int vCount = 0;
int iCount = 0;
float left = 0;
float right = divisions;
for (int i = 0; i < horizontalCount; ++i)
{
float top = 0;
float bottom = divisions;
for (int j = 0; j < verticalCount; ++j)
{
colorIndex = (i ^ j) & 1;
Color32 color = colors[colorIndex];
vertices[vCount + 0] = new Vertex { position = new Vector3(left, bottom), tint = color };
vertices[vCount + 1] = new Vertex { position = new Vector3(left, top), tint = color };
vertices[vCount + 2] = new Vertex { position = new Vector3(right, top), tint = color };
vertices[vCount + 3] = new Vertex { position = new Vector3(right, bottom), tint = color };
indices[iCount + 0] = (ushort)(vCount + 0);
indices[iCount + 1] = (ushort)(vCount + 1);
indices[iCount + 2] = (ushort)(vCount + 2);
indices[iCount + 3] = (ushort)(vCount + 2);
indices[iCount + 4] = (ushort)(vCount + 3);
indices[iCount + 5] = (ushort)(vCount + 0);
vCount += 4;
iCount += 6;
top += divisions;
bottom += divisions;
}
left += divisions;
right += divisions;
}
// Add the mesh to the draw sequence of the node
node.DrawMesh(vertices, indices);
}
}
int m_Divisions;
Color32 m_Color1;
Color32 m_Color2;
public CheckerboardElement_AllocFromJob(int divisions, Color32 color1, Color32 color2)
{
generateVisualContent = OnGenerateVisualContent;
m_Divisions = divisions;
m_Color1 = color1;
m_Color2 = color2;
}
void OnGenerateVisualContent(MeshGenerationContext mgc)
{
int horizontalCount = Mathf.FloorToInt(layout.width / m_Divisions);
int verticalCount = Mathf.FloorToInt(layout.height / m_Divisions);
if (horizontalCount == 0 || verticalCount == 0)
return;
var job = new CheckerboardJob
{
horizontalCount = horizontalCount,
verticalCount = verticalCount,
divisions = m_Divisions,
color1 = m_Color1,
color2 = m_Color2
};
mgc.GetTempMeshAllocator(out job.allocator); // Provide the job with the allocator
mgc.InsertMeshGenerationNode(out job.node); // Insert a node in the element draw sequence
JobHandle jobHandle = job.Schedule();
mgc.AddMeshGenerationJob(jobHandle);
}
}