*Edit: I finally figured out after some profiling that the problematic code was not actually the ones part of ECS, but this line over here:

objectManagerController.GenerateMetaballEdgePointVerteciesList(metaballEdgePointVertexCoordinates); //writes magnets, this is the culprit for the lagg!!!

in OnRenderImage() of MainCameraController:

    public void OnRenderImage(RenderTexture src, RenderTexture dest) {
        //Debug.Log("Metaball Shader, OnRenderImage for main camera");
        //called after operationCamera's OnRenderImage is called game object count times?
            //How the fuck was I able to make sure that src contains the full texture upon function call?
            //Since all rendered colored slimes are moved back to default layer after each of them are separately
            //rendered. Thus by the time MainCameraController.OnRenderImage is called all slimes are back in default layer

        //Not the case where main camera is not seeing anything

        Vector2Int resolution = new Vector2Int( preprocessCameraRender.width, preprocessCameraRender.height );

        //Thread control (hoping this is accurate enough)
        threadGroupX = Mathf.CeilToInt(preprocessCameraRender.width / (float)threadsPerGroup);
        threadGroupY = Mathf.CeilToInt(preprocessCameraRender.height / (float)threadsPerGroup);

        //Make a writable copy of src and dest
        //RenderTexture srcWritable = new RenderTexture(src.width, src.height, 32, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default){ useMipMap = false };
        RenderTexture srcWritable = new RenderTexture(preprocessCameraRender.width, preprocessCameraRender.height, 32, colorFormat, 1)
            useMipMap = false,
            autoGenerateMips = false,
            enableRandomWrite = true // Set the UAV usage flag

        //At this point both srcWritable and destWritable empty
        RenderTexture destWritable = new RenderTexture(srcWritable);

        RenderTexture metaballEdgeTexture = new RenderTexture(srcWritable);

        //Copying src to srcWritable
        RenderTexture.active = src;
        Graphics.Blit(src, srcWritable);

        //metaball edge texture
        // RenderTexture metaballEdgeTexture = CreateTexture("MetaballEdgeTexture", resolution, colorFormat);

        //Fill compute shader variables
        metaballComputeShader.SetTexture(kernelHandleFirst, "Src", srcWritable);
        metaballComputeShader.SetTexture(kernelHandleFirst, "Dest", destWritable);
        //Inner Edge Texture(empty at this point)
        metaballComputeShader.SetTexture(kernelHandleFirst, "MetaballEdgeTex", metaballEdgeTexture);
        metaballComputeShader.SetFloat("MaxWidth", preprocessCameraRender.width);
        metaballComputeShader.SetFloat("MaxHeight", preprocessCameraRender.height);
        metaballComputeShader.SetFloat("GameObjectListCount", colorSlimes.Count);
        metaballComputeShader.SetVector( "_Resolution", (Vector2) resolution );
    //Texture Arrays
        //First dispatch prep
        //Read (Raw) Color Texture Array
        metaballComputeShader.SetTexture(kernelHandleFirst, "ColorTextureArray", colorTextureArray);
        //Read (Larger) Color Texture's Array
        metaballComputeShader.SetTexture(kernelHandleFirst, "ColorTextureArrayBeta", colorTextureArrayBeta);
        //SDF Texture Array
        metaballComputeShader.SetTexture(kernelHandleFirst, "SDFTextureArray", sdfTextureArray);
        //Delta X Texture Array
        metaballComputeShader.SetTexture(kernelHandleFirst, "DeltaXTextureArray", deltaXTextureArray);
        //Delta Y Texture Array
        metaballComputeShader.SetTexture(kernelHandleFirst, "DeltaYTextureArray", deltaYTextureArray);
        //Weight Texture Array(fill weight values in it)
        metaballComputeShader.SetTexture(kernelHandleFirst, "WeightTextureArray", weightTextureArray);
        //Blur Texture Array
        // metaballComputeShader.SetTexture(kernelHandleFirst, "BlurTextureArray", blurTextureArray);
        //First metaball compute shader dispatch
        metaballComputeShader.Dispatch(kernelHandleFirst, threadGroupX, threadGroupY, 1);
        //Debug.Log("First metaball effect complete!");
        // Debug.Log("destWritable size: " + destWritable.width + " " + destWritable.height);
        // Debug.Log("metaballEdgeTexture size: " + metaballEdgeTexture.width + " " + metaballEdgeTexture.height);

        //Copying destWritable(which accounts for overlap blending) to srcWritable
        // RenderTexture.active = dest;
        // Graphics.Blit(dest, src);
        // RenderTexture.active = null;

        //Apply sobel filter to metaballEdgeTexture
        RenderTexture sobelledMetaballEdgeTexture = Sobel(sobelComputeShader, metaballEdgeTexture, resolution, threadGroupX, threadGroupY);
        //Debug.Log("sobelledMetaballEdgeTexture size: " + sobelledMetaballEdgeTexture.width + " " + sobelledMetaballEdgeTexture.height);
        RenderTexture sobelledMetaballEdgeTextureFinal = new RenderTexture(metaballEdgeTexture);

    //Final dispatch prep
        //Receive previous srcWritable(which is now simply a copy of destWritable) as Src
        metaballComputeShader.SetTexture(kernelHandleFinal, "Src", srcWritable);
        //First write same value as Src to Dest and then rewrite during dispatch
        metaballComputeShader.SetTexture(kernelHandleFinal, "Dest", destWritable);
        metaballComputeShader.SetTexture(kernelHandleFinal, "MetaballEdgeTex", sobelledMetaballEdgeTexture);
        //Final processed sobelled metaball edge tex
        metaballComputeShader.SetTexture(kernelHandleFinal, "MetaballEdgeTexFinal", sobelledMetaballEdgeTextureFinal);
        //Buffer.SetCounterValue(0); only used for Append/Consume buffers

        //Color Texture Array
        metaballComputeShader.SetTexture(kernelHandleFinal, "ColorTextureArray", colorTextureArray);
        //SDF Texture Array
        metaballComputeShader.SetTexture(kernelHandleFinal, "SDFTextureArray", sdfTextureArray);
        //Delta X Texture Array
        metaballComputeShader.SetTexture(kernelHandleFinal, "DeltaXTextureArray", deltaXTextureArray);
        //Delta Y Texture Array
        metaballComputeShader.SetTexture(kernelHandleFinal, "DeltaYTextureArray", deltaYTextureArray);
        //Weight Texture Array(now read from it)
        metaballComputeShader.SetTexture(kernelHandleFinal, "WeightTextureArray", weightTextureArray);

        ComputeBuffer magnetVertexCoordinateBuffer = new ComputeBuffer(src.width * src.height, sizeof(uint) * 2, ComputeBufferType.Append);

        // Create a buffer to hold the counter.
        ComputeBuffer countBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.Raw);
        int[] countArray = new int[1]; //to read back counter data later

        //Set buffer to hold values and buffer to hold counter
        metaballComputeShader.SetBuffer(kernelHandleFinal, "MagnetVertexCoordinates", magnetVertexCoordinateBuffer);
        //Final metaball compute shader dispatch
        metaballComputeShader.Dispatch(kernelHandleFinal, threadGroupX, threadGroupY, 1);
        //Debug.Log("sobelledEdgeTextureFinal size: " + sobelledMetaballEdgeTextureFinal.width + " " + sobelledMetaballEdgeTextureFinal.height);

        // Copy the append buffer count to the count buffer.
        ComputeBuffer.CopyCount(magnetVertexCoordinateBuffer, countBuffer, 0);

        // Read the count to a c# int[]
        int numEdgePoints = countArray[0];

        // Now you can read back just that many edge points.
        uint2[] edgePoints = new uint2[numEdgePoints];
        magnetVertexCoordinateBuffer.GetData(edgePoints, 0, 0, numEdgePoints);

        Debug.Log("edgePoints (post EdgePointsFilled) Count: " + edgePoints.Length);
        List<Vector3> metaballEdgePointVertexCoordinates = edgePoints.Select(ep => new Vector3(ep.x, ep.y, 0)).ToList();

        //Call object manager controller's GenerateMetaballEdgePointVerteciesList(magnetic)
        objectManagerController.GenerateMetaballEdgePointVerteciesList(metaballEdgePointVertexCoordinates); //writes magnets, this is the culprit for the lagg!!!

        //Write metaball effect to final dest (non-metaballed, but hoping the magnets will work)
        Graphics.Blit(destWritable, dest);
        RenderTexture.active = null;

        //Debug.Log("Metaball effect complete!");

This is the code for GenerateMetaballEdgePointVerticesList in objectManagerController:

    public void GenerateMetaballEdgePointVerteciesList(List<Vector3> metaballEdgePixels) // Generates and initialises metaball edge vertices passed from MainCameraController
        //if passed list is empty, reset all vertices (it means game objects are not close enough to metaball)
        if (metaballEdgePixels.Count == 0) {
            //Reset vertices for each game object
            foreach (GameObject colorSlimesDisplay in colorSlimesDisplayGlobal) {
            //Reset metaballEdgePointVertices
            foreach (var item in GetComponentsInChildren<MetaballEdgePointVertex>())
            //Reset metaballEdgePointVertex GOs
        else {
            //Create magnetic metaball edge points
            foreach (var item in GetComponentsInChildren<MetaballEdgePointVertex>())
            //Reset metaballEdgePointVertex GOs
            int cntr = 0;
            // float circleColliderRadius = 10.0f;
            Debug.Log("Logging metaball vertex locations");

            //typeof(metaballEdgePointVertex) == typeof(Vector3)
            foreach (Vector3 metaballEdgePointVertex in metaballEdgePixels)
                //Convert passed compute shader RenderTexture.id.xy to world coord
                Vector2 normalizedCoords = new Vector2(
                    metaballEdgePointVertex.x / preprocessCameraRenderTexture.width,
                    metaballEdgePointVertex.y / preprocessCameraRenderTexture.height

                Vector2 screenCoords = new Vector2(
                    normalizedCoords.x * Screen.width,
                    normalizedCoords.y * Screen.height

                Vector3 metaballEdgePointVertexWorldCoords = _displayCameraGlobal.ScreenToWorldPoint(new Vector3(screenCoords.x, screenCoords.y, 0));
                metaballEdgePointVertexWorldCoords.z = 0;

                var go = Instantiate(MetaballEdgePointVertex, transform); //transform of the game object that will become parent of GOs == ObjectManager.transform
                go.name = go.name + " " + cntr;

                // Add CircleCollider2D
                // CircleCollider2D circleCollider = go.AddComponent<CircleCollider2D>();
                // // Configure CircleCollider2D properties
                // circleCollider.radius = circleColliderRadius;
                // circleCollider.isTrigger = true;


                var ver = go.GetComponent<MetaballEdgePointVertex>();
                ver.SetInitialPos(metaballEdgePointVertexWorldCoords, cntr, go); //this is reference to SpriteMeshModifier object that owns vertices

Here is a visual of that shows the magnets that each vertex is trying to go to:

enter image description here

Here are 2 techniques I can think of:

  1. Create a public RenderTexture in the Unity editor using create -> RenderTexture simply Blit in MainCameraController's OnRenderImage when the magnet texture is created. The logic for extracting opaque pixels from the public RenderTexture to a List and then generation of metaballEdgePointVerticesList in objectManagerController.cs(since this is the de-facto MonoBehavior global game object manager script)

  2. In MainCameraController's OnRenderImage, update a newly created public List metaballEdgePointPositionsList(objectManagerController.cs) instead of instantiating a new List metaballEdgePointPositionsList in OnRenderImage every time.

metaballEdgePointPositionsList = edgePoints.Select(ep => new Vector3(ep.x, ep.y, 0)).ToList();

Then the ECS SystemBase class will fetch from its reference of objectManagerController its metaballEdgePointPositionsList and generate the actual global List metaballEdgePointVertexList in the first compute job using the elements of metaballEdgePointPositionsList.

My question is, which one is the better method of optimization? Also, in order to optimize the generation of vertices, what is the general method of updating MonoBehavior global list in a SystemBase object job? I am having trouble figuring out how exactly I should translate GenerateMetaballEdgePointVerteciesList to a ComputeJob, since the job GenerateMetaballEdgePointVerteciesList is to Add Vertex objects to a MonoBehavior global objectManager, and ComputeJobs require to operate on elements of a NativeArray and cannot modify MonoBehavior objects directly.

May I have some more sophisticated advice in this area in general?

  • \$\begingroup\$ What bottlenecks do you find when you profile in the code? You've identified that it's likely related to your ECS, but the more you can narrow down the situations that lead performance problems, the faster and better answers you are likely to get. We can try to make educated guesses about what might be impacting performance, but it's better to start by using the profiler to measure the performance issues on an actual run. \$\endgroup\$
    – Pikalek
    Sep 19 at 14:14
  • \$\begingroup\$ What is "darts"? Maybe you mean "DOTS"? \$\endgroup\$
    – Philipp
    3 hours ago


You must log in to answer this question.

Browse other questions tagged .