You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1358 lines
62 KiB
1358 lines
62 KiB
using NUnit.Framework;
|
|
using System.Collections.Generic;
|
|
using Unity.Collections;
|
|
using Unity.Jobs;
|
|
using UnityEngine.TestTools;
|
|
using UnityEditor;
|
|
using Unity.Mathematics;
|
|
using NUnit.Framework.Internal;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor.Rendering;
|
|
#endif
|
|
|
|
namespace UnityEngine.Rendering.Tests
|
|
{
|
|
[InitializeOnLoad]
|
|
class OnLoad
|
|
{
|
|
static bool IsGraphicsAPISupported()
|
|
{
|
|
var gfxAPI = SystemInfo.graphicsDeviceType;
|
|
//@Any other API we should ignore ?
|
|
if (gfxAPI == GraphicsDeviceType.OpenGLCore)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static OnLoad()
|
|
{
|
|
ConditionalIgnoreAttribute.AddConditionalIgnoreMapping("ignoreGfxAPI", !IsGraphicsAPISupported());
|
|
}
|
|
}
|
|
|
|
class GPUDrivenRenderingTests
|
|
{
|
|
private MeshTestData m_MeshTestData;
|
|
private RenderPassTest m_RenderPipe;
|
|
private RenderPipelineAsset m_OldPipelineAsset;
|
|
private GPUResidentDrawerResources m_Resources;
|
|
private RenderPassGlobalSettings m_GlobalSettings;
|
|
|
|
class BoxedCounter
|
|
{
|
|
public int Value { get; set; }
|
|
}
|
|
|
|
[OneTimeSetUp]
|
|
public void OneTimeSetup()
|
|
{
|
|
m_GlobalSettings = ScriptableObject.CreateInstance<RenderPassGlobalSettings>();
|
|
#if UNITY_EDITOR
|
|
EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset<RenderPassTestCullInstance>(m_GlobalSettings);
|
|
#endif
|
|
}
|
|
|
|
[OneTimeTearDown]
|
|
public void OneTimeTearDown()
|
|
{
|
|
#if UNITY_EDITOR
|
|
EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset<RenderPassTestCullInstance>(null);
|
|
#endif
|
|
Object.DestroyImmediate(m_GlobalSettings);
|
|
}
|
|
|
|
[SetUp]
|
|
public void OnSetup()
|
|
{
|
|
m_MeshTestData.Initialize();
|
|
m_RenderPipe = ScriptableObject.CreateInstance<RenderPassTest>();
|
|
m_OldPipelineAsset = GraphicsSettings.defaultRenderPipeline;
|
|
GraphicsSettings.defaultRenderPipeline = m_RenderPipe;
|
|
m_Resources = GraphicsSettings.GetRenderPipelineSettings<GPUResidentDrawerResources>();
|
|
}
|
|
|
|
[TearDown]
|
|
public void OnTearDown()
|
|
{
|
|
m_RenderPipe = null;
|
|
m_MeshTestData.Dispose();
|
|
GraphicsSettings.defaultRenderPipeline = m_OldPipelineAsset;
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestInstanceCullingBatcherAddRemove()
|
|
{
|
|
var go0 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var go1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var go2 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
|
|
|
var objList = new List<MeshRenderer>();
|
|
objList.Add(go0.GetComponent<MeshRenderer>());
|
|
objList.Add(go1.GetComponent<MeshRenderer>());
|
|
objList.Add(go2.GetComponent<MeshRenderer>());
|
|
|
|
var objIDs = new NativeList<int>(Allocator.TempJob);
|
|
|
|
Shader dotsShader = Shader.Find("Unlit/SimpleDots");
|
|
var dotsMaterial = new Material(dotsShader);
|
|
foreach (var obj in objList)
|
|
{
|
|
obj.material = dotsMaterial;
|
|
objIDs.Add(obj.GetInstanceID());
|
|
}
|
|
|
|
var instances = new NativeArray<InstanceHandle>(objList.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
for (int i = 0; i < objList.Count; ++i)
|
|
instances[i] = InstanceHandle.Invalid;
|
|
|
|
var rbcDesc = RenderersBatchersContextDesc.NewDefault();
|
|
rbcDesc.instanceNumInfo = new InstanceNumInfo(meshRendererNum: 4096, 0);
|
|
rbcDesc.supportDitheringCrossFade = false;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var brgContext = new RenderersBatchersContext(rbcDesc, gpuDrivenProcessor, m_Resources))
|
|
{
|
|
using (var brg = new GPUResidentBatcher(brgContext, InstanceCullingBatcherDesc.NewDefault(), gpuDrivenProcessor))
|
|
{
|
|
brg.UpdateRenderers(objIDs.AsArray());
|
|
|
|
Assert.IsTrue(brg.instanceCullingBatcher.GetDrawInstanceData().valid);
|
|
Assert.IsTrue(brg.instanceCullingBatcher.GetDrawInstanceData().drawInstances.Length == 3);
|
|
|
|
brgContext.ScheduleQueryRendererGroupInstancesJob(objIDs.AsArray(), instances).Complete();
|
|
brg.DestroyInstances(instances);
|
|
|
|
Assert.IsTrue(brg.instanceCullingBatcher.GetDrawInstanceData().drawInstances.Length == 0);
|
|
}
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
|
|
instances.Dispose();
|
|
objIDs.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestInstanceCullingTier0()
|
|
{
|
|
var go0 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var go1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var go2 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
|
|
|
var objList = new List<MeshRenderer>();
|
|
objList.Add(go0.GetComponent<MeshRenderer>());
|
|
objList.Add(go1.GetComponent<MeshRenderer>());
|
|
objList.Add(go2.GetComponent<MeshRenderer>());
|
|
|
|
var objIDs = new NativeList<int>(Allocator.TempJob);
|
|
|
|
Shader simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
Material simpleDotsMat = new Material(simpleDots);
|
|
|
|
foreach (var obj in objList)
|
|
{
|
|
obj.material = simpleDotsMat;
|
|
objIDs.Add(obj.GetInstanceID());
|
|
}
|
|
|
|
var instances = new NativeArray<InstanceHandle>(objList.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
for (int i = 0; i < objList.Count; ++i)
|
|
instances[i] = InstanceHandle.Invalid;
|
|
|
|
var rbcDesc = RenderersBatchersContextDesc.NewDefault();
|
|
rbcDesc.instanceNumInfo = new InstanceNumInfo(meshRendererNum: 1, 0);
|
|
rbcDesc.supportDitheringCrossFade = false;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
//Using instance count of 1 to test for instance grow
|
|
using (var brgContext = new RenderersBatchersContext(rbcDesc, gpuDrivenProcessor, m_Resources))
|
|
{
|
|
var cpuDrivenDesc = InstanceCullingBatcherDesc.NewDefault();
|
|
var callbackCounter = new BoxedCounter();
|
|
cpuDrivenDesc.onCompleteCallback = (JobHandle jobHandle, in BatchCullingContext cc, in BatchCullingOutput cullingOutput) =>
|
|
{
|
|
if (cc.viewType != BatchCullingViewType.Camera)
|
|
return;
|
|
|
|
jobHandle.Complete();
|
|
BatchCullingOutputDrawCommands drawCommands = cullingOutput.drawCommands[0];
|
|
|
|
var materials = new NativeParallelHashSet<BatchMaterialID>(10, Allocator.Temp);
|
|
|
|
var drawCommandCount = 0U;
|
|
unsafe
|
|
{
|
|
for (int i = 0; i < drawCommands.drawRangeCount; ++i)
|
|
{
|
|
BatchDrawRange range = drawCommands.drawRanges[i];
|
|
drawCommandCount += range.drawCommandsCount;
|
|
for (int c = 0; c < range.drawCommandsCount; ++c)
|
|
{
|
|
BatchDrawCommand cmd = drawCommands.drawCommands[range.drawCommandsBegin + c];
|
|
materials.Add(cmd.materialID);
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert.AreEqual(2, drawCommandCount);
|
|
Assert.AreEqual(1, materials.Count());
|
|
callbackCounter.Value += 1;
|
|
|
|
materials.Dispose();
|
|
};
|
|
|
|
using (var brg = new GPUResidentBatcher(brgContext, cpuDrivenDesc, gpuDrivenProcessor))
|
|
{
|
|
brg.UpdateRenderers(objIDs.AsArray());
|
|
|
|
var cameraObject = new GameObject("myCamera");
|
|
var mainCamera = cameraObject.AddComponent<Camera>();
|
|
|
|
mainCamera.Render();
|
|
Assert.AreEqual(1, callbackCounter.Value);
|
|
|
|
mainCamera = null;
|
|
GameObject.DestroyImmediate(cameraObject);
|
|
|
|
brgContext.ScheduleQueryRendererGroupInstancesJob(objIDs.AsArray(), instances).Complete();
|
|
brg.DestroyInstances(instances);
|
|
}
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
|
|
instances.Dispose();
|
|
objIDs.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestSceneViewHiddenRenderersCullingTier0()
|
|
{
|
|
var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
var renderer = go.GetComponent<MeshRenderer>();
|
|
renderer.material = simpleDotsMat;
|
|
|
|
var objIDs = new NativeArray<int>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
var instances = new NativeArray<InstanceHandle>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
objIDs[0] = renderer.GetInstanceID();
|
|
instances[0] = InstanceHandle.Invalid;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var brgContext = new RenderersBatchersContext(RenderersBatchersContextDesc.NewDefault(), gpuDrivenProcessor, m_Resources))
|
|
{
|
|
var cpuDrivenDesc = InstanceCullingBatcherDesc.NewDefault();
|
|
var callbackCounter = new BoxedCounter();
|
|
|
|
cpuDrivenDesc.onCompleteCallback = (JobHandle jobHandle, in BatchCullingContext cc, in BatchCullingOutput cullingOutput) =>
|
|
{
|
|
if (cc.viewType != BatchCullingViewType.Camera)
|
|
return;
|
|
|
|
jobHandle.Complete();
|
|
BatchCullingOutputDrawCommands drawCommands = cullingOutput.drawCommands[0];
|
|
callbackCounter.Value = drawCommands.visibleInstanceCount;
|
|
};
|
|
|
|
using (var brg = new GPUResidentBatcher(brgContext, cpuDrivenDesc, gpuDrivenProcessor))
|
|
{
|
|
brg.UpdateRenderers(objIDs);
|
|
|
|
var cameraObject = new GameObject("SceneViewCamera");
|
|
var mainCamera = cameraObject.AddComponent<Camera>();
|
|
mainCamera.cameraType = CameraType.SceneView;
|
|
|
|
SceneVisibilityManager.instance.Hide(go, true);
|
|
|
|
brg.OnBeginCameraRendering(mainCamera);
|
|
mainCamera.Render();
|
|
brg.OnEndCameraRendering(mainCamera);
|
|
Assert.AreEqual(callbackCounter.Value, 0);
|
|
|
|
SceneVisibilityManager.instance.Show(go, true);
|
|
|
|
brg.OnBeginCameraRendering(mainCamera);
|
|
mainCamera.Render();
|
|
brg.OnEndCameraRendering(mainCamera);
|
|
Assert.AreEqual(callbackCounter.Value, 1);
|
|
|
|
GameObject.DestroyImmediate(cameraObject);
|
|
brgContext.ScheduleQueryRendererGroupInstancesJob(objIDs, instances).Complete();
|
|
brg.DestroyInstances(instances);
|
|
}
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
instances.Dispose();
|
|
objIDs.Dispose();
|
|
}
|
|
|
|
[Test, Ignore("Error in test shader (it is not DOTS compatible"), ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestMultipleMetadata()
|
|
{
|
|
var go0 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var go1 = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
|
var go2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
|
|
var objList = new List<MeshRenderer>();
|
|
objList.Add(go0.GetComponent<MeshRenderer>());
|
|
objList.Add(go1.GetComponent<MeshRenderer>());
|
|
objList.Add(go2.GetComponent<MeshRenderer>());
|
|
|
|
var objIDs = new NativeList<int>(Allocator.TempJob);
|
|
|
|
Shader simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
Material simpleDotsMat = new Material(simpleDots);
|
|
|
|
foreach (var obj in objList)
|
|
{
|
|
obj.receiveGI = ReceiveGI.LightProbes;
|
|
obj.lightProbeUsage = LightProbeUsage.BlendProbes;
|
|
obj.material = simpleDotsMat;
|
|
objIDs.Add(obj.GetInstanceID());
|
|
}
|
|
objList[2].lightProbeUsage = LightProbeUsage.Off;
|
|
|
|
var instances = new NativeArray<InstanceHandle>(objList.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
for (int i = 0; i < objList.Count; ++i)
|
|
instances[i] = InstanceHandle.Invalid;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var brgContext = new RenderersBatchersContext(new RenderersBatchersContextDesc() { instanceNumInfo = new InstanceNumInfo(meshRendererNum: 64, 0), supportDitheringCrossFade = false }, gpuDrivenProcessor, m_Resources))
|
|
{
|
|
var cpuDrivenDesc = InstanceCullingBatcherDesc.NewDefault();
|
|
cpuDrivenDesc.onCompleteCallback = (JobHandle jobHandle, in BatchCullingContext cc, in BatchCullingOutput cullingOutput) =>
|
|
{
|
|
if (cc.viewType != BatchCullingViewType.Camera)
|
|
return;
|
|
|
|
jobHandle.Complete();
|
|
BatchCullingOutputDrawCommands drawCommands = cullingOutput.drawCommands[0];
|
|
|
|
var drawCommandCount = 0U;
|
|
unsafe
|
|
{
|
|
for (int i = 0; i < drawCommands.drawRangeCount; ++i)
|
|
{
|
|
BatchDrawRange range = drawCommands.drawRanges[i];
|
|
drawCommandCount += range.drawCommandsCount;
|
|
for (int c = 0; c < range.drawCommandsCount; ++c)
|
|
{
|
|
BatchDrawCommand cmd = drawCommands.drawCommands[range.drawCommandsBegin + c];
|
|
}
|
|
}
|
|
}
|
|
Assert.AreEqual(3, drawCommandCount);
|
|
};
|
|
|
|
using (var brg = new GPUResidentBatcher(brgContext, cpuDrivenDesc, gpuDrivenProcessor))
|
|
{
|
|
brg.UpdateRenderers(objIDs.AsArray());
|
|
|
|
var cameraObject = new GameObject("myCamera");
|
|
var mainCamera = cameraObject.AddComponent<Camera>();
|
|
|
|
mainCamera.Render();
|
|
|
|
mainCamera = null;
|
|
GameObject.DestroyImmediate(cameraObject);
|
|
|
|
brgContext.ScheduleQueryRendererGroupInstancesJob(objIDs.AsArray(), instances).Complete();
|
|
brg.DestroyInstances(instances);
|
|
}
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
|
|
instances.Dispose();
|
|
objIDs.Dispose();
|
|
}
|
|
|
|
[Test, Ignore("Error in test shader (it is not DOTS compatible"), ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestCPULODSelection()
|
|
{
|
|
var previousLodBias = QualitySettings.lodBias;
|
|
QualitySettings.lodBias = 1.0f;
|
|
|
|
var gameObject = new GameObject("LODGroup");
|
|
gameObject.AddComponent<LODGroup>();
|
|
|
|
GameObject[] gos = new GameObject[] {
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Sphere),
|
|
GameObject.CreatePrimitive(PrimitiveType.Capsule),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cylinder)
|
|
};
|
|
|
|
var lodGroup = gameObject.GetComponent<LODGroup>();
|
|
var lodCount = 3;
|
|
LOD[] lods = new LOD[lodCount];
|
|
for (var i = 0; i < lodCount; i++)
|
|
{
|
|
gos[i].transform.parent = gameObject.transform;
|
|
lods[i].screenRelativeTransitionHeight = 0.3f - (0.14f * i);
|
|
lods[i].fadeTransitionWidth = 0.0f;
|
|
lods[i].renderers = new Renderer[1] { gos[i].GetComponent<MeshRenderer>() as Renderer };
|
|
}
|
|
gos[lodCount].transform.parent = gameObject.transform;
|
|
lodGroup.SetLODs(lods);
|
|
|
|
var lodGroupInstancesID = new NativeList<int>(Allocator.TempJob);
|
|
lodGroupInstancesID.Add(lodGroup.GetInstanceID());
|
|
|
|
var objList = new List<MeshRenderer>();
|
|
for (var i = 0; i < lodCount; i++)
|
|
{
|
|
objList.Add(gos[i].GetComponent<MeshRenderer>());
|
|
}
|
|
objList.Add(gos[lodCount].GetComponent<MeshRenderer>());
|
|
|
|
var objIDs = new NativeList<int>(Allocator.TempJob);
|
|
|
|
Shader dotsShader = Shader.Find("Unlit/SimpleDots");
|
|
var dotsMaterial = new Material(dotsShader);
|
|
foreach (var obj in objList)
|
|
{
|
|
obj.material = dotsMaterial;
|
|
objIDs.Add(obj.GetInstanceID());
|
|
}
|
|
|
|
var instances = new NativeArray<InstanceHandle>(objList.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
for (int i = 0; i < objList.Count; ++i)
|
|
instances[i] = InstanceHandle.Invalid;
|
|
|
|
var rbcDesc = RenderersBatchersContextDesc.NewDefault();
|
|
rbcDesc.instanceNumInfo = new InstanceNumInfo(meshRendererNum: 64, 0);
|
|
rbcDesc.supportDitheringCrossFade = false;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var brgContext = new RenderersBatchersContext(rbcDesc, gpuDrivenProcessor, m_Resources))
|
|
{
|
|
var cpuDrivenDesc = InstanceCullingBatcherDesc.NewDefault();
|
|
var callbackCounter = new BoxedCounter();
|
|
var expectedMeshID = 1;
|
|
var expectedDrawCommandCount = 2;
|
|
cpuDrivenDesc.onCompleteCallback = (JobHandle jobHandle, in BatchCullingContext cc, in BatchCullingOutput cullingOutput) =>
|
|
{
|
|
if (cc.viewType != BatchCullingViewType.Camera)
|
|
return;
|
|
|
|
jobHandle.Complete();
|
|
BatchCullingOutputDrawCommands drawCommands = cullingOutput.drawCommands[0];
|
|
|
|
var drawCommandCount = 0U;
|
|
unsafe
|
|
{
|
|
for (int i = 0; i < drawCommands.drawRangeCount; ++i)
|
|
{
|
|
BatchDrawRange range = drawCommands.drawRanges[i];
|
|
drawCommandCount += range.drawCommandsCount;
|
|
BatchDrawCommand cmd = drawCommands.drawCommands[range.drawCommandsBegin];
|
|
Assert.AreEqual(expectedMeshID, cmd.meshID.value, "Incorrect mesh rendered");
|
|
}
|
|
}
|
|
Assert.IsTrue(drawCommandCount == expectedDrawCommandCount, "Incorrect draw command count");
|
|
|
|
callbackCounter.Value += 1;
|
|
};
|
|
|
|
using (var brg = new GPUResidentBatcher(brgContext, cpuDrivenDesc, gpuDrivenProcessor))
|
|
{
|
|
brgContext.UpdateLODGroups(lodGroupInstancesID.AsArray());
|
|
brg.UpdateRenderers(objIDs.AsArray());
|
|
|
|
var cameraObject = new GameObject("myCamera");
|
|
var mainCamera = cameraObject.AddComponent<Camera>();
|
|
mainCamera.fieldOfView = 60;
|
|
|
|
//Test 1 - Should render Lod0 (range 0 - 6.66)
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -1.0f);
|
|
mainCamera.Render();
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -5.65f);
|
|
mainCamera.Render();
|
|
|
|
//Test 2 - Should render Lod1(range 6.66 - 12.5)
|
|
expectedMeshID = 2;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -6.67f);
|
|
mainCamera.Render();
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -10.5f);
|
|
mainCamera.Render();
|
|
|
|
//Test 3 - Should render Lod2 (range 12.5 - 99.9)
|
|
expectedMeshID = 3;
|
|
gameObject.transform.localScale *= 0.5f;
|
|
|
|
// For now we have to manually dispatch lod group transform changes.
|
|
Vector3 worldRefPoint = lodGroup.GetWorldReferencePoint();
|
|
float worldSize = lodGroup.GetWorldSpaceSize();
|
|
|
|
var transformedLODGroups = new NativeArray<int>(1, Allocator.Temp);
|
|
transformedLODGroups[0] = lodGroup.GetInstanceID();
|
|
|
|
brgContext.TransformLODGroups(transformedLODGroups);
|
|
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -6.5f);
|
|
mainCamera.Render();
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -40.3f);
|
|
mainCamera.Render();
|
|
|
|
//Test 3 - Should size cull (range 99.9 - Inf.)
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -50.4f);
|
|
expectedMeshID = 4;
|
|
expectedDrawCommandCount = 1;
|
|
mainCamera.Render();
|
|
|
|
Assert.AreEqual(7, callbackCounter.Value);
|
|
|
|
mainCamera = null;
|
|
GameObject.DestroyImmediate(cameraObject);
|
|
|
|
brgContext.ScheduleQueryRendererGroupInstancesJob(objIDs.AsArray(), instances).Complete();
|
|
brg.DestroyInstances(instances);
|
|
}
|
|
}
|
|
|
|
lodGroupInstancesID.Dispose();
|
|
gpuDrivenProcessor.Dispose();
|
|
|
|
objIDs.Dispose();
|
|
instances.Dispose();
|
|
|
|
QualitySettings.lodBias = previousLodBias;
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestCPULODCrossfade()
|
|
{
|
|
var previousLodBias = QualitySettings.lodBias;
|
|
QualitySettings.lodBias = 1.0f;
|
|
|
|
var gameObject = new GameObject("LODGroup");
|
|
gameObject.AddComponent<LODGroup>();
|
|
|
|
GameObject[] gos = new GameObject[] {
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Sphere),
|
|
GameObject.CreatePrimitive(PrimitiveType.Sphere),
|
|
};
|
|
|
|
var lodGroup = gameObject.GetComponent<LODGroup>();
|
|
var lodCount = 2;
|
|
LOD[] lods = new LOD[lodCount];
|
|
for (var i = 0; i < lodCount; i++)
|
|
{
|
|
gos[i].transform.parent = gameObject.transform;
|
|
lods[i].screenRelativeTransitionHeight = 0.4f - (0.2f * i);
|
|
lods[i].fadeTransitionWidth = 0.3f;
|
|
lods[i].renderers = new Renderer[1] { gos[i].GetComponent<MeshRenderer>() as Renderer };
|
|
}
|
|
lodGroup.fadeMode = LODFadeMode.CrossFade;
|
|
lodGroup.SetLODs(lods);
|
|
|
|
var objList = new List<MeshRenderer>();
|
|
for (var i = 0; i < lodCount; i++)
|
|
{
|
|
objList.Add(gos[i].GetComponent<MeshRenderer>());
|
|
}
|
|
objList.Add(gos[lodCount].GetComponent<MeshRenderer>());
|
|
|
|
var lodGroupInstancesID = new NativeList<int>(Allocator.TempJob);
|
|
lodGroupInstancesID.Add(lodGroup.GetInstanceID());
|
|
|
|
var objIDs = new NativeList<int>(Allocator.TempJob);
|
|
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
foreach (var obj in objList)
|
|
{
|
|
obj.material = simpleDotsMat;
|
|
objIDs.Add(obj.GetInstanceID());
|
|
}
|
|
|
|
var instances = new NativeArray<InstanceHandle>(objList.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
for (int i = 0; i < objList.Count; ++i)
|
|
instances[i] = InstanceHandle.Invalid;
|
|
|
|
var rbcDesc = RenderersBatchersContextDesc.NewDefault();
|
|
rbcDesc.instanceNumInfo = new InstanceNumInfo(meshRendererNum: 64, 0);
|
|
rbcDesc.supportDitheringCrossFade = true;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var brgContext = new RenderersBatchersContext(rbcDesc, gpuDrivenProcessor, m_Resources))
|
|
{
|
|
var cpuDrivenDesc = InstanceCullingBatcherDesc.NewDefault();
|
|
var expectedMeshIDs = new List<int>();
|
|
var expectedFlags = new List<BatchDrawCommandFlags>();
|
|
var expectedDrawCommandCount = 0;
|
|
cpuDrivenDesc.onCompleteCallback = (JobHandle jobHandle, in BatchCullingContext cc, in BatchCullingOutput cullingOutput) =>
|
|
{
|
|
if (cc.viewType != BatchCullingViewType.Camera)
|
|
return;
|
|
|
|
jobHandle.Complete();
|
|
BatchCullingOutputDrawCommands drawCommands = cullingOutput.drawCommands[0];
|
|
|
|
unsafe
|
|
{
|
|
Assert.AreEqual(1, drawCommands.drawRangeCount);
|
|
BatchDrawRange range = drawCommands.drawRanges[0];
|
|
Assert.AreEqual(range.drawCommandsCount, expectedDrawCommandCount, " Incorrect draw Command Count");
|
|
for (int i = 0; i < range.drawCommandsCount; ++i)
|
|
{
|
|
BatchDrawCommand cmd = drawCommands.drawCommands[range.drawCommandsBegin + i];
|
|
Assert.AreEqual(expectedMeshIDs[i], cmd.meshID.value, "Incorrect mesh rendered");
|
|
Assert.AreEqual(cmd.flags & BatchDrawCommandFlags.LODCrossFade, expectedFlags[i], "Incorrect flag for the current draw command");
|
|
}
|
|
}
|
|
};
|
|
|
|
using (var brg = new GPUResidentBatcher(brgContext, cpuDrivenDesc, gpuDrivenProcessor))
|
|
{
|
|
brgContext.UpdateLODGroups(lodGroupInstancesID.AsArray());
|
|
brg.UpdateRenderers(objIDs.AsArray());
|
|
|
|
var cameraObject = new GameObject("myCamera");
|
|
var mainCamera = cameraObject.AddComponent<Camera>();
|
|
mainCamera.fieldOfView = 60;
|
|
|
|
// Cube Mesh ID : 1 (Lod 0)
|
|
// Sphere Mesh ID : 2 (Lod 1 + non Loded)
|
|
//Test 0 - Should render Lod0 (cube) + non loded sphere
|
|
expectedMeshIDs.Add(1);
|
|
expectedMeshIDs.Add(2);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedDrawCommandCount = 2;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -1.0f);
|
|
mainCamera.Render();
|
|
|
|
//Test 1 - Should render Lod0 and 1 crossfaded + non loded sphere
|
|
expectedMeshIDs.Clear();
|
|
expectedMeshIDs.Add(1);
|
|
expectedMeshIDs.Add(2);
|
|
expectedMeshIDs.Add(2);
|
|
expectedFlags.Clear();
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFade);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFade);
|
|
expectedDrawCommandCount = 3;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -2.0f);
|
|
mainCamera.Render();
|
|
|
|
//Test 2 - Should render Lod1 + non loded sphere (single Draw Command as they are both spheres)
|
|
expectedMeshIDs.Clear();
|
|
expectedMeshIDs.Add(2);
|
|
expectedFlags.Clear();
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedDrawCommandCount = 1;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -3.0f);
|
|
mainCamera.Render();
|
|
|
|
//Test 3 - Should render Lod1 crossfaded + non loded sphere
|
|
expectedMeshIDs.Clear();
|
|
expectedMeshIDs.Add(2);
|
|
expectedMeshIDs.Add(2);
|
|
expectedFlags.Clear();
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFade);
|
|
expectedDrawCommandCount = 2;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -4.0f);
|
|
mainCamera.Render();
|
|
|
|
mainCamera = null;
|
|
GameObject.DestroyImmediate(cameraObject);
|
|
|
|
brgContext.ScheduleQueryRendererGroupInstancesJob(objIDs.AsArray(), instances).Complete();
|
|
brg.DestroyInstances(instances);
|
|
}
|
|
}
|
|
|
|
lodGroupInstancesID.Dispose();
|
|
gpuDrivenProcessor.Dispose();
|
|
|
|
objIDs.Dispose();
|
|
instances.Dispose();
|
|
|
|
QualitySettings.lodBias = previousLodBias;
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestGpuDrivenSmallMeshCulling()
|
|
{
|
|
var gameObject = new GameObject("Root");
|
|
var sphere0 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
|
sphere0.transform.parent = gameObject.transform;
|
|
var sphere1 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
|
sphere1.AddComponent<DisallowSmallMeshCulling>();
|
|
sphere1.transform.parent = gameObject.transform;
|
|
|
|
var objList = new List<MeshRenderer>();
|
|
objList.Add(sphere0.GetComponent<MeshRenderer>());
|
|
objList.Add(sphere1.GetComponent<MeshRenderer>());
|
|
|
|
var objIDs = new NativeList<int>(Allocator.TempJob);
|
|
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
foreach (var obj in objList)
|
|
{
|
|
obj.material = simpleDotsMat;
|
|
objIDs.Add(obj.GetInstanceID());
|
|
}
|
|
|
|
var instances = new NativeArray<InstanceHandle>(objList.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
for (int i = 0; i < objList.Count; ++i)
|
|
instances[i] = InstanceHandle.Invalid;
|
|
|
|
var rbcDesc = RenderersBatchersContextDesc.NewDefault();
|
|
rbcDesc.instanceNumInfo = new InstanceNumInfo(meshRendererNum: 64, 0);
|
|
rbcDesc.supportDitheringCrossFade = true;
|
|
rbcDesc.smallMeshScreenPercentage = 10.0f;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var brgContext = new RenderersBatchersContext(rbcDesc, gpuDrivenProcessor, m_Resources))
|
|
{
|
|
var cpuDrivenDesc = InstanceCullingBatcherDesc.NewDefault();
|
|
var expectedMeshIDs = new List<int>();
|
|
var expectedFlags = new List<BatchDrawCommandFlags>();
|
|
var expectedDrawCommandCount = 0;
|
|
cpuDrivenDesc.onCompleteCallback = (JobHandle jobHandle, in BatchCullingContext cc, in BatchCullingOutput cullingOutput) =>
|
|
{
|
|
if (cc.viewType != BatchCullingViewType.Camera)
|
|
return;
|
|
|
|
jobHandle.Complete();
|
|
BatchCullingOutputDrawCommands drawCommands = cullingOutput.drawCommands[0];
|
|
|
|
unsafe
|
|
{
|
|
Assert.AreEqual(1, drawCommands.drawRangeCount);
|
|
BatchDrawRange range = drawCommands.drawRanges[0];
|
|
Assert.AreEqual(range.drawCommandsCount, expectedDrawCommandCount, " Incorrect draw Command Count");
|
|
for (int i = 0; i < range.drawCommandsCount; ++i)
|
|
{
|
|
BatchDrawCommand cmd = drawCommands.drawCommands[range.drawCommandsBegin + i];
|
|
Assert.AreEqual(expectedMeshIDs[i], cmd.meshID.value, "Incorrect mesh rendered");
|
|
Assert.AreEqual(cmd.flags & BatchDrawCommandFlags.LODCrossFade, expectedFlags[i], "Incorrect flag for the current draw command");
|
|
}
|
|
}
|
|
};
|
|
|
|
using (var brg = new GPUResidentBatcher(brgContext, cpuDrivenDesc, gpuDrivenProcessor))
|
|
{
|
|
brg.UpdateRenderers(objIDs.AsArray());
|
|
|
|
var cameraObject = new GameObject("myCamera");
|
|
var mainCamera = cameraObject.AddComponent<Camera>();
|
|
mainCamera.fieldOfView = 60;
|
|
|
|
//Test 0 - (1m) Should render both spheres.
|
|
expectedMeshIDs.Add(1);
|
|
expectedMeshIDs.Add(1);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedDrawCommandCount = 1;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -1.0f);
|
|
mainCamera.Render();
|
|
|
|
//Test 1 - (8.5m) Should render sphere1 + crossfaded sphere0.
|
|
expectedMeshIDs.Clear();
|
|
expectedMeshIDs.Add(1);
|
|
expectedMeshIDs.Add(1);
|
|
expectedFlags.Clear();
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFade);
|
|
expectedDrawCommandCount = 1;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -8.5f);
|
|
mainCamera.Render();
|
|
|
|
//Test 2 - (10m) Should only render sphere1.
|
|
expectedMeshIDs.Clear();
|
|
expectedMeshIDs.Add(1);
|
|
expectedFlags.Clear();
|
|
expectedFlags.Add(BatchDrawCommandFlags.LODCrossFadeValuePacked);
|
|
expectedDrawCommandCount = 1;
|
|
cameraObject.transform.position = new Vector3(0.0f, 0.0f, -10.0f);
|
|
mainCamera.Render();
|
|
|
|
mainCamera = null;
|
|
GameObject.DestroyImmediate(cameraObject);
|
|
|
|
brgContext.ScheduleQueryRendererGroupInstancesJob(objIDs.AsArray(), instances).Complete();
|
|
brg.DestroyInstances(instances);
|
|
}
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
|
|
objIDs.Dispose();
|
|
instances.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestInstanceDataBuffer()
|
|
{
|
|
var gpuResources = new GPUInstanceDataBufferUploader.GPUResources();
|
|
gpuResources.LoadShaders(m_Resources);
|
|
|
|
var meshInstancesCount = 4;
|
|
var instanceNumInfo = new InstanceNumInfo(meshRendererNum: meshInstancesCount, 0);
|
|
|
|
using (var instanceBuffer = RenderersParameters.CreateInstanceDataBuffer(RenderersParameters.Flags.None, instanceNumInfo))
|
|
{
|
|
var renderersParameters = new RenderersParameters(instanceBuffer);
|
|
|
|
var instances = new NativeArray<GPUInstanceIndex>(meshInstancesCount, Allocator.TempJob);
|
|
var lightmapScaleOffsets = new NativeArray<Vector4>(meshInstancesCount, Allocator.TempJob);
|
|
|
|
for (int i = 0; i < meshInstancesCount; ++i)
|
|
instances[i] = new GPUInstanceIndex { index = i };
|
|
|
|
for (int i = 0; i < meshInstancesCount; ++i)
|
|
lightmapScaleOffsets[i] = Vector4.one * i;
|
|
|
|
using (var instanceUploader0 = new GPUInstanceDataBufferUploader(instanceBuffer.descriptions, meshInstancesCount, InstanceType.MeshRenderer))
|
|
{
|
|
instanceUploader0.AllocateUploadHandles(instances.Length);
|
|
instanceUploader0.WriteInstanceDataJob(renderersParameters.lightmapScale.index, lightmapScaleOffsets).Complete();
|
|
instanceUploader0.SubmitToGpu(instanceBuffer, instances, ref gpuResources, submitOnlyWrittenParams: false);
|
|
}
|
|
|
|
using (var readbackData = new InstanceDataBufferCPUReadbackData())
|
|
{
|
|
if (readbackData.Load(instanceBuffer))
|
|
{
|
|
for (int i = 0; i < meshInstancesCount; ++i)
|
|
{
|
|
var lightmapScaleOffset = readbackData.LoadData<Vector4>(instances[i], RenderersParameters.ParamNames.unity_LightmapST);
|
|
Assert.AreEqual(lightmapScaleOffset, lightmapScaleOffsets[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
instances.Dispose();
|
|
lightmapScaleOffsets.Dispose();
|
|
}
|
|
gpuResources.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestGrowInstanceDataBuffer()
|
|
{
|
|
var uploadResources = new GPUInstanceDataBufferUploader.GPUResources();
|
|
var growResources = new GPUInstanceDataBufferGrower.GPUResources();
|
|
uploadResources.LoadShaders(m_Resources);
|
|
growResources.LoadShaders(m_Resources);
|
|
|
|
var meshInstancesCount = 8;
|
|
var instanceNumInfo = new InstanceNumInfo(meshRendererNum: meshInstancesCount, 0);
|
|
|
|
using (var instanceBuffer = RenderersParameters.CreateInstanceDataBuffer(RenderersParameters.Flags.None, instanceNumInfo))
|
|
{
|
|
var renderersParameters = new RenderersParameters(instanceBuffer);
|
|
|
|
var instances = new NativeArray<GPUInstanceIndex>(meshInstancesCount, Allocator.TempJob);
|
|
var lightmapTextureIndices = new NativeArray<Vector4>(meshInstancesCount, Allocator.TempJob);
|
|
var lightmapScaleOffsets = new NativeArray<Vector4>(meshInstancesCount, Allocator.TempJob);
|
|
|
|
for (int i = 0; i < meshInstancesCount; ++i)
|
|
instances[i] = new GPUInstanceIndex { index = i };
|
|
|
|
for (int i = 0; i < meshInstancesCount; ++i)
|
|
lightmapScaleOffsets[i] = Vector4.one * i;
|
|
|
|
using (var instanceUploader0 = new GPUInstanceDataBufferUploader(instanceBuffer.descriptions, meshInstancesCount, InstanceType.MeshRenderer))
|
|
{
|
|
instanceUploader0.AllocateUploadHandles(instances.Length);
|
|
instanceUploader0.WriteInstanceDataJob(renderersParameters.lightmapScale.index, lightmapScaleOffsets).Complete();
|
|
instanceUploader0.SubmitToGpu(instanceBuffer, instances, ref uploadResources, submitOnlyWrittenParams: false);
|
|
}
|
|
|
|
var instanceGrower = new GPUInstanceDataBufferGrower(instanceBuffer, new InstanceNumInfo(meshRendererNum: meshInstancesCount * 2, 0));
|
|
var newGPUDataBuffer = instanceGrower.SubmitToGpu(ref growResources);
|
|
instanceGrower.Dispose();
|
|
|
|
using (var readbackData = new InstanceDataBufferCPUReadbackData())
|
|
{
|
|
if (readbackData.Load(newGPUDataBuffer))
|
|
{
|
|
for (int i = 0; i < meshInstancesCount; ++i)
|
|
{
|
|
var lightmapScaleOffset = readbackData.LoadData<Vector4>(instances[i], RenderersParameters.ParamNames.unity_LightmapST);
|
|
Assert.AreEqual(lightmapScaleOffset, lightmapScaleOffsets[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
newGPUDataBuffer.Dispose();
|
|
|
|
instances.Dispose();
|
|
lightmapTextureIndices.Dispose();
|
|
lightmapScaleOffsets.Dispose();
|
|
}
|
|
uploadResources.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestInstanceData()
|
|
{
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var instanceSystem = new InstanceDataSystem(5, enableBoundingSpheres: false, m_Resources))
|
|
{
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
|
|
var gameObjects = new GameObject[7]
|
|
{
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube)
|
|
};
|
|
|
|
foreach(var go in gameObjects)
|
|
{
|
|
MeshRenderer renderer = go.GetComponent<MeshRenderer>();
|
|
renderer.sharedMaterial = simpleDotsMat;
|
|
}
|
|
|
|
var renderersID = new NativeArray<int>(3, Allocator.TempJob);
|
|
renderersID[0] = gameObjects[0].GetComponent<MeshRenderer>().GetInstanceID();
|
|
renderersID[1] = gameObjects[1].GetComponent<MeshRenderer>().GetInstanceID();
|
|
renderersID[2] = gameObjects[2].GetComponent<MeshRenderer>().GetInstanceID();
|
|
|
|
var lodGroupDataMap = new NativeParallelHashMap<int, GPUInstanceIndex>(64, Allocator.TempJob);
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(renderersID, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
var instances = new NativeArray<InstanceHandle>(3, Allocator.TempJob);
|
|
instances[0] = InstanceHandle.Invalid;
|
|
instances[1] = InstanceHandle.Invalid;
|
|
instances[2] = InstanceHandle.Invalid;
|
|
|
|
instanceSystem.ReallocateAndGetInstances(rendererData, instances);
|
|
instanceSystem.ScheduleUpdateInstanceDataJob(instances, rendererData, lodGroupDataMap).Complete();
|
|
|
|
Assert.IsTrue(instanceSystem.InternalSanityCheckStates());
|
|
|
|
instanceSystem.FreeInstances(instances);
|
|
|
|
instances.Dispose();
|
|
});
|
|
|
|
Assert.IsTrue(instanceSystem.InternalSanityCheckStates());
|
|
|
|
renderersID[0] = gameObjects[3].GetComponent<MeshRenderer>().GetInstanceID();
|
|
renderersID[1] = gameObjects[4].GetComponent<MeshRenderer>().GetInstanceID();
|
|
renderersID[2] = gameObjects[5].GetComponent<MeshRenderer>().GetInstanceID();
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(renderersID, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
var instances = new NativeArray<InstanceHandle>(3, Allocator.TempJob);
|
|
instances[0] = InstanceHandle.Invalid;
|
|
instances[1] = InstanceHandle.Invalid;
|
|
instances[2] = InstanceHandle.Invalid;
|
|
|
|
instanceSystem.ReallocateAndGetInstances(rendererData, instances);
|
|
instanceSystem.ScheduleUpdateInstanceDataJob(instances, rendererData, lodGroupDataMap).Complete();
|
|
|
|
Assert.IsTrue(instanceSystem.InternalSanityCheckStates());
|
|
|
|
instanceSystem.FreeInstances(instances);
|
|
|
|
instances.Dispose();
|
|
});
|
|
|
|
Assert.IsTrue(instanceSystem.InternalSanityCheckStates());
|
|
|
|
renderersID.Dispose();
|
|
|
|
renderersID = new NativeArray<int>(1, Allocator.TempJob);
|
|
renderersID[0] = gameObjects[6].GetComponent<MeshRenderer>().GetInstanceID();
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(renderersID, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
var instances = new NativeArray<InstanceHandle>(1, Allocator.TempJob);
|
|
instances[0] = InstanceHandle.Invalid;
|
|
|
|
instanceSystem.ReallocateAndGetInstances(rendererData, instances);
|
|
instanceSystem.ScheduleUpdateInstanceDataJob(instances, rendererData, lodGroupDataMap).Complete();
|
|
|
|
Assert.IsTrue(instanceSystem.InternalSanityCheckStates());
|
|
|
|
instanceSystem.FreeInstances(instances);
|
|
|
|
instances.Dispose();
|
|
});
|
|
|
|
Assert.IsTrue(instanceSystem.InternalSanityCheckStates());
|
|
|
|
renderersID.Dispose();
|
|
lodGroupDataMap.Dispose();
|
|
|
|
foreach (var go in gameObjects)
|
|
GameObject.DestroyImmediate(go);
|
|
|
|
GameObject.DestroyImmediate(simpleDotsMat);
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestStaticBatching()
|
|
{
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
var dispatcher = new ObjectDispatcher();
|
|
dispatcher.EnableTransformTracking<MeshRenderer>(ObjectDispatcher.TransformTrackingType.GlobalTRS);
|
|
|
|
using (var brgContext = new RenderersBatchersContext(RenderersBatchersContextDesc.NewDefault(), gpuDrivenProcessor, m_Resources))
|
|
{
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
|
|
var staticBatchingRoot = new GameObject();
|
|
staticBatchingRoot.transform.position = new Vector3(10, 0, 0);
|
|
|
|
var gameObjects = new GameObject[2]
|
|
{
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube),
|
|
GameObject.CreatePrimitive(PrimitiveType.Cube)
|
|
};
|
|
|
|
foreach (var go in gameObjects)
|
|
{
|
|
MeshRenderer renderer = go.GetComponent<MeshRenderer>();
|
|
renderer.sharedMaterial = simpleDotsMat;
|
|
}
|
|
|
|
gameObjects[0].transform.position = new Vector3(2, 0, 0);
|
|
gameObjects[1].transform.position = new Vector3(-2, 0, 0);
|
|
|
|
StaticBatchingUtility.Combine(gameObjects, staticBatchingRoot);
|
|
|
|
var renderersID = new NativeArray<int>(2, Allocator.TempJob);
|
|
renderersID[0] = gameObjects[0].GetComponent<MeshRenderer>().GetInstanceID();
|
|
renderersID[1] = gameObjects[1].GetComponent<MeshRenderer>().GetInstanceID();
|
|
|
|
var lodGroupDataMap = new NativeParallelHashMap<int, InstanceHandle>(64, Allocator.TempJob);
|
|
var instances = new NativeArray<InstanceHandle>(2, Allocator.TempJob);
|
|
var localToWorldMatrices = new NativeArray<float4x4>(2, Allocator.Temp);
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(renderersID, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
Assert.IsTrue(rendererData.packedRendererData[0].isPartOfStaticBatch);
|
|
Assert.IsTrue(rendererData.packedRendererData[1].isPartOfStaticBatch);
|
|
|
|
brgContext.ReallocateAndGetInstances(rendererData, instances);
|
|
brgContext.ScheduleUpdateInstanceDataJob(instances, rendererData).Complete();
|
|
brgContext.InitializeInstanceTransforms(instances, rendererData.localToWorldMatrix, rendererData.localToWorldMatrix);
|
|
|
|
for(int i = 0; i < localToWorldMatrices.Length; ++i)
|
|
localToWorldMatrices[i] = rendererData.localToWorldMatrix[i];
|
|
});
|
|
|
|
gameObjects[0].transform.position = new Vector3(100, 0, 0);
|
|
gameObjects[1].transform.position = new Vector3(-100, 0, 0);
|
|
|
|
var transfomData = dispatcher.GetTransformChangesAndClear<MeshRenderer>(ObjectDispatcher.TransformTrackingType.GlobalTRS, Allocator.TempJob);
|
|
Assert.AreEqual(transfomData.transformedID.Length, 2);
|
|
|
|
brgContext.UpdateInstanceTransforms(instances, transfomData.localToWorldMatrices);
|
|
|
|
using (var readbackData = new InstanceDataBufferCPUReadbackData())
|
|
{
|
|
if (readbackData.Load(brgContext.GetInstanceDataBuffer()))
|
|
{
|
|
var localToWorldMatrix0 = readbackData.LoadData<PackedMatrix>(instances[0], RenderersParameters.ParamNames.unity_ObjectToWorld);
|
|
var localToWorldMatrix1 = readbackData.LoadData<PackedMatrix>(instances[1], RenderersParameters.ParamNames.unity_ObjectToWorld);
|
|
|
|
Assert.AreEqual(localToWorldMatrix0, PackedMatrix.FromMatrix4x4(localToWorldMatrices[0]));
|
|
Assert.AreEqual(localToWorldMatrix1, PackedMatrix.FromMatrix4x4(localToWorldMatrices[1]));
|
|
}
|
|
else
|
|
{
|
|
Assert.IsTrue(false, "Unable to read instance data.");
|
|
}
|
|
}
|
|
|
|
transfomData.Dispose();
|
|
renderersID.Dispose();
|
|
lodGroupDataMap.Dispose();
|
|
instances.Dispose();
|
|
|
|
foreach (var go in gameObjects)
|
|
GameObject.DestroyImmediate(go);
|
|
|
|
GameObject.DestroyImmediate(simpleDotsMat);
|
|
GameObject.DestroyImmediate(staticBatchingRoot);
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
dispatcher.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestDisallowGPUDrivenRendering()
|
|
{
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
|
|
var gameObject0 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var gameObject1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var renderer0 = gameObject0.GetComponent<MeshRenderer>();
|
|
var renderer1 = gameObject1.GetComponent<MeshRenderer>();
|
|
renderer0.sharedMaterial = simpleDotsMat;
|
|
renderer1.sharedMaterial = simpleDotsMat;
|
|
|
|
var rendererIDs = new NativeArray<int>(2, Allocator.Temp);
|
|
rendererIDs[0] = renderer0.GetInstanceID();
|
|
rendererIDs[1] = renderer1.GetInstanceID();
|
|
|
|
bool dispatched = false;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(rendererIDs, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
Assert.IsTrue(rendererData.rendererGroupID.Length == 2);
|
|
dispatched = true;
|
|
});
|
|
|
|
Assert.IsTrue(dispatched);
|
|
|
|
dispatched = false;
|
|
renderer0.allowGPUDrivenRendering = false;
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(rendererIDs, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
Assert.IsTrue(rendererData.rendererGroupID.Length == 1);
|
|
Assert.IsTrue(rendererData.rendererGroupID[0] == renderer1.GetInstanceID());
|
|
Assert.IsTrue(rendererData.invalidRendererGroupID.Length == 1);
|
|
Assert.IsTrue(rendererData.invalidRendererGroupID[0] == renderer0.GetInstanceID());
|
|
dispatched = true;
|
|
});
|
|
|
|
Assert.IsTrue(dispatched);
|
|
|
|
dispatched = false;
|
|
renderer1.allowGPUDrivenRendering = false;
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(rendererIDs, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
Assert.IsTrue(rendererData.invalidRendererGroupID.Length == 2);
|
|
dispatched = true;
|
|
});
|
|
|
|
Assert.IsTrue(dispatched);
|
|
|
|
Object.DestroyImmediate(simpleDotsMat);
|
|
Object.DestroyImmediate(gameObject0);
|
|
Object.DestroyImmediate(gameObject1);
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestUnsupportedCallbacks()
|
|
{
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
|
|
var gameObject0 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var gameObject1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var gameObject2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var gameObject3 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
|
|
var renderer0 = gameObject0.GetComponent<MeshRenderer>();
|
|
var renderer1 = gameObject1.GetComponent<MeshRenderer>();
|
|
var renderer2 = gameObject1.GetComponent<MeshRenderer>();
|
|
var renderer3 = gameObject1.GetComponent<MeshRenderer>();
|
|
|
|
renderer0.sharedMaterial = simpleDotsMat;
|
|
renderer1.sharedMaterial = simpleDotsMat;
|
|
renderer2.sharedMaterial = simpleDotsMat;
|
|
renderer3.sharedMaterial = simpleDotsMat;
|
|
|
|
var rendererIDs = new NativeArray<int>(4, Allocator.Temp);
|
|
rendererIDs[0] = renderer0.GetInstanceID();
|
|
rendererIDs[1] = renderer1.GetInstanceID();
|
|
rendererIDs[2] = renderer2.GetInstanceID();
|
|
rendererIDs[3] = renderer3.GetInstanceID();
|
|
|
|
bool dispatched = false;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(rendererIDs, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
Assert.IsTrue(rendererData.rendererGroupID.Length == 4);
|
|
dispatched = true;
|
|
});
|
|
|
|
Assert.IsTrue(dispatched);
|
|
|
|
gameObject1.AddComponent<OnWillRenderObjectBehaviour>();
|
|
gameObject2.AddComponent<OnBecameInvisibleBehaviour>();
|
|
gameObject3.AddComponent<OnBecameVisibleBehaviour>();
|
|
|
|
dispatched = false;
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(rendererIDs, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
Assert.IsTrue(rendererData.rendererGroupID.Length == 1);
|
|
dispatched = true;
|
|
});
|
|
|
|
Assert.IsTrue(dispatched);
|
|
|
|
Object.DestroyImmediate(simpleDotsMat);
|
|
Object.DestroyImmediate(gameObject0);
|
|
Object.DestroyImmediate(gameObject1);
|
|
Object.DestroyImmediate(gameObject2);
|
|
Object.DestroyImmediate(gameObject3);
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
}
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestForceRenderingOff()
|
|
{
|
|
var simpleDots = Shader.Find("Unlit/SimpleDots");
|
|
var simpleDotsMat = new Material(simpleDots);
|
|
|
|
var gameObject0 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var gameObject1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
|
|
var renderer0 = gameObject0.GetComponent<MeshRenderer>();
|
|
var renderer1 = gameObject1.GetComponent<MeshRenderer>();
|
|
|
|
renderer0.sharedMaterial = simpleDotsMat;
|
|
renderer1.sharedMaterial = simpleDotsMat;
|
|
|
|
renderer0.forceRenderingOff = true;
|
|
|
|
var rendererIDs = new NativeArray<int>(2, Allocator.Temp);
|
|
rendererIDs[0] = renderer0.GetInstanceID();
|
|
rendererIDs[1] = renderer1.GetInstanceID();
|
|
|
|
bool dispatched = false;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
gpuDrivenProcessor.EnableGPUDrivenRenderingAndDispatchRendererData(rendererIDs, (in GPUDrivenRendererGroupData rendererData, IList<Mesh> meshes, IList<Material> materials) =>
|
|
{
|
|
Assert.IsTrue(rendererData.rendererGroupID.Length == 1);
|
|
dispatched = true;
|
|
});
|
|
|
|
Assert.IsTrue(dispatched);
|
|
|
|
Object.DestroyImmediate(simpleDotsMat);
|
|
Object.DestroyImmediate(gameObject0);
|
|
Object.DestroyImmediate(gameObject1);
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
}
|
|
|
|
class OnWillRenderObjectBehaviour : MonoBehaviour { void OnWillRenderObject() { } }
|
|
class OnBecameInvisibleBehaviour : MonoBehaviour { void OnBecameInvisible() { } }
|
|
class OnBecameVisibleBehaviour : MonoBehaviour { void OnBecameVisible() { } }
|
|
|
|
[Test, ConditionalIgnore("IgnoreGfxAPI", "Graphics API Not Supported.")]
|
|
public void TestSimpleSpeedTree()
|
|
{
|
|
var simpleSpeedTreeDots = Shader.Find("Unlit/SimpleSpeedTreeDots");
|
|
var simpleSpeedTreeDotsMat = new Material(simpleSpeedTreeDots);
|
|
|
|
// SpeedTreeWindAsset doesn't have publicly exposed constructor.
|
|
// Without SpeedTreeWindAsset trees will not be treated as speed tree trees with wind.
|
|
SpeedTreeWindAsset CreateDummySpeedTreeWindAsset(params object[] args)
|
|
{
|
|
var type = typeof(SpeedTreeWindAsset);
|
|
var instance = type.Assembly.CreateInstance(type.FullName, false, BindingFlags.Instance | BindingFlags.NonPublic, null, args, null, null);
|
|
return (SpeedTreeWindAsset)instance;
|
|
}
|
|
|
|
SpeedTreeWindAsset dummyWindAsset = CreateDummySpeedTreeWindAsset(0, null);
|
|
Assert.NotNull(dummyWindAsset);
|
|
dummyWindAsset.Version = 0; // st8 version.
|
|
|
|
var tree0 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
var tree1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
tree0.AddComponent<Tree>().windAsset = dummyWindAsset;
|
|
tree1.AddComponent<Tree>().windAsset = dummyWindAsset;
|
|
|
|
var renderers = new List<MeshRenderer>
|
|
{
|
|
tree0.GetComponent<MeshRenderer>(),
|
|
tree1.GetComponent<MeshRenderer>()
|
|
};
|
|
|
|
var rendererIDs = new NativeList<int>(Allocator.TempJob);
|
|
|
|
foreach (var renderer in renderers)
|
|
{
|
|
renderer.receiveGI = ReceiveGI.LightProbes;
|
|
renderer.lightProbeUsage = LightProbeUsage.BlendProbes;
|
|
renderer.material = simpleSpeedTreeDotsMat;
|
|
rendererIDs.Add(renderer.GetInstanceID());
|
|
}
|
|
|
|
var instances = new NativeArray<InstanceHandle>(renderers.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
|
|
|
|
for (int i = 0; i < renderers.Count; ++i)
|
|
instances[i] = InstanceHandle.Invalid;
|
|
|
|
var gpuDrivenProcessor = new GPUDrivenProcessor();
|
|
|
|
using (var context = new RenderersBatchersContext(new RenderersBatchersContextDesc() { instanceNumInfo = new InstanceNumInfo(speedTreeNum: 8), supportDitheringCrossFade = true }, gpuDrivenProcessor, m_Resources))
|
|
using (var batcher = new GPUResidentBatcher(context, InstanceCullingBatcherDesc.NewDefault(), gpuDrivenProcessor))
|
|
{
|
|
batcher.UpdateRenderers(rendererIDs.AsArray());
|
|
context.ScheduleQueryRendererGroupInstancesJob(rendererIDs.AsArray(), instances).Complete();
|
|
|
|
Assert.AreEqual(2, instances.Length);
|
|
Assert.AreEqual(instances[0].type, InstanceType.SpeedTree);
|
|
Assert.AreEqual(instances[1].type, InstanceType.SpeedTree);
|
|
|
|
Assert.AreEqual(context.GetAliveInstancesOfType(InstanceType.MeshRenderer), 0);
|
|
Assert.AreEqual(context.GetAliveInstancesOfType(InstanceType.SpeedTree), 2);
|
|
|
|
var instanceIndex0 = context.instanceData.InstanceToIndex(instances[0]);
|
|
var instanceIndex1 = context.instanceData.InstanceToIndex(instances[1]);
|
|
var sharedInstance0 = context.instanceData.sharedInstances[instanceIndex0];
|
|
var sharedInstance1 = context.instanceData.sharedInstances[instanceIndex1];
|
|
var sharedInstanceIndex0 = context.sharedInstanceData.SharedInstanceToIndex(sharedInstance0);
|
|
var sharedInstanceIndex1 = context.sharedInstanceData.SharedInstanceToIndex(sharedInstance1);
|
|
|
|
Assert.AreEqual(context.sharedInstanceData.rendererGroupIDs[sharedInstanceIndex0], tree0.GetComponent<Renderer>().GetInstanceID());
|
|
Assert.AreEqual(context.sharedInstanceData.rendererGroupIDs[sharedInstanceIndex1], tree1.GetComponent<Renderer>().GetInstanceID());
|
|
|
|
context.FreeInstances(instances);
|
|
|
|
Assert.AreEqual(context.GetAliveInstancesOfType(InstanceType.SpeedTree), 0);
|
|
}
|
|
|
|
gpuDrivenProcessor.Dispose();
|
|
|
|
instances.Dispose();
|
|
rendererIDs.Dispose();
|
|
}
|
|
}
|
|
}
|