public object Invoke(IMethodInvocation invocation) { string assemblyName = invocation.TargetType.Assembly.GetName().Name; string className = invocation.TargetType.Name; string methodName = invocation.Method.Name; if (assemblyName == "IBB360.Socials" && className == "ActivityApi" && methodName == "BulkInsertProfilerMessages") { return(invocation.Proceed()); } PerfProfiler p = new PerfProfiler(); p.OnMethodEnter(); try { return(invocation.Proceed()); } catch { p.OnMethodException(); throw; } finally { string requestUrl = ""; if (HttpContext.Current != null && HttpContext.Current.Request != null && HttpContext.Current.Request.Url != null) { requestUrl = HttpContext.Current.Request.Url.ToString(); } p.OnMethodExit(requestUrl, assemblyName, className, methodName); } }
protected override void CreateInternal(byte[] data) { byte[] pixels = null; var width = 0; var height = 0; var flipped = false; // Whether the image was uploaded flipped - top to bottom. PerfProfiler.ProfilerEventStart("Decoding Image", "Loading"); // Check if PNG. if (PngFormat.IsPng(data)) { pixels = PngFormat.Decode(data, out PngFileHeader header); width = header.Width; height = header.Height; } // Check if BMP. else if (BmpFormat.IsBmp(data)) { pixels = BmpFormat.Decode(data, out BmpFileHeader header); width = header.Width; height = header.Height; flipped = true; } if (pixels == null || width == 0 || height == 0) { Engine.Log.Warning($"Couldn't load texture - {Name}.", MessageSource.AssetLoader); return; } PerfProfiler.ProfilerEventEnd("Decoding Image", "Loading"); UploadTexture(new Vector2(width, height), pixels, flipped); }
/// <summary> /// Set a new state. /// </summary> /// <param name="newState">The state to set.</param> /// <param name="force">Whether to set it regardless of the previous state.</param> public void SetState(RenderState newState, bool force = false) { FlushRenderStream(); RenderState currentState = Engine.Renderer.CurrentState; // Check which state changes should apply, by checking which were set and which differ from the current. PerfProfiler.FrameEventStart("ShaderSet"); if (newState.Shader != null && (force || newState.Shader != currentState.Shader)) { ShaderProgram.EnsureBound(newState.Shader.Pointer); Engine.Renderer.CurrentState.Shader = newState.Shader; Engine.Renderer.SyncShader(); } PerfProfiler.FrameEventEnd("ShaderSet"); PerfProfiler.FrameEventStart("Depth/Stencil/Blend Set"); if (newState.DepthTest != null && (force || newState.DepthTest != currentState.DepthTest)) { SetDepthTest((bool)newState.DepthTest); } if (newState.StencilTest != null && (force || newState.StencilTest != currentState.StencilTest)) { SetStencilTest((bool)newState.StencilTest); } if (newState.SFactorRgb != null && newState.SFactorRgb != currentState.SFactorRgb || newState.DFactorRgb != null && newState.DFactorRgb != currentState.DFactorRgb || newState.SFactorA != null && newState.SFactorA != currentState.SFactorA || newState.DFactorA != null && newState.DFactorA != currentState.DFactorA) { SetAlphaBlendType( newState.SFactorRgb ?? currentState.SFactorRgb !.Value, newState.DFactorRgb ?? currentState.DFactorRgb !.Value, newState.SFactorA ?? currentState.SFactorA !.Value, newState.DFactorA ?? currentState.DFactorA !.Value); } if (newState.AlphaBlending != null && (force || newState.AlphaBlending != currentState.AlphaBlending)) { SetAlphaBlend((bool)newState.AlphaBlending); } PerfProfiler.FrameEventEnd("Depth/Stencil/Blend Set"); PerfProfiler.FrameEventStart("View/Clip Set"); if (force || newState.ViewMatrix != null && newState.ViewMatrix != currentState.ViewMatrix) { SetUseViewMatrix((bool)newState.ViewMatrix); } if (force || newState.ProjectionBehavior != null && newState.ProjectionBehavior != currentState.ProjectionBehavior) { SetProjectionBehavior((ProjectionBehavior)newState.ProjectionBehavior); } if (force || newState.ClipRect != currentState.ClipRect) { SetClipRect(newState.ClipRect); } PerfProfiler.FrameEventEnd("Depth/Stencil/Blend Set"); }
/// <summary> /// Invalidates the current batch - flushing it to the current buffer. /// This should be done when the state changes in some way because calls afterwards will differ from those before and /// cannot be batched. /// </summary> public void InvalidateStateBatches() { if (ActiveQuadBatch == null || ActiveQuadBatch.BatchedSprites == 0) { return; } PerfProfiler.FrameEventStart($"RenderBatch {ActiveQuadBatch.BatchedSprites} Sprites {ActiveQuadBatch.TextureSlotUtilization} Textures"); ActiveQuadBatch.Render(this); ActiveQuadBatch.Recycle(); PerfProfiler.FrameEventEnd($"RenderBatch {ActiveQuadBatch.BatchedSprites} Sprites {ActiveQuadBatch.TextureSlotUtilization} Textures"); }
protected virtual void UploadTexture(Vector2 size, byte[] bgraPixels, bool flipped) { GLThread.ExecuteGLThread(() => { PerfProfiler.ProfilerEventStart($"Uploading Image {Name}", "Loading"); Texture = new Texture(size, bgraPixels) { FlipY = flipped }; PerfProfiler.ProfilerEventEnd($"Uploading Image {Name}", "Loading"); }); }
public void RenderObjects(RenderComposer composer) { // Check if anything is loaded. List <T> objects = QueryObjectsToRender(); PerfProfiler.FrameEventStart("TileMap: Objects"); for (var i = 0; i < objects.Count; i++) { objects[i].Render(composer); } PerfProfiler.FrameEventEnd("TileMap: Objects"); }
protected virtual void UploadTexture(Vector2 size, byte[] pixels, bool flipped, PixelFormat pixelFormat) { Texture = Texture.NonGLThreadInitialize(size); Texture.FlipY = flipped; GLThread.ExecuteGLThreadAsync(() => { PerfProfiler.ProfilerEventStart($"Uploading Image {Name}", "Loading"); Texture.NonGLThreadInitializedCreatePointer(Texture); Texture.Upload(size, pixels, pixelFormat, pixelFormat == PixelFormat.Red ? InternalFormat.Red : null); PerfProfiler.ProfilerEventEnd($"Uploading Image {Name}", "Loading"); #if DEBUG Texture.CreationStack = new string(' ', 3) + Name + new string(' ', 100) + "\n" + Texture.CreationStack; #endif }); }
/// <summary> /// Set whether to use alpha blending. /// This causes transparent objects to blend their colors when drawn on top of each other. /// </summary> /// <param name="alphaBlend">Whether to use alpha blending.</param> public void SetAlphaBlend(bool alphaBlend) { PerfProfiler.FrameEventStart("StateChange: AlphaBlend"); FlushRenderStream(); if (alphaBlend) { Gl.Enable(EnableCap.Blend); Gl.BlendFuncSeparate(CurrentState.SFactorRgb !.Value, CurrentState.DFactorRgb !.Value, CurrentState.SFactorA !.Value, CurrentState.DFactorA !.Value); } else { Gl.Disable(EnableCap.Blend); } Engine.Renderer.CurrentState.AlphaBlending = alphaBlend; PerfProfiler.FrameEventEnd("StateChange: AlphaBlend"); }
/// <summary> /// Set whether to use depth testing. /// </summary> /// <param name="depth">Whether to use depth testing.</param> public void SetDepthTest(bool depth) { PerfProfiler.FrameEventStart("StateChange: DepthTest"); FlushRenderStream(); if (depth) { Gl.Enable(EnableCap.DepthTest); Gl.DepthFunc(DepthFunction.Lequal); } else { Gl.Disable(EnableCap.DepthTest); } Engine.Renderer.CurrentState.DepthTest = depth; PerfProfiler.FrameEventEnd("StateChange: DepthTest"); }
protected override void CreateInternal(ReadOnlyMemory <byte> data) { byte[] pixels = null; Vector2 size = Vector2.Zero; var flipped = false; // Whether the image was uploaded flipped - top to bottom. var format = PixelFormat.Unknown; PerfProfiler.ProfilerEventStart("Decoding Image", "Loading"); // Magic number check to find out format. if (PngFormat.IsPng(data)) { pixels = PngFormat.Decode(data, out PngFileHeader header); size = header.Size; format = header.PixelFormat; } else if (BmpFormat.IsBmp(data)) { pixels = BmpFormat.Decode(data, out BmpFileHeader header); size = new Vector2(header.Width, header.HeaderSize); flipped = true; format = PixelFormat.Bgra; } else if (ImgBinFormat.IsImgBin(data)) { pixels = ImgBinFormat.Decode(data, out ImgBinFileHeader header); size = header.Size; format = header.Format; } if (pixels == null || size.X == 0 || size.Y == 0) { Texture = Texture.EmptyWhiteTexture; Engine.Log.Warning($"Couldn't load texture - {Name}.", MessageSource.AssetLoader); return; } ByteSize = pixels.Length; PerfProfiler.ProfilerEventEnd("Decoding Image", "Loading"); UploadTexture(size, pixels, flipped, format); }
/// <summary> /// Whether, and where to clip. /// </summary> /// <param name="clip">The rectangle to clip outside of.</param> public void SetClipRect(Rectangle?clip) { PerfProfiler.FrameEventStart("StateChange: Clip"); FlushRenderStream(); if (clip == null) { Gl.Disable(EnableCap.ScissorTest); } else { Gl.Enable(EnableCap.ScissorTest); Rectangle c = clip.Value; Gl.Scissor((int)c.X, (int)(Engine.Renderer.CurrentTarget.Viewport.Height - c.Height - c.Y), (int)c.Width, (int)c.Height); } Engine.Renderer.CurrentState.ClipRect = clip; PerfProfiler.FrameEventEnd("StateChange: Clip"); }
public DrawableFontAtlas GetAtlas(float fontSize, uint firstChar = 0, int numChars = -1, bool smooth = true) { int hash = $"{fontSize}-{firstChar}-{numChars}".GetHashCode(); // Check if the atlas is already loaded. bool found = _loadedAtlases.TryGetValue(hash, out DrawableFontAtlas atlas); if (found) { return(atlas); } // Load the atlas manually. PerfProfiler.ProfilerEventStart($"FontAtlas {Name} {fontSize} {hash}", "Loading"); FontAtlas standardAtlas = Font.GetAtlas(fontSize, firstChar, numChars, _rasterizer); atlas = new DrawableFontAtlas(standardAtlas, smooth); PerfProfiler.ProfilerEventEnd($"FontAtlas {Name} {fontSize} {hash}", "Loading"); _loadedAtlases.Add(hash, atlas); return(atlas); }
/// <summary> /// Enable or disable stencil testing. /// When enabling the stencil buffer is cleared. /// </summary> /// <param name="stencil">Whether to enable or disable stencil testing.</param> public void SetStencilTest(bool stencil) { PerfProfiler.FrameEventStart("StateChange: Stencil"); FlushRenderStream(); // Set the stencil test to it's default state - don't write to it. if (stencil) { Gl.Enable(EnableCap.StencilTest); // Clear after enabling. ClearStencil(); StencilStateDefault(); } else { Gl.Disable(EnableCap.StencilTest); StencilStateDefault(); // Some drivers don't understand that off means off } Engine.Renderer.CurrentState.StencilTest = stencil; PerfProfiler.FrameEventEnd("StateChange: Stencil"); }
/// <summary> /// Provides default button behavior for all platforms. /// </summary> protected bool DefaultButtonBehavior(Key key, KeyStatus state) { if (Engine.Configuration.DebugMode) { Engine.Log.Trace($"Key {key} is {state}.", MessageSource.Input); bool ctrl = IsKeyHeld(Key.LeftControl) || IsKeyHeld(Key.RightControl); if (key >= Key.F1 && key <= Key.F10 && state == KeyStatus.Down && ctrl && Window != null) { Vector2 chosenSize = _windowSizes[key - Key.F1]; Window.Size = chosenSize; Engine.Log.Info($"Set window size to {chosenSize}", MessageSource.Platform); return(false); } switch (key) { case Key.F11 when state == KeyStatus.Down && ctrl && Window != null: Window.Size = (Engine.Configuration.RenderSize * 1.999f) - Vector2.One; break; case Key.Pause when state == KeyStatus.Down: PerfProfiler.ProfileNextFrame(); break; } } bool alt = IsKeyHeld(Key.LeftAlt) || IsKeyHeld(Key.RightAlt); if (key == Key.Enter && state == KeyStatus.Down && alt && Window != null) { Window.DisplayMode = Window.DisplayMode == DisplayMode.Fullscreen ? DisplayMode.Windowed : DisplayMode.Fullscreen; } return(true); }
/// <summary> /// Decode the provided png file to a BGRA pixel array, Y flipped. /// </summary> /// <param name="pngData">The png file as bytes.</param> /// <param name="fileHeader">The png file's header.</param> /// <returns>The RGBA pixel data from the png.</returns> public static byte[] Decode(ReadOnlyMemory <byte> pngData, out PngFileHeader fileHeader) { fileHeader = new PngFileHeader(); if (!IsPng(pngData)) { Engine.Log.Warning("Tried to decode a non-png image!", MessageSource.ImagePng); return(null); } using var stream = new ByteReader(pngData); stream.Seek(8, SeekOrigin.Current); // Increment by header bytes. // Read chunks while there are valid chunks. var dataStream = new ReadOnlyLinkedMemoryStream(); PngChunk currentChunk; var endChunkReached = false; ReadOnlyMemory <byte> palette = null, paletteAlpha = null; int width = 0, height = 0; while ((currentChunk = new PngChunk(stream)).Valid) { if (endChunkReached) { Engine.Log.Warning("Image did not end with an end chunk...", MessageSource.ImagePng); continue; } switch (currentChunk.Type) { case PngChunkTypes.HEADER: { ByteReader chunkReader = currentChunk.ChunkReader; width = chunkReader.ReadInt32BE(); height = chunkReader.ReadInt32BE(); fileHeader.Size = new Vector2(width, height); fileHeader.BitDepth = chunkReader.ReadByte(); fileHeader.ColorType = chunkReader.ReadByte(); fileHeader.CompressionMethod = chunkReader.ReadByte(); fileHeader.FilterMethod = chunkReader.ReadByte(); fileHeader.InterlaceMethod = chunkReader.ReadByte(); break; } case PngChunkTypes.DATA: dataStream.AddMemory(currentChunk.ChunkReader.Data); break; case PngChunkTypes.PALETTE: palette = currentChunk.ChunkReader.Data; break; case PngChunkTypes.PALETTE_ALPHA: paletteAlpha = currentChunk.ChunkReader.Data; break; case PngChunkTypes.END: endChunkReached = true; break; } } // Decompress data. PerfProfiler.ProfilerEventStart("PNG Decompression", "Loading"); byte[] data = ZlibStreamUtility.Decompress(dataStream); PerfProfiler.ProfilerEventEnd("PNG Decompression", "Loading"); if (data == null) { return(null); } var channelsPerColor = 0; int bytesPerPixelOutput = 4; ColorReader reader; fileHeader.PixelFormat = PixelFormat.Bgra; // Default. switch (fileHeader.ColorType) { case 0: // Grayscale - No Alpha channelsPerColor = 1; bytesPerPixelOutput = 1; reader = Grayscale; fileHeader.PixelFormat = PixelFormat.Red; break; case 2: // RGB channelsPerColor = 3; reader = Rgb; break; case 3: // Palette channelsPerColor = 1; reader = (rowPixels, row, imageDest, destRow) => { Palette(palette.Span, paletteAlpha.Span, rowPixels, row, imageDest, destRow); }; break; case 4: // Grayscale - Alpha channelsPerColor = 2; reader = GrayscaleAlpha; break; case 6: // RGBA channelsPerColor = 4; reader = Rgba; fileHeader.PixelFormat = PixelFormat.Rgba; break; default: reader = null; break; } // Unknown color mode. if (reader == null) { Engine.Log.Warning($"Unsupported color type - {fileHeader.ColorType}", MessageSource.ImagePng); return(new byte[width * height * bytesPerPixelOutput]); } // Calculate the bytes per pixel. var bytesPerPixel = 1; if (fileHeader.BitDepth == 0 || fileHeader.BitDepth == 3) { Engine.Log.Warning("Invalid bit depth.", MessageSource.ImagePng); return(null); } if (fileHeader.BitDepth != 8) { Engine.Log.Warning("Loading PNGs with a bit depth different than 8 will be deprecated in future versions.", MessageSource.ImagePng); } if (fileHeader.BitDepth >= 8) { bytesPerPixel = channelsPerColor * fileHeader.BitDepth / 8; } // Check interlacing. if (fileHeader.InterlaceMethod == 1) { Engine.Log.Warning("Loading interlaced PNGs will be deprecated in future versions. Convert your images!", MessageSource.ImagePng); return(ParseInterlaced(data, fileHeader, bytesPerPixel, channelsPerColor, reader)); } int scanlineLength = GetScanlineLength(fileHeader, channelsPerColor) + 1; int scanLineCount = data.Length / scanlineLength; return(Parse(scanlineLength, scanLineCount, data, bytesPerPixel, fileHeader, reader, bytesPerPixelOutput)); }
private static byte[] Parse(int scanlineLength, int scanlineCount, byte[] pureData, int bytesPerPixel, PngFileHeader header, ColorReader reader, int bytesPerPixelOutput) { var width = (int)header.Size.X; var height = (int)header.Size.Y; var pixels = new byte[width * height * bytesPerPixelOutput]; int length = scanlineLength - 1; var data = new Span <byte>(pureData); // Analyze if the scanlines can be read in parallel. byte filterMode = data[0]; for (var i = 0; i < scanlineCount; i++) { byte f = data[scanlineLength * i]; if (f == filterMode) { continue; } filterMode = byte.MaxValue; break; } // Multiple filters or a dependency filter are in affect. if (filterMode == byte.MaxValue || filterMode != 0 && filterMode != 1) { if (scanlineCount >= 1500) { Engine.Log.Trace("Loaded a big PNG with scanlines which require filtering. If you re-export it without filters, it will load faster.", MessageSource.ImagePng); } PerfProfiler.ProfilerEventStart("PNG Parse Sequential", "Loading"); var readOffset = 0; var previousScanline = Span <byte> .Empty; for (var i = 0; i < scanlineCount; i++) { // Early out for invalid data. if (data.Length - readOffset < scanlineLength) { break; } Span <byte> scanline = data.Slice(readOffset + 1, length); int filter = data[readOffset]; ApplyFilter(scanline, previousScanline, filter, bytesPerPixel); reader(width, ConvertBitArray(scanline, header), pixels, i); previousScanline = scanline; readOffset += scanlineLength; } PerfProfiler.ProfilerEventEnd("PNG Parse Sequential", "Loading"); return(pixels); } // Single line filter if (filterMode == 1) { PerfProfiler.ProfilerEventStart("PNG Parse Threaded", "Loading"); ParallelWork.FastLoops(scanlineCount, (start, end) => { int readOffset = start * scanlineLength; for (int i = start; i < end; i++) { // Early out for invalid data. if (pureData.Length - readOffset < scanlineLength) { break; } Span <byte> scanline = new Span <byte>(pureData).Slice(readOffset + 1, length); for (int j = bytesPerPixel; j < scanline.Length; j++) { scanline[j] = (byte)(scanline[j] + scanline[j - bytesPerPixel]); } reader(width, ConvertBitArray(scanline, header), pixels, i); readOffset += scanlineLength; } }).Wait(); PerfProfiler.ProfilerEventEnd("PNG Parse Threaded", "Loading"); } // No filter! // ReSharper disable once InvertIf if (filterMode == 0) { PerfProfiler.ProfilerEventStart("PNG Parse Threaded", "Loading"); ParallelWork.FastLoops(scanlineCount, (start, end) => { int readOffset = start * scanlineLength; for (int i = start; i < end; i++) { // Early out for invalid data. if (pureData.Length - readOffset < scanlineLength) { break; } Span <byte> row = ConvertBitArray(new Span <byte>(pureData).Slice(readOffset + 1, length), header); reader(width, row, pixels, i); readOffset += scanlineLength; } }).Wait(); PerfProfiler.ProfilerEventEnd("PNG Parse Threaded", "Loading"); } return(pixels); }
private static byte[] ParseInterlaced(byte[] pureData, PngFileHeader fileHeader, int bytesPerPixel, int channelsPerColor, ColorReader reader) { PerfProfiler.ProfilerEventStart("PNG Parse Interlaced", "Loading"); // Combine interlaced pixels into one image here. var width = (int)fileHeader.Size.X; var height = (int)fileHeader.Size.Y; var combination = new byte[width * height * channelsPerColor]; var pixels = new byte[width * height * 4]; const int passes = 7; var readOffset = 0; var done = false; var data = new Span <byte>(pureData); for (var i = 0; i < passes; i++) { int columns = Adam7.ComputeColumns(width, i); if (columns == 0) { continue; } int scanlineLength = GetScanlineLengthInterlaced(columns, fileHeader, channelsPerColor) + 1; int length = scanlineLength - 1; int pixelsInLine = Adam7.ComputeBlockWidth(width, i); // Read scanlines in this pass. var previousScanline = Span <byte> .Empty; for (int row = Adam7.FirstRow[i]; row < height; row += Adam7.RowIncrement[i]) { // Early out if invalid pass. if (data.Length - readOffset < scanlineLength) { done = true; break; } Span <byte> scanLine = data.Slice(readOffset + 1, length); int filter = data[readOffset]; readOffset += scanlineLength; ApplyFilter(scanLine, previousScanline, filter, bytesPerPixel); // Dump the row into the combined image. Span <byte> convertedLine = ConvertBitArray(scanLine, fileHeader); for (var pixel = 0; pixel < pixelsInLine; pixel++) { int offset = row * bytesPerPixel * width + (Adam7.FirstColumn[i] + pixel * Adam7.ColumnIncrement[i]) * bytesPerPixel; int offsetSrc = pixel * bytesPerPixel; for (var p = 0; p < bytesPerPixel; p++) { combination[offset + p] = convertedLine[offsetSrc + p]; } } previousScanline = scanLine; } if (done) { break; } } // Read the combined image. int stride = width * bytesPerPixel; int scanlineCount = combination.Length / stride; for (var i = 0; i < scanlineCount; i++) { Span <byte> row = new Span <byte>(combination).Slice(stride * i, stride); reader(width, row, pixels, i); } PerfProfiler.ProfilerEventEnd("PNG Parse Interlaced", "Loading"); return(pixels); }
/// <summary> /// Decode the provided png file to a BGRA pixel array, Y flipped. /// </summary> /// <param name="pngData">The png file as bytes.</param> /// <param name="fileHeader">The png file's header.</param> /// <returns>The RGBA pixel data from the png.</returns> public static byte[] Decode(byte[] pngData, out PngFileHeader fileHeader) { fileHeader = new PngFileHeader(); if (!IsPng(pngData)) { Engine.Log.Warning("Tried to decode a non-png image!", MessageSource.ImagePng); return(null); } using var stream = new MemoryStream(pngData); stream.Seek(8, SeekOrigin.Current); var endChunkReached = false; byte[] palette = null; byte[] paletteAlpha = null; using var dataStream = new MemoryStream(); // Read chunks while there are valid chunks. PngChunk currentChunk; while ((currentChunk = new PngChunk(stream)).Valid) { if (endChunkReached) { Engine.Log.Warning("Image did not end with an end chunk...", MessageSource.ImagePng); continue; } switch (currentChunk.Type) { case PngChunkTypes.HEADER: { Array.Reverse(currentChunk.Data, 0, 4); Array.Reverse(currentChunk.Data, 4, 4); fileHeader.Width = BitConverter.ToInt32(currentChunk.Data, 0); fileHeader.Height = BitConverter.ToInt32(currentChunk.Data, 4); fileHeader.BitDepth = currentChunk.Data[8]; fileHeader.ColorType = currentChunk.Data[9]; fileHeader.FilterMethod = currentChunk.Data[11]; fileHeader.InterlaceMethod = currentChunk.Data[12]; fileHeader.CompressionMethod = currentChunk.Data[10]; // Validate header. if (fileHeader.ColorType >= ColorTypes.Length || ColorTypes[fileHeader.ColorType] == null) { Engine.Log.Warning($"Color type '{fileHeader.ColorType}' is not supported or not valid.", MessageSource.ImagePng); return(null); } if (!ColorTypes[fileHeader.ColorType].SupportedBitDepths.Contains(fileHeader.BitDepth)) { Engine.Log.Warning($"Bit depth '{fileHeader.BitDepth}' is not supported or not valid for color type {fileHeader.ColorType}.", MessageSource.ImagePng); } if (fileHeader.FilterMethod != 0) { Engine.Log.Warning("The png specification only defines 0 as filter method.", MessageSource.ImagePng); } break; } case PngChunkTypes.DATA: dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length); break; case PngChunkTypes.PALETTE: palette = currentChunk.Data; break; case PngChunkTypes.PALETTE_ALPHA: paletteAlpha = currentChunk.Data; break; case PngChunkTypes.END: endChunkReached = true; break; } } stream.Dispose(); // Determine color reader to use. PngColorTypeInformation colorTypeInformation = ColorTypes[fileHeader.ColorType]; if (colorTypeInformation == null) { return(null); } IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha); // Calculate the bytes per pixel. var bytesPerPixel = 1; if (fileHeader.BitDepth >= 8) { bytesPerPixel = colorTypeInformation.ChannelsPerColor * fileHeader.BitDepth / 8; } // Decompress data. dataStream.Position = 0; PerfProfiler.ProfilerEventStart("PNG Decompression", "Loading"); byte[] data = ZlibStreamUtility.Decompress(dataStream); PerfProfiler.ProfilerEventEnd("PNG Decompression", "Loading"); // Parse into pixels. var pixels = new byte[fileHeader.Width * fileHeader.Height * 4]; if (fileHeader.InterlaceMethod == 1) { ParseInterlaced(data, fileHeader, bytesPerPixel, colorReader, pixels); } else { Parse(data, fileHeader, bytesPerPixel, colorReader, pixels); } return(pixels); }
private static void Parse(byte[] data, PngFileHeader fileHeader, int bytesPerPixel, IColorReader reader, byte[] pixels) { // Find the scan line length. int scanlineLength = GetScanlineLength(fileHeader.Width, fileHeader) + 1; int scanLineCount = data.Length / scanlineLength; // Run through all scanlines. var cannotParallel = new List <int>(); ParallelWork.FastLoops(scanLineCount, (start, end) => { int readOffset = start * scanlineLength; for (int i = start; i < end; i++) { // Early out for invalid data. if (data.Length - readOffset < scanlineLength) { break; } // Get the current scanline. var rowData = new Span <byte>(data, readOffset + 1, scanlineLength - 1); int filter = data[readOffset]; readOffset += scanlineLength; // Check if it has a filter. // PNG filters require the previous row. // We can't do those in parallel. if (filter != 0) { lock (cannotParallel) { cannotParallel.Add(i); } continue; } reader.ReadScanline(rowData, pixels, fileHeader, i); } }).Wait(); if (cannotParallel.Count == 0) { return; } PerfProfiler.ProfilerEventStart("PNG Parse Sequential", "Loading"); // Run scanlines which couldn't be parallel processed. if (scanLineCount >= 2000) { Engine.Log.Trace("Loaded a big PNG with scanlines which require filtering. If you re-export it without that, it will load faster.", MessageSource.ImagePng); } cannotParallel.Sort(); for (var i = 0; i < cannotParallel.Count; i++) { int idx = cannotParallel[i]; int rowStart = idx * scanlineLength; Span <byte> prevRowData = idx == 0 ? null : new Span <byte>(data, (idx - 1) * scanlineLength + 1, scanlineLength - 1); var rowData = new Span <byte>(data, rowStart + 1, scanlineLength - 1); // Apply filter to the whole row. int filter = data[rowStart]; for (var column = 0; column < rowData.Length; column++) { rowData[column] = ApplyFilter(rowData, prevRowData, filter, column, bytesPerPixel); } reader.ReadScanline(rowData, pixels, fileHeader, idx); } PerfProfiler.ProfilerEventEnd("PNG Parse Sequential", "Loading"); }