Material Clustering Benchmark
MeasurementRunner.cs
Go to the documentation of this file.
1using UnityEngine;
3using System;
4using System.Collections.Generic;
5using static Diagnostics;
6
7/// <summary>
8/// Call <see cref="Dispose" /> after using.
9/// </summary>
10public class MeasurementRunner : IDisposable
11{
12 private const int sectionLength = 1000;
13 private const int totalSections = 100; // counting repeats as unique sections
14
15 private class VideoSection
16 {
17 public readonly long start;
18 public readonly long end;
19
20 public VideoSection(long start, long end)
21 {
22 this.start = start;
23 this.end = end;
24 }
25 }
26
27 private List<VideoSection> sections;
28
29 private bool loadingVideo;
30
31 private void FillSectionList(long numFrames)
32 {
33 this.sections = new List<VideoSection>();
34
35 long numSections = numFrames / sectionLength;
36 Assert(numSections > 0, "Video file has no sections.");
37 for (
38 long sectionStart = 0;
39 sectionStart + sectionLength <= numFrames && sections.Count < totalSections;
40 sectionStart += sectionLength
41 )
42 {
43 sections.Add(new VideoSection(start: sectionStart, end: sectionStart + sectionLength));
44 }
45 for (int i = 0; sections.Count < totalSections; i++)
46 {
47 sections.Add(sections[i]);
48 }
49 }
50
51 private readonly ComputeShader csHighlightRemoval;
52 private readonly int kernelShowResult;
53
54 private readonly BenchmarkGeneration.LaunchParameters launchParameters;
55
56 private UnityEngine.Video.VideoPlayer videoPlayer;
57 private readonly long frameStart;
58 private readonly long? frameEndOrNull;
59
60 private readonly BenchmarkMeasurementVariance benchmarkMeasurementVariance;
61 private readonly BenchmarkMeasurementFrameTime benchmarkMeasurementFrameTime;
62
63 private readonly ClusteringTest.LogType logType;
64
65 private long? lastProcessedFrame;
66
67 public string paramsJSON => JsonUtility.ToJson(this.launchParameters.GetSerializable());
68 public bool finished;
69 public int warningCounter => this.launchParameters.dispatcher.warningCounter;
70
71 /// <summary>
72 /// Takes ownership of launchParameters
73 /// </summary>
76 UnityEngine.Video.VideoPlayer videoPlayer,
77 long? frameStart,
78 long? frameEnd,
79 ClusteringTest.LogType logType,
80 ComputeShader csHighlightRemoval
81 )
82 {
83 this.csHighlightRemoval = csHighlightRemoval;
84 this.kernelShowResult = csHighlightRemoval.FindKernel("ShowResult");
85 this.logType = logType;
86 this.launchParameters = launchParameters;
87 this.lastProcessedFrame = null;
88 this.benchmarkMeasurementVariance = new BenchmarkMeasurementVariance();
89 this.benchmarkMeasurementFrameTime = new BenchmarkMeasurementFrameTime();
90 this.frameEndOrNull = frameEnd;
91 this.frameStart = frameStart ?? 0;
92 this.finished = false;
93 this.loadingVideo = true;
94
95 if (this.launchParameters.dispatcher.clusteringRTsAndBuffers.isAllocated == false)
96 {
97 this.launchParameters.dispatcher.clusteringRTsAndBuffers.Allocate();
98 }
99
100 this.SetTextureSize();
101 this.InitVideoPlayer(videoPlayer, frameStart);
102
103 FillSectionList((long)this.videoPlayer.frameCount);
104 }
105
106 private void InitVideoPlayer(UnityEngine.Video.VideoPlayer videoPlayer, long? frameStart)
107 {
108 this.videoPlayer = videoPlayer;
109 this.videoPlayer.playbackSpeed = 0;
110 this.videoPlayer.clip = this.launchParameters.video;
111 this.videoPlayer.Play();
112 /*
113 if the frame was 0
114 we won't be able to figure out when the video loads
115
116 TODO robust check
117 */
118 Assert(
119 this.videoPlayer.frame != 0,
120 "Video player set to frame 0. Normally this does not happen."
121 );
122 this.videoPlayer.frame = frameStart ?? 0;
123 }
124
125 private void SetTextureSize()
126 {
127 int workingTextureSize = this.launchParameters
128 .dispatcher
129 .clusteringRTsAndBuffers
130 .workingSize;
131
132 int fullTextureSize = this.launchParameters.dispatcher.clusteringRTsAndBuffers.fullSize;
133
134 Assert(
135 // positive power of 2
136 (workingTextureSize & (workingTextureSize - 1)) == 0
137 && workingTextureSize > 0,
138 $"Invalid texture size provided to {System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name + System.Reflection.MethodBase.GetCurrentMethod().Name}"
139 );
140 Assert(
141 workingTextureSize <= fullTextureSize,
142 "Full texture size can not be smaller than working texture size."
143 );
144
145 this.csHighlightRemoval.SetInt("texture_size", workingTextureSize);
146 }
147
149 {
150 ABenchmarkMeasurement measurement = null;
151 switch (this.logType)
152 {
153 case ClusteringTest.LogType.Variance:
154 measurement = this.benchmarkMeasurementVariance;
155 break;
156 case ClusteringTest.LogType.FrameTime:
157 measurement = this.benchmarkMeasurementFrameTime;
158 break;
159 default:
160 Throw(
161 new System.NotImplementedException($"Log type not implemented: {this.logType}")
162 );
163 break;
164 }
165
166 return new BenchmarkReport(
167 measurement: measurement,
168 serializableLaunchParameters: this.launchParameters.GetSerializable(),
169 logType: this.logType
170 );
171 }
172
173 private void ProcessFrame_VarianceMode()
174 {
175 this.RunDispatcher();
176
177 if (this.videoPlayer.frame != this.sections[0].start)
178 {
179 this.MakeVarianceLogEntry();
180 }
181 }
182
183 private void ProcessFrame_FrameTimeMode()
184 {
185 /*
186 in order to re-start the clustering "from scratch"
187 all we need to do is to reset the cluster centers
188
189 clustering only edits:
190 * attribution texture array
191 * cluster centers ComputeBuffer
192
193 clustering starts with attribution
194 so the texture array will be messed by the reset of cluster centers
195 */
196
197 using (
198 ClusterCenters clusterCenters =
199 this.launchParameters.dispatcher.clusteringRTsAndBuffers.GetClusterCenters()
200 )
201 {
202 const int numRepetitions = 10;
203
204 float avgTime =
206 () =>
207 {
208 for (int i = 0; i < numRepetitions; i++)
209 {
210 this.launchParameters.dispatcher.clusteringRTsAndBuffers.SetClusterCenters(
211 clusterCenters.centers
212 );
213 this.RunDispatcher();
214 }
215
216 using (
217 // force current thread to wait until GPU finishes computations
218 ClusterCenters temp =
219 this.launchParameters.dispatcher.clusteringRTsAndBuffers.GetClusterCenters()
220 )
221 {
222 // useless line to prevent compiler optimization
223 temp.centers[0] = temp.centers[1];
224 }
225 }
226 ) / numRepetitions;
227
228 this.benchmarkMeasurementFrameTime.frameTimeRecords.Add(
230 frameIndex: this.videoPlayer.frame,
231 time: avgTime
232 )
233 );
234 }
235 }
236
237 private bool IsFrameReady()
238 {
239 if (
240 // not yet loaded video file
241 this.videoPlayer.frame == -1
242 /*
243 ! will be a bug if previous run ended on frame 0
244 (which should not normally happen)
245
246 TODO robust check
247 */
248 || this.videoPlayer.frame != 0 && this.loadingVideo
249 // not yet loaded next frame
250 || this.lastProcessedFrame == this.videoPlayer.frame
251 )
252 {
253 return false;
254 }
255
256 this.loadingVideo = false;
257
258 return true;
259 }
260
261 public void ProcessNextFrame(RenderTexture src, RenderTexture dst)
262 {
263 if (!IsFrameReady())
264 {
265 Graphics.Blit(src, dst);
266 return;
267 }
268
269 /*
270 ProcessNextFrame() should not be called
271 after measurements were finished
272 */
273 Assert(
274 this.finished == false,
275 $"{System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name + System.Reflection.MethodBase.GetCurrentMethod().Name} should not be called after measurements were finished"
276 );
277
278 /*
279 check, that frames are being processed one by one
280 without skips or repeats
281 */
282 if (this.lastProcessedFrame != null)
283 {
284 if (this.lastProcessedFrame + 1 != this.videoPlayer.frame)
285 {
286 /*
287 the order will not be x => x+1
288 when jumping from section to section
289
290 setting lastProcessedFrame to null doesn't work
291 lastProcessedFrame = videoPlayer.frame
292 means the frame is still loading
293
294 setting lastProcessedFrame to null
295 will loose this information
296 and we won't know how many frames to skip
297 */
298 Assert(
299 this.videoPlayer.frame == this.sections[0].start,
300 $"Incorrect frame processing order. Current frame: {this.videoPlayer.frame}. Previous frame: {this.lastProcessedFrame}"
301 );
302 }
303 }
304 this.lastProcessedFrame = this.videoPlayer.frame;
305
306 Graphics.Blit(
307 this.videoPlayer.texture,
308 this.launchParameters.dispatcher.clusteringRTsAndBuffers.texturesFullRes.rtInput
309 );
310
311 this.launchParameters.dispatcher.clusteringRTsAndBuffers.Downsample(
312 this.csHighlightRemoval,
313 staggeredJitter: this.launchParameters.staggeredJitter,
314 doDownscale: this.launchParameters.doDownscale
315 );
316
317 switch (this.logType)
318 {
319 case ClusteringTest.LogType.Variance:
320 this.ProcessFrame_VarianceMode();
321 break;
322 case ClusteringTest.LogType.FrameTime:
323 ProcessFrame_FrameTimeMode();
324 break;
325 default:
326 new System.NotImplementedException($"Log type not implemented: {this.logType}");
327 break;
328 }
329
330 this.AdvanceFrame();
331
332 this.RenderResult(dst);
333 }
334
335 private void AdvanceFrame()
336 {
337 // if the frame we just processed is the last in current section
338 if (this.videoPlayer.frame == this.sections[0].end - 1)
339 {
340 // drop processed section
341 this.sections.RemoveAt(0);
342
343 // if there are no sections left
344 if (this.sections.Count == 0)
345 {
346 this.finished = true;
347 }
348 // if there are sections left
349 else
350 {
351 this.videoPlayer.frame = this.sections[0].start;
352 /*
353 ! we do not reset this.lastProcessedFrame
354
355 while the video is loading
356 this.lastProcessedFrame == this.videoPlayer.frame
357 we can use this to wait for the frame to load
358 */
359
360 // new init for each new section
361#pragma warning disable 162
363 {
364 this.launchParameters.dispatcher.clusteringRTsAndBuffers.RandomizeClusterCenters();
365 }
366 else
367 {
368 this.launchParameters.dispatcher.clusteringRTsAndBuffers.SetDeterministicClusterCenters();
369 }
370#pragma warning restore 162
371 }
372 }
373 // if the frame we just processed is not the last in current section
374 else
375 {
376 this.videoPlayer.frame++;
377 }
378 }
379
380 /// <summary>
381 /// Makes a variance log entry for the current cluster centers.
382 /// </summary>
383 private void MakeVarianceLogEntry()
384 {
385 this.benchmarkMeasurementVariance.frameVarianceRecords.Add(
387 frameIndex: this.videoPlayer.frame,
388 variance: this.launchParameters.dispatcher.GetVariance()
389 )
390 );
391 }
392
393 /// <summary>
394 /// Runs clustering iterations plus one final attribution. Without the final attribution we would have the latest cluster centers, but we wouldn't know which pixels belong to which cluster. The additional attribution does not create a "bonus" iteration because the next frame starts with new attribution (this is required as the input has changed).
395 /// </summary>
396 private void RunDispatcher()
397 {
398 this.launchParameters.dispatcher.RunClustering(
399 this.launchParameters.dispatcher.clusteringRTsAndBuffers.texturesWorkRes
400 );
401
402 /*
403 RunClustering finishes with updating cluster centers
404 so we have the latest cluster centers
405 but we don't have appropriate attribution
406
407 this final attribution does not create a "bonus" iteration
408 because the next frame starts with attribution
409 */
410 this.launchParameters.dispatcher.AttributeClustersKM(
411 this.launchParameters.dispatcher.clusteringRTsAndBuffers.texturesWorkRes
412 );
413 }
414
415 public void RenderResult(RenderTexture target)
416 {
417 this.csHighlightRemoval.SetTexture(
418 this.kernelShowResult,
419 "tex_arr_clusters_r",
420 this.launchParameters.dispatcher.clusteringRTsAndBuffers.texturesWorkRes.rtArr
421 );
422
423 this.csHighlightRemoval.SetTexture(
424 this.kernelShowResult,
425 "tex_output",
426 this.launchParameters.dispatcher.clusteringRTsAndBuffers.rtResult
427 );
428
429 this.csHighlightRemoval.SetBuffer(
430 this.kernelShowResult,
431 "cbuf_cluster_centers",
432 this.launchParameters.dispatcher.clusteringRTsAndBuffers.cbufClusterCenters
433 );
434
435 this.csHighlightRemoval.SetTexture(
436 this.kernelShowResult,
437 "tex_input",
438 this.launchParameters.dispatcher.clusteringRTsAndBuffers.texturesWorkRes.rtInput
439 );
440
441 this.csHighlightRemoval.Dispatch(
442 this.kernelShowResult,
443 Math.Max(
444 this.launchParameters.dispatcher.clusteringRTsAndBuffers.rtResult.width
446 1
447 ),
448 Math.Max(
449 this.launchParameters.dispatcher.clusteringRTsAndBuffers.rtResult.height
451 1
452 ),
453 1
454 );
455
456 Graphics.Blit(this.launchParameters.dispatcher.clusteringRTsAndBuffers.rtResult, target);
457
458 this.launchParameters.dispatcher.clusteringRTsAndBuffers.rtResult.DiscardContents();
459 }
460
461 public void Dispose()
462 {
463 this.launchParameters.Dispose();
464 }
465}
static long MeasureTime(Action action)
Runs the provided action and measures the time it required to finish. Ensures GC will not trigger wh...
Call Dispose after using.
Call Allocate before using and Dispose after using.
const int kernelSize
Must match the shader.
Call Dispose after using.
BenchmarkReport GetReport()
MeasurementRunner(BenchmarkGeneration.LaunchParameters launchParameters, UnityEngine.Video.VideoPlayer videoPlayer, long? frameStart, long? frameEnd, ClusteringTest.LogType logType, ComputeShader csHighlightRemoval)
Takes ownership of launchParameters
void ProcessNextFrame(RenderTexture src, RenderTexture dst)
void RenderResult(RenderTexture target)