/// <summary> /// Consumer-producer based multithreading work distribution /// Each thread waits for a new Assignment to be added to availableAssignments queue /// Most of the time is number of items in availableAssignments expected to be several times larger than number of threads /// </summary> protected void Consume() { MT.InitThreadData(); while (!availableAssignments.IsEmpty || finishedAssignments < totalNumberOfAssignments - threads || NetworkWorker.assignmentsAtClients > 0) { availableAssignments.TryDequeue(out Assignment newAssignment); if (!progressData.Continue) // test whether rendering should end (Stop button pressed) { return; } if (newAssignment == null) // TryDequeue was not successful { continue; } float[] colorArray = newAssignment.Render(false, renderer, progressData); BitmapMerger(colorArray, newAssignment.x1, newAssignment.y1, newAssignment.x2 + 1, newAssignment.y2 + 1); if (newAssignment.stride == 1) { finishedAssignments++; assignmentRoundsFinished++; } else { newAssignment.stride = newAssignment.stride >> 1; // stride values: 8 > 4 > 2 > 1 assignmentRoundsFinished++; availableAssignments.Enqueue(newAssignment); } } }
/// <summary> /// Routine of one worker-thread. /// Result image and rendering progress are the only two shared objects. /// </summary> /// <param name="spec">Thread-specific data (worker-thread-selector).</param> private void RenderWorker(object spec) { if (spec is WorkerThreadInit init) { MT.InitThreadData(); init.rend.RenderRectangle(init.image, 0, 0, init.width, init.height, init.sel); } }
/// <summary> /// Consumer-producer based multithreading work distribution /// Each thread waits for a new Assignment to be added to availableAssignments queue /// Most of the time is number of items in availableAssignments expected to be several times larger than number of threads /// </summary> protected void Consume() { // Set TLS. MT.InitThreadData(); MT.SetRendering(scene, imageFunction, renderer); // Animation time (fore debugging animated scenes). if (scene is ITimeDependent sc) { sc.Time = 0.0; } while (!availableAssignments.IsEmpty || finishedAssignments < totalNumberOfAssignments - threads || NetworkWorker.assignmentsAtClients > 0) { availableAssignments.TryDequeue(out Assignment newAssignment); if (!progressData.Continue) // test whether rendering should end (Stop button pressed) { return; } if (newAssignment == null) // TryDequeue was not successful { continue; } float[] colorArray = newAssignment.Render(false, renderer, progressData); BitmapMerger(colorArray, newAssignment.x1, newAssignment.y1, newAssignment.x2 + 1, newAssignment.y2 + 1); if (newAssignment.stride == 1) { finishedAssignments++; assignmentRoundsFinished++; } else { newAssignment.stride >>= 1; // stride values: 8 > 4 > 2 > 1 assignmentRoundsFinished++; availableAssignments.Enqueue(newAssignment); } } }
/// <summary> /// [Re]-renders the whole image (in separate thread). OLD VERSION!!! /// </summary> private void RenderImage_OLD() { Cursor.Current = Cursors.WaitCursor; // determine output image size: int width = ImageWidth; if (width <= 0) { width = panel1.Width; } int height = ImageHeight; if (height <= 0) { height = panel1.Height; } Bitmap newImage = new Bitmap(width, height, PixelFormat.Format24bppRgb); int threads = CheckMultithreading.Checked ? Environment.ProcessorCount : 1; int t; // thread ordinal number WorkerThreadInit[] wti = new WorkerThreadInit[threads]; // separate renderer, image function and the scene for each thread (safety precaution) for (t = 0; t < threads; t++) { IRayScene sc = FormSupport.getScene(); IImageFunction imf = getImageFunction(sc, width, height); IRenderer r = getRenderer(imf, width, height); wti[t] = new WorkerThreadInit(r, sc as ITimeDependent, imf as ITimeDependent, newImage, width, height, t, threads); } progress.SyncInterval = ((width * (long)height) > (2L << 20)) ? 3000L : 1000L; progress.Reset(); CSGInnerNode.ResetStatistics(); lock (sw) sw.Restart(); if (threads > 1) { Thread[] pool = new Thread[threads]; for (t = 0; t < threads; t++) { pool[t] = new Thread(new ParameterizedThreadStart(RenderWorker)); } for (t = threads; --t >= 0;) { pool[t].Start(wti[t]); } for (t = 0; t < threads; t++) { pool[t].Join(); pool[t] = null; } } else { MT.InitThreadData(); wti[0].rend.RenderRectangle(newImage, 0, 0, width, height); } long elapsed; lock (sw) { sw.Stop(); elapsed = sw.ElapsedMilliseconds; } string msg = string.Format(CultureInfo.InvariantCulture, "{0:f1}s [ {1}x{2}, mt{3}, r{4:#,#}k, i{5:#,#}k, bb{6:#,#}k, t{7:#,#}k ]", 1.0e-3 * elapsed, width, height, threads, (Intersection.countRays + 500L) / 1000L, (Intersection.countIntersections + 500L) / 1000L, (CSGInnerNode.countBoundingBoxes + 500L) / 1000L, (CSGInnerNode.countTriangles + 500L) / 1000L); SetText(msg); Console.WriteLine(@"Rendering finished: " + msg); SetImage(newImage); Cursor.Current = Cursors.Default; StopRendering(); }
/// <summary> /// Creates threadpool and starts all threads on Consume method /// Thread which calls this method will take care of preparing assignments and receiving rendered images from RenderClients meanwhile /// </summary> public void RunThreads() { pool = new Thread[MT.threads]; AssignNetworkWorkerToStream(); WaitHandle[] waitHandles = new WaitHandle[MT.threads]; // Multiply animated instances. int i; // Scene definitions. if (scenes[0] is ITimeDependent scenea) { scenea.Time = time; scenes = new IRayScene[MT.threads]; for (i = 0; i < MT.threads; i++) { scenes[i] = i == 0 ? (IRayScene)scenea : (IRayScene)scenea.Clone(); } } // Image functions. if (imageFunctions[0] is ITimeDependent imfa) { imfa.Time = time; imageFunctions = new IImageFunction[MT.threads]; for (i = 0; i < MT.threads; i++) { imageFunctions[i] = i == 0 ? (IImageFunction)imfa : (IImageFunction)imfa.Clone(); } } // Renderers. if (renderers[0] is ITimeDependent renda) { renda.Time = time; renderers = new IRenderer[MT.threads]; for (i = 0; i < MT.threads; i++) { renderers[i] = i == 0 ? (IRenderer)renda : (IRenderer)renda.Clone(); } } for (i = 0; i < MT.threads; i++) { EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.ManualReset); // Thread-specific instances: thread's id. int tid = i; Thread newThread = new Thread(() => { // Set TLS. MT.threadID = tid; MT.InitThreadData(); MT.SetRendering( scenes[Math.Min(tid, scenes.Length - 1)], imageFunctions[Math.Min(tid, imageFunctions.Length - 1)], renderers[Math.Min(tid, renderers.Length - 1)]); Consume(); // Signal finishing the work. handle.Set(); }); newThread.Name = "RenderThread #" + i; pool[i] = newThread; newThread.Start(); waitHandles[i] = handle; } mainRenderThread = pool[0]; Thread imageReceiver = new Thread(RenderedImageReceiver); imageReceiver.Name = "ImageReceiver"; imageReceiver.Start(); WaitHandle.WaitAll(waitHandles); if (networkWorkers?.Count > 0) { foreach (NetworkWorker worker in networkWorkers) // sends ending assignment to all clients { worker.SendSpecialAssignment(Assignment.AssignmentType.Ending); } } // Reset the pool-thread. pool = null; }
/// <summary> /// [Re]-renders the whole image (in separate thread). OLD VERSION!!! /// </summary> private void RenderImage_OLD() { Cursor.Current = Cursors.WaitCursor; // Determine output image size. int width = ImageWidth; if (width <= 0) { width = panel1.Width; } int height = ImageHeight; if (height <= 0) { height = panel1.Height; } Bitmap newImage = new Bitmap(width, height, PixelFormat.Format24bppRgb); int threads = CheckMultithreading.Checked ? Environment.ProcessorCount : 1; int t; // thread ordinal number int superSampling = (int)NumericSupersampling.Value; WorkerThreadInit[] wti = new WorkerThreadInit[threads]; // 1. preprocessing - compute simulation, animation data, etc. _ = FormSupport.getScene(true, out _, out _, superSampling, TextParam.Text); // Separate renderer, image function and the scene for each thread (safety precaution). for (t = 0; t < threads; t++) { // 2. initialize data for regular frames (using the pre-computed context). IRayScene sc = FormSupport.getScene( false, out IImageFunction imf, out IRenderer rend, superSampling, TextParam.Text); // IImageFunction. imf = getImageFunction(imf, sc); imf.Width = width; imf.Height = height; // IRenderer. if (rend == null) // not defined in the script { rend = getRenderer(); } rend.ImageFunction = imf; rend.Width = width; rend.Height = height; rend.Adaptive = 8; rend.ProgressData = progress; wti[t] = new WorkerThreadInit(rend, sc as ITimeDependent, imf as ITimeDependent, newImage, width, height, t, threads); } progress.SyncInterval = ((width * (long)height) > (2L << 20)) ? 3000L : 1000L; progress.Reset(); CSGInnerNode.ResetStatistics(); lock (sw) sw.Restart(); if (threads > 1) { Thread[] pool = new Thread[threads]; for (t = 0; t < threads; t++) { pool[t] = new Thread(new ParameterizedThreadStart(RenderWorker)); } for (t = threads; --t >= 0;) { pool[t].Start(wti[t]); } for (t = 0; t < threads; t++) { pool[t].Join(); pool[t] = null; } } else { MT.InitThreadData(); wti[0].rend.RenderRectangle(newImage, 0, 0, width, height); } long elapsed; lock (sw) { sw.Stop(); elapsed = sw.ElapsedMilliseconds; } string msg = string.Format(CultureInfo.InvariantCulture, "{0:f1}s [ {1}x{2}, mt{3}, r{4:#,#}k, i{5:#,#}k, bb{6:#,#}k, t{7:#,#}k ]", 1.0e-3 * elapsed, width, height, threads, (Intersection.countRays + 500L) / 1000L, (Intersection.countIntersections + 500L) / 1000L, (CSGInnerNode.countBoundingBoxes + 500L) / 1000L, (CSGInnerNode.countTriangles + 500L) / 1000L); SetText(msg); Console.WriteLine(@"Rendering finished: " + msg); SetImage(newImage); Cursor.Current = Cursors.Default; StopRendering(); }