Search Unity

Resolved I could use some feedback on a DOTS polygon solution design

Discussion in 'Entity Component System' started by TheGabelle, Feb 19, 2021.

  1. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Although I am still quite green, I'm working on a polygon solution for DOTS. For preliminary tests I used one mesh to one polygon using MeshTopology.Lines which clearly won't scale. Therefore I am attempting to render all polygons using a single mesh (with the same topology for now). The general idea is simple: append each polygon's vertex/index data to one mesh. Where I begin to struggle is detecting and handling adding, updating, and removing polygon data from the mesh without rebuilding the entire thing.

    The polygon data is very simple. I won't use blobs because this data will be modified at runtime.
    Code (CSharp):
    1. public struct Polygon: IComponentData
    2. {
    3.     public bool IsClosed;
    4. }
    5.  
    6. public struct PolygonPoint: IBufferElementData
    7. {
    8.     public float3 Value;
    9. }

    For rendering, I intend on using a singleton entity with a tag.
    Code (CSharp):
    1. public struct PolygonRenderer : IComponentData {}

    Systems can access it and modify the mesh data to account for all the Polygons using:
    Code (CSharp):
    1. GetSingletonEntity<PolygonRenderer>()

    For associating a Polygon with the PolygonRenderer, a link component is used.
    Code (CSharp):
    1. public struct PolygonRendererLink : ISharedComponentData
    2. {
    3.     public Entity Value;
    4. }

    For tracking what sections of mesh data pertain to a particular polygon
    Code (CSharp):
    1. public struct PolygonRendererIndexes : IComponentData
    2. {
    3.     public int MeshVertexStartIndex;     // length is PolygonPoint buffer length
    4.     public int MeshIndicesStartIndex;    // length is PolygonPoint buffer length * 2, ( -2 if polygon is open )
    5. }


    Adding Polygons

    IF entity has (Polygon, PolygonRendererLink) and none (PolygonRendererIndexes)
    • append data to mesh
    • add PolygonRendererIndexes component to polygon entity
    Removing Polygons
    IF entity has (Polygon, PolygonRendererIndexes) and none (PolygonRendererLink)
    • remove data from mesh, update bounds
    • remove PolygonRendererIndexes from polygon entity
    • update all polygons' PolygonRendererIndexes?
    Polygon Updates
    I'm not sure how to efficiently handle updates for Polygon.IsClosed and PolygonPoint Buffer value and length changes. I don't want to rebuild the entire mesh if only one point is added, removed, or modified. Also, not sure how to go about updating all the PolygonRendererIndexes components.

    Am I heading in the right direction?
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,269
    Don't expose the backing buffers. That's just asking for maintainability nightmares. Use proper dynamic buffers per instance to represent publicly modifiable state. And then use change filters (bonus points if you abstract the real component types behind custom accessors that also manipulate per-entity change version numbers). You can then use a system, system state components, and the change filters to mirror the changed dynamic buffers to the real mesh data.

    As for how to pack the mesh? Well that's a much more difficult problem, and why you want to hide it away completely behind a system. If you can use Graphics.DrawProcedural or Graphics.DrawProceduralIndirect, then you can perform mesh compaction on the GPU using a compute shader.
     
  3. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    By 'backing buffers' do you mean the PolygonPoint buffer or PolygonRendererIndexes?


    Could you explain this in more detail?


    This is an interesting idea and at first glance appears to be a better rendering solution. Haven't touched anything Graphics related so I've got some homework to do.
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,269
    Reading through what you have a second time, PolygonRendererIndexes should be internal and SystemState. In fact I would probably make a dynamic buffer and operate on meshlets for simpler GPU memory management.

    Get it working with normal change filters first. It requires writing wrappers around BufferFromEntity and friends and having the real dynamic buffer element types be internal.
     
  5. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    I can get a quick rendering test to work using a Monobehavior, not in a system. Used this post as reference.
    The float4 w value is color.

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8. using Unity.Collections.LowLevel.Unsafe;
    9. using Unity.Rendering;
    10.  
    11. [UpdateInGroup(typeof(PresentationSystemGroup))]
    12. public class PolygonDrawSystem : SystemBase
    13. {
    14.  
    15.     [SerializeField] Material _lineMaterial;
    16.     ComputeBuffer _lineVertexBuffer;
    17.     static readonly int PositionBuffer = Shader.PropertyToID("positionBuffer");
    18.     NativeArray<float4> pts;
    19.  
    20.     protected override void OnCreate()
    21.     {
    22.         pts = new NativeArray<float4>(8, Allocator.Persistent);
    23.          // left line
    24.         pts[0] = new float4(-1f, -1f, 0f, 55555f);
    25.         pts[1] = new float4(-1f,  1f, 0f, 55555f);
    26.      
    27.         // top line
    28.         pts[2] = new float4(-1f,  1f, 0f, 55555f);
    29.         pts[3] = new float4( 1f,  1f, 0f, 55555f);
    30.  
    31.         // right line
    32.         pts[4] = new float4( 1f,  1f, 0f, 55555f);
    33.         pts[5] = new float4( 1f, -1f, 0f, 55555f);
    34.  
    35.         // bottom line
    36.         pts[6] = new float4( 1f, -1f, 0f, 55555f);
    37.         pts[7] = new float4(-1f, -1f, 0f, 55555f);
    38.  
    39.         RequireForUpdate(GetEntityQuery(
    40.             typeof(PolygonDrawSettings),
    41.             typeof(PolygonDrawEntity)
    42.         ));
    43.  
    44.         _lineMaterial = (Material)Resources.Load("PolygonLine");
    45.     }
    46.  
    47.  
    48.     protected override void OnUpdate()
    49.     {
    50.         UpdateBuffer();
    51.         _lineMaterial.SetPass(0);
    52.         Graphics.DrawProceduralNow(MeshTopology.Lines, pts.Length);
    53.     }
    54.  
    55.     protected override void OnDestroy()
    56.     {
    57.         pts.Dispose();
    58.     }
    59.  
    60.     void UpdateBuffer()
    61.     {
    62.         if (_lineVertexBuffer != null && _lineVertexBuffer.count != pts.Length)
    63.         {
    64.             _lineVertexBuffer.Release();
    65.             _lineVertexBuffer = null;
    66.         }
    67.  
    68.         if(_lineVertexBuffer == null )
    69.         {
    70.             _lineVertexBuffer = new ComputeBuffer(pts.Length, UnsafeUtility.SizeOf<float4>());
    71.             _lineVertexBuffer.SetData(pts, 0, 0, pts.Length);
    72.             _lineMaterial.SetBuffer(PositionBuffer, _lineVertexBuffer);
    73.         }
    74.     }
    75. }
    Code (CSharp):
    1. Shader "LineShaderProc" {
    2.     Properties
    3.     {
    4.     }
    5.     SubShader
    6.     {
    7.         Pass
    8.         {
    9.             // Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
    10.  
    11.             //ZWrite off
    12.             //ZTest Always
    13.             //Cull off
    14.             //Blend SrcAlpha OneMinusSrcAlpha
    15.  
    16.             CGPROGRAM
    17.  
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
    21.             #pragma target 4.5
    22.  
    23.             #include "UnityCG.cginc"
    24.  
    25.             StructuredBuffer<float4> positionBuffer;
    26.  
    27.             struct v2f
    28.             {
    29.                 float4 pos : SV_POSITION;
    30.                 float4 color : TEXCOORD0;
    31.             };
    32.  
    33.             float4 unpack(float i)
    34.             {
    35.                 return float4(i / 262144.0, i / 4096.0, i / 64.0, i) % 64.0 / 63;
    36.             }
    37.  
    38.             v2f vert(uint vid : SV_VertexID)
    39.             {
    40.                 float4 pos = positionBuffer[vid];
    41.  
    42.                 float col = pos.w;
    43.                 float4 worldPos = float4(pos.xyz, 1);
    44.                 float4 projectionPos = mul(UNITY_MATRIX_VP, worldPos);
    45.  
    46.                 v2f o;
    47.                 o.pos = projectionPos;
    48.                 o.color = unpack(pos.w);
    49.                 return o;
    50.             }
    51.  
    52.             fixed4 frag(v2f i) : SV_Target
    53.             {
    54.                 return i.color;
    55.             }
    56.  
    57.             ENDCG
    58.         }
    59.     }
    60. }
    61.  
     
  6. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Scratch my last post.

    I spoke with the author of the linked package, and he pointed me to another package that only handles line rendering. Due to how it works I do not have to manage updates at all. How rare it is to find a perfect solution that also integrates flawlessly.

    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5.  
    6. [Serializable]
    7. [GenerateAuthoringComponent]
    8. public struct Polygon : IComponentData
    9. {
    10.     public bool isClosed;
    11. }
    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5.  
    6. [Serializable]
    7. [InternalBufferCapacity(0)]
    8. [GenerateAuthoringComponent]
    9. public struct PolygonPoint : IBufferElementData
    10. {
    11.     public float3 Value;
    12.  
    13.     public PolygonPoint(float3 v)
    14.     {
    15.         Value = v;
    16.     }
    17. }
    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5.  
    6. [Serializable]
    7. [GenerateAuthoringComponent]
    8. public struct PolygonDrawTag : IComponentData {}
    9.  
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8. using BurstDraw;
    9.  
    10. public class PolygonDrawSystem : SystemBase
    11. {
    12.     protected override void OnUpdate()
    13.     {
    14.  
    15.         Entities
    16.             .WithAll<PolygonDrawTag>()
    17.             .ForEach((DynamicBuffer<PolygonPoint> points, in Polygon polygon, in LocalToWorld localToWorld) =>
    18.             {
    19.              
    20.                 for(var i = 0; i < points.Length-1; i++)
    21.                 {
    22.                     Line.Draw(
    23.                         math.transform(localToWorld.Value, points[i].Value),
    24.                         math.transform(localToWorld.Value, points[i+1].Value),
    25.                         Color.blue);
    26.                 }
    27.                 if(polygon.isClosed)
    28.                 {
    29.                     Line.Draw(
    30.                         math.transform(localToWorld.Value, points[points.Length-1].Value),
    31.                         math.transform(localToWorld.Value, points[0].Value),
    32.                         Color.blue);
    33.                 }
    34.             })
    35.             .ScheduleParallel();
    36.     }
    37. }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7.  
    8. public class PolygonTest : MonoBehaviour
    9. {
    10.     public int numberOfPolygons = 10;
    11.     public GameObject prefab;
    12.  
    13.     void Start()
    14.     {
    15.         var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
    16.         var entityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefab, settings);
    17.         var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
    18.  
    19.         for(var i = 0; i < numberOfPolygons; i++)
    20.         {
    21.             var instance = entityManager.Instantiate(entityPrefab);
    22.             entityManager.RemoveComponent<Scale>(instance);
    23.             entityManager.AddComponent<NonUniformScale>(instance);
    24.             entityManager.SetComponentData<Translation>(instance, new Translation(){Value = new float3(UnityEngine.Random.Range(-10f, 10f), UnityEngine.Random.Range(-10f, 10f), 0f) });
    25.             entityManager.SetComponentData<NonUniformScale>(instance, new NonUniformScale(){Value = new float3(UnityEngine.Random.Range(1f, 4f), UnityEngine.Random.Range(1f, 4f), 1f) });
    26.             entityManager.SetComponentData<Rotation>(instance, new Rotation(){Value =  quaternion.EulerXYZ( 0f,0f, UnityEngine.Random.Range(-180f, 180f) )});
    27.             var buffer = entityManager.GetBuffer<PolygonPoint>(instance);
    28.          
    29.             buffer.Clear();
    30.             buffer.Add(new PolygonPoint(new float3(-1f, 1f, 0f)));
    31.             buffer.Add(new PolygonPoint(new float3(1f,  1f, 0f)));
    32.             buffer.Add(new PolygonPoint(new float3(1f, -1f, 0f)));
    33.             buffer.Add(new PolygonPoint(new float3(-1f, -1f, 0f)));
    34.  
    35.             entityManager.SetComponentData<Polygon>(instance, new Polygon(){isClosed = true});
    36.         }
    37.     }
    38.  
    39. }
     
    jasons-novaleaf likes this.