/// <summary> /// Determines if a given texture is eligible for upscaling from its info. /// </summary> /// <param name="info">The texture info to check</param> /// <returns>True if eligible</returns> public bool IsUpscaleCompatible(TextureInfo info) { return((info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && info.Levels == 1 && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info)); }
/// <summary> /// Checks if the view format is compatible with this texture format. /// In general, the formats are considered compatible if the bytes per pixel values are equal, /// but there are more complex rules for some formats, like compressed or depth-stencil formats. /// This follows the host API copy compatibility rules. /// </summary> /// <param name="info">Texture information of the texture view</param> /// <returns>True if the formats are compatible, false otherwise</returns> private bool ViewFormatCompatible(TextureInfo info) { return(TextureCompatibility.FormatCompatible(Info.FormatInfo, info.FormatInfo)); }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <param name="info">Texture information of the texture to be found or created</param> /// <param name="layerSize">Size in bytes of a single texture layer</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <param name="range">Optional ranges of physical memory where the texture data is located</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture( MemoryManager memoryManager, TextureSearchFlags flags, TextureInfo info, int layerSize = 0, Size?sizeHint = null, MultiRange?range = null) { bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; TextureScaleMode scaleMode = IsUpscaleCompatible(info, (flags & TextureSearchFlags.WithUpscale) != 0); ulong address; if (range != null) { address = range.Value.GetSubRange(0).Address; } else { address = memoryManager.Translate(info.GpuAddress); if (address == MemoryManager.PteUnmapped) { return(null); } } int sameAddressOverlapsCount; lock (_textures) { // Try to find a perfect texture match, with the same address and parameters. sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); } Texture texture = null; TextureMatchQuality bestQuality = TextureMatchQuality.NoMatch; for (int index = 0; index < sameAddressOverlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); if (matchQuality != TextureMatchQuality.NoMatch) { // If the parameters match, we need to make sure the texture is mapped to the same memory regions. if (range != null) { // If a range of memory was supplied, just check if the ranges match. if (!overlap.Range.Equals(range.Value)) { continue; } } else { // If no range was supplied, we can check if the GPU virtual address match. If they do, // we know the textures are located at the same memory region. // If they don't, it may still be mapped to the same physical region, so we // do a more expensive check to tell if they are mapped into the same physical regions. // If the GPU VA for the texture has ever been unmapped, then the range must be checked regardless. if ((overlap.Info.GpuAddress != info.GpuAddress || overlap.ChangedMapping) && !memoryManager.CompareRange(overlap.Range, info.GpuAddress)) { continue; } } } if (matchQuality == TextureMatchQuality.Perfect) { texture = overlap; break; } else if (matchQuality > bestQuality) { texture = overlap; bestQuality = matchQuality; } } if (texture != null) { ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); texture.SynchronizeMemory(); return(texture); } // Calculate texture sizes, used to find all overlapping textures. SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); ulong size = (ulong)sizeInfo.TotalSize; if (range == null) { range = memoryManager.GetPhysicalRegions(info.GpuAddress, size); } // Find view compatible matches. int overlapsCount; lock (_textures) { overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); } if (_overlapInfo.Length != _textureOverlaps.Length) { Array.Resize(ref _overlapInfo, _textureOverlaps.Length); } // =============== Find Texture View of Existing Texture =============== int fullyCompatible = 0; // Evaluate compatibility of overlaps for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel); if (overlapCompatibility == TextureViewCompatibility.Full) { if (overlap.IsView) { overlapCompatibility = TextureViewCompatibility.CopyOnly; } else { fullyCompatible++; } } _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel); } // Search through the overlaps to find a compatible view and establish any copy dependencies. for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility == TextureViewCompatibility.Full) { TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel); if (!isSamplerTexture) { info = adjInfo; } texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); texture.SynchronizeMemory(); break; } else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) { // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); texture.InitializeGroup(true, true); texture.InitializeData(false, false); overlap.SynchronizeMemory(); overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); break; } } if (texture != null) { // This texture could be a view of multiple parent textures with different storages, even if it is a view. // When a texture is created, make sure all possible dependencies to other textures are created as copies. // (even if it could be fulfilled without a copy) for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group) { overlap.SynchronizeMemory(); overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); } } texture.SynchronizeMemory(); } // =============== Create a New Texture =============== // No match, create a new texture. if (texture == null) { texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); // Step 1: Find textures that are view compatible with the new texture. // Any textures that are incompatible will contain garbage data, so they should be removed where possible. int viewCompatible = 0; fullyCompatible = 0; bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); bool hasLayerViews = false; bool hasMipViews = false; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; bool overlapInCache = overlap.CacheNode != null; TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel); if (overlap.IsView && compatibility == TextureViewCompatibility.Full) { compatibility = TextureViewCompatibility.CopyOnly; } if (compatibility != TextureViewCompatibility.Incompatible) { if (compatibility == TextureViewCompatibility.Full) { if (viewCompatible == fullyCompatible) { _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[viewCompatible++] = overlap; } else { // Swap overlaps so that the fully compatible views have priority. _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible]; _textureOverlaps[viewCompatible++] = _textureOverlaps[fullyCompatible]; _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[fullyCompatible] = overlap; } fullyCompatible++; } else { _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[viewCompatible++] = overlap; } hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices(); hasMipViews |= overlap.Info.Levels < texture.Info.Levels; } else { bool removeOverlap; bool modified = overlap.CheckModified(false); if (overlapInCache || !setData) { if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ) { // Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap. continue; } if (!texture.DataOverlaps(overlap)) { // Allow textures to overlap if their data does not actually overlap. // This typically happens when mip level subranges of a layered texture are used. (each texture fills the gaps of the others) continue; } // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. // If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us, // it must be flushed before removal, so that the data is not lost. // If the texture was modified since its last use, then that data is probably meant to go into this texture. // If the data has been modified by the CPU, then it also shouldn't be flushed. bool viewCompatibleChild = overlap.HasViewCompatibleChild(texture); bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && viewCompatibleChild; setData |= modified || flush; if (overlapInCache) { _cache.Remove(overlap, flush); } removeOverlap = modified && !viewCompatibleChild; } else { // If an incompatible overlapping texture has been modified, then it's data is likely destined for this texture, // and the overlapped texture will contain garbage. In this case, it should be removed to save memory. removeOverlap = modified; } if (removeOverlap && overlap.Info.Target != Target.TextureBuffer) { overlap.RemoveFromPools(false); } } } texture.InitializeGroup(hasLayerViews, hasMipViews); // We need to synchronize before copying the old view data to the texture, // otherwise the copied data would be overwritten by a future synchronization. texture.InitializeData(false, setData); for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (overlap.Group == texture.Group) { // If the texture group is equal, then this texture (or its parent) is already a view. continue; } TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); if (texture.ScaleFactor != overlap.ScaleFactor) { // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. texture.PropagateScale(overlap); } if (oInfo.Compatibility != TextureViewCompatibility.Full) { // Copy only compatibility, or target texture is already a view. overlap.SynchronizeMemory(); texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false); } else { TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); overlap.SynchronizeMemory(); overlap.HostTexture.CopyTo(newView, 0, 0); overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); } } texture.SynchronizeMemory(); } // Sampler textures are managed by the texture pool, all other textures // are managed by the auto delete cache. if (!isSamplerTexture) { _cache.Add(texture); } lock (_textures) { _textures.Add(texture); } ShrinkOverlapsBufferIfNeeded(); return(texture); }
/// <summary> /// Checks if the texture sizes of the supplied texture information matches the given level of /// this texture. /// </summary> /// <param name="info">Texture information to compare with</param> /// <param name="level">Mipmap level of this texture to compare with</param> /// <returns>True if the size matches with the level, false otherwise</returns> public bool SizeMatches(TextureInfo info, int level) { return(Math.Max(1, Info.Width >> level) == info.Width && Math.Max(1, Info.Height >> level) == info.Height && Math.Max(1, Info.GetDepth() >> level) == info.GetDepth()); }
/// <summary> /// Check if the texture target and samples count (for multisampled textures) matches. /// </summary> /// <param name="info">Texture information to compare with</param> /// <returns>True if the texture target and samples count matches, false otherwise</returns> private bool TargetAndSamplesCompatible(TextureInfo info) { return(Info.Target == info.Target && Info.SamplesInX == info.SamplesInX && Info.SamplesInY == info.SamplesInY); }
/// <summary> /// Adjusts the size of the texture information for a given mipmap level, /// based on the size of a parent texture. /// </summary> /// <param name="parent">The parent texture</param> /// <param name="info">The texture information to be adjusted</param> /// <param name="firstLevel">The first level of the texture view</param> /// <returns>The adjusted texture information with the new size</returns> private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel) { // When the texture is used as view of another texture, we must // ensure that the sizes are valid, otherwise data uploads would fail // (and the size wouldn't match the real size used on the host API). // Given a parent texture from where the view is created, we have the // following rules: // - The view size must be equal to the parent size, divided by (2 ^ l), // where l is the first mipmap level of the view. The division result must // be rounded down, and the result must be clamped to 1. // - If the parent format is compressed, and the view format isn't, the // view size is calculated as above, but the width and height of the // view must be also divided by the compressed format block width and height. // - If the parent format is not compressed, and the view is, the view // size is calculated as described on the first point, but the width and height // of the view must be also multiplied by the block width and height. int width = Math.Max(1, parent.Info.Width >> firstLevel); int height = Math.Max(1, parent.Info.Height >> firstLevel); if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed) { width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); } else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed) { width *= info.FormatInfo.BlockWidth; height *= info.FormatInfo.BlockHeight; } int depthOrLayers; if (info.Target == Target.Texture3D) { depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); } else { depthOrLayers = info.DepthOrLayers; } return(new TextureInfo( info.Address, width, height, depthOrLayers, info.Levels, info.SamplesInX, info.SamplesInY, info.Stride, info.IsLinear, info.GobBlocksInY, info.GobBlocksInZ, info.GobBlocksInTileX, info.Target, info.FormatInfo, info.DepthStencilMode, info.SwizzleR, info.SwizzleG, info.SwizzleB, info.SwizzleA)); }
/// <summary> /// Checks if the texture sizes of the supplied texture informations match. /// </summary> /// <param name="lhs">Texture information to compare</param> /// <param name="rhs">Texture information to compare with</param> /// <returns>True if the size matches, false otherwise</returns> public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs) { return(SizeMatches(lhs, rhs, alignSizes: false)); }
/// <summary> /// Gets texture information from a texture descriptor. /// </summary> /// <param name="descriptor">The texture descriptor</param> /// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param> /// <returns>The texture information</returns> private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize) { int depthOrLayers = descriptor.UnpackDepth(); int levels = descriptor.UnpackLevels(); TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode(); int samplesInX = msaaMode.SamplesInX(); int samplesInY = msaaMode.SamplesInY(); int stride = descriptor.UnpackStride(); TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType(); bool isLinear = descriptorType == TextureDescriptorType.Linear; Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1); int width = target == Target.TextureBuffer ? descriptor.UnpackBufferTextureWidth() : descriptor.UnpackWidth(); int height = descriptor.UnpackHeight(); // We use 2D targets for 1D textures as that makes texture cache // management easier. We don't know the target for render target // and copies, so those would normally use 2D targets, which are // not compatible with 1D targets. By doing that we also allow those // to match when looking for compatible textures on the cache. if (target == Target.Texture1D) { target = Target.Texture2D; height = 1; } else if (target == Target.Texture1DArray) { target = Target.Texture2DArray; height = 1; } uint format = descriptor.UnpackFormat(); bool srgb = descriptor.UnpackSrgb(); ulong gpuVa = descriptor.UnpackAddress(); if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo)) { if (gpuVa != 0 && (int)format > 0) { Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb})."); } formatInfo = FormatInfo.Default; } int gobBlocksInY = descriptor.UnpackGobBlocksInY(); int gobBlocksInZ = descriptor.UnpackGobBlocksInZ(); 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) { 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 && minLod < levels) { // 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. gpuVa += (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(); SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert(); DepthStencilMode depthStencilMode = GetDepthStencilMode( formatInfo.Format, swizzleR, swizzleG, swizzleB, swizzleA); if (formatInfo.Format.IsDepthOrStencil()) { swizzleR = SwizzleComponent.Red; swizzleG = SwizzleComponent.Red; swizzleB = SwizzleComponent.Red; if (depthStencilMode == DepthStencilMode.Depth) { swizzleA = SwizzleComponent.One; } else { swizzleA = SwizzleComponent.Red; } } return(new TextureInfo( gpuVa, width, height, depthOrLayers, levels, samplesInX, samplesInY, stride, isLinear, gobBlocksInY, gobBlocksInZ, gobBlocksInTileX, target, formatInfo, depthStencilMode, swizzleR, swizzleG, swizzleB, swizzleA)); }
/// <summary> /// Gets the texture with the given ID. /// </summary> /// <param name="id">ID of the texture. This is effectively a zero-based index</param> /// <returns>The texture with the given ID</returns> public override Texture Get(int id) { if ((uint)id >= Items.Length) { return(null); } if (SequenceNumber != Context.SequenceNumber) { SequenceNumber = Context.SequenceNumber; SynchronizeMemory(); } Texture texture = Items[id]; if (texture == null) { TextureDescriptor descriptor = GetDescriptor(id); TextureInfo info = GetInfo(descriptor, out int layerSize); ProcessDereferenceQueue(); texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize); // If this happens, then the texture address is invalid, we can't add it to the cache. if (texture == null) { return(null); } texture.IncrementReferenceCount(this, id); Items[id] = texture; DescriptorCache[id] = descriptor; } else { if (texture.ChangedSize) { // Texture changed size at one point - it may be a different size than the sampler expects. // This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before. TextureDescriptor descriptor = GetDescriptor(id); int baseLevel = descriptor.UnpackBaseLevel(); int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel); int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel); if (texture.Info.Width != width || texture.Info.Height != height) { texture.ChangeSize(width, height, texture.Info.DepthOrLayers); } } // Memory is automatically synchronized on texture creation. texture.SynchronizeMemory(); } return(texture); }
/// <summary> /// Check if the target of the first texture view information is compatible with the target of the second texture view information. /// This follows the host API target compatibility rules. /// </summary> /// <param name="lhs">Texture information of the texture view</param /// <param name="rhs">Texture information of the texture view</param> /// <param name="caps">Host GPU capabilities</param> /// <returns>True if the targets are compatible, false otherwise</returns> public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, ref Capabilities caps) { bool result = false; switch (lhs.Target) { case Target.Texture1D: case Target.Texture1DArray: result = rhs.Target == Target.Texture1D || rhs.Target == Target.Texture1DArray; break; case Target.Texture2D: result = rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray; break; case Target.Texture2DArray: result = rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray; if (rhs.Target == Target.Cubemap || rhs.Target == Target.CubemapArray) { return(caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly); } break; case Target.Cubemap: case Target.CubemapArray: result = rhs.Target == Target.Cubemap || rhs.Target == Target.CubemapArray; if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray) { return(caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly); } break; case Target.Texture2DMultisample: case Target.Texture2DMultisampleArray: if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray) { return(TextureViewCompatibility.CopyOnly); } result = rhs.Target == Target.Texture2DMultisample || rhs.Target == Target.Texture2DMultisampleArray; break; case Target.Texture3D: if (rhs.Target == Target.Texture2D) { return(TextureViewCompatibility.CopyOnly); } result = rhs.Target == Target.Texture3D; break; } return(result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible); }
/// <summary> /// Check if the texture target and samples count (for multisampled textures) matches. /// </summary> /// <param name="first">Texture information to compare with</param> /// <param name="rhs">Texture information to compare with</param> /// <returns>True if the texture target and samples count matches, false otherwise</returns> public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs) { return(lhs.Target == rhs.Target && lhs.SamplesInX == rhs.SamplesInX && lhs.SamplesInY == rhs.SamplesInY); }
/// <summary> /// Converts a incompatible format to a host compatible format, or return the format directly /// if it is already host compatible. /// </summary> /// <remarks> /// This can be used to convert a incompatible compressed format to the decompressor /// output format. /// </remarks> /// <param name="info">Texture information</param> /// <param name="caps">Host GPU capabilities</param> /// <returns>A host compatible format</returns> public static FormatInfo ToHostCompatibleFormat(TextureInfo info, Capabilities caps) { if (!caps.SupportsAstcCompression) { if (info.FormatInfo.Format.IsAstcUnorm()) { return(GraphicsConfig.EnableTextureRecompression ? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4) : new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4)); } else if (info.FormatInfo.Format.IsAstcSrgb()) { return(GraphicsConfig.EnableTextureRecompression ? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4) : new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4)); } } if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm) { return(new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4)); } if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps)) { // The host API does not this compressed format. // We assume software decompression will be done for those textures, // and so we adjust the format here to match the decompressor output. switch (info.FormatInfo.Format) { case Format.Bc1RgbaSrgb: case Format.Bc2Srgb: case Format.Bc3Srgb: case Format.Bc7Srgb: return(new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4)); case Format.Bc1RgbaUnorm: case Format.Bc2Unorm: case Format.Bc3Unorm: case Format.Bc7Unorm: return(new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4)); case Format.Bc4Unorm: return(new FormatInfo(Format.R8Unorm, 1, 1, 1, 1)); case Format.Bc4Snorm: return(new FormatInfo(Format.R8Snorm, 1, 1, 1, 1)); case Format.Bc5Unorm: return(new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2)); case Format.Bc5Snorm: return(new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2)); case Format.Bc6HSfloat: case Format.Bc6HUfloat: return(new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4)); } } return(info.FormatInfo); }
/// <summary> /// Checks if a format is host incompatible. /// </summary> /// <remarks> /// Host incompatible formats can't be used directly, the texture data needs to be converted /// to a compatible format first. /// </remarks> /// <param name="info">Texture information</param> /// <param name="caps">Host GPU capabilities</param> /// <returns>True if the format is incompatible, false otherwise</returns> public static bool IsFormatHostIncompatible(TextureInfo info, Capabilities caps) { Format originalFormat = info.FormatInfo.Format; return(ToHostCompatibleFormat(info, caps).Format != originalFormat); }
/// <summary> /// Checks if the texture sizes of the supplied texture informations match the given level /// </summary> /// <param name="lhs">Texture information to compare</param> /// <param name="rhs">Texture information to compare with</param> /// <param name="level">Mipmap level of this texture to compare with</param> /// <returns>True if the size matches with the level, false otherwise</returns> public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, int level) { return(Math.Max(1, lhs.Width >> level) == rhs.Width && Math.Max(1, lhs.Height >> level) == rhs.Height && Math.Max(1, lhs.GetDepth() >> level) == rhs.GetDepth()); }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="colorState">Color buffer texture to find or create</param> /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint) { ulong address = _context.MemoryManager.Translate(colorState.Address.Pack()); if (address == MemoryManager.BadAddress) { return(null); } bool isLinear = colorState.MemoryLayout.UnpackIsLinear(); int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ(); Target target; if (colorState.MemoryLayout.UnpackIsTarget3D()) { target = Target.Texture3D; } else if ((samplesInX | samplesInY) != 1) { target = colorState.Depth > 1 ? Target.Texture2DMultisampleArray : Target.Texture2DMultisample; } else { target = colorState.Depth > 1 ? Target.Texture2DArray : Target.Texture2D; } FormatInfo formatInfo = colorState.Format.Convert(); int width, stride; // For linear textures, the width value is actually the stride. // We can easily get the width by dividing the stride by the bpp, // since the stride is the total number of bytes occupied by a // line. The stride should also meet alignment constraints however, // so the width we get here is the aligned width. if (isLinear) { width = colorState.WidthOrStride / formatInfo.BytesPerPixel; stride = colorState.WidthOrStride; } else { width = colorState.WidthOrStride; stride = 0; } TextureInfo info = new TextureInfo( address, width, colorState.Height, colorState.Depth, 1, samplesInX, samplesInY, stride, isLinear, gobBlocksInY, gobBlocksInZ, 1, target, formatInfo); Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, sizeHint); texture.SynchronizeMemory(); return(texture); }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="info">Texture information of the texture to be found or created</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None) { bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; bool isScalable = IsUpscaleCompatible(info); TextureScaleMode scaleMode = TextureScaleMode.Blacklisted; if (isScalable) { scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; } // Try to find a perfect texture match, with the same address and parameters. int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); for (int index = 0; index < sameAddressOverlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (overlap.IsPerfectMatch(info, flags)) { if (!isSamplerTexture) { // If not a sampler texture, it is managed by the auto delete // cache, ensure that it is on the "top" of the list to avoid // deletion. _cache.Lift(overlap); } else if (!TextureCompatibility.SizeMatches(overlap.Info, info)) { // If this is used for sampling, the size must match, // otherwise the shader would sample garbage data. // To fix that, we create a new texture with the correct // size, and copy the data from the old one to the new one. overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers); } overlap.SynchronizeMemory(); return(overlap); } } // 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); } // Find view compatible matches. ulong size = (ulong)sizeInfo.TotalSize; int overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); Texture texture = null; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel)) { if (!isSamplerTexture) { info = AdjustSizes(overlap, info, firstLevel); } texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); if (IsTextureModified(overlap)) { CacheTextureModified(texture); } // The size only matters (and is only really reliable) when the // texture is used on a sampler, because otherwise the size will be // aligned. if (!TextureCompatibility.SizeMatches(overlap.Info, info, firstLevel) && isSamplerTexture) { texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); } break; } } // No match, create a new texture. if (texture == null) { texture = new Texture(_context, info, sizeInfo, scaleMode); // We need to synchronize before copying the old view data to the texture, // otherwise the copied data would be overwritten by a future synchronization. texture.SynchronizeMemory(); for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel)) { TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel); TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities); if (texture.ScaleFactor != overlap.ScaleFactor) { // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. texture.PropagateScale(overlap); } ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel); overlap.HostTexture.CopyTo(newView, 0, 0); // Inherit modification from overlapping texture, do that before replacing // the view since the replacement operation removes it from the list. if (IsTextureModified(overlap)) { CacheTextureModified(texture); } overlap.ReplaceView(texture, overlapInfo, newView, firstLayer, firstLevel); } } // If the texture is a 3D texture, we need to additionally copy any slice // of the 3D texture to the newly created 3D texture. if (info.Target == Target.Texture3D) { for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (texture.IsViewCompatible( overlap.Info, overlap.Size, isCopy: true, out int firstLayer, out int firstLevel)) { overlap.BlacklistScale(); overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel); if (IsTextureModified(overlap)) { CacheTextureModified(texture); } } } } } // Sampler textures are managed by the texture pool, all other textures // are managed by the auto delete cache. if (!isSamplerTexture) { _cache.Add(texture); texture.Modified += CacheTextureModified; texture.Disposed += CacheTextureDisposed; } _textures.Add(texture); ShrinkOverlapsBufferIfNeeded(); return(texture); }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="info">Texture information of the texture to be found or created</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None, Size?sizeHint = null) { bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; bool isScalable = IsUpscaleCompatible(info); TextureScaleMode scaleMode = TextureScaleMode.Blacklisted; if (isScalable) { scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; } int sameAddressOverlapsCount; lock (_textures) { // Try to find a perfect texture match, with the same address and parameters. sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); } for (int index = 0; index < sameAddressOverlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (overlap.IsPerfectMatch(info, flags)) { if (!isSamplerTexture) { // If not a sampler texture, it is managed by the auto delete // cache, ensure that it is on the "top" of the list to avoid // deletion. _cache.Lift(overlap); } ChangeSizeIfNeeded(info, overlap, isSamplerTexture, sizeHint); overlap.SynchronizeMemory(); return(overlap); } } // 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); } // Find view compatible matches. ulong size = (ulong)sizeInfo.TotalSize; int overlapsCount; lock (_textures) { overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); } Texture texture = null; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel); if (overlapCompatibility == TextureViewCompatibility.Full) { TextureInfo oInfo = AdjustSizes(overlap, info, firstLevel); if (!isSamplerTexture) { info = oInfo; } texture = overlap.CreateView(oInfo, sizeInfo, firstLayer, firstLevel); if (overlap.IsModified) { texture.SignalModified(); } ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); break; } else if (overlapCompatibility == TextureViewCompatibility.CopyOnly) { // TODO: Copy rules for targets created after the container texture. See below. overlap.DisableMemoryTracking(); } } // No match, create a new texture. if (texture == null) { texture = new Texture(_context, info, sizeInfo, scaleMode); // Step 1: Find textures that are view compatible with the new texture. // Any textures that are incompatible will contain garbage data, so they should be removed where possible. int viewCompatible = 0; bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; bool overlapInCache = overlap.CacheNode != null; TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel); if (compatibility != TextureViewCompatibility.Incompatible) { if (_overlapInfo.Length != _textureOverlaps.Length) { Array.Resize(ref _overlapInfo, _textureOverlaps.Length); } _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[viewCompatible++] = overlap; } else if (overlapInCache || !setData) { if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ) { // Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap. continue; } // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. // If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us, // it must be flushed before removal, so that the data is not lost. // If the texture was modified since its last use, then that data is probably meant to go into this texture. // If the data has been modified by the CPU, then it also shouldn't be flushed. bool modified = overlap.ConsumeModified(); bool flush = overlapInCache && !modified && (overlap.Address <texture.Address || overlap.EndAddress> texture.EndAddress) && overlap.HasViewCompatibleChild(texture); setData |= modified || flush; if (overlapInCache) { _cache.Remove(overlap, flush); } } } // We need to synchronize before copying the old view data to the texture, // otherwise the copied data would be overwritten by a future synchronization. texture.InitializeData(false, setData); for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility != TextureViewCompatibility.Full) { continue; // Copy only compatibilty. } TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); if (texture.ScaleFactor != overlap.ScaleFactor) { // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. texture.PropagateScale(overlap); } ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); overlap.HostTexture.CopyTo(newView, 0, 0); // Inherit modification from overlapping texture, do that before replacing // the view since the replacement operation removes it from the list. if (overlap.IsModified) { texture.SignalModified(); } overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); } // If the texture is a 3D texture, we need to additionally copy any slice // of the 3D texture to the newly created 3D texture. if (info.Target == Target.Texture3D && viewCompatible > 0) { // TODO: This copy can currently only happen when the 3D texture is created. // If a game clears and redraws the slices, we won't be able to copy the new data to the 3D texture. // Disable tracking to try keep at least the original data in there for as long as possible. texture.DisableMemoryTracking(); for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility != TextureViewCompatibility.Incompatible) { overlap.BlacklistScale(); overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel); if (overlap.IsModified) { texture.SignalModified(); } } } } } // Sampler textures are managed by the texture pool, all other textures // are managed by the auto delete cache. if (!isSamplerTexture) { _cache.Add(texture); } lock (_textures) { _textures.Add(texture); } ShrinkOverlapsBufferIfNeeded(); return(texture); }
/// <summary> /// Checks if the texture sizes of the supplied texture information matches this texture. /// </summary> /// <param name="info">Texture information to compare with</param> /// <returns>True if the size matches, false otherwise</returns> public bool SizeMatches(TextureInfo info) { return(SizeMatches(info, alignSizes: false)); }
/// <summary> /// Gets a texture creation information from texture information. /// This can be used to create new host textures. /// </summary> /// <param name="info">Texture information</param> /// <param name="caps">GPU capabilities</param> /// <param name="scale">Texture scale factor, to be applied to the texture size</param> /// <returns>The texture creation information</returns> public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps, float scale) { FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(info, caps); if (info.Target == Target.TextureBuffer) { // We assume that the host does not support signed normalized format // (as is the case with OpenGL), so we just use a unsigned format. // The shader will need the appropriate conversion code to compensate. switch (formatInfo.Format) { case Format.R8Snorm: formatInfo = new FormatInfo(Format.R8Sint, 1, 1, 1, 1); break; case Format.R16Snorm: formatInfo = new FormatInfo(Format.R16Sint, 1, 1, 2, 1); break; case Format.R8G8Snorm: formatInfo = new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2); break; case Format.R16G16Snorm: formatInfo = new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2); break; case Format.R8G8B8A8Snorm: formatInfo = new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4); break; case Format.R16G16B16A16Snorm: formatInfo = new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4); break; } } int width = info.Width / info.SamplesInX; int height = info.Height / info.SamplesInY; int depth = info.GetDepth() * info.GetLayers(); if (scale != 1f) { width = (int)MathF.Ceiling(width * scale); height = (int)MathF.Ceiling(height * scale); } return(new TextureCreateInfo( width, height, depth, info.Levels, info.Samples, formatInfo.BlockWidth, formatInfo.BlockHeight, formatInfo.BytesPerPixel, formatInfo.Format, info.DepthStencilMode, info.Target, info.SwizzleR, info.SwizzleG, info.SwizzleB, info.SwizzleA)); }
/// <summary> /// Checks if the potential child texture fits within the level and layer bounds of the parent. /// </summary> /// <param name="parent">Texture information for the parent</param> /// <param name="child">Texture information for the child</param> /// <param name="layer">Base layer of the child texture</param> /// <param name="level">Base level of the child texture</param> /// <returns>Full compatiblity if the child's layer and level count fit within the parent, incompatible otherwise</returns> public static TextureViewCompatibility ViewSubImagesInBounds(TextureInfo parent, TextureInfo child, int layer, int level) { if (level + child.Levels <= parent.Levels && layer + child.GetSlices() <= parent.GetSlices()) { return(TextureViewCompatibility.Full); } else { return(TextureViewCompatibility.LayoutIncompatible); } }