using OpenTK.Graphics.OpenGL; using Ryujinx.Graphics.Texture; using System; namespace Ryujinx.Graphics.Gal.OpenGL { class OglTexture : IGalTexture { private const long MaxTextureCacheSize = 768 * 1024 * 1024; private OglCachedResource _textureCache; public EventHandler TextureDeleted { get; set; } public OglTexture() { _textureCache = new OglCachedResource(DeleteTexture, MaxTextureCacheSize); } public void LockCache() { _textureCache.Lock(); } public void UnlockCache() { _textureCache.Unlock(); } private void DeleteTexture(ImageHandler cachedImage) { TextureDeleted?.Invoke(this, cachedImage.Handle); GL.DeleteTexture(cachedImage.Handle); } public void Create(long key, int size, GalImage image) { int handle = GL.GenTexture(); TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); GL.BindTexture(target, handle); const int level = 0; //TODO: Support mipmap textures. const int border = 0; _textureCache.AddOrUpdate(key, new ImageHandler(handle, image), (uint)size); if (ImageUtils.IsCompressed(image.Format)) { throw new InvalidOperationException("Surfaces with compressed formats are not supported!"); } (PixelInternalFormat internalFmt, PixelFormat format, PixelType type) = OglEnumConverter.GetImageFormat(image.Format); switch (target) { case TextureTarget.Texture1D: GL.TexImage1D( target, level, internalFmt, image.Width, border, format, type, IntPtr.Zero); break; case TextureTarget.Texture2D: GL.TexImage2D( target, level, internalFmt, image.Width, image.Height, border, format, type, IntPtr.Zero); break; case TextureTarget.Texture3D: GL.TexImage3D( target, level, internalFmt, image.Width, image.Height, image.Depth, border, format, type, IntPtr.Zero); break; // Cube map arrays are just 2D texture arrays with 6 entries // per cube map so we can handle them in the same way case TextureTarget.TextureCubeMapArray: case TextureTarget.Texture2DArray: GL.TexImage3D( target, level, internalFmt, image.Width, image.Height, image.LayerCount, border, format, type, IntPtr.Zero); break; default: throw new NotImplementedException($"Unsupported texture target type: {target}"); } } public void Create(long key, byte[] data, GalImage image) { int handle = GL.GenTexture(); TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); GL.BindTexture(target, handle); const int level = 0; //TODO: Support mipmap textures. const int border = 0; _textureCache.AddOrUpdate(key, new ImageHandler(handle, image), (uint)data.Length); if (ImageUtils.IsCompressed(image.Format) && !IsAstc(image.Format)) { InternalFormat internalFmt = OglEnumConverter.GetCompressedImageFormat(image.Format); switch (target) { case TextureTarget.Texture1D: GL.CompressedTexImage1D( target, level, internalFmt, image.Width, border, data.Length, data); break; case TextureTarget.Texture2D: GL.CompressedTexImage2D( target, level, internalFmt, image.Width, image.Height, border, data.Length, data); break; case TextureTarget.Texture3D: GL.CompressedTexImage3D( target, level, internalFmt, image.Width, image.Height, image.Depth, border, data.Length, data); break; // Cube map arrays are just 2D texture arrays with 6 entries // per cube map so we can handle them in the same way case TextureTarget.TextureCubeMapArray: case TextureTarget.Texture2DArray: GL.CompressedTexImage3D( target, level, internalFmt, image.Width, image.Height, image.LayerCount, border, data.Length, data); break; case TextureTarget.TextureCubeMap: Span array = new Span(data); int faceSize = ImageUtils.GetSize(image) / 6; for (int Face = 0; Face < 6; Face++) { GL.CompressedTexImage2D( TextureTarget.TextureCubeMapPositiveX + Face, level, internalFmt, image.Width, image.Height, border, faceSize, array.Slice(Face * faceSize, faceSize).ToArray()); } break; default: throw new NotImplementedException($"Unsupported texture target type: {target}"); } } else { // TODO: Use KHR_texture_compression_astc_hdr when available if (IsAstc(image.Format)) { int textureBlockWidth = ImageUtils.GetBlockWidth(image.Format); int textureBlockHeight = ImageUtils.GetBlockHeight(image.Format); int textureBlockDepth = ImageUtils.GetBlockDepth(image.Format); data = AstcDecoder.DecodeToRgba8888( data, textureBlockWidth, textureBlockHeight, textureBlockDepth, image.Width, image.Height, image.Depth); image.Format = GalImageFormat.Rgba8 | (image.Format & GalImageFormat.TypeMask); } (PixelInternalFormat internalFmt, PixelFormat format, PixelType type) = OglEnumConverter.GetImageFormat(image.Format); switch (target) { case TextureTarget.Texture1D: GL.TexImage1D( target, level, internalFmt, image.Width, border, format, type, data); break; case TextureTarget.Texture2D: GL.TexImage2D( target, level, internalFmt, image.Width, image.Height, border, format, type, data); break; case TextureTarget.Texture3D: GL.TexImage3D( target, level, internalFmt, image.Width, image.Height, image.Depth, border, format, type, data); break; // Cube map arrays are just 2D texture arrays with 6 entries // per cube map so we can handle them in the same way case TextureTarget.TextureCubeMapArray: case TextureTarget.Texture2DArray: GL.TexImage3D( target, level, internalFmt, image.Width, image.Height, image.LayerCount, border, format, type, data); break; case TextureTarget.TextureCubeMap: Span array = new Span(data); int faceSize = ImageUtils.GetSize(image) / 6; for (int face = 0; face < 6; face++) { GL.TexImage2D( TextureTarget.TextureCubeMapPositiveX + face, level, internalFmt, image.Width, image.Height, border, format, type, array.Slice(face * faceSize, faceSize).ToArray()); } break; default: throw new NotImplementedException($"Unsupported texture target type: {target}"); } } } private static bool IsAstc(GalImageFormat format) { format &= GalImageFormat.FormatMask; return format > GalImageFormat.Astc2DStart && format < GalImageFormat.Astc2DEnd; } public bool TryGetImage(long key, out GalImage image) { if (_textureCache.TryGetValue(key, out ImageHandler cachedImage)) { image = cachedImage.Image; return true; } image = default(GalImage); return false; } public bool TryGetImageHandler(long key, out ImageHandler cachedImage) { if (_textureCache.TryGetValue(key, out cachedImage)) { return true; } cachedImage = null; return false; } public void Bind(long key, int index, GalImage image) { if (_textureCache.TryGetValue(key, out ImageHandler cachedImage)) { GL.ActiveTexture(TextureUnit.Texture0 + index); TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); GL.BindTexture(target, cachedImage.Handle); int[] swizzleRgba = new int[] { (int)OglEnumConverter.GetTextureSwizzle(image.XSource), (int)OglEnumConverter.GetTextureSwizzle(image.YSource), (int)OglEnumConverter.GetTextureSwizzle(image.ZSource), (int)OglEnumConverter.GetTextureSwizzle(image.WSource) }; GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba); } } public void SetSampler(GalImage image, GalTextureSampler sampler) { int wrapS = (int)OglEnumConverter.GetTextureWrapMode(sampler.AddressU); int wrapT = (int)OglEnumConverter.GetTextureWrapMode(sampler.AddressV); int wrapR = (int)OglEnumConverter.GetTextureWrapMode(sampler.AddressP); int minFilter = (int)OglEnumConverter.GetTextureMinFilter(sampler.MinFilter, sampler.MipFilter); int magFilter = (int)OglEnumConverter.GetTextureMagFilter(sampler.MagFilter); TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); GL.TexParameter(target, TextureParameterName.TextureWrapS, wrapS); GL.TexParameter(target, TextureParameterName.TextureWrapT, wrapT); GL.TexParameter(target, TextureParameterName.TextureWrapR, wrapR); GL.TexParameter(target, TextureParameterName.TextureMinFilter, minFilter); GL.TexParameter(target, TextureParameterName.TextureMagFilter, magFilter); float[] color = new float[] { sampler.BorderColor.Red, sampler.BorderColor.Green, sampler.BorderColor.Blue, sampler.BorderColor.Alpha }; GL.TexParameter(target, TextureParameterName.TextureBorderColor, color); if (sampler.DepthCompare) { GL.TexParameter(target, TextureParameterName.TextureCompareMode, (int)All.CompareRToTexture); GL.TexParameter(target, TextureParameterName.TextureCompareFunc, (int)OglEnumConverter.GetDepthCompareFunc(sampler.DepthCompareFunc)); } else { GL.TexParameter(target, TextureParameterName.TextureCompareMode, (int)All.None); GL.TexParameter(target, TextureParameterName.TextureCompareFunc, (int)All.Never); } } } }