restructure video threading implementation
							parent
							
								
									871b32d742
								
							
						
					
					
						commit
						b4021156cf
					
				|  | @ -11,10 +11,6 @@ namespace MoonWorks.Video | |||
| 	{ | ||||
| 		public string Filename { get; } | ||||
| 
 | ||||
| 		// "double buffering" so we can loop without a stutter | ||||
| 		internal VideoAV1Stream StreamA { get; } | ||||
| 		internal VideoAV1Stream StreamB { get; } | ||||
| 
 | ||||
| 		public int Width => width; | ||||
| 		public int Height => height; | ||||
| 		public double FramesPerSecond { get; set; } | ||||
|  | @ -67,23 +63,6 @@ namespace MoonWorks.Video | |||
| 			FramesPerSecond = framesPerSecond; | ||||
| 
 | ||||
| 			Filename = filename; | ||||
| 
 | ||||
| 			StreamA = new VideoAV1Stream(device, this); | ||||
| 			StreamB = new VideoAV1Stream(device, this); | ||||
| 		} | ||||
| 
 | ||||
| 		// NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode | ||||
| 		protected override void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				if (disposing) | ||||
| 				{ | ||||
| 					StreamA.Dispose(); | ||||
| 					StreamB.Dispose(); | ||||
| 				} | ||||
| 			} | ||||
| 			base.Dispose(disposing); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,8 +1,11 @@ | |||
| using System; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Threading; | ||||
| using MoonWorks.Graphics; | ||||
| 
 | ||||
| namespace MoonWorks.Video | ||||
| { | ||||
| 	// Note that all public methods are async. | ||||
| 	internal class VideoAV1Stream : GraphicsResource | ||||
| 	{ | ||||
| 		public IntPtr Handle => handle; | ||||
|  | @ -21,20 +24,65 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		public bool FrameDataUpdated { get; set; } | ||||
| 
 | ||||
| 		private VideoAV1 Parent; | ||||
| 		private BlockingCollection<Action> Actions = new BlockingCollection<Action>(); | ||||
| 
 | ||||
| 		public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device) | ||||
| 		private bool Running = false; | ||||
| 
 | ||||
| 		Thread Thread; | ||||
| 
 | ||||
| 		public VideoAV1Stream(GraphicsDevice device) : base(device) | ||||
| 		{ | ||||
| 			handle = IntPtr.Zero; | ||||
| 			Parent = video; | ||||
| 
 | ||||
| 			Running = true; | ||||
| 
 | ||||
| 			Thread = new Thread(ThreadMain); | ||||
| 			Thread.Start(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load() | ||||
| 		private void ThreadMain() | ||||
| 		{ | ||||
| 			while (Running) | ||||
| 			{ | ||||
| 				// block until we can take an action, then run it | ||||
| 				var action = Actions.Take(); | ||||
| 				action.Invoke(); | ||||
| 			} | ||||
| 
 | ||||
| 			// shutting down... | ||||
| 			while (Actions.TryTake(out var action)) | ||||
| 			{ | ||||
| 				action.Invoke(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(string filename) | ||||
| 		{ | ||||
| 			Actions.Add(() => LoadHelper(filename)); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Reset() | ||||
| 		{ | ||||
| 			Actions.Add(ResetHelper); | ||||
| 		} | ||||
| 
 | ||||
| 		public void ReadNextFrame() | ||||
| 		{ | ||||
| 			Actions.Add(ReadNextFrameHelper); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Unload() | ||||
| 		{ | ||||
| 			Actions.Add(UnloadHelper); | ||||
| 		} | ||||
| 
 | ||||
| 		private void LoadHelper(string filename) | ||||
| 		{ | ||||
| 			if (!Loaded) | ||||
| 			{ | ||||
| 				if (Dav1dfile.df_fopen(Parent.Filename, out handle) == 0) | ||||
| 				if (Dav1dfile.df_fopen(filename, out handle) == 0) | ||||
| 				{ | ||||
| 					Logger.LogError("Failed to load video file: " + filename); | ||||
| 					throw new Exception("Failed to load video file!"); | ||||
| 				} | ||||
| 
 | ||||
|  | @ -42,29 +90,20 @@ namespace MoonWorks.Video | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Unload() | ||||
| 		private void ResetHelper() | ||||
| 		{ | ||||
| 			if (Loaded) | ||||
| 			{ | ||||
| 				Dav1dfile.df_close(handle); | ||||
| 				handle = IntPtr.Zero; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Reset() | ||||
| 		{ | ||||
| 			lock (this) | ||||
| 			{ | ||||
| 				Dav1dfile.df_reset(handle); | ||||
| 				ReadNextFrame(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void ReadNextFrame() | ||||
| 		private void ReadNextFrameHelper() | ||||
| 		{ | ||||
| 			if (Loaded && !Ended) | ||||
| 			{ | ||||
| 				lock (this) | ||||
| 			{ | ||||
| 				if (!Ended) | ||||
| 				{ | ||||
| 					if (Dav1dfile.df_readvideo( | ||||
| 						handle, | ||||
|  | @ -91,11 +130,26 @@ namespace MoonWorks.Video | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private void UnloadHelper() | ||||
| 		{ | ||||
| 			if (Loaded) | ||||
| 			{ | ||||
| 				Dav1dfile.df_close(handle); | ||||
| 				handle = IntPtr.Zero; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected override void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				Unload(); | ||||
| 				Running = false; | ||||
| 
 | ||||
| 				if (disposing) | ||||
| 				{ | ||||
| 					Thread.Join(); | ||||
| 				} | ||||
| 			} | ||||
| 			base.Dispose(disposing); | ||||
| 		} | ||||
|  |  | |||
|  | @ -18,9 +18,9 @@ namespace MoonWorks.Video | |||
| 		private VideoAV1 Video = null; | ||||
| 		private VideoAV1Stream CurrentStream = null; | ||||
| 
 | ||||
| 		private Task ReadNextFrameTask; | ||||
| 		private Task ResetTask; | ||||
| 		private Task ResetSecondaryStreamTask; | ||||
| 		// "double buffering" so we can loop without a stutter | ||||
| 		private VideoAV1Stream StreamA { get; } | ||||
| 		private VideoAV1Stream StreamB { get; } | ||||
| 
 | ||||
| 		private Texture yTexture = null; | ||||
| 		private Texture uTexture = null; | ||||
|  | @ -37,6 +37,9 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		public VideoPlayer(GraphicsDevice device) : base(device) | ||||
| 		{ | ||||
| 			StreamA = new VideoAV1Stream(device); | ||||
| 			StreamB = new VideoAV1Stream(device); | ||||
| 
 | ||||
| 			LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp); | ||||
| 
 | ||||
| 			timer = new Stopwatch(); | ||||
|  | @ -50,7 +53,7 @@ namespace MoonWorks.Video | |||
| 		{ | ||||
| 			if (Video != video) | ||||
| 			{ | ||||
| 				Stop(); | ||||
| 				Unload(); | ||||
| 
 | ||||
| 				if (RenderTexture == null) | ||||
| 				{ | ||||
|  | @ -154,7 +157,7 @@ namespace MoonWorks.Video | |||
| 			lastTimestamp = 0; | ||||
| 			timeElapsed = 0; | ||||
| 
 | ||||
| 			ResetDav1dStream(); | ||||
| 			ResetDav1dStreams(); | ||||
| 
 | ||||
| 			State = VideoState.Stopped; | ||||
| 		} | ||||
|  | @ -164,9 +167,10 @@ namespace MoonWorks.Video | |||
| 		/// </summary> | ||||
| 		public void Unload() | ||||
| 		{ | ||||
| 			ReadNextFrameTask?.Wait(); | ||||
| 			ResetTask?.Wait(); | ||||
| 			ResetSecondaryStreamTask?.Wait(); | ||||
| 			if (Video == null) | ||||
| 			{ | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			timer.Stop(); | ||||
| 			timer.Reset(); | ||||
|  | @ -176,12 +180,9 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 			State = VideoState.Stopped; | ||||
| 
 | ||||
| 			Video.StreamA.Unload(); | ||||
| 			Video.StreamB.Unload(); | ||||
| 			StreamA.Unload(); | ||||
| 			StreamB.Unload(); | ||||
| 
 | ||||
| 			ReadNextFrameTask = null; | ||||
| 			ResetTask = null; | ||||
| 			ResetSecondaryStreamTask = null; | ||||
| 			Video = null; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -208,8 +209,7 @@ namespace MoonWorks.Video | |||
| 				} | ||||
| 
 | ||||
| 				currentFrame = thisFrame; | ||||
| 				ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame); | ||||
| 				ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 				CurrentStream.ReadNextFrame(); | ||||
| 			} | ||||
| 
 | ||||
| 			if (CurrentStream.Ended) | ||||
|  | @ -217,13 +217,12 @@ namespace MoonWorks.Video | |||
| 				timer.Stop(); | ||||
| 				timer.Reset(); | ||||
| 
 | ||||
| 				ResetSecondaryStreamTask = Task.Run(CurrentStream.Reset); | ||||
| 				ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 				CurrentStream.Reset(); | ||||
| 
 | ||||
| 				if (Loop) | ||||
| 				{ | ||||
| 					// Start over on the next stream! | ||||
| 					CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA; | ||||
| 					CurrentStream = (CurrentStream == StreamA) ? StreamB : StreamA; | ||||
| 					currentFrame = -1; | ||||
| 					timer.Start(); | ||||
| 				} | ||||
|  | @ -236,13 +235,11 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		private void UpdateRenderTexture() | ||||
| 		{ | ||||
| 			uint uOffset; | ||||
| 			uint vOffset; | ||||
| 
 | ||||
| 			lock (CurrentStream) | ||||
| 			{ | ||||
| 				ResetTask?.Wait(); | ||||
| 				ResetTask = null; | ||||
| 
 | ||||
| 				var commandBuffer = Device.AcquireCommandBuffer(); | ||||
| 
 | ||||
| 				var ySpan = new Span<byte>((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength); | ||||
| 				var uSpan = new Span<byte>((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength); | ||||
| 				var vSpan = new Span<byte>((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength); | ||||
|  | @ -256,6 +253,12 @@ namespace MoonWorks.Video | |||
| 				TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe); | ||||
| 				TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe); | ||||
| 
 | ||||
| 				uOffset = (uint) ySpan.Length; | ||||
| 				vOffset = (uint) (ySpan.Length + vSpan.Length); | ||||
| 			} | ||||
| 
 | ||||
| 			var commandBuffer = Device.AcquireCommandBuffer(); | ||||
| 
 | ||||
| 			commandBuffer.BeginCopyPass(); | ||||
| 
 | ||||
| 			commandBuffer.UploadToTexture( | ||||
|  | @ -274,7 +277,7 @@ namespace MoonWorks.Video | |||
| 				TransferBuffer, | ||||
| 				uTexture, | ||||
| 				new BufferImageCopy{ | ||||
| 						BufferOffset = (uint) ySpan.Length, | ||||
| 					BufferOffset = uOffset, | ||||
| 					BufferStride = CurrentStream.uvStride, | ||||
| 					BufferImageHeight = uTexture.Height | ||||
| 				}, | ||||
|  | @ -286,7 +289,7 @@ namespace MoonWorks.Video | |||
| 				vTexture, | ||||
| 				new BufferImageCopy | ||||
| 				{ | ||||
| 						BufferOffset = (uint) (ySpan.Length + uSpan.Length), | ||||
| 					BufferOffset = vOffset, | ||||
| 					BufferStride = CurrentStream.uvStride, | ||||
| 					BufferImageHeight = vTexture.Height | ||||
| 				}, | ||||
|  | @ -312,7 +315,6 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 			Device.Submit(commandBuffer); | ||||
| 		} | ||||
| 		} | ||||
| 
 | ||||
| 		private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height) | ||||
| 		{ | ||||
|  | @ -338,35 +340,19 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		private void InitializeDav1dStream() | ||||
| 		{ | ||||
| 			ReadNextFrameTask?.Wait(); | ||||
| 			ReadNextFrameTask = null; | ||||
| 			StreamA.Load(Video.Filename); | ||||
| 			StreamB.Load(Video.Filename); | ||||
| 
 | ||||
| 			ResetTask?.Wait(); | ||||
| 			ResetTask = null; | ||||
| 
 | ||||
| 			ResetSecondaryStreamTask?.Wait(); | ||||
| 			ResetSecondaryStreamTask = null; | ||||
| 
 | ||||
| 			ResetTask = Task.Run(Video.StreamA.Load); | ||||
| 			ResetTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 			ResetSecondaryStreamTask = Task.Run(Video.StreamB.Load); | ||||
| 			ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 
 | ||||
| 			CurrentStream = Video.StreamA; | ||||
| 			CurrentStream = StreamA; | ||||
| 			currentFrame = -1; | ||||
| 		} | ||||
| 
 | ||||
| 		private void ResetDav1dStream() | ||||
| 		private void ResetDav1dStreams() | ||||
| 		{ | ||||
| 			ReadNextFrameTask?.Wait(); | ||||
| 			ReadNextFrameTask = null; | ||||
| 			StreamA.Reset(); | ||||
| 			StreamB.Reset(); | ||||
| 
 | ||||
| 			ResetTask = Task.Run(Video.StreamA.Reset); | ||||
| 			ResetTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 			ResetSecondaryStreamTask = Task.Run(Video.StreamB.Reset); | ||||
| 			ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 
 | ||||
| 			CurrentStream = Video.StreamA; | ||||
| 			CurrentStream = StreamA; | ||||
| 			currentFrame = -1; | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue