using UnityEngine;
using UnityEngine.Rendering;

	
public enum TypeRender //https://docs.unity3d.com/ScriptReference/Graphics.html
{
	//with native unity's materials (HDRP/Lit Shader) 
	RenderMesh = 0, //https://docs.unity3d.com/ScriptReference/Graphics.RenderMesh.html
	RenderMeshInstanced = 1, //https://docs.unity3d.com/ScriptReference/Graphics.RenderMeshInstanced.html
	
	//with custom shaders
	RenderMeshIndirect = 2, //https://docs.unity3d.com/ScriptReference/Graphics.RenderMeshIndirect.html
	RenderMeshPrimitives = 3, //https://docs.unity3d.com/ScriptReference/Graphics.RenderMeshPrimitives.html
	RenderPrimitive = 4, //https://docs.unity3d.com/ScriptReference/Graphics.RenderPrimitives.html
	RenderPrimitivesIndexed = 5, //https://docs.unity3d.com/ScriptReference/Graphics.RenderPrimitivesIndexed.html
	RenderPrimitivesIndexedIndirect = 6, //https://docs.unity3d.com/ScriptReference/Graphics.RenderPrimitivesIndexedIndirect.html
	RenderPrimitivesIndirect = 7, //https://docs.unity3d.com/ScriptReference/Graphics.RenderPrimitivesIndirect.html
}
public class ManualMeshRenderingAndGPUInstancing : MonoBehaviour
{
	[Space]
	public Mesh Mesh;
	public TypeRender Type;
	
	//Native Unity materials with HDRP/Lit shader
	private Material _materialRenderMesh;
	private Material _materialRenderMeshInstanced;
	
	//Materials with custom shaders
	private Material _materialRenderMeshIndirect;
	private Material _materialRenderMeshPrimitives;
	private Material _materialRenderPrimitives;
	private Material _materialRenderPrimitivesIndexed;
	private Material _materialRenderPrimitivesIndexedIndirect;
	private Material _materialRenderPrimitivesIndirect;
	
	private readonly int _numInstances = 10;
	
	private GraphicsBuffer _commandBuf1;
	private GraphicsBuffer _commandBuf2;
	private GraphicsBuffer.IndirectDrawIndexedArgs[] _commandData1;
	private GraphicsBuffer.IndirectDrawArgs[] _commandData2;
	private const int COMMAND_COUNT = 2;
	
	private GraphicsBuffer _meshTriangles;
	private GraphicsBuffer _meshPositions;
	
	void Start()
	{
		if (_meshTriangles != null)
			_meshTriangles.Release();
		_meshTriangles = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Mesh.triangles.Length, sizeof(int));
		_meshTriangles.SetData(Mesh.triangles);
		
		if (_meshPositions != null)
			_meshPositions.Release();
		_meshPositions = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Mesh.vertices.Length, 3 * sizeof(float));
		_meshPositions.SetData(Mesh.vertices);
	
		_commandBuf1 = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, COMMAND_COUNT, GraphicsBuffer.IndirectDrawIndexedArgs.size);
		_commandData1 = new GraphicsBuffer.IndirectDrawIndexedArgs[COMMAND_COUNT];
		_commandBuf2 = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, COMMAND_COUNT, GraphicsBuffer.IndirectDrawArgs.size);
		_commandData2 = new GraphicsBuffer.IndirectDrawArgs[COMMAND_COUNT];
		
		//You can create material with these shaders in Editor, make them public and set through the Inspector
		
		//Native Unity materials with HDRP/Lit shader
		_materialRenderMesh = CoreUtils.CreateEngineMaterial(Shader.Find("HDRP/Lit"));
		_materialRenderMesh.enableInstancing = true; //require for instancing
		_materialRenderMeshInstanced = CoreUtils.CreateEngineMaterial(Shader.Find("HDRP/Lit"));
		_materialRenderMeshInstanced.enableInstancing = true; //require for instancing
		
		//Materials with custom shaders
		_materialRenderMeshIndirect = CoreUtils.CreateEngineMaterial(Shader.Find("Unlit/RenderMeshIndirectShader"));
		_materialRenderMeshPrimitives = CoreUtils.CreateEngineMaterial(Shader.Find("Unlit/RenderMeshPrimitivesShader"));
		_materialRenderPrimitives = CoreUtils.CreateEngineMaterial(Shader.Find("Unlit/RenderPrimitivesIndirectShader"));
		_materialRenderPrimitivesIndexed = CoreUtils.CreateEngineMaterial(Shader.Find("Unlit/RenderPrimitivesIndexedShader"));
		_materialRenderPrimitivesIndexedIndirect = CoreUtils.CreateEngineMaterial(Shader.Find("Unlit/RenderPrimitivesIndexedIndirectShader"));
		_materialRenderPrimitivesIndirect = CoreUtils.CreateEngineMaterial(Shader.Find("Unlit/RenderPrimitivesIndirectShader"));
	}

	void OnDestroy()
	{
		_meshTriangles?.Dispose();
		_meshTriangles = null;
		_meshPositions?.Dispose();
		_meshPositions = null;
	
		_commandBuf1?.Release();
		_commandBuf1 = null;
		_commandBuf2?.Release();
		_commandBuf2 = null;
	}

	void Update()
	{
		switch (Type)
		{
			//Native Unity materials with HDRP/Lit shader
			case TypeRender.RenderMesh:
				RenderMeshTest();
				break;
			case TypeRender.RenderMeshInstanced:
				RenderMeshInstancedTest();
				break;
			
			//Materials with custom shaders
			case TypeRender.RenderMeshIndirect:
				RenderMeshIndirectTest();
				break;
			case TypeRender.RenderMeshPrimitives:
				RenderMeshPrimitivesTest();
				break;
			case TypeRender.RenderPrimitive:
				RenderPrimitivesTest();
				break;
			case TypeRender.RenderPrimitivesIndexed:
				RenderPrimitivesIndexedTest();
				break;
			case TypeRender.RenderPrimitivesIndexedIndirect:
				RenderPrimitivesIndexedIndirectTest();
				break;
			case TypeRender.RenderPrimitivesIndirect:
				RenderPrimitivesIndirectTest();
				break;
		}
	}

	
	//Native Unity materials with HDRP/Lit shader
	private void RenderMeshTest()
	{
		RenderParams rp = new RenderParams(_materialRenderMesh) { shadowCastingMode = ShadowCastingMode.On };
		rp.matProps = new MaterialPropertyBlock();
		//rp.material.shader = Shader.Find("Hidden/Internal-Colored");
		rp.matProps.SetInt("_NumInstances", _numInstances);
		rp.matProps.SetInt("_TypeRender", (int)Type);
		rp.matProps.SetColor("_BaseColor", new Color(0.1098039f, 0.1400719f, 0.8784314f, 1f));
		Matrix4x4[] instData = new Matrix4x4[_numInstances];
		for (int i = 0; i < _numInstances; ++i)
		{
			instData[i] = Matrix4x4.Translate(new Vector3(-_numInstances + 1 + 2 * i, 1.0f, 0.0f));
		}
		Graphics.RenderMeshInstanced(rp, Mesh, 0, instData);
	}

	private void RenderMeshInstancedTest()
	{
		RenderParams rp = new RenderParams(_materialRenderMeshInstanced) { shadowCastingMode = ShadowCastingMode.On };
		Matrix4x4[] instData = new Matrix4x4[_numInstances];
		rp.matProps = new MaterialPropertyBlock();
		rp.matProps.SetInt("_TypeRender", (int)Type);
		rp.matProps.SetColor("_BaseColor", new Color(0.8784314f, 0.1913517f, 0.1098039f, 1f));
		for(int i=0; i < _numInstances; ++i)
			instData[i] = Matrix4x4.Translate(new Vector3(-_numInstances + 1 + 2 * i, 1.0f, 0.0f));
		Graphics.RenderMeshInstanced(rp, Mesh, 0, instData);
	}

	
	
	//Materials with custom shaders
	private void RenderMeshIndirectTest()
	{
		RenderParams rp = new RenderParams(_materialRenderMeshIndirect) { shadowCastingMode = ShadowCastingMode.On };
		rp.worldBounds = new Bounds(Vector3.zero, 1000 * Vector3.one); // use tighter bounds for better FOV culling
		rp.matProps = new MaterialPropertyBlock();
		rp.matProps.SetMatrix("_ObjectToWorld", Matrix4x4.Translate(new Vector3(1, 1, 0)));
		rp.matProps.SetInt("_NumInstances", _numInstances);
		rp.matProps.SetInt("_TypeRender", (int)Type);
		_commandData1[0].indexCountPerInstance = Mesh.GetIndexCount(0);
		_commandData1[0].instanceCount = (uint)_numInstances;
		_commandData1[1].indexCountPerInstance = Mesh.GetIndexCount(0);
		_commandData1[1].instanceCount = (uint)_numInstances;
		_commandBuf1.SetData(_commandData1);
		Graphics.RenderMeshIndirect(rp, Mesh, _commandBuf1, COMMAND_COUNT);
	}

	private void RenderMeshPrimitivesTest()
	{
		RenderParams rp = new RenderParams(_materialRenderMeshPrimitives) { shadowCastingMode = ShadowCastingMode.On };
		rp.worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one); // use tighter bounds
		rp.matProps = new MaterialPropertyBlock();
		rp.matProps.SetMatrix("_ObjectToWorld", Matrix4x4.Translate(new Vector3(-_numInstances + 1, 1.0f, 0)));
		rp.matProps.SetFloat("_NumInstances", _numInstances);
		rp.matProps.SetInt("_TypeRender", (int)Type);
		Graphics.RenderMeshPrimitives(rp, Mesh, 0, _numInstances);
	}

	private void RenderPrimitivesTest()
	{
		RenderParams rp = new RenderParams(_materialRenderPrimitives)
		{
			//motionVectorMode = MotionVectorGenerationMode.Camera,
			worldBounds = new Bounds(Vector3.zero, 1000 * Vector3.one), // use tighter bounds
			shadowCastingMode = ShadowCastingMode.On,
		};
		rp.matProps = new MaterialPropertyBlock();
		rp.matProps.SetBuffer("_Triangles", _meshTriangles);
		rp.matProps.SetBuffer("_Positions", _meshPositions);
		rp.matProps.SetInt("_StartIndex", (int)Mesh.GetIndexStart(0));
		rp.matProps.SetInt("_BaseVertexIndex", (int)Mesh.GetBaseVertex(0));
		rp.matProps.SetFloat("_NumInstances", _numInstances);
		rp.matProps.SetInt("_TypeRender", (int)Type);
		Graphics.RenderPrimitives(rp, MeshTopology.Triangles, (int)Mesh.GetIndexCount(0), _numInstances);
	}
	
	private void RenderPrimitivesIndexedTest()
	{
		RenderParams rp = new RenderParams(_materialRenderPrimitivesIndexed) { shadowCastingMode = ShadowCastingMode.On };
		rp.worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one); // use tighter bounds
		rp.matProps = new MaterialPropertyBlock();
		rp.matProps.SetBuffer("_Positions", _meshPositions);
		rp.matProps.SetInt("_BaseVertexIndex", (int)Mesh.GetBaseVertex(0));
		rp.matProps.SetFloat("_NumInstances", _numInstances);
		rp.matProps.SetInt("_TypeRender", (int)Type);
		Graphics.RenderPrimitivesIndexed(rp, MeshTopology.Triangles, _meshTriangles, _meshTriangles.count, (int)Mesh.GetIndexStart(0), _numInstances);
	}
	
	private void RenderPrimitivesIndexedIndirectTest()
	{
		RenderParams rp = new RenderParams(_materialRenderPrimitivesIndexedIndirect) { shadowCastingMode = ShadowCastingMode.On };
		rp.worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one); // use tighter bounds
		rp.matProps = new MaterialPropertyBlock();
		rp.matProps.SetBuffer("_Positions", _meshPositions);
		rp.matProps.SetFloat("_NumInstances", _numInstances);
		rp.matProps.SetInt("_TypeRender", (int)Type);
		_commandData1[0].indexCountPerInstance = Mesh.GetIndexCount(0);
		_commandData1[0].baseVertexIndex = Mesh.GetBaseVertex(0);
		_commandData1[0].startIndex = Mesh.GetIndexStart(0);
		_commandData1[0].instanceCount = (uint)_numInstances;
		_commandData1[1].indexCountPerInstance = Mesh.GetIndexCount(0);
		_commandData1[1].baseVertexIndex = Mesh.GetBaseVertex(0);
		_commandData1[1].startIndex = Mesh.GetIndexStart(0);
		_commandData1[1].instanceCount = (uint)_numInstances;
		_commandBuf1.SetData(_commandData1);
		Graphics.RenderPrimitivesIndexedIndirect(rp, MeshTopology.Triangles, _meshTriangles, _commandBuf1, COMMAND_COUNT);
	}
	
	private void RenderPrimitivesIndirectTest()
	{
		RenderParams rp = new RenderParams(_materialRenderPrimitivesIndirect) { shadowCastingMode = ShadowCastingMode.On };
		rp.worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one); // use tighter bounds
		rp.matProps = new MaterialPropertyBlock();
		rp.matProps.SetBuffer("_Triangles", _meshTriangles);
		rp.matProps.SetBuffer("_Positions", _meshPositions);
		rp.matProps.SetInt("_BaseVertexIndex", (int)Mesh.GetBaseVertex(0));
		rp.matProps.SetMatrix("_ObjectToWorld", Matrix4x4.Translate(new Vector3(-_numInstances + 1, 1.0f, 0)));
		rp.matProps.SetFloat("_NumInstances", _numInstances);
		rp.matProps.SetInt("_TypeRender", (int)Type);
		_commandData2[0].vertexCountPerInstance = Mesh.GetIndexCount(0);
		_commandData2[0].instanceCount = (uint)_numInstances;
		_commandData2[1].vertexCountPerInstance = Mesh.GetIndexCount(0);
		_commandData2[1].instanceCount = (uint)_numInstances;
		_commandBuf2.SetData(_commandData2);
		Graphics.RenderPrimitivesIndirect(rp, MeshTopology.Triangles, _commandBuf2, COMMAND_COUNT);
	}
}