Skip to content

Commit deb0328

Browse files
committed
Update NonBlockingCustomTextureRenderer
1 parent 54ba463 commit deb0328

File tree

6 files changed

+214
-64
lines changed

6 files changed

+214
-64
lines changed

Assets/CustomTextureRenderer.Samples/Test.cs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ enum TextureSize
2020
}
2121

2222
[SerializeField] TextureSize _textureSize;
23-
[SerializeField] bool _useAnotherThread;
23+
[SerializeField] bool _useNonBlockingVersion;
2424

2525
uint _frame;
2626

27-
Texture2D _texture;
27+
Texture _texture;
2828
CustomTextureRenderer _customTextureRenderer;
2929
NonBlockingCustomTextureRenderer _nonBlockingCustomTextureRenderer;
3030

@@ -42,16 +42,36 @@ void Start()
4242
_ => 64,
4343
};
4444

45-
_texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
46-
_texture.wrapMode = TextureWrapMode.Clamp;
45+
var asyncGPUUploadCount = _textureSize switch
46+
{
47+
TextureSize._4096x4096 => 4,
48+
_ => 1,
49+
};
4750

48-
if (_useAnotherThread)
51+
if (_useNonBlockingVersion)
4952
{
50-
_nonBlockingCustomTextureRenderer = new NonBlockingCustomTextureRenderer(UpdateRawTextureDataFunction, _texture);
53+
var rt = new RenderTexture(size, size, 0, RenderTextureFormat.ARGB32);
54+
rt.enableRandomWrite = true;
55+
rt.Create();
56+
57+
_texture = rt;
58+
59+
_nonBlockingCustomTextureRenderer =
60+
new NonBlockingCustomTextureRenderer(
61+
this.UpdateRawTextureDataFunction,
62+
targetTexture: (RenderTexture)_texture,
63+
targetFrameRateOfPluginRenderThread: 60,
64+
asyncGPUUploadCount: asyncGPUUploadCount
65+
);
5166
}
5267
else
5368
{
54-
_customTextureRenderer = new CustomTextureRenderer(UpdateRawTextureDataFunction, _texture);
69+
var tex2d = new Texture2D(size, size, TextureFormat.RGBA32, false);
70+
tex2d.wrapMode = TextureWrapMode.Clamp;
71+
72+
_texture = tex2d;
73+
74+
_customTextureRenderer = new CustomTextureRenderer(UpdateRawTextureDataFunction, (Texture2D)_texture);
5575
}
5676

5777
// Set the texture to the renderer with using a property block.
@@ -70,7 +90,7 @@ void Update()
7090
_frame = (uint)(Time.time * 60);
7191

7292
// Update texture
73-
if (_useAnotherThread)
93+
if (_useNonBlockingVersion)
7494
{
7595
_nonBlockingCustomTextureRenderer.Update();
7696
}

Assets/CustomTextureRenderer.Samples/Test.unity

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,8 @@ MonoBehaviour:
324324
m_Script: {fileID: 11500000, guid: 9d39ea10283fd714bbec63b7904af0d4, type: 3}
325325
m_Name:
326326
m_EditorClassIdentifier:
327-
_textureSize: 3
328-
_useAnotherThread: 1
327+
_textureSize: 4
328+
_useNonBlockingVersion: 1
329329
--- !u!1 &1786066545
330330
GameObject:
331331
m_ObjectHideFlags: 0

Assets/CustomTextureRenderer/Runtime/NonBlockingCustomTextureRenderer.cs

Lines changed: 133 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
// Copyright (c) 2022 Soichiro Sugimoto
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
// #define DISABLE_ASYNC_GPU_UPLOAD // Run as a performance test without async GPU upload.
5+
16
using System;
27
using System.Diagnostics;
38
using System.Threading;
49
using System.Runtime.InteropServices;
510
using UnityEngine;
6-
using UnityEngine.Rendering;
711

812
#if DEVELOPMENT_BUILD || UNITY_EDITOR
913
using UnityEngine.Profiling;
@@ -17,12 +21,19 @@ namespace UnityCustomTextureRenderer
1721
/// </summary>
1822
public sealed class NonBlockingCustomTextureRenderer : IDisposable
1923
{
24+
static readonly string ComputeShaderName = "NonBlockingCustomTextureRenderer";
25+
static readonly string KernelName = "LoadRawTextureDataRGBA32";
26+
static readonly string RawTextureDataPropertyName = "RawTextureData";
27+
static readonly string OutputTexturePropertyName = "OutputTexture";
28+
static readonly string TextureWidthPropertyName = "Width";
29+
static readonly string TextureHeightPropertyName = "Height";
30+
2031
UpdateRawTextureDataFunction _updateRawTextureDataFunction;
2132

22-
Texture2D _targetTexture;
23-
int _textureWidth;
24-
int _textureHeight;
25-
int _bytesPerPixel;
33+
RenderTexture _targetTexture;
34+
readonly int _textureWidth;
35+
readonly int _textureHeight;
36+
readonly int _bytesPerPixel;
2637

2738
bool _disposed;
2839

@@ -34,38 +45,61 @@ public sealed class NonBlockingCustomTextureRenderer : IDisposable
3445
GCHandle _nextBufferHandle;
3546
IntPtr _nextBufferPtr;
3647

37-
static readonly double TimestampsToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
38-
readonly int _targetFrameTimeMilliseconds;
3948
readonly Thread _pluginRenderThread;
4049
readonly CancellationTokenSource _cts;
50+
readonly int _targetFrameTimeMilliseconds;
51+
static readonly double TimestampsToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
4152

42-
delegate void UnityRenderingEventAndData(int eventID, IntPtr data);
43-
readonly UnityRenderingEventAndData _callback;
44-
readonly CommandBuffer _commandBuffer = new CommandBuffer()
45-
{
46-
name = "CustomTextureRenderer.IssuePluginCustomTextureUpdateV2"
47-
};
53+
readonly ComputeShader _computeShader;
54+
readonly int _kernelIndex;
55+
readonly Vector3Int _kernelThreads;
56+
57+
readonly int _rawTextureDataPropertyId;
58+
readonly int _outputTexturePropertyId;
59+
readonly int _textureWidthPropertyId;
60+
readonly int _textureHeightPropertyId;
61+
62+
ComputeBuffer _rawTextureDataComputeBuffer;
63+
64+
readonly int _asyncGPUUploadCount;
65+
int _asyncGPUUploadFrame;
4866

4967
#if DEVELOPMENT_BUILD || UNITY_EDITOR
5068
CustomSampler _updateRawTextureDataFunctionSampler;
69+
CustomSampler _asyncGPUUploadSampler;
5170
#endif
5271

5372
/// <summary>
5473
/// The UpdateRawTextureDataFunction runs on another thread.
5574
/// </summary>
5675
/// <param name="updateRawTextureDataFunction"></param>
5776
/// <param name="targetTexture"></param>
58-
/// <param name="bytesPerPixel"></param>
59-
/// <param name="Dispose"></param>
60-
/// <param name="targetFrameTimeMilliseconds"></param>
61-
public NonBlockingCustomTextureRenderer(UpdateRawTextureDataFunction updateRawTextureDataFunction, Texture2D targetTexture,
62-
int bytesPerPixel = 4, bool autoDispose = true, int targetFrameTimeMilliseconds = 20)
77+
/// <param name="targetFrameRateOfPluginRenderThread"></param>
78+
/// <param name="asyncGPUUploadCount"></param>
79+
/// <param name="autoDispose"></param>
80+
public NonBlockingCustomTextureRenderer(UpdateRawTextureDataFunction updateRawTextureDataFunction, RenderTexture targetTexture,
81+
int targetFrameRateOfPluginRenderThread = 60, int asyncGPUUploadCount = 1, bool autoDispose = true)
6382
{
6483
#if DEVELOPMENT_BUILD || UNITY_EDITOR
6584
_updateRawTextureDataFunctionSampler = CustomSampler.Create("UpdateRawTextureDataFunction");
85+
_asyncGPUUploadSampler = CustomSampler.Create("AsyncGPUUpload");
6686
#endif
87+
if (!SystemInfo.supportsComputeShaders)
88+
{
89+
_disposed = true;
90+
DebugLogError($"[{nameof(NonBlockingCustomTextureRenderer)}] Compute shaders are not supported in this device.");
91+
return;
92+
}
6793

68-
if (targetTexture.format != TextureFormat.RGBA32)
94+
_computeShader = UnityEngine.Object.Instantiate(Resources.Load<ComputeShader>(ComputeShaderName));
95+
if (_computeShader is null)
96+
{
97+
_disposed = true;
98+
DebugLogError($"[{nameof(NonBlockingCustomTextureRenderer)}] The compute shader '{ComputeShaderName}' could not be found in 'Resources'.");
99+
return;
100+
}
101+
102+
if (targetTexture.format != RenderTextureFormat.ARGB32)
69103
{
70104
_disposed = true;
71105
DebugLogError($"[{nameof(NonBlockingCustomTextureRenderer)}] Unsupported texture format: {targetTexture.format}");
@@ -74,15 +108,16 @@ public NonBlockingCustomTextureRenderer(UpdateRawTextureDataFunction updateRawTe
74108

75109
if (autoDispose){ Application.quitting += Dispose; }
76110

77-
_targetFrameTimeMilliseconds = targetFrameTimeMilliseconds;
78-
79111
_updateRawTextureDataFunction = updateRawTextureDataFunction;
80-
_callback = new UnityRenderingEventAndData(TextureUpdateCallback);
112+
DebugLog($"[{nameof(NonBlockingCustomTextureRenderer)}] The UpdateRawTextureDataFunction is \n'{_updateRawTextureDataFunction.Target}.{_updateRawTextureDataFunction.Method.Name}'.");
113+
114+
_targetFrameTimeMilliseconds = (int)(1000.0f / targetFrameRateOfPluginRenderThread);
115+
DebugLog($"[{nameof(NonBlockingCustomTextureRenderer)}] Target frame time milliseconds: {_targetFrameTimeMilliseconds}");
81116

82117
_targetTexture = targetTexture;
83118
_textureWidth = targetTexture.width;
84119
_textureHeight = targetTexture.height;
85-
_bytesPerPixel = bytesPerPixel;
120+
_bytesPerPixel = 4; // RGBA32. 1 byte (8 bits) per channel.
86121

87122
_currentBuffer = new uint[_targetTexture.width * _targetTexture.height];
88123
_currentBufferHandle = GCHandle.Alloc(_currentBuffer, GCHandleType.Pinned);
@@ -92,6 +127,27 @@ public NonBlockingCustomTextureRenderer(UpdateRawTextureDataFunction updateRawTe
92127
_nextBufferHandle = GCHandle.Alloc(_nextBuffer, GCHandleType.Pinned);
93128
_nextBufferPtr = _nextBufferHandle.AddrOfPinnedObject();
94129

130+
DebugLog($"[{nameof(NonBlockingCustomTextureRenderer)}] Texture size: {_targetTexture.width}x{_targetTexture.height}");
131+
DebugLog($"[{nameof(NonBlockingCustomTextureRenderer)}] Texture buffer size: {_targetTexture.width * _targetTexture.height * _bytesPerPixel} [Bytes]");
132+
133+
_kernelIndex = _computeShader.FindKernel(KernelName);
134+
135+
_computeShader.GetKernelThreadGroupSizes(_kernelIndex, out uint numThreadsX, out uint numThreadsY, out uint numThreadsZ);
136+
_kernelThreads.x = (int)numThreadsX;
137+
_kernelThreads.y = (int)numThreadsY;
138+
_kernelThreads.z = (int)numThreadsZ;
139+
140+
_rawTextureDataPropertyId = Shader.PropertyToID(RawTextureDataPropertyName);
141+
_outputTexturePropertyId = Shader.PropertyToID(OutputTexturePropertyName);
142+
_textureWidthPropertyId = Shader.PropertyToID(TextureWidthPropertyName);
143+
_textureHeightPropertyId = Shader.PropertyToID(TextureHeightPropertyName);
144+
145+
_rawTextureDataComputeBuffer = new ComputeBuffer(_targetTexture.width * _targetTexture.height, sizeof(uint));
146+
147+
_asyncGPUUploadCount = (asyncGPUUploadCount < 1) ? 1 : asyncGPUUploadCount;
148+
149+
DebugLog($"[{nameof(NonBlockingCustomTextureRenderer)}] Async GPU Upload Count: {_asyncGPUUploadCount}");
150+
95151
_cts = new CancellationTokenSource();
96152
_pluginRenderThread = new Thread(PluginRenderThread);
97153
_pluginRenderThread.Start();
@@ -113,17 +169,60 @@ public void Dispose()
113169
_updateRawTextureDataFunction = null;
114170
_targetTexture = null;
115171

172+
_rawTextureDataComputeBuffer?.Dispose();
173+
_rawTextureDataComputeBuffer = null;
174+
116175
DebugLog($"[{nameof(NonBlockingCustomTextureRenderer)}] Disposed");
117176
}
118177

119178
public void Update()
120179
{
121180
if (_disposed) { return; }
181+
AsyncGPUUpload();
182+
Render();
183+
}
184+
185+
void AsyncGPUUpload()
186+
{
187+
#if DEVELOPMENT_BUILD || UNITY_EDITOR
188+
_asyncGPUUploadSampler.Begin();
189+
#endif
122190

123-
// Request texture update via the command buffer.
124-
_commandBuffer.IssuePluginCustomTextureUpdateV2(GetTextureUpdateCallback(), _targetTexture, 0);
125-
Graphics.ExecuteCommandBuffer(_commandBuffer);
126-
_commandBuffer.Clear();
191+
#if DISABLE_ASYNC_GPU_UPLOAD
192+
// Run as a performance test without async GPU upload.
193+
_rawTextureDataComputeBuffer.SetData(_currentBuffer, 0, 0, _currentBuffer.Length);
194+
#else
195+
if (_asyncGPUUploadFrame < _asyncGPUUploadCount)
196+
{
197+
var partialCopyLength = _currentBuffer.Length / _asyncGPUUploadCount;
198+
var startIndex = partialCopyLength * _asyncGPUUploadFrame;
199+
_rawTextureDataComputeBuffer.SetData(_currentBuffer, startIndex, startIndex, partialCopyLength);
200+
}
201+
#endif
202+
_asyncGPUUploadFrame++;
203+
204+
#if DEVELOPMENT_BUILD || UNITY_EDITOR
205+
_asyncGPUUploadSampler.End();
206+
#endif
207+
}
208+
209+
void Render()
210+
{
211+
#if !DISABLE_ASYNC_GPU_UPLOAD
212+
if (_asyncGPUUploadFrame == _asyncGPUUploadCount)
213+
#endif
214+
{
215+
_computeShader.SetInt(_textureWidthPropertyId, _textureWidth);
216+
_computeShader.SetInt(_textureHeightPropertyId, _textureHeight);
217+
_computeShader.SetBuffer(_kernelIndex, _rawTextureDataPropertyId, _rawTextureDataComputeBuffer);
218+
_computeShader.SetTexture(_kernelIndex, _outputTexturePropertyId, _targetTexture);
219+
220+
int threadGroupsX = Mathf.CeilToInt((float)_textureWidth / _kernelThreads.x);
221+
int threadGroupsY = Mathf.CeilToInt((float)_textureHeight / _kernelThreads.y);
222+
_computeShader.Dispatch(_kernelIndex, threadGroupsX, threadGroupsY, 1);
223+
224+
// DebugLog($"[{nameof(NonBlockingCustomTextureRenderer)}.Render] Dispatch the compute shader. Async GPU Upload Frame: {_asyncGPUUploadFrame}");
225+
}
127226
}
128227

129228
/// <summary>
@@ -146,7 +245,13 @@ void PluginRenderThread()
146245
// Main loop
147246
{
148247
_updateRawTextureDataFunction?.Invoke(_nextBufferPtr, _textureWidth, _textureHeight, _bytesPerPixel);
248+
249+
// Swap the buffers.
149250
_nextBufferPtr = Interlocked.Exchange(ref _currentBufferPtr, _nextBufferPtr);
251+
_nextBuffer = Interlocked.Exchange(ref _currentBuffer, _nextBuffer);
252+
253+
// Reset to execute async gpu upload.
254+
_asyncGPUUploadFrame = 0;
150255
}
151256

152257
#if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -169,32 +274,6 @@ void PluginRenderThread()
169274
#endif
170275
}
171276

172-
/// <summary>
173-
/// This function runs on Unity's Render Thread.
174-
/// </summary>
175-
/// <param name="eventID"></param>
176-
/// <param name="data"></param>
177-
unsafe void TextureUpdateCallback(int eventID, IntPtr data)
178-
{
179-
if (_currentBufferPtr == IntPtr.Zero) { return; }
180-
181-
var updateParams = (UnityRenderingExtTextureUpdateParamsV2*)data.ToPointer();
182-
183-
if (eventID == (int)UnityRenderingExtEventType.kUnityRenderingExtEventUpdateTextureBeginV2)
184-
{
185-
updateParams->texData = _currentBufferPtr.ToPointer();
186-
}
187-
else if (eventID == (int)UnityRenderingExtEventType.kUnityRenderingExtEventUpdateTextureEndV2)
188-
{
189-
updateParams->texData = null;
190-
}
191-
}
192-
193-
IntPtr GetTextureUpdateCallback()
194-
{
195-
return Marshal.GetFunctionPointerForDelegate(_callback);
196-
}
197-
198277
/// <summary>
199278
/// Logs a message to the Unity Console
200279
/// only when DEVELOPMENT_BUILD or UNITY_EDITOR is defined.
@@ -209,7 +288,7 @@ static void DebugLog(object message)
209288
{
210289
UnityEngine.Debug.Log(message);
211290
}
212-
291+
213292
/// <summary>
214293
/// Logs a message to the Unity Console
215294
/// only when DEVELOPMENT_BUILD or UNITY_EDITOR is defined.

Assets/CustomTextureRenderer/Runtime/Resources.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)