public void Initialize() { // *** Perform basic init. Set up the bitmap, cache stride, etc _RenderableChunks = _Config.RenderSubregion ? 0 : _Metrics.NumberOfChunks; try { _OutputMap = new Bitmap((_Config.SubregionChunks.Width + 1) * 16, (_Config.SubregionChunks.Height + 1) * 16, PixelFormat.Format32bppArgb); _PendingRender = true; } catch (OutOfMemoryException) { RenderingErrorEventArgs E = new RenderingErrorEventArgs { ErrorCode = ErrorNoMemory, IsFatal = true, UserErrorMessage = "Failed to allocate dimension bitmap: memory unavailable. Try rendering a smaller area" }; if (RenderError != null) { RenderError.Invoke(this, E); } } catch (Exception Ex) { RenderingErrorEventArgs E = new RenderingErrorEventArgs { ErrorException = Ex, ErrorCode = ErrorNoMemory, ShowInnerException = true, IsFatal = true, UserErrorMessage = "Failed to allocate dimension bitmap." }; if (RenderError != null) { RenderError.Invoke(this, E); } } _RenderTarget = _OutputMap.LockBits(new Rectangle(0, 0, _OutputMap.Width, _OutputMap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); _Stride = _RenderTarget.Stride; }
/// <summary> /// Propagate the Render Error event from root container. /// </summary> protected virtual void OnRenderError(HtmlRenderErrorEventArgs e) { RenderError?.Invoke(this, e); }
// ReSharper disable once FunctionComplexityOverflow // *** I do ^ because this function is the 'hottest' block of code in the program, and saving cycles during execution is ridiculously important here. void RenderChunk(ChunkRef Chunk, ParallelLoopState LoopState) { // *** Track how many chunks have been processed, for user feedback Interlocked.Increment(ref _ProcessedChunks); Interlocked.Increment(ref _ActiveRenderThreads); #if !DEBUG && !FAST // *** In release mode, gracefully handle bad chunks. Explode in debug mode so I can track down the issue. try { #endif int[][] DepthOpacities = _ColourPalette.DepthOpacities; // *** Cancellation logic for parallel processing if (LoopState != null && _Cancellation.IsCancellationRequested) { LoopState.Stop(); } if (LoopState != null && LoopState.IsStopped) { Interlocked.Decrement(ref _ActiveRenderThreads); return; } // *** Hold off on rendering if the user needs to attend to an issue while (_PauseRendering > 0) { Thread.Sleep(50); } // *** Load the chunk from disk here AlphaBlockCollection Blocks = Chunk.Blocks; for (int X = 0; X < 16; X++) { for (int Z = 0; Z < 16; Z++) { // *** Start by finding the topmost block to render int EndY = _RenderStartY(Blocks, X, Z); int Y = EndY; if (Y < 0) { continue; // *** No valid renderable blocks in this column, so continue with the next column } // *** Drill into the column to determine how many blocks down to render int RenderVal = 255; while (RenderVal > 0) { RenderVal -= DepthOpacities[Blocks.GetID(X, Y, Z)][Blocks.GetData(X, Y, Z)]; if (Y == 0) // *** If we've hit the bottom of the map, don't try and keep going. { break; // *** It wouldn't end well. } Y--; } Colour SetColour = Colour.Transparent; // *** What colour to set the current column's pixel to. // *** The Block-Metadata palette for this column's biome Colour[][] BiomePalette = _ColourPalette.FastPalette[Chunk.Biomes.GetBiome(X, Z)]; for (; Y <= EndY; Y++) // *** Now render up from the lowest block to the starting block { // *** For each block we render, grab its palette entry. Colour Entry = BiomePalette[Blocks.GetID(X, Y, Z)][Blocks.GetData(X, Y, Z)]; // *** If it has an associated entity colours list, then it needs special consideration to get its colour if ((Entry.Color & 0xFFFF0000U) == 0x00FF0000U) // *** Check for the flag value (0 Alpha, 255 Red - Blue and Green form the 0-65535 index) { PaletteEntry Entry2 = _ColourPalette.GetPaletteEntry((int)(Entry.Color & 0x0000FFFFU)).First(e => e.IsMatch(Blocks.GetData(X, Y, Z), Blocks.SafeGetTileEntity(X, Y, Z))); if (Entry2 != null) { Entry = Entry2.Color; } } if (Entry.A == 0) { continue; // *** If we're trying to render air, let's not. } // *** Blend in our working colour to the column's pixel, after applying altitude and light-level blends. SetColour.Blend(Entry.Copy().LightLevel((uint)Math.Max(_Config.MinLightLevel, Blocks.GetBlockLight(X, Math.Min(Y + 1, 255), Z))).Altitude(Y)); } Marshal.WriteInt32(_RenderTarget.Scan0 + (_Stride * (((Chunk.Z - _Config.SubregionChunks.Y) << 4) + Z)) + ((((Chunk.X - _Config.SubregionChunks.X) << 4) + X) << 2), (int)SetColour.FullAlpha().Color); } } #if !DEBUG && !FAST // *** When not running in debug mode, chunks that fail to render should NOT crash everything. } catch (Exception Ex) { Interlocked.Increment(ref _PauseRendering); _CorruptChunks = true; RenderingErrorEventArgs E = new RenderingErrorEventArgs { ErrorException = Ex, IsFatal = false, UserErrorMessage = "A chunk failed to render", ErrorCode = ErrorBadChunk }; if (RenderError != null) { RenderError.Invoke(this, E); } Interlocked.Decrement(ref _PauseRendering); } #endif Interlocked.Decrement(ref _ActiveRenderThreads); }
// *** Rendering Functions public void Render() { // *** Define the chunk query, in order to cleanly support subregions IEnumerable <ChunkRef> ChunkProvider = from ChunkRef Chunk in _Chunks where !_Config.RenderSubregion || _Config.SubregionChunks.ContainsPoint(Chunk.X, Chunk.Z) select Chunk; _PendingRender = false; // *** If rendering a subregion, then wipe the _RenderableChunks var and recalculate it from the number of chunks in the subregion, not the world if (_Config.RenderSubregion) { _RenderableChunks = ChunkProvider.Count(); } // *** If rendering was cancelled between init and now, abort. if (_Cancellation.IsCancellationRequested) { _OutputMap.Dispose(); _OutputMap = null; return; } // *** Render if (_Config.EnableMultithreading) { Thread UpdateThread = new Thread(DisplayProgress); UpdateThread.Start(); _RenderingParallelOptions.CancellationToken = _Cancellation.Token; _RenderingParallelOptions.MaxDegreeOfParallelism = _Config.MaxThreads; try { Parallel.ForEach(ChunkProvider, _RenderingParallelOptions, RenderChunk); UpdateThread.Join(); } catch (OperationCanceledException) { // Discard exception - this just means the user cancelled. } } else { foreach (ChunkRef Chunk in ChunkProvider) { RenderChunk(Chunk, null); if (_Cancellation.IsCancellationRequested) { break; } DoProgressUpdate(); } } _OutputMap.UnlockBits(_RenderTarget); if (_Cancellation.IsCancellationRequested) { _OutputMap.Dispose(); _OutputMap = null; return; } // *** If this is not a preview, then make sure the output directory exists then save the output bitmap if (!_Config.IsPreview) { // ReSharper disable once AssignNullToNotNullAttribute Directory.CreateDirectory(Path.GetDirectoryName(_Config.SaveFilename)); _OutputMap.Save(_Config.SaveFilename); _OutputMap.Dispose(); _OutputMap = null; } // *** If a chunk failed to render, let the user know. Unless we aborted, because there's no point then. if (_CorruptChunks) { RenderingErrorEventArgs E = new RenderingErrorEventArgs { ErrorCode = ErrorErrors, IsFatal = true, UserErrorMessage = "WARNING: At least one potentially corrupt chunk was encountered during rendering.\r\nThe map image may contain missing sections as a result." }; if (RenderError != null) { RenderError.Invoke(this, E); } } }