/// <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 >>= 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> /// Shoots single primary ray only /// </summary> /// <param name="x">X-coordinate inside the raster image</param> /// <param name="y">Y-coordinate inside the raster image</param> private void singleSample(int x, int y) { MT.singleRayTracing = true; rayVisualizer.Reset(); // determine output image size: ActualWidth = ImageWidth; if (ActualWidth <= 0) { ActualWidth = panel1.Width; } ActualHeight = ImageHeight; if (ActualHeight <= 0) { ActualHeight = panel1.Height; } if (dirty || imfs == null) { int ss = 1; // Force preprocess. ctx = null; _ = FormSupport.getScene( out _, out _, ref ActualWidth, ref ActualHeight, ref ss, TextParam.Text); sc = FormSupport.getScene( out imfs, out rend, ref ActualWidth, ref ActualHeight, ref ss, TextParam.Text); // IImageFunction. if (imfs == null) // not defined in the script { imfs = getImageFunction(imfs, sc); } else if (imfs is RayCasting imfray) { imfray.Scene = sc; } imfs.Width = ActualWidth; imfs.Height = ActualHeight; // IRenderer. if (rend == null) // not defined in the script { rend = getRenderer(); } rend.ImageFunction = imfs; rend.Width = ActualWidth; rend.Height = ActualHeight; rend.Adaptive = 0; // 8? rend.ProgressData = progress; dirty = false; } // Set TLS. MT.SetRendering(sc, imfs, rend); double[] color = new double[3]; long hash = imfs.GetSample(x + 0.5, y + 0.5, color); labelSample.Text = string.Format(CultureInfo.InvariantCulture, "Sample at [{0},{1}] = [{2:f},{3:f},{4:f}], {5:X}", x, y, color[0], color[1], color[2], hash); // Reset TLS. MT.ResetRendering(); rayVisualizer.AddingRaysFinished(); MT.singleRayTracing = false; }
/// <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(); }