public static unsafe Texture2D CreateFromStreamUnsynchronized(RenderContext renderContext, Stream stream) { using var image = Image.Load <Rgba32>(stream); image.Mutate(x => x.Flip(FlipMode.Vertical)); var texture = new Texture2D(renderContext) { Width = image.Width, Height = image.Height, }; var span = image.GetPixelSpan(); int size = span.Length * sizeof(Rgba32); fixed(void *data = &MemoryMarshal.GetReference(span)) { var start = new IntPtr(data); using var pixelBuffer = PixelBuffer.Create <Rgba32>(renderContext, size, true, true); GLHandle prevPixelBuffer = (GLHandle)GL.GetInteger(GetPName.PixelUnpackBufferBinding); GL.BindBuffer(BufferTarget.PixelUnpackBuffer, pixelBuffer.Handle); // When we load assets asynchronously, using SubData to set the data will cause the main thread // to wait for the GL call to complete, so we use a PBO and fill it with a mapped memory range to // prevent OpenGL synchronization from causing frame drops on the main thread. IntPtr pixelPointer = GL.MapBufferRange(BufferTarget.PixelUnpackBuffer, IntPtr.Zero, size, BufferAccessMask.MapWriteBit | BufferAccessMask.MapUnsynchronizedBit | BufferAccessMask.MapInvalidateRangeBit); if (pixelPointer == IntPtr.Zero) { throw new GraphicsContextException("Could not map PixelUnbackBuffer range."); } // We send the data to mapped memory in multiple batches instead of one large one for the same // reason. int remaining = size; const int step = 2048; while (remaining > 0) { int currentStep = Math.Min(remaining, step); int point = size - remaining; void *dest = (void *)IntPtr.Add(pixelPointer, point); void *src = (void *)IntPtr.Add(start, point); Unsafe.CopyBlock(dest, src, (uint)currentStep); remaining -= step; } GL.UnmapBuffer(BufferTarget.PixelUnpackBuffer); if (GLInfo.HasDirectStateAccess) { GL.TextureStorage2D(texture.Handle, 1, SizedInternalFormat.Rgba8, texture.Width, texture.Height); GL.TextureSubImage2D(texture.Handle, 0, 0, 0, texture.Width, texture.Height, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); GL.GenerateTextureMipmap(texture.Handle); } else { var previousTexture = renderContext.TextureUnits[0].Texture2D; renderContext.BindTexture(0, texture); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, texture.Width, texture.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); renderContext.BindTexture(0, previousTexture); } GL.BindBuffer(BufferTarget.PixelUnpackBuffer, prevPixelBuffer); } texture.SetDefaultTextureParameters(renderContext); // TODO: Look into if this is necessary (?) GL.Finish(); return(texture); }