Search Unity

Question IJobParallelFor Run Slower than main thread solution ?

Discussion in 'Entity Component System' started by hoangcuong2k1a, Mar 25, 2023.

  1. hoangcuong2k1a

    hoangcuong2k1a

    Joined:
    Jan 9, 2023
    Posts:
    14
    Hi, my current job does not run faster than solution for main thread. What am i wrong ?
    This is my job and the way i use it on main thread.
    Code (CSharp):
    1.  public struct ImagePixel
    2.     {
    3.         public Vector2 Position;
    4.         public Color Color;
    5.     }
    6.  
    7.     [BurstCompile]
    8.     public struct PaintingJob : IJobParallelFor
    9.     {
    10.         public int BrushScale;
    11.         public int BrushWidth;
    12.         public int BrushHeight;
    13.         public int TargetWidth;
    14.         public int TargetHeight;
    15.         public int PaintPositionX;
    16.         public int PaintPositionY;
    17.         public Color BrushColor;
    18.         public NativeArray<ImagePixel> ImagePixelColors;
    19.  
    20.         public void Execute(int index)
    21.         {
    22.             int i = index % BrushHeight;
    23.             int j = index / BrushWidth;
    24.             int x = PaintPositionX + i - BrushHeight * BrushScale / 2;
    25.             int y = PaintPositionY + j - BrushWidth * BrushScale / 2;
    26.             if (x < 0 || x >= TargetWidth || y < 0 || y >= TargetHeight) return;
    27.  
    28.             ImagePixelColors[index] = new ImagePixel
    29.             {
    30.                 Color = BrushColor,
    31.                 Position = new Vector2(x, y)
    32.             };
    33.         }
    34.     }
    35.  
    This is Function when i execute my job on Update:
    Code (CSharp):
    1. void PaintingWithBurst()
    2.         {
    3.             _imagePixels = new NativeArray<Vector2>(_textureTarget.width * _textureTarget.height, Allocator.TempJob);
    4.             _imagePixelColors =
    5.                 new NativeArray<ImagePixel>(_textureTarget.width * _textureTarget.height, Allocator.TempJob);
    6.             Vector3 screenPosition = Input.mousePosition;
    7.             screenPosClicked = _camera.ScreenToViewportPoint(screenPosition);
    8.             _paintingJob = new PaintingJob
    9.             {
    10.                 BrushScale = brushScale,
    11.                 BrushWidth = _textureBrush.width,
    12.                 BrushHeight = _textureBrush.height,
    13.                 TargetWidth = _textureTarget.width,
    14.                 TargetHeight = _textureTarget.height,
    15.                 PaintPositionX = (int)(screenPosClicked.x * _textureTarget.width),
    16.                 PaintPositionY = (int)(screenPosClicked.y * _textureTarget.height),
    17.                 BrushColor = brushColor,
    18.                 ImagePixelColors = _imagePixelColors,
    19.             };
    20.             _jobHandle = _paintingJob.Schedule(_textureBrush.width * _textureBrush.height, 64);
    21.  
    22.             _jobHandle.Complete();
    23.             _imagePixelColors = _paintingJob.ImagePixelColors;
    24.  
    25.             for (int i = 0; i < _imagePixelColors.Length; i++)
    26.             {
    27.                 int x = (int)_imagePixelColors[i].Position.x;
    28.                 int y = (int)_imagePixelColors[i].Position.y;
    29.                 _textureTarget.SetPixel(x, y, _imagePixelColors[i].Color);
    30.             }
    31.  
    32.             _textureTarget.Apply();
    33.  
    34.             _imagePixels.Dispose();
    35.             _imagePixelColors.Dispose();
    36.         }
    And this is my old code version for main thread(not using job burst), and it run faster:

    Code (CSharp):
    1.  void Painting()
    2.         {
    3.             // Get the position of the mouse click in screen space
    4.             Vector3 screenPosition = Input.mousePosition;
    5.  
    6.             // Convert the screen space position to img space
    7.             screenPosClicked = _camera.ScreenToViewportPoint(screenPosition);
    8.             // Convert the screen space position to world space
    9.             worldPosClicked = _camera.ScreenToWorldPoint(screenPosition);
    10.  
    11.             // Calculate the x and y indices of the pixel within the target image
    12.             int xAxisPixel = (int)(screenPosClicked.x * _textureTarget.width);
    13.             int yAxisPixel = (int)(screenPosClicked.y * _textureTarget.height);
    14.  
    15.  
    16.             for (int i = 0; i < _textureBrush.height * brushScale; i++)
    17.             {
    18.                 for (int j = 0; j < _textureBrush.width * brushScale; j++)
    19.                 {
    20.                     int x = xAxisPixel + i - _textureBrush.height * brushScale / 2;
    21.                     int y = yAxisPixel + j - _textureBrush.width * brushScale / 2;
    22.                     if (x < 0 || x >= _textureTarget.width || y < 0 || y >= _textureTarget.height) continue;
    23.  
    24.                     Color eraseColor = _textureBrush.GetPixel(i % _textureBrush.width, j % _textureBrush.height);
    25.                     Color currentColor = _textureTarget.GetPixel(x, y);
    26.                     Color newColor = Color.Lerp(currentColor, brushColor, brushStrength * eraseColor.a);
    27.                     _textureTarget.SetPixel(x, y, newColor);
    28.                 }
    29.             }
    30.  
    31.  
    32.          
    33.             _textureTarget.Apply();
    34.         }

    This is the way i call my job and normal solution on main thread:
    Code (CSharp):
    1. void Update()
    2.         {
    3.             if (Input.GetMouseButton(0))
    4.             {
    5.                 PaintingWithBurst();
    6.                 // Painting();
    7.             }
    8.         }
    9.  
    10.  
    This is profiler while i run with burst - PaintingWithBurst():
    upload_2023-3-25_22-44-3.png

    This is profiler while i run without burst - Painting():

    upload_2023-3-25_22-46-15.png


    Note: I enabled Burst Compilation, Safety Check On.
    Noy have any Log Error. And it run Slower than my old Solution. What am i wrong ?
    upload_2023-3-25_22-47-53.png
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,775
    Your job is no multithreaded.
    Your job is not bursted.
    Your job uses vectors instead floats. Like Vector3 should be float3.
    NativeArray inside job is not marked NativeDisbaleSafetyRestrictions

    Possibly other things too. But try to correct these first.
     
    Rukhanka likes this.
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,266
    None of this is true. Look at the profile capture closely. There's a green parallel sliver. The issue isn't the job as much as it is what is outside the job.

    Issue 1) You are allocating two NativeArrays, one that contains positions, and one that contains colors and positions. The positions are completely unnecessary. What you actually want is a single NativeArray<Color32>.
    Issue 2) You are clearing the NativeArrays on the main thread. Use the optional fourth constructor argument NativeArrayOptions.UninitializedMemory.
    Issue 3) You are not taking advantage of native APIs for setting the texture. Use LoadRawTextureData() and then Apply().
     
    hoangcuong2k1a likes this.
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
    Or better - GetRawTextureData -> modify texture data directly without allocating additional arrays at all -> Apply()
     
    DreamingImLatios likes this.
  5. Rukhanka

    Rukhanka

    Joined:
    Dec 14, 2022
    Posts:
    204
    Or even better -> use compute shader :)
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,266
    If you know the internal texture format, then 100% this. But if not, what I proposed is a good stepping stone.
     
    eizenhorn likes this.
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
    Well, that will require you to use RenderTexture and swapping render target with Blit and ReadPixels back and forth, in common case burst compiled job with writing directly to raw texture data will show you pretty good performance, probably even better in good half of cases (well depend of course on size, format, target API, hardware, operations you're doing in your logic for every pixel, [put more circumstances]):D
     
    Rukhanka likes this.
  8. hoangcuong2k1a

    hoangcuong2k1a

    Joined:
    Jan 9, 2023
    Posts:
    14
    Thank you.
    DreamingImLatios.
    I fixed it. My problem is that i set wrong size for NativeArray of job.

    I refactored and used deep profiler and check that:
    My Texture.SetPixel() is called 2073600 times. It equals 1920x1080(same as my target texture size).
    So i change size of NativeArr to size of my Brush Texture and everything work correctly.
    Thanks everyone !
     
    Laicasaane likes this.