Skip to content

Instantly share code, notes, and snippets.

@stonstad
Last active May 17, 2023 00:39
Show Gist options
  • Save stonstad/8db9bfd80d189b55ec7d9edf810e18b7 to your computer and use it in GitHub Desktop.
Save stonstad/8db9bfd80d189b55ec7d9edf810e18b7 to your computer and use it in GitHub Desktop.
using System.Collections.Generic;
using System.IO;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
namespace Sample
{
public struct Buffer
{
public NativeArray<byte> NativeArray;
public uint Width;
public uint Height;
}
[RequireComponent(typeof(Camera))]
public class SampleColor : MonoBehaviour
{
private RenderTexture _RenderTexture;
private Queue<Buffer> _InactiveBuffers = new Queue<Buffer>();
private Queue<Buffer> _ActiveBuffers = new Queue<Buffer>();
[Header("Settings")]
public Vector2 Scale = new Vector2(1.0f, 1.0f);
public Vector2 Offset = new Vector2(0.0f, 0.0f);
public float SizeScalar = 0.25f;
public int SampleN = 3;
[Header("Color")]
public Color SampledColor;
public Color InterpolatedColor;
public static Color SharedInterpolatedColor;
[Header("Debug")]
public bool WriteDebugOutput = false;
private Action<AsyncGPUReadbackRequest> _Callback;
public SampleColor()
{
_Callback = OnCompleteReadback;
}
private void Update()
{
InterpolatedColor = Color.Lerp(InterpolatedColor, SampledColor, SampleN * Time.deltaTime);
SharedInterpolatedColor = InterpolatedColor;
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (Time.frameCount % SampleN != 0)
{
RenderTexture.active = destination;
return;
}
int width = (int)(Screen.width * SizeScalar);
int height = (int)(Screen.height * SizeScalar);
if (_RenderTexture != null && (_RenderTexture.width != width || _RenderTexture.height != height))
{
Destroy(_RenderTexture);
_RenderTexture = null;
}
if (_RenderTexture == null)
{
_RenderTexture = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32);
_RenderTexture.useMipMap = true;
}
Graphics.Blit(source, _RenderTexture, Scale, Offset);
// acquire or create unused buffer with matching dimensions
Buffer buffer;
if (_InactiveBuffers.Count > 0)
{
buffer = _InactiveBuffers.Dequeue();
if (buffer.Width != width || buffer.Height != height)
{
buffer.NativeArray.Dispose();
buffer = CreateBuffer(width, height);
}
}
else
buffer = CreateBuffer(width, height);
_ActiveBuffers.Enqueue(buffer);
if (WriteDebugOutput)
AsyncGPUReadback.RequestIntoNativeArray(ref buffer.NativeArray, _RenderTexture, 0, _Callback);
else
AsyncGPUReadback.RequestIntoNativeArray(ref buffer.NativeArray, _RenderTexture, _RenderTexture.mipmapCount - 1, _Callback);
RenderTexture.active = destination;
}
private void OnDestroy()
{
AsyncGPUReadback.WaitAllRequests();
Destroy(_RenderTexture);
foreach (Buffer buffer in _ActiveBuffers)
buffer.NativeArray.Dispose();
foreach (var buffer in _InactiveBuffers)
buffer.NativeArray.Dispose();
}
private void OnCompleteReadback(AsyncGPUReadbackRequest request)
{
if (request.hasError)
{
Debug.Log("GPU readback error detected.");
return;
}
Buffer buffer = _ActiveBuffers.Dequeue();
{
// output render target texture
if (WriteDebugOutput && Time.renderedFrameCount % 20 == 0)
{
using var encoded = ImageConversion.EncodeNativeArrayToPNG(buffer.NativeArray, _RenderTexture.graphicsFormat, buffer.Width, buffer.Height);
File.WriteAllBytes("output.png", encoded.ToArray());
}
else
SampledColor = new Color(buffer.NativeArray[0] / 255.0f, buffer.NativeArray[1] / 255.0f, buffer.NativeArray[2] / 255.0f);
}
_InactiveBuffers.Enqueue(buffer);
}
private Buffer CreateBuffer(int width, int height)
{
if (WriteDebugOutput)
return new Buffer()
{
NativeArray = new NativeArray<byte>(width * height * 4, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
Width = (uint)width,
Height = (uint)height
};
else
return new Buffer()
{
NativeArray = new NativeArray<byte>(1 * 1 * 4, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
Width = (uint)width,
Height = (uint)height
};
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment