diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs index 2bcafd727f..d27194b85b 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Texture; namespace Ryujinx.Graphics.Gpu.Image { @@ -92,14 +93,17 @@ namespace Ryujinx.Graphics.Gpu.Image /// Texture swizzle for the red color channel. /// public SwizzleComponent SwizzleR { get; } + /// /// Texture swizzle for the green color channel. /// public SwizzleComponent SwizzleG { get; } + /// /// Texture swizzle for the blue color channel. /// public SwizzleComponent SwizzleB { get; } + /// /// Texture swizzle for the alpha color channel. /// @@ -176,7 +180,19 @@ namespace Ryujinx.Graphics.Gpu.Image /// Texture depth public int GetDepth() { - return Target == Target.Texture3D ? DepthOrLayers : 1; + return GetDepth(Target, DepthOrLayers); + } + + /// + /// Gets the real texture depth. + /// Returns 1 for any target other than 3D textures. + /// + /// Texture target + /// Texture depth if the texture is 3D, otherwise ignored + /// Texture depth + public static int GetDepth(Target target, int depthOrLayers) + { + return target == Target.Texture3D ? depthOrLayers : 1; } /// @@ -186,15 +202,27 @@ namespace Ryujinx.Graphics.Gpu.Image /// The number of texture layers public int GetLayers() { - if (Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray) + return GetLayers(Target, DepthOrLayers); + } + + /// + /// Gets the number of layers of the texture. + /// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures. + /// + /// Texture target + /// Texture layers if the is a array texture, ignored otherwise + /// The number of texture layers + public static int GetLayers(Target target, int depthOrLayers) + { + if (target == Target.Texture2DArray || target == Target.Texture2DMultisampleArray) { - return DepthOrLayers; + return depthOrLayers; } - else if (Target == Target.CubemapArray) + else if (target == Target.CubemapArray) { - return DepthOrLayers * 6; + return depthOrLayers * 6; } - else if (Target == Target.Cubemap) + else if (target == Target.Cubemap) { return 6; } @@ -203,5 +231,41 @@ namespace Ryujinx.Graphics.Gpu.Image return 1; } } + + /// + /// Calculates the size information from the texture information. + /// + /// Optional size of each texture layer in bytes + /// Texture size information + public SizeInfo CalculateSizeInfo(int layerSize = 0) + { + if (Target == Target.TextureBuffer) + { + return new SizeInfo(Width * FormatInfo.BytesPerPixel); + } + else if (IsLinear) + { + return SizeCalculator.GetLinearTextureSize( + Stride, + Height, + FormatInfo.BlockHeight); + } + else + { + return SizeCalculator.GetBlockLinearTextureSize( + Width, + Height, + GetDepth(), + Levels, + GetLayers(), + FormatInfo.BlockWidth, + FormatInfo.BlockHeight, + FormatInfo.BytesPerPixel, + GobBlocksInY, + GobBlocksInZ, + GobBlocksInTileX, + layerSize); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 993218ce09..5bad3952d4 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -514,7 +514,7 @@ namespace Ryujinx.Graphics.Gpu.Image flags |= TextureSearchFlags.WithUpscale; } - Texture texture = FindOrCreateTexture(info, flags, sizeHint); + Texture texture = FindOrCreateTexture(info, flags, 0, sizeHint); texture.SynchronizeMemory(); @@ -598,7 +598,9 @@ namespace Ryujinx.Graphics.Gpu.Image target, formatInfo); - Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, sizeHint); + int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; + + Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, layerSize, sizeHint); texture.SynchronizeMemory(); @@ -648,7 +650,7 @@ namespace Ryujinx.Graphics.Gpu.Image target, formatInfo); - Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, sizeHint); + Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, dsState.LayerSize * 4, sizeHint); texture.SynchronizeMemory(); @@ -660,9 +662,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Texture information of the texture to be found or created /// The texture search flags, defines texture comparison rules + /// Size in bytes of a single texture layer /// A hint indicating the minimum used size for the texture /// The texture - public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None, Size? sizeHint = null) + public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None, int layerSize = 0, Size? sizeHint = null) { bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; @@ -722,34 +725,7 @@ namespace Ryujinx.Graphics.Gpu.Image } // Calculate texture sizes, used to find all overlapping textures. - SizeInfo sizeInfo; - - if (info.Target == Target.TextureBuffer) - { - sizeInfo = new SizeInfo(info.Width * info.FormatInfo.BytesPerPixel); - } - else if (info.IsLinear) - { - sizeInfo = SizeCalculator.GetLinearTextureSize( - info.Stride, - info.Height, - info.FormatInfo.BlockHeight); - } - else - { - sizeInfo = SizeCalculator.GetBlockLinearTextureSize( - info.Width, - info.Height, - info.GetDepth(), - info.Levels, - info.GetLayers(), - info.FormatInfo.BlockWidth, - info.FormatInfo.BlockHeight, - info.FormatInfo.BytesPerPixel, - info.GobBlocksInY, - info.GobBlocksInZ, - info.GobBlocksInTileX); - } + SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); // Find view compatible matches. ulong size = (ulong)sizeInfo.TotalSize; diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 9c7e849b82..333ebaedc0 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -1,6 +1,8 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image @@ -50,7 +52,7 @@ namespace Ryujinx.Graphics.Gpu.Image { TextureDescriptor descriptor = GetDescriptor(id); - TextureInfo info = GetInfo(descriptor); + TextureInfo info = GetInfo(descriptor, out int layerSize); // Bad address. We can't add a texture with a invalid address // to the cache. @@ -59,7 +61,7 @@ namespace Ryujinx.Graphics.Gpu.Image return null; } - texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.ForSampler); + texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.ForSampler, layerSize); texture.IncrementReferenceCount(); @@ -121,7 +123,7 @@ namespace Ryujinx.Graphics.Gpu.Image // If the descriptors are the same, the texture is the same, // we don't need to remove as it was not modified. Just continue. - if (texture.IsExactMatch(GetInfo(descriptor), TextureSearchFlags.Strict) != TextureMatchQuality.NoMatch) + if (texture.IsExactMatch(GetInfo(descriptor, out _), TextureSearchFlags.Strict) != TextureMatchQuality.NoMatch) { continue; } @@ -137,10 +139,12 @@ namespace Ryujinx.Graphics.Gpu.Image /// Gets texture information from a texture descriptor. /// /// The texture descriptor + /// Layer size for textures using a sub-range of mipmap levels, otherwise 0 /// The texture information - private TextureInfo GetInfo(TextureDescriptor descriptor) + private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize) { ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress()); + bool addressIsValid = address != MemoryManager.PteUnmapped; int width = descriptor.UnpackWidth(); int height = descriptor.UnpackHeight(); @@ -181,7 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo)) { - if ((long)address > 0L && (int)format > 0) + if (addressIsValid && (int)format > 0) { Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb})."); } @@ -194,6 +198,53 @@ namespace Ryujinx.Graphics.Gpu.Image int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX(); + layerSize = 0; + + int minLod = descriptor.UnpackBaseLevel(); + int maxLod = descriptor.UnpackMaxLevelInclusive(); + + // Linear textures don't support mipmaps, so we don't handle this case here. + if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear && addressIsValid) + { + int depth = TextureInfo.GetDepth(target, depthOrLayers); + int layers = TextureInfo.GetLayers(target, depthOrLayers); + + SizeInfo sizeInfo = SizeCalculator.GetBlockLinearTextureSize( + width, + height, + depth, + levels, + layers, + formatInfo.BlockWidth, + formatInfo.BlockHeight, + formatInfo.BytesPerPixel, + gobBlocksInY, + gobBlocksInZ, + gobBlocksInTileX); + + layerSize = sizeInfo.LayerSize; + + if (minLod != 0) + { + // If the base level is not zero, we additionally add the mip level offset + // to the address, this allows the texture manager to find the base level from the + // address if there is a overlapping texture on the cache that can contain the new texture. + address += (ulong)sizeInfo.GetMipOffset(minLod); + + width = Math.Max(1, width >> minLod); + height = Math.Max(1, height >> minLod); + + if (target == Target.Texture3D) + { + depthOrLayers = Math.Max(1, depthOrLayers >> minLod); + } + + (gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ); + } + + levels = (maxLod - minLod) + 1; + } + SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert(); SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert(); SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert(); diff --git a/Ryujinx.Graphics.Texture/SizeCalculator.cs b/Ryujinx.Graphics.Texture/SizeCalculator.cs index 9339ba12e8..2dc6086972 100644 --- a/Ryujinx.Graphics.Texture/SizeCalculator.cs +++ b/Ryujinx.Graphics.Texture/SizeCalculator.cs @@ -20,7 +20,8 @@ namespace Ryujinx.Graphics.Texture int bytesPerPixel, int gobBlocksInY, int gobBlocksInZ, - int gobBlocksInTileX) + int gobBlocksInTileX, + int gpuLayerSize = 0) { bool is3D = depth > 1; @@ -94,14 +95,29 @@ namespace Ryujinx.Graphics.Texture layerSize += totalBlocksOfGobsInZ * totalBlocksOfGobsInY * robSize; } - layerSize = AlignLayerSize( - layerSize, - height, - depth, - blockHeight, - gobBlocksInY, - gobBlocksInZ, - gobBlocksInTileX); + if (layers > 1) + { + layerSize = AlignLayerSize( + layerSize, + height, + depth, + blockHeight, + gobBlocksInY, + gobBlocksInZ, + gobBlocksInTileX); + } + + int totalSize; + + if (layerSize < gpuLayerSize) + { + totalSize = (layers - 1) * gpuLayerSize + layerSize; + layerSize = gpuLayerSize; + } + else + { + totalSize = layerSize * layers; + } if (!is3D) { @@ -117,8 +133,6 @@ namespace Ryujinx.Graphics.Texture } } - int totalSize = layerSize * layers; - return new SizeInfo(mipOffsets, allOffsets, levels, layerSize, totalSize); }