private static class GLExt { private static class Generic where T : class? { internal delegate T? LoadFunctionDelegate(string function, bool throwIfNotFound = false); internal static readonly LoadFunctionDelegate LoadFunction = typeof(MonoGame.OpenGL.GL).GetStaticMethod("LoadFunction")?. MakeGenericMethod(typeof(T)).CreateDelegate() ?? ((_, _) => null); } [System.Security.SuppressUnmanagedCodeSecurity] [UnmanagedFunctionPointer(CallingConvention.Winapi)] [MonoNativeFunctionWrapper] internal delegate void TexStorage2DDelegate( TextureTarget target, int levels, PixelInternalFormat internalFormat, int width, int height ); internal static readonly TexStorage2DDelegate? TexStorage2D = Generic.LoadFunction("glTexStorage2D"); } private int ConstructLevel( ReadOnlyPinnedSpan data, Vector2I size, int level, int levelSize, bool useSubImage ) { if (glFormat == PixelFormat.CompressedTextureFormats) { if (useSubImage) { MonoGame.OpenGL.GL.CompressedTexSubImage2D( TextureTarget.Texture2D, level, 0, 0, size.X, size.Y, glInternalFormat, levelSize, data[..levelSize].GetIntPointer() ); } else { MonoGame.OpenGL.GL.CompressedTexImage2D( TextureTarget.Texture2D, level, glInternalFormat, size.X, size.Y, 0, levelSize, data[..levelSize].GetIntPointer() ); } } else { if (useSubImage) { MonoGame.OpenGL.GL.TexSubImage2D( TextureTarget.Texture2D, level, 0, 0, size.X, size.Y, glFormat, glType, data[..levelSize].GetIntPointer() ); } else { MonoGame.OpenGL.GL.TexImage2D( TextureTarget.Texture2D, level, glInternalFormat, size.X, size.Y, 0, glFormat, glType, data[..levelSize].GetIntPointer() ); } } GraphicsExtensions.CheckGLError(); return levelSize; } private void Construct( ReadOnlyPinnedSpan.FixedSpan dataIn, Vector2I size, bool mipmap, SurfaceFormat format, SurfaceType type, bool shared ) { glTarget = TextureTarget.Texture2D; format.GetGLFormat(GraphicsDevice, out glInternalFormat, out glFormat, out glType); // Use glTexStorage2D if it's available. // Presently, since we are not yet overriding 'SetData' to use glMeowTexSubImage2D, // only use it if we are populating the texture now bool useSubImage = !dataIn.IsEmpty && GLExt.TexStorage2D is not null; // Calculate the number of texture levels int levels = 1; if (useSubImage) { if (mipmap) { var tempDimensions = size; while (tempDimensions != (1, 1)) { tempDimensions >>= 1; tempDimensions = tempDimensions.Min(1); ++levels; } } } // Mostly taken from MonoGame, but completely refactored. // Returns the size given dimensions, adjusted/aligned for block formats. Func getLevelSize = format switch { // PVRTC has explicit calculations for imageSize // https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt SurfaceFormat.RgbPvrtc2Bpp or SurfaceFormat.RgbaPvrtc2Bpp => static size => { var maxDimensions = size.Max((16, 8)); return ((maxDimensions.X * maxDimensions.Y) << 1 + 7) >> 3; }, SurfaceFormat.RgbPvrtc4Bpp or SurfaceFormat.RgbaPvrtc4Bpp => static size => { var maxDimensions = size.Max((8, 8)); return ((maxDimensions.X * maxDimensions.Y) << 2 + 7) >> 3; }, _ when glFormat == PixelFormat.CompressedTextureFormats => size => { int blockSize = format.GetSize(); var blockDimensions = format.BlockEdge(); var blocks = (size + (blockDimensions - 1)) / blockDimensions; return blocks.X * blocks.Y * blockSize; }, _ => size => (int)format.SizeBytes(size.Area) }; Threading.BlockOnUIThread(() => { ReadOnlyPinnedSpan data = default; if (!dataIn.IsEmpty) { data = dataIn.AsSpan; } GenerateGLTextureIfRequired(); if (!data.IsEmpty) { MonoGame.OpenGL.GL.PixelStore(PixelStoreParameter.UnpackAlignment, Math.Min(Format.GetSize(), 8)); } if (useSubImage) { // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexStorage2D.xhtml // Using glTexStorage and glMeowTexSubImage2D to populate textures is more efficient, // as the way that MonoGame normally does it requires the texture to be kept largely in flux, // and also requires it to be discarded a significant number of times. GLExt.TexStorage2D!( TextureTarget.Texture2D, levels, glInternalFormat, size.Width, size.Height ); } var levelDimensions = size; int level = 0; int currentOffset = 0; // Loop over every level and populate it, starting from the largest. while (true) { currentOffset += ConstructLevel( data: data[currentOffset..], size: levelDimensions, level: level++, levelSize: getLevelSize(levelDimensions), useSubImage: useSubImage ); if (levelDimensions == (1, 1) || !mipmap) break; levelDimensions >>= 1; levelDimensions = levelDimensions.Min(1); ++level; } }); }