Files
LuaCsForBarotraumaEP/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/Texture2D.OpenGL.cs
2019-11-21 18:22:25 +01:00

558 lines
22 KiB
C#

// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.IO;
using System.Runtime.InteropServices;
using MonoGame.Utilities;
using MonoGame.Utilities.Png;
#if IOS
using UIKit;
using CoreGraphics;
using Foundation;
using System.Drawing;
#endif
#if OPENGL
using MonoGame.OpenGL;
using GLPixelFormat = MonoGame.OpenGL.PixelFormat;
using PixelFormat = MonoGame.OpenGL.PixelFormat;
#if ANDROID
using Android.Graphics;
#endif
#endif // OPENGL
namespace Microsoft.Xna.Framework.Graphics
{
public partial class Texture2D : Texture
{
private void PlatformConstruct(int width, int height, bool mipmap, SurfaceFormat format, SurfaceType type, bool shared)
{
this.glTarget = TextureTarget.Texture2D;
format.GetGLFormat(GraphicsDevice, out glInternalFormat, out glFormat, out glType);
Threading.BlockOnUIThread(() =>
{
GenerateGLTextureIfRequired();
int w = width;
int h = height;
int level = 0;
while (true)
{
if (glFormat == (PixelFormat)GLPixelFormat.CompressedTextureFormats)
{
int imageSize = 0;
// PVRTC has explicit calculations for imageSize
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
if (format == SurfaceFormat.RgbPvrtc2Bpp || format == SurfaceFormat.RgbaPvrtc2Bpp)
{
imageSize = (Math.Max(w, 16) * Math.Max(h, 8) * 2 + 7) / 8;
}
else if (format == SurfaceFormat.RgbPvrtc4Bpp || format == SurfaceFormat.RgbaPvrtc4Bpp)
{
imageSize = (Math.Max(w, 8) * Math.Max(h, 8) * 4 + 7) / 8;
}
else
{
int blockSize = format.GetSize();
int blockWidth, blockHeight;
format.GetBlockSize(out blockWidth, out blockHeight);
int wBlocks = (w + (blockWidth - 1)) / blockWidth;
int hBlocks = (h + (blockHeight - 1)) / blockHeight;
imageSize = wBlocks * hBlocks * blockSize;
}
GL.CompressedTexImage2D(TextureTarget.Texture2D, level, glInternalFormat, w, h, 0, imageSize, IntPtr.Zero);
GraphicsExtensions.CheckGLError();
}
else
{
GL.TexImage2D(TextureTarget.Texture2D, level, glInternalFormat, w, h, 0, glFormat, glType, IntPtr.Zero);
GraphicsExtensions.CheckGLError();
}
if ((w == 1 && h == 1) || !mipmap)
break;
if (w > 1)
w = w / 2;
if (h > 1)
h = h / 2;
++level;
}
});
}
private void PlatformSetData<T>(int level, T[] data, int startIndex, int elementCount) where T : struct
{
int w, h;
GetSizeForLevel(Width, Height, level, out w, out h);
Threading.BlockOnUIThread(() =>
{
var elementSizeInByte = ReflectionHelpers.SizeOf<T>.Get();
var dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
// Use try..finally to make sure dataHandle is freed in case of an error
try
{
var startBytes = startIndex * elementSizeInByte;
var dataPtr = new IntPtr(dataHandle.AddrOfPinnedObject().ToInt64() + startBytes);
// Store the current bound texture.
var prevTexture = GraphicsExtensions.GetBoundTexture2D();
if (prevTexture != glTexture)
{
GL.BindTexture(TextureTarget.Texture2D, glTexture);
GraphicsExtensions.CheckGLError();
}
GenerateGLTextureIfRequired();
GL.PixelStore(PixelStoreParameter.UnpackAlignment, Math.Min(_format.GetSize(), 8));
if (glFormat == (PixelFormat)GLPixelFormat.CompressedTextureFormats)
{
GL.CompressedTexImage2D(TextureTarget.Texture2D, level, glInternalFormat, w, h, 0, elementCount * elementSizeInByte, dataPtr);
}
else
{
GL.TexImage2D(TextureTarget.Texture2D, level, glInternalFormat, w, h, 0, glFormat, glType, dataPtr);
}
GraphicsExtensions.CheckGLError();
if (CurrentPlatform.OS != OS.MacOSX)
{
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
GraphicsExtensions.CheckGLError();
}
#if !ANDROID
// Required to make sure that any texture uploads on a thread are completed
// before the main thread tries to use the texture.
GL.Finish();
GraphicsExtensions.CheckGLError();
#endif
// Restore the bound texture.
if (prevTexture != glTexture)
{
GL.BindTexture(TextureTarget.Texture2D, prevTexture);
GraphicsExtensions.CheckGLError();
}
}
finally
{
dataHandle.Free();
}
});
}
private void PlatformSetData<T>(int level, int arraySlice, Rectangle rect, T[] data, int startIndex, int elementCount) where T : struct
{
Threading.BlockOnUIThread(() =>
{
var elementSizeInByte = ReflectionHelpers.SizeOf<T>.Get();
var dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
// Use try..finally to make sure dataHandle is freed in case of an error
try
{
var startBytes = startIndex * elementSizeInByte;
var dataPtr = new IntPtr(dataHandle.AddrOfPinnedObject().ToInt64() + startBytes);
// Store the current bound texture.
var prevTexture = GraphicsExtensions.GetBoundTexture2D();
if (prevTexture != glTexture)
{
GL.BindTexture(TextureTarget.Texture2D, glTexture);
GraphicsExtensions.CheckGLError();
}
GenerateGLTextureIfRequired();
GL.PixelStore(PixelStoreParameter.UnpackAlignment, Math.Min(_format.GetSize(), 8));
if (glFormat == (PixelFormat)GLPixelFormat.CompressedTextureFormats)
{
GL.CompressedTexSubImage2D(TextureTarget.Texture2D, level, rect.X, rect.Y, rect.Width, rect.Height,
(PixelInternalFormat)glInternalFormat, elementCount * elementSizeInByte, dataPtr);
}
else
{
GL.TexSubImage2D(TextureTarget.Texture2D, level, rect.X, rect.Y,
rect.Width, rect.Height, glFormat, glType, dataPtr);
}
GraphicsExtensions.CheckGLError();
#if !ANDROID
// Required to make sure that any texture uploads on a thread are completed
// before the main thread tries to use the texture.
GL.Finish();
GraphicsExtensions.CheckGLError();
#endif
// Restore the bound texture.
if (prevTexture != glTexture)
{
GL.BindTexture(TextureTarget.Texture2D, prevTexture);
GraphicsExtensions.CheckGLError();
}
}
finally
{
dataHandle.Free();
}
});
}
private void PlatformGetData<T>(int level, int arraySlice, Rectangle rect, T[] data, int startIndex, int elementCount) where T : struct
{
Threading.EnsureUIThread();
#if GLES
// TODO: check for for non renderable formats (formats that can't be attached to FBO)
var framebufferId = 0;
GL.GenFramebuffers(1, out framebufferId);
GraphicsExtensions.CheckGLError();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebufferId);
GraphicsExtensions.CheckGLError();
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, this.glTexture, 0);
GraphicsExtensions.CheckGLError();
GL.ReadPixels(rect.X, rect.Y, rect.Width, rect.Height, this.glFormat, this.glType, data);
GraphicsExtensions.CheckGLError();
GraphicsDevice.DisposeFramebuffer(framebufferId);
#else
var tSizeInByte = ReflectionHelpers.SizeOf<T>.Get();
GL.BindTexture(TextureTarget.Texture2D, this.glTexture);
GL.PixelStore(PixelStoreParameter.PackAlignment, Math.Min(tSizeInByte, 8));
if (glFormat == (PixelFormat) GLPixelFormat.CompressedTextureFormats)
{
// Note: for compressed format Format.GetSize() returns the size of a 4x4 block
var pixelToT = Format.GetSize() / tSizeInByte;
var tFullWidth = Math.Max(this.width >> level, 1) / 4 * pixelToT;
var temp = new T[Math.Max(this.height >> level, 1) / 4 * tFullWidth];
GL.GetCompressedTexImage(TextureTarget.Texture2D, level, temp);
GraphicsExtensions.CheckGLError();
var rowCount = rect.Height / 4;
var tRectWidth = rect.Width / 4 * Format.GetSize() / tSizeInByte;
for (var r = 0; r < rowCount; r++)
{
var tempStart = rect.X / 4 * pixelToT + (rect.Top / 4 + r) * tFullWidth;
var dataStart = startIndex + r * tRectWidth;
Array.Copy(temp, tempStart, data, dataStart, tRectWidth);
}
}
else
{
// we need to convert from our format size to the size of T here
var tFullWidth = Math.Max(this.width >> level, 1) * Format.GetSize() / tSizeInByte;
var temp = new T[Math.Max(this.height >> level, 1) * tFullWidth];
GL.GetTexImage(TextureTarget.Texture2D, level, glFormat, glType, temp);
GraphicsExtensions.CheckGLError();
var pixelToT = Format.GetSize() / tSizeInByte;
var rowCount = rect.Height;
var tRectWidth = rect.Width * pixelToT;
for (var r = 0; r < rowCount; r++)
{
var tempStart = rect.X * pixelToT + (r + rect.Top) * tFullWidth;
var dataStart = startIndex + r * tRectWidth;
Array.Copy(temp, tempStart, data, dataStart, tRectWidth);
}
}
#endif
}
private unsafe static Texture2D PlatformFromStream(GraphicsDevice graphicsDevice, Stream stream)
{
int width, height, channels;
// The data returned is always four channel BGRA
var data = ImageReader.Read(stream, out width, out height, out channels, Imaging.STBI_rgb_alpha);
// XNA blacks out any pixels with an alpha of zero.
if (channels == 4)
{
fixed (byte* b = &data[0])
{
for (var i = 0; i < data.Length; i += 4)
{
if (b[i + 3] == 0)
{
b[i + 0] = 0;
b[i + 1] = 0;
b[i + 2] = 0;
}
}
}
}
Texture2D texture = null;
texture = new Texture2D(graphicsDevice, width, height);
texture.SetData(data);
return texture;
}
#if IOS
[CLSCompliant(false)]
public static Texture2D FromStream(GraphicsDevice graphicsDevice, UIImage uiImage)
{
return PlatformFromStream(graphicsDevice, uiImage.CGImage);
}
#elif ANDROID
[CLSCompliant(false)]
public static Texture2D FromStream(GraphicsDevice graphicsDevice, Bitmap bitmap)
{
return PlatformFromStream(graphicsDevice, bitmap);
}
[CLSCompliant(false)]
public void Reload(Bitmap image)
{
var width = image.Width;
var height = image.Height;
int[] pixels = new int[width * height];
if ((width != image.Width) || (height != image.Height))
{
using (Bitmap imagePadded = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888))
{
Canvas canvas = new Canvas(imagePadded);
canvas.DrawARGB(0, 0, 0, 0);
canvas.DrawBitmap(image, 0, 0, null);
imagePadded.GetPixels(pixels, 0, width, 0, 0, width, height);
imagePadded.Recycle();
}
}
else
{
image.GetPixels(pixels, 0, width, 0, 0, width, height);
}
image.Recycle();
this.SetData<int>(pixels);
}
#endif
#if IOS
private static Texture2D PlatformFromStream(GraphicsDevice graphicsDevice, CGImage cgImage)
{
var width = cgImage.Width;
var height = cgImage.Height;
var data = new byte[width * height * 4];
var colorSpace = CGColorSpace.CreateDeviceRGB();
var bitmapContext = new CGBitmapContext(data, width, height, 8, width * 4, colorSpace, CGBitmapFlags.PremultipliedLast);
bitmapContext.DrawImage(new RectangleF(0, 0, width, height), cgImage);
bitmapContext.Dispose();
colorSpace.Dispose();
Texture2D texture = null;
Threading.BlockOnUIThread(() =>
{
texture = new Texture2D(graphicsDevice, (int)width, (int)height, false, SurfaceFormat.Color);
texture.SetData(data);
});
return texture;
}
#elif ANDROID
private static Texture2D PlatformFromStream(GraphicsDevice graphicsDevice, Bitmap image)
{
var width = image.Width;
var height = image.Height;
int[] pixels = new int[width * height];
if ((width != image.Width) || (height != image.Height))
{
using (Bitmap imagePadded = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888))
{
Canvas canvas = new Canvas(imagePadded);
canvas.DrawARGB(0, 0, 0, 0);
canvas.DrawBitmap(image, 0, 0, null);
imagePadded.GetPixels(pixels, 0, width, 0, 0, width, height);
imagePadded.Recycle();
}
}
else
{
image.GetPixels(pixels, 0, width, 0, 0, width, height);
}
image.Recycle();
// Convert from ARGB to ABGR
ConvertToABGR(height, width, pixels);
Texture2D texture = null;
Threading.BlockOnUIThread(() =>
{
texture = new Texture2D(graphicsDevice, width, height, false, SurfaceFormat.Color);
texture.SetData<int>(pixels);
});
return texture;
}
#endif
private void FillTextureFromStream(Stream stream)
{
#if ANDROID
using (Bitmap image = BitmapFactory.DecodeStream(stream, null, new BitmapFactory.Options
{
InScaled = false,
InDither = false,
InJustDecodeBounds = false,
InPurgeable = true,
InInputShareable = true,
}))
{
var width = image.Width;
var height = image.Height;
int[] pixels = new int[width * height];
image.GetPixels(pixels, 0, width, 0, 0, width, height);
// Convert from ARGB to ABGR
ConvertToABGR(height, width, pixels);
this.SetData<int>(pixels);
image.Recycle();
}
#endif
}
private void PlatformSaveAsJpeg(Stream stream, int width, int height)
{
#if DESKTOPGL
SaveAsImage(stream, width, height, ImageWriterFormat.Jpg);
#elif ANDROID
SaveAsImage(stream, width, height, Bitmap.CompressFormat.Jpeg);
#else
throw new NotImplementedException();
#endif
}
private void PlatformSaveAsPng(Stream stream, int width, int height)
{
#if DESKTOPGL
SaveAsImage(stream, width, height, ImageWriterFormat.Png);
#elif ANDROID
SaveAsImage(stream, width, height, Bitmap.CompressFormat.Png);
#else
var pngWriter = new PngWriter();
pngWriter.Write(this, stream);
#endif
}
#if DESKTOPGL
internal void SaveAsImage(Stream stream, int width, int height, ImageWriterFormat format)
{
if (stream == null)
{
throw new ArgumentNullException("stream", "'stream' cannot be null (Nothing in Visual Basic)");
}
if (width <= 0)
{
throw new ArgumentOutOfRangeException("width", width, "'width' cannot be less than or equal to zero");
}
if (height <= 0)
{
throw new ArgumentOutOfRangeException("height", height, "'height' cannot be less than or equal to zero");
}
byte[] data = null;
try
{
data = new byte[width * height * 4];
GetData(data);
var writer = new ImageWriter();
writer.Write(data, width, height, 4, format, stream);
}
finally
{
if (data != null)
{
data = null;
}
}
}
#elif ANDROID
private void SaveAsImage(Stream stream, int width, int height, Bitmap.CompressFormat format)
{
int[] data = new int[width * height];
GetData(data);
// internal structure is BGR while bitmap expects RGB
for (int i = 0; i < data.Length; ++i)
{
uint pixel = (uint)data[i];
data[i] = (int)((pixel & 0xFF00FF00) | ((pixel & 0x00FF0000) >> 16) | ((pixel & 0x000000FF) << 16));
}
using (Bitmap bitmap = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888))
{
bitmap.SetPixels(data, 0, width, 0, 0, width, height);
bitmap.Compress(format, 100, stream);
bitmap.Recycle();
}
}
#endif
// This method allows games that use Texture2D.FromStream
// to reload their textures after the GL context is lost.
private void PlatformReload(Stream textureStream)
{
var prev = GraphicsExtensions.GetBoundTexture2D();
GenerateGLTextureIfRequired();
FillTextureFromStream(textureStream);
GL.BindTexture(TextureTarget.Texture2D, prev);
}
private void GenerateGLTextureIfRequired()
{
if (this.glTexture < 0)
{
GL.GenTextures(1, out this.glTexture);
GraphicsExtensions.CheckGLError();
// For best compatibility and to keep the default wrap mode of XNA, only set ClampToEdge if either
// dimension is not a power of two.
var wrap = TextureWrapMode.Repeat;
if (((width & (width - 1)) != 0) || ((height & (height - 1)) != 0))
wrap = TextureWrapMode.ClampToEdge;
GL.BindTexture(TextureTarget.Texture2D, this.glTexture);
GraphicsExtensions.CheckGLError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
(_levelCount > 1) ? (int)TextureMinFilter.LinearMipmapLinear : (int)TextureMinFilter.Linear);
GraphicsExtensions.CheckGLError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
(int)TextureMagFilter.Linear);
GraphicsExtensions.CheckGLError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)wrap);
GraphicsExtensions.CheckGLError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)wrap);
GraphicsExtensions.CheckGLError();
// Set mipmap levels
#if !GLES
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0);
#endif
GraphicsExtensions.CheckGLError();
if (GraphicsDevice.GraphicsCapabilities.SupportsTextureMaxLevel)
{
if (_levelCount > 0)
{
GL.TexParameter(TextureTarget.Texture2D, SamplerState.TextureParameterNameTextureMaxLevel, _levelCount - 1);
}
else
{
GL.TexParameter(TextureTarget.Texture2D, SamplerState.TextureParameterNameTextureMaxLevel, 1000);
}
GraphicsExtensions.CheckGLError();
}
}
}
}
}