Skip to content

Instantly share code, notes, and snippets.

@badjano
Last active March 13, 2023 12:32
Show Gist options
  • Save badjano/e51d61237b9dde1df74536b1bc0bcbcc to your computer and use it in GitHub Desktop.
Save badjano/e51d61237b9dde1df74536b1bc0bcbcc to your computer and use it in GitHub Desktop.
Multi-Threaded pixel painting on the CPU in Unity
using System;
using System.Linq;
using System.Threading;
using UnityEngine;
using Random = System.Random;
public class PixelThreading : MonoBehaviour
{
[SerializeField] private Material material;
[SerializeField] private int width = 512;
[SerializeField] private int threadCount = 32;
private Texture2D _texture;
private static readonly int BaseMap = Shader.PropertyToID("_BaseMap");
private Color[] _data;
private Thread _thread;
private readonly object _lock = new();
private bool _alive;
private bool[] _wait;
private string _printBuffer;
private Thread[] _threads;
private float _time;
struct ThreadData
{
public int index;
public int total;
public int partial;
public int start;
public float percentage => (float)partial / total;
}
private void Awake()
{
_alive = true;
}
void Start()
{
_texture = new Texture2D(width, width, TextureFormat.RGBA32, false);
_texture.filterMode = FilterMode.Point;
_texture.name = "Procedural Texture";
_data = new Color[width * width];
material.SetTexture(BaseMap, _texture);
_threads = new Thread[threadCount];
_wait = new bool[threadCount];
for (int i = 0; i < threadCount; i++)
{
ThreadData data = new ThreadData
{
index = i,
total = width * width,
partial = width * width / threadCount,
start = i * (width * width / threadCount)
};
var thread = new Thread(DoMultiThreadWork);
thread.Start(data);
_threads[i] = thread;
}
}
void Update()
{
_texture.SetPixels(_data);
_texture.Apply();
_time = Time.time;
_wait = Enumerable.Repeat(false, threadCount).ToArray();
if (!string.IsNullOrEmpty(_printBuffer))
{
Debug.Log(_printBuffer);
_printBuffer = string.Empty;
}
transform.Rotate(Vector3.forward, Time.deltaTime * 10);
}
private float rand(Random random, float x, float y)
{
return (float)(random.NextDouble() * x % y);
}
private void DoMultiThreadWork(object obj)
{
ThreadData tData = (ThreadData)obj;
var random = new Random(Thread.CurrentThread.ManagedThreadId);
int index = tData.index;
var perc = tData.percentage;
var total = tData.total;
var partial = tData.partial;
var start = tData.start;
var data = new Color[partial];
try
{
while (_alive)
{
var t = _time;
for (int i = 0; i < partial; i++)
{
var x = (i + start) % width - width / 2;
var y = (i + start) / width - width / 2;
var a = Mathf.Sin(Mathf.Atan2(y, x) * 12f);
var dist = Vector2.Distance(Vector2.zero, new Vector2(x, y));
var sinx = Mathf.Sin(dist * 0.01f + t + a * 0.1f) * .5f + .5f;
var siny = Mathf.Sin(dist * 0.01f + t + a * 0.1f + Mathf.PI * .5f) * .5f + .5f;
var sinz = Mathf.Sin(dist * 0.01f + t + a * 0.1f + Mathf.PI) * .5f + .5f;
dist *= 0.005f;
data[i] = new Color(
1 - Mathf.Pow(sinx, dist) + rand(random, 0.1f, 1.0f),
1 - Mathf.Pow(siny, dist) + rand(random, 0.1f, 1.0f),
1 - Mathf.Pow(sinz, dist) + rand(random, 0.1f, 1.0f),
1);
}
Array.Copy(data, 0, _data, start, partial);
_wait[index] = true;
while (_wait[index])
{
Thread.Sleep(1);
}
}
}
catch (Exception e)
{
lock (_lock)
{
_printBuffer = $"index: {index}, start: {start}, partial: {partial} , total: {total} \n {e}";
}
}
_threads[index] = null;
}
private void OnDestroy()
{
_alive = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment