From cf7044e37bc628f25525941d25b830594b833428 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Fri, 20 Nov 2020 16:30:59 +0000 Subject: [PATCH] Perform Compressed<->Uncompressed copies using Pixel Buffer Objects (#1732) * PBO single layer copy, part 1 Still needs ability to take and set width/height slices. (using pack paramaters) * PBO Copies pt 2 * Some fixes and cleanup. * Misc Cleanup * Move handle into the TextureInfo interface. This interface is shared between texture storages and views. * Move unscaled copy to the TextureCopy class. * Address feedback. --- Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs | 10 + Ryujinx.Graphics.OpenGL/Image/TextureBase.cs | 2 +- Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs | 195 ++++++++++++++- .../Image/TextureCopyUnscaled.cs | 93 ------- .../Image/TextureStorage.cs | 2 +- Ryujinx.Graphics.OpenGL/Image/TextureView.cs | 232 +++++++++++++++++- Ryujinx.Graphics.Texture/SizeCalculator.cs | 3 +- 7 files changed, 432 insertions(+), 105 deletions(-) create mode 100644 Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs delete mode 100644 Ryujinx.Graphics.OpenGL/Image/TextureCopyUnscaled.cs diff --git a/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs b/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs new file mode 100644 index 000000000..92bd597c1 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs @@ -0,0 +1,10 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + interface ITextureInfo + { + int Handle { get; } + TextureCreateInfo Info { get; } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs index 2e70fa82a..ebf0cacc7 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs @@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL; namespace Ryujinx.Graphics.OpenGL.Image { - class TextureBase + class TextureBase : ITextureInfo { public int Handle { get; protected set; } diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs index 191e9b634..74832dd8c 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs @@ -1,5 +1,6 @@ -using Ryujinx.Graphics.GAL; using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; using System; namespace Ryujinx.Graphics.OpenGL.Image @@ -80,6 +81,116 @@ namespace Ryujinx.Graphics.OpenGL.Image } } + public void CopyUnscaled( + ITextureInfo src, + ITextureInfo dst, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel) + { + TextureCreateInfo srcInfo = src.Info; + TextureCreateInfo dstInfo = dst.Info; + + int srcHandle = src.Handle; + int dstHandle = dst.Handle; + + int srcWidth = srcInfo.Width; + int srcHeight = srcInfo.Height; + int srcDepth = srcInfo.GetDepthOrLayers(); + int srcLevels = srcInfo.Levels; + + int dstWidth = dstInfo.Width; + int dstHeight = dstInfo.Height; + int dstDepth = dstInfo.GetDepthOrLayers(); + int dstLevels = dstInfo.Levels; + + srcWidth = Math.Max(1, srcWidth >> srcLevel); + srcHeight = Math.Max(1, srcHeight >> srcLevel); + + dstWidth = Math.Max(1, dstWidth >> dstLevel); + dstHeight = Math.Max(1, dstHeight >> dstLevel); + + if (dstInfo.Target == Target.Texture3D) + { + dstDepth = Math.Max(1, dstDepth >> dstLevel); + } + + int blockWidth = 1; + int blockHeight = 1; + bool sizeInBlocks = false; + + // When copying from a compressed to a non-compressed format, + // the non-compressed texture will have the size of the texture + // in blocks (not in texels), so we must adjust that size to + // match the size in texels of the compressed texture. + if (!srcInfo.IsCompressed && dstInfo.IsCompressed) + { + srcWidth *= dstInfo.BlockWidth; + srcHeight *= dstInfo.BlockHeight; + blockWidth = dstInfo.BlockWidth; + blockHeight = dstInfo.BlockHeight; + + sizeInBlocks = true; + } + else if (srcInfo.IsCompressed && !dstInfo.IsCompressed) + { + dstWidth *= srcInfo.BlockWidth; + dstHeight *= srcInfo.BlockHeight; + blockWidth = srcInfo.BlockWidth; + blockHeight = srcInfo.BlockHeight; + } + + int width = Math.Min(srcWidth, dstWidth); + int height = Math.Min(srcHeight, dstHeight); + int depth = Math.Min(srcDepth, dstDepth); + int levels = Math.Min(srcLevels, dstLevels); + + for (int level = 0; level < levels; level++) + { + // Stop copy if we are already out of the levels range. + if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels) + { + break; + } + + if ((width % blockWidth != 0 || height % blockHeight != 0) && src is TextureView srcView && dst is TextureView dstView) + { + PboCopy(srcView, dstView, srcLayer, dstLayer, srcLevel + level, dstLevel + level, width, height); + } + else + { + int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width; + int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height; + + GL.CopyImageSubData( + srcHandle, + srcInfo.Target.ConvertToImageTarget(), + srcLevel + level, + 0, + 0, + srcLayer, + dstHandle, + dstInfo.Target.ConvertToImageTarget(), + dstLevel + level, + 0, + 0, + dstLayer, + copyWidth, + copyHeight, + depth); + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (srcInfo.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + private static void Attach(FramebufferTarget target, Format format, int handle) { if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) @@ -147,6 +258,88 @@ namespace Ryujinx.Graphics.OpenGL.Image return to; } + private TextureView PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height) + { + int dstWidth = width; + int dstHeight = height; + + // The size of the source texture. + int unpackWidth = from.Width; + int unpackHeight = from.Height; + + if (from.Info.IsCompressed != to.Info.IsCompressed) + { + if (from.Info.IsCompressed) + { + // Dest size is in pixels, but should be in blocks + dstWidth = BitUtils.DivRoundUp(width, from.Info.BlockWidth); + dstHeight = BitUtils.DivRoundUp(height, from.Info.BlockHeight); + + // When copying from a compressed texture, the source size must be taken in blocks for unpacking to the uncompressed block texture. + unpackWidth = BitUtils.DivRoundUp(from.Info.Width, from.Info.BlockWidth); + unpackHeight = BitUtils.DivRoundUp(from.Info.Height, from.Info.BlockHeight); + } + else + { + // When copying to a compressed texture, the source size must be scaled by the block width for unpacking on the compressed target. + unpackWidth = from.Info.Width * to.Info.BlockWidth; + unpackHeight = from.Info.Height * to.Info.BlockHeight; + } + } + + EnsurePbo(from); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle); + + // The source texture is written out in full, then the destination is taken as a slice from the data using unpack params. + // The offset points to the base at which the requested layer is at. + + int offset = from.WriteToPbo2D(0, srcLayer, srcLevel); + + // If the destination size is not an exact match for the source unpack parameters, we need to set them to slice the data correctly. + + bool slice = (unpackWidth != dstWidth || unpackHeight != dstHeight); + + if (slice) + { + // Set unpack parameters to take a slice of width/height: + GL.PixelStore(PixelStoreParameter.UnpackRowLength, unpackWidth); + GL.PixelStore(PixelStoreParameter.UnpackImageHeight, unpackHeight); + + if (to.Info.IsCompressed) + { + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockWidth, to.Info.BlockWidth); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockHeight, to.Info.BlockHeight); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockDepth, 1); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockSize, to.Info.BytesPerPixel); + } + } + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, _copyPboHandle); + + to.ReadFromPbo2D(offset, dstLayer, dstLevel, dstWidth, dstHeight); + + if (slice) + { + // Reset unpack parameters + GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0); + GL.PixelStore(PixelStoreParameter.UnpackImageHeight, 0); + + if (to.Info.IsCompressed) + { + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockWidth, 0); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockHeight, 0); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockDepth, 0); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockSize, 0); + } + } + + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); + + return to; + } + private void EnsurePbo(TextureView view) { int requiredSize = 0; diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopyUnscaled.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopyUnscaled.cs deleted file mode 100644 index 8fc8f85f9..000000000 --- a/Ryujinx.Graphics.OpenGL/Image/TextureCopyUnscaled.cs +++ /dev/null @@ -1,93 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Common; -using Ryujinx.Graphics.GAL; -using System; - -namespace Ryujinx.Graphics.OpenGL.Image -{ - static class TextureCopyUnscaled - { - public static void Copy( - TextureCreateInfo srcInfo, - TextureCreateInfo dstInfo, - int srcHandle, - int dstHandle, - int srcLayer, - int dstLayer, - int srcLevel, - int dstLevel) - { - int srcWidth = srcInfo.Width; - int srcHeight = srcInfo.Height; - int srcDepth = srcInfo.GetDepthOrLayers(); - int srcLevels = srcInfo.Levels; - - int dstWidth = dstInfo.Width; - int dstHeight = dstInfo.Height; - int dstDepth = dstInfo.GetDepthOrLayers(); - int dstLevels = dstInfo.Levels; - - dstWidth = Math.Max(1, dstWidth >> dstLevel); - dstHeight = Math.Max(1, dstHeight >> dstLevel); - - if (dstInfo.Target == Target.Texture3D) - { - dstDepth = Math.Max(1, dstDepth >> dstLevel); - } - - // When copying from a compressed to a non-compressed format, - // the non-compressed texture will have the size of the texture - // in blocks (not in texels), so we must adjust that size to - // match the size in texels of the compressed texture. - if (!srcInfo.IsCompressed && dstInfo.IsCompressed) - { - dstWidth = BitUtils.DivRoundUp(dstWidth, dstInfo.BlockWidth); - dstHeight = BitUtils.DivRoundUp(dstHeight, dstInfo.BlockHeight); - } - else if (srcInfo.IsCompressed && !dstInfo.IsCompressed) - { - dstWidth *= srcInfo.BlockWidth; - dstHeight *= srcInfo.BlockHeight; - } - - int width = Math.Min(srcWidth, dstWidth); - int height = Math.Min(srcHeight, dstHeight); - int depth = Math.Min(srcDepth, dstDepth); - int levels = Math.Min(srcLevels, dstLevels); - - for (int level = 0; level < levels; level++) - { - // Stop copy if we are already out of the levels range. - if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels) - { - break; - } - - GL.CopyImageSubData( - srcHandle, - srcInfo.Target.ConvertToImageTarget(), - srcLevel + level, - 0, - 0, - srcLayer, - dstHandle, - dstInfo.Target.ConvertToImageTarget(), - dstLevel + level, - 0, - 0, - dstLayer, - width, - height, - depth); - - width = Math.Max(1, width >> 1); - height = Math.Max(1, height >> 1); - - if (srcInfo.Target == Target.Texture3D) - { - depth = Math.Max(1, depth >> 1); - } - } - } - } -} diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs b/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs index b34b02bf7..e96d8d85f 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs @@ -5,7 +5,7 @@ using System; namespace Ryujinx.Graphics.OpenGL.Image { - class TextureStorage + class TextureStorage : ITextureInfo { public int Handle { get; private set; } public float ScaleFactor { get; private set; } diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 449c18d4b..89f74ceec 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -112,6 +112,14 @@ namespace Ryujinx.Graphics.OpenGL.Image // so it doesn't work for all cases. TextureView emulatedView = (TextureView)_renderer.CreateTexture(info, ScaleFactor); + _renderer.TextureCopy.CopyUnscaled( + this, + emulatedView, + 0, + firstLayer, + 0, + firstLevel); + emulatedView._emulatedViewParent = this; emulatedView.FirstLayer = firstLayer; @@ -134,7 +142,7 @@ namespace Ryujinx.Graphics.OpenGL.Image _incompatibleFormatView = (TextureView)_renderer.CreateTexture(Info, ScaleFactor); } - TextureCopyUnscaled.Copy(_parent.Info, _incompatibleFormatView.Info, _parent.Handle, _incompatibleFormatView.Handle, FirstLayer, 0, FirstLevel, 0); + _renderer.TextureCopy.CopyUnscaled(_parent, _incompatibleFormatView, FirstLayer, 0, FirstLevel, 0); return _incompatibleFormatView.Handle; } @@ -146,7 +154,7 @@ namespace Ryujinx.Graphics.OpenGL.Image { if (_incompatibleFormatView != null) { - TextureCopyUnscaled.Copy(_incompatibleFormatView.Info, _parent.Info, _incompatibleFormatView.Handle, _parent.Handle, 0, FirstLayer, 0, FirstLevel); + _renderer.TextureCopy.CopyUnscaled(_incompatibleFormatView, _parent, 0, FirstLayer, 0, FirstLevel); } } @@ -154,15 +162,13 @@ namespace Ryujinx.Graphics.OpenGL.Image { TextureView destinationView = (TextureView)destination; - TextureCopyUnscaled.Copy(Info, destinationView.Info, Handle, destinationView.Handle, 0, firstLayer, 0, firstLevel); + _renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel); if (destinationView._emulatedViewParent != null) { - TextureCopyUnscaled.Copy( - Info, - destinationView._emulatedViewParent.Info, - Handle, - destinationView._emulatedViewParent.Handle, + _renderer.TextureCopy.CopyUnscaled( + this, + destinationView._emulatedViewParent, 0, destinationView.FirstLayer, 0, @@ -202,6 +208,50 @@ namespace Ryujinx.Graphics.OpenGL.Image WriteTo(IntPtr.Zero + offset, forceBgra); } + public int WriteToPbo2D(int offset, int layer, int level) + { + return WriteTo2D(IntPtr.Zero + offset, layer, level); + } + + private int WriteTo2D(IntPtr data, int layer, int level) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + PixelFormat pixelFormat = format.PixelFormat; + PixelType pixelType = format.PixelType; + + if (target == TextureTarget.TextureCubeMap || target == TextureTarget.TextureCubeMapArray) + { + target = TextureTarget.TextureCubeMapPositiveX + (layer % 6); + } + + int mipSize = Info.GetMipSize2D(level); + + // The GL function returns all layers. Must return the offset of the layer we're interested in. + int resultOffset = target switch + { + TextureTarget.TextureCubeMapArray => (layer / 6) * mipSize, + TextureTarget.Texture1DArray => layer * mipSize, + TextureTarget.Texture2DArray => layer * mipSize, + _ => 0 + }; + + if (format.IsCompressed) + { + GL.GetCompressedTexImage(target, level, data); + } + else + { + GL.GetTexImage(target, level, pixelFormat, pixelType, data); + } + + return resultOffset; + } + private void WriteTo(IntPtr data, bool forceBgra = false) { TextureTarget target = Target.Convert(); @@ -263,6 +313,172 @@ namespace Ryujinx.Graphics.OpenGL.Image ReadFrom(IntPtr.Zero + offset, size); } + public void ReadFromPbo2D(int offset, int layer, int level, int width, int height) + { + ReadFrom2D(IntPtr.Zero + offset, layer, level, width, height); + } + + private void ReadFrom2D(IntPtr data, int layer, int level, int width, int height) + { + TextureTarget target = Target.Convert(); + + int mipSize = Info.GetMipSize2D(level); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + switch (Target) + { + case Target.Texture1D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture1DArray: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + target, + level, + 0, + layer, + width, + 1, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + target, + level, + 0, + layer, + width, + 1, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture2D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture2DArray: + case Target.Texture3D: + case Target.CubemapArray: + if (format.IsCompressed) + { + GL.CompressedTexSubImage3D( + target, + level, + 0, + 0, + layer, + width, + height, + 1, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage3D( + target, + level, + 0, + 0, + layer, + width, + height, + 1, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Cubemap: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + layer, + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + layer, + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data); + } + break; + } + } + private void ReadFrom(IntPtr data, int size) { TextureTarget target = Target.Convert(); diff --git a/Ryujinx.Graphics.Texture/SizeCalculator.cs b/Ryujinx.Graphics.Texture/SizeCalculator.cs index b02b5a8d5..9339ba12e 100644 --- a/Ryujinx.Graphics.Texture/SizeCalculator.cs +++ b/Ryujinx.Graphics.Texture/SizeCalculator.cs @@ -197,7 +197,8 @@ namespace Ryujinx.Graphics.Texture alignment = GobStride / bytesPerPixel; } - (gobBlocksInY, gobBlocksInZ) = GetMipGobBlockSizes(height, depth, blockHeight, gobBlocksInY, gobBlocksInZ); + // Height has already been divided by block height, so pass it as 1. + (gobBlocksInY, gobBlocksInZ) = GetMipGobBlockSizes(height, depth, 1, gobBlocksInY, gobBlocksInZ); int blockOfGobsHeight = gobBlocksInY * GobHeight; int blockOfGobsDepth = gobBlocksInZ;