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+
16using System ;
27using System . Diagnostics ;
38using System . Threading ;
49using System . Runtime . InteropServices ;
510using UnityEngine ;
6- using UnityEngine . Rendering ;
711
812#if DEVELOPMENT_BUILD || UNITY_EDITOR
913using 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.
0 commit comments