using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture.Astc; using Ryujinx.Memory.Range; using System; using System.Collections.Generic; using System.Diagnostics; namespace Ryujinx.Graphics.Gpu.Image { /// /// Represents a cached GPU texture. /// class Texture : IMultiRangeItem, IDisposable { // How many updates we need before switching to the byte-by-byte comparison // modification check method. // This method uses much more memory so we want to avoid it if possible. private const int ByteComparisonSwitchThreshold = 4; private struct TexturePoolOwner { public TexturePool Pool; public int ID; } private GpuContext _context; private SizeInfo _sizeInfo; /// /// Texture format. /// public Format Format => Info.FormatInfo.Format; /// /// Texture target. /// public Target Target { get; private set; } /// /// Texture information. /// public TextureInfo Info { get; private set; } /// /// Host scale factor. /// public float ScaleFactor { get; private set; } /// /// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling. /// public TextureScaleMode ScaleMode { get; private set; } /// /// Group that this texture belongs to. Manages read/write memory tracking. /// public TextureGroup Group { get; private set; } /// /// Set when a texture has been modified by the Host GPU since it was last flushed. /// public bool IsModified { get; internal set; } /// /// Set when a texture has been changed size. This indicates that it may need to be /// changed again when obtained as a sampler. /// public bool ChangedSize { get; private set; } /// /// Set when a texture's GPU VA has ever been partially or fully unmapped. /// This indicates that the range must be fully checked when matching the texture. /// public bool ChangedMapping { get; private set; } private int _depth; private int _layers; public int FirstLayer { get; private set; } public int FirstLevel { get; private set; } private bool _hasData; private bool _dirty = true; private int _updateCount; private byte[] _currentData; private ITexture _arrayViewTexture; private Target _arrayViewTarget; private ITexture _flushHostTexture; private Texture _viewStorage; private List _views; /// /// Host texture. /// public ITexture HostTexture { get; private set; } /// /// Intrusive linked list node used on the auto deletion texture cache. /// public LinkedListNode CacheNode { get; set; } /// /// Event to fire when texture data is disposed. /// public event Action Disposed; /// /// Physical memory ranges where the texture data is located. /// public MultiRange Range { get; private set; } /// /// Layer size in bytes. /// public int LayerSize => _sizeInfo.LayerSize; /// /// Texture size in bytes. /// public ulong Size => (ulong)_sizeInfo.TotalSize; /// /// Whether or not the texture belongs is a view. /// public bool IsView => _viewStorage != this; private int _referenceCount; private List _poolOwners; /// /// Constructs a new instance of the cached GPU texture. /// /// GPU context that the texture belongs to /// Texture information /// Size information of the texture /// Physical memory ranges where the texture data is located /// The first layer of the texture, or 0 if the texture has no parent /// The first mipmap level of the texture, or 0 if the texture has no parent /// The floating point scale factor to initialize with /// The scale mode to initialize with private Texture( GpuContext context, TextureInfo info, SizeInfo sizeInfo, MultiRange range, int firstLayer, int firstLevel, float scaleFactor, TextureScaleMode scaleMode) { InitializeTexture(context, info, sizeInfo, range); FirstLayer = firstLayer; FirstLevel = firstLevel; ScaleFactor = scaleFactor; ScaleMode = scaleMode; InitializeData(true); } /// /// Constructs a new instance of the cached GPU texture. /// /// GPU context that the texture belongs to /// Texture information /// Size information of the texture /// Physical memory ranges where the texture data is located /// The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, MultiRange range, TextureScaleMode scaleMode) { ScaleFactor = 1f; // Texture is first loaded at scale 1x. ScaleMode = scaleMode; InitializeTexture(context, info, sizeInfo, range); } /// /// Common texture initialization method. /// This sets the context, info and sizeInfo fields. /// Other fields are initialized with their default values. /// /// GPU context that the texture belongs to /// Texture information /// Size information of the texture /// Physical memory ranges where the texture data is located private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, MultiRange range) { _context = context; _sizeInfo = sizeInfo; Range = range; SetInfo(info); _viewStorage = this; _views = new List(); _poolOwners = new List(); } /// /// Initializes the data for a texture. Can optionally initialize the texture with or without data. /// If the texture is a view, it will initialize memory tracking to be non-dirty. /// /// True if the texture is a view, false otherwise /// True if the texture is to be initialized with data public void InitializeData(bool isView, bool withData = false) { if (withData) { Debug.Assert(!isView); TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); SynchronizeMemory(); // Load the data. if (ScaleMode == TextureScaleMode.Scaled) { SetScale(GraphicsConfig.ResScale); // Scale the data up. } } else { _hasData = true; if (!isView) { // Don't update this texture the next time we synchronize. ConsumeModified(); if (ScaleMode == TextureScaleMode.Scaled) { // Don't need to start at 1x as there is no data to scale, just go straight to the target scale. ScaleFactor = GraphicsConfig.ResScale; } TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); } } } /// /// Initialize a new texture group with this texture as storage. /// /// True if the texture will have layer views /// True if the texture will have mip views public void InitializeGroup(bool hasLayerViews, bool hasMipViews) { Group = new TextureGroup(_context, this); Group.Initialize(ref _sizeInfo, hasLayerViews, hasMipViews); } /// /// Create a texture view from this texture. /// A texture view is defined as a child texture, from a sub-range of their parent texture. /// For example, the initial layer and mipmap level of the view can be defined, so the texture /// will start at the given layer/level of the parent texture. /// /// Child texture information /// Child texture size information /// Physical memory ranges where the texture data is located /// Start layer of the child texture on the parent texture /// Start mipmap level of the child texture on the parent texture /// The child texture public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, MultiRange range, int firstLayer, int firstLevel) { Texture texture = new Texture( _context, info, sizeInfo, range, FirstLayer + firstLayer, FirstLevel + firstLevel, ScaleFactor, ScaleMode); TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities, ScaleFactor); texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel); _viewStorage.AddView(texture); return texture; } /// /// Adds a child texture to this texture. /// /// The child texture private void AddView(Texture texture) { IncrementReferenceCount(); _views.Add(texture); texture._viewStorage = this; Group.UpdateViews(_views); if (texture.Group != null && texture.Group != Group) { if (texture.Group.Storage == texture) { // This texture's group is no longer used. Group.Inherit(texture.Group); texture.Group.Dispose(); } } texture.Group = Group; } /// /// Removes a child texture from this texture. /// /// The child texture private void RemoveView(Texture texture) { _views.Remove(texture); texture._viewStorage = texture; DecrementReferenceCount(); } /// /// Create a copy dependency to a texture that is view compatible with this one. /// When either texture is modified, the texture data will be copied to the other to keep them in sync. /// This is essentially an emulated view, useful for handling multiple view parents or format incompatibility. /// This also forces a copy on creation, to or from the given texture to get them in sync immediately. /// /// The view compatible texture to create a dependency to /// The base layer of the given texture relative to this one /// The base level of the given texture relative to this one /// True if this texture is first copied to the given one, false for the opposite direction public void CreateCopyDependency(Texture contained, int layer, int level, bool copyTo) { if (contained.Group == Group) { return; } Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo); } /// /// Changes the texture size. /// /// /// This operation may also change the size of all mipmap levels, including from the parent /// and other possible child textures, to ensure that all sizes are consistent. /// /// The new texture width /// The new texture height /// The new texture depth (for 3D textures) or layers (for layered textures) public void ChangeSize(int width, int height, int depthOrLayers) { int blockWidth = Info.FormatInfo.BlockWidth; int blockHeight = Info.FormatInfo.BlockHeight; width <<= FirstLevel; height <<= FirstLevel; if (Target == Target.Texture3D) { depthOrLayers <<= FirstLevel; } else { depthOrLayers = _viewStorage.Info.DepthOrLayers; } _viewStorage.RecreateStorageOrView(width, height, blockWidth, blockHeight, depthOrLayers); foreach (Texture view in _viewStorage._views) { int viewWidth = Math.Max(1, width >> view.FirstLevel); int viewHeight = Math.Max(1, height >> view.FirstLevel); int viewDepthOrLayers; if (view.Info.Target == Target.Texture3D) { viewDepthOrLayers = Math.Max(1, depthOrLayers >> view.FirstLevel); } else { viewDepthOrLayers = view.Info.DepthOrLayers; } view.RecreateStorageOrView(viewWidth, viewHeight, blockWidth, blockHeight, viewDepthOrLayers); } } /// /// Recreates the texture storage (or view, in the case of child textures) of this texture. /// This allows recreating the texture with a new size. /// A copy is automatically performed from the old to the new texture. /// /// The new texture width /// The new texture height /// The block width related to the given width /// The block height related to the given height /// The new texture depth (for 3D textures) or layers (for layered textures) private void RecreateStorageOrView(int width, int height, int blockWidth, int blockHeight, int depthOrLayers) { RecreateStorageOrView( BitUtils.DivRoundUp(width * Info.FormatInfo.BlockWidth, blockWidth), BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight), depthOrLayers); } /// /// Recreates the texture storage (or view, in the case of child textures) of this texture. /// This allows recreating the texture with a new size. /// A copy is automatically performed from the old to the new texture. /// /// The new texture width /// The new texture height /// The new texture depth (for 3D textures) or layers (for layered textures) private void RecreateStorageOrView(int width, int height, int depthOrLayers) { ChangedSize = true; SetInfo(new TextureInfo( Info.GpuAddress, 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)); TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); if (_viewStorage != this) { ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, FirstLayer, FirstLevel)); } else { ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor); HostTexture.CopyTo(newStorage, 0, 0); ReplaceStorage(newStorage); } } /// /// Blacklists this texture from being scaled. Resets its scale to 1 if needed. /// public void BlacklistScale() { ScaleMode = TextureScaleMode.Blacklisted; SetScale(1f); } /// /// Propagates the scale between this texture and another to ensure they have the same scale. /// If one texture is blacklisted from scaling, the other will become blacklisted too. /// /// The other texture public void PropagateScale(Texture other) { if (other.ScaleMode == TextureScaleMode.Blacklisted || ScaleMode == TextureScaleMode.Blacklisted) { BlacklistScale(); other.BlacklistScale(); } else { // Prefer the configured scale if present. If not, prefer the max. float targetScale = GraphicsConfig.ResScale; float sharedScale = (ScaleFactor == targetScale || other.ScaleFactor == targetScale) ? targetScale : Math.Max(ScaleFactor, other.ScaleFactor); SetScale(sharedScale); other.SetScale(sharedScale); } } /// /// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale. /// /// Scale factor /// Texture to use instead of creating one /// A host texture containing a scaled version of this texture private ITexture GetScaledHostTexture(float scale, ITexture storage = null) { if (storage == null) { TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, scale); storage = _context.Renderer.CreateTexture(createInfo, scale); } HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true); return storage; } /// /// Sets the Scale Factor on this texture, and immediately recreates it at the correct size. /// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost. /// If scale is equivalent, this only propagates the blacklisted/scaled mode. /// If called on a view, its storage is resized instead. /// When resizing storage, all texture views are recreated. /// /// The new scale factor for this texture public void SetScale(float scale) { TextureScaleMode newScaleMode = ScaleMode == TextureScaleMode.Blacklisted ? ScaleMode : TextureScaleMode.Scaled; if (_viewStorage != this) { _viewStorage.ScaleMode = newScaleMode; _viewStorage.SetScale(scale); return; } if (ScaleFactor != scale) { Logger.Debug?.Print(LogClass.Gpu, $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()} to ({ScaleFactor} to {scale}). "); ScaleFactor = scale; ITexture newStorage = GetScaledHostTexture(ScaleFactor); Logger.Debug?.Print(LogClass.Gpu, $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}"); ReplaceStorage(newStorage); // All views must be recreated against the new storage. foreach (var view in _views) { Logger.Debug?.Print(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()}."); view.ScaleFactor = scale; TextureCreateInfo viewCreateInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, scale); ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel); view.ReplaceStorage(newView); view.ScaleMode = newScaleMode; } } if (ScaleMode != newScaleMode) { ScaleMode = newScaleMode; foreach (var view in _views) { view.ScaleMode = newScaleMode; } } } /// /// Checks if the memory for this texture was modified, and returns true if it was. /// The modified flags are consumed as a result. /// /// True if the texture was modified, false otherwise. public bool ConsumeModified() { return Group.ConsumeDirty(this); } /// /// Synchronizes guest and host memory. /// This will overwrite the texture data with the texture data on the guest memory, if a CPU /// modification is detected. /// Be aware that this can cause texture data written by the GPU to be lost, this is just a /// one way copy (from CPU owned to GPU owned memory). /// public void SynchronizeMemory() { if (Target == Target.TextureBuffer) { return; } if (!_dirty) { return; } _dirty = false; if (_hasData) { Group.SynchronizeMemory(this); } else { Group.ConsumeDirty(this); SynchronizeFull(); } } /// /// Signal that this texture is dirty, indicating that the texture group must be checked. /// public void SignalGroupDirty() { _dirty = true; } /// /// Fully synchronizes guest and host memory. /// This will replace the entire texture with the data present in guest memory. /// public void SynchronizeFull() { if (_hasData) { BlacklistScale(); } ReadOnlySpan data = _context.PhysicalMemory.GetSpan(Range); IsModified = false; // If the host does not support ASTC compression, we need to do the decompression. // The decompression is slow, so we want to avoid it as much as possible. // This does a byte-by-byte check and skips the update if the data is equal in this case. // This improves the speed on applications that overwrites ASTC data without changing anything. if (Info.FormatInfo.Format.IsAstc() && !_context.Capabilities.SupportsAstcCompression) { if (_updateCount < ByteComparisonSwitchThreshold) { _updateCount++; } else { bool dataMatches = _currentData != null && data.SequenceEqual(_currentData); _currentData = data.ToArray(); if (dataMatches) { return; } } } data = ConvertToHostCompatibleFormat(data); HostTexture.SetData(data); _hasData = true; } /// /// Uploads new texture data to the host GPU. /// /// New data public void SetData(ReadOnlySpan data) { BlacklistScale(); Group.ConsumeDirty(this); IsModified = false; HostTexture.SetData(data); _hasData = true; } /// /// Uploads new texture data to the host GPU for a specific layer/level. /// /// New data /// Target layer /// Target level public void SetData(ReadOnlySpan data, int layer, int level) { BlacklistScale(); HostTexture.SetData(data, layer, level); _currentData = null; _hasData = true; } /// /// Converts texture data to a format and layout that is supported by the host GPU. /// /// Data to be converted /// Converted data public ReadOnlySpan ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) { int width = Info.Width; int height = Info.Height; int depth = single ? 1 : _depth; int layers = single ? 1 : _layers; int levels = single ? 1 : Info.Levels; width = Math.Max(width >> level, 1); height = Math.Max(height >> level, 1); depth = Math.Max(depth >> level, 1); if (Info.IsLinear) { data = LayoutConverter.ConvertLinearStridedToLinear( width, height, Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockHeight, Info.Stride, Info.FormatInfo.BytesPerPixel, data); } else { data = LayoutConverter.ConvertBlockLinearToLinear( width, height, depth, levels, layers, Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockHeight, Info.FormatInfo.BytesPerPixel, Info.GobBlocksInY, Info.GobBlocksInZ, Info.GobBlocksInTileX, _sizeInfo, data); } // Handle compressed cases not supported by the host: // - ASTC is usually not supported on desktop cards. // - BC4/BC5 is not supported on 3D textures. if (!_context.Capabilities.SupportsAstcCompression && Info.FormatInfo.Format.IsAstc()) { if (!AstcDecoder.TryDecodeToRgba8P( data.ToArray(), Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockHeight, width, height, depth, levels, layers, out Span decoded)) { string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); } data = decoded; } else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc4()) { data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc4Snorm); } else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc5()) { data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc5Snorm); } return data; } /// /// Flushes the texture data. /// This causes the texture data to be written back to guest memory. /// If the texture was written by the GPU, this includes all modification made by the GPU /// up to this point. /// Be aware that this is an expensive operation, avoid calling it unless strictly needed. /// This may cause data corruption if the memory is already being used for something else on the CPU side. /// /// Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either. public void Flush(bool tracked = true) { IsModified = false; if (TextureCompatibility.IsFormatHostIncompatible(Info, _context.Capabilities)) { return; // Flushing this format is not supported, as it may have been converted to another host format. } if (tracked) { _context.PhysicalMemory.Write(Range, GetTextureDataFromGpu(tracked)); } else { _context.PhysicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(tracked)); } } /// /// Flushes the texture data, to be called from an external thread. /// The host backend must ensure that we have shared access to the resource from this thread. /// This is used when flushing from memory access handlers. /// public void ExternalFlush(ulong address, ulong size) { if (!IsModified) { return; } _context.Renderer.BackgroundContextAction(() => { IsModified = false; if (TextureCompatibility.IsFormatHostIncompatible(Info, _context.Capabilities)) { return; // Flushing this format is not supported, as it may have been converted to another host format. } if (Info.Target == Target.Texture2DMultisample || Info.Target == Target.Texture2DMultisampleArray) { return; // Flushing multisample textures is not supported, the host does not allow getting their data. } ITexture texture = HostTexture; if (ScaleFactor != 1f) { // If needed, create a texture to flush back to host at 1x scale. texture = _flushHostTexture = GetScaledHostTexture(1f, _flushHostTexture); } _context.PhysicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(false, texture)); }); } /// /// Gets data from the host GPU. /// /// /// This method should be used to retrieve data that was modified by the host GPU. /// This is not cheap, avoid doing that unless strictly needed. /// /// Host texture data private Span GetTextureDataFromGpu(bool blacklist, ITexture texture = null) { Span data; if (texture != null) { data = texture.GetData(); } else { if (blacklist) { BlacklistScale(); data = HostTexture.GetData(); } else if (ScaleFactor != 1f) { float scale = ScaleFactor; SetScale(1f); data = HostTexture.GetData(); SetScale(scale); } else { data = HostTexture.GetData(); } } if (Target != Target.TextureBuffer) { if (Info.IsLinear) { data = LayoutConverter.ConvertLinearToLinearStrided( Info.Width, Info.Height, Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockHeight, Info.Stride, Info.FormatInfo.BytesPerPixel, data); } else { data = LayoutConverter.ConvertLinearToBlockLinear( Info.Width, Info.Height, _depth, Info.Levels, _layers, Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockHeight, Info.FormatInfo.BytesPerPixel, Info.GobBlocksInY, Info.GobBlocksInZ, Info.GobBlocksInTileX, _sizeInfo, data); } } return data; } /// /// This performs a strict comparison, used to check if this texture is equal to the one supplied. /// /// Texture information to compare against /// Comparison flags /// A value indicating how well this texture matches the given info public TextureMatchQuality IsExactMatch(TextureInfo info, TextureSearchFlags flags) { TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, (flags & TextureSearchFlags.ForSampler) != 0, (flags & TextureSearchFlags.ForCopy) != 0); if (matchQuality == TextureMatchQuality.NoMatch) { return matchQuality; } if (!TextureCompatibility.LayoutMatches(Info, info)) { return TextureMatchQuality.NoMatch; } if (!TextureCompatibility.SizeMatches(Info, info, (flags & TextureSearchFlags.Strict) == 0, FirstLevel)) { return TextureMatchQuality.NoMatch; } if ((flags & TextureSearchFlags.ForSampler) != 0 || (flags & TextureSearchFlags.Strict) != 0) { if (!TextureCompatibility.SamplerParamsMatches(Info, info)) { return TextureMatchQuality.NoMatch; } } if ((flags & TextureSearchFlags.ForCopy) != 0) { bool msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D; if (!msTargetCompatible && !TextureCompatibility.TargetAndSamplesCompatible(Info, info)) { return TextureMatchQuality.NoMatch; } } else if (!TextureCompatibility.TargetAndSamplesCompatible(Info, info)) { return TextureMatchQuality.NoMatch; } return Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch; } /// /// Check if it's possible to create a view, with the given parameters, from this texture. /// /// Texture view information /// Texture view physical memory ranges /// Texture view initial layer on this texture /// Texture view first mipmap level on this texture /// The level of compatiblilty a view with the given parameters created from this texture has public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, out int firstLayer, out int firstLevel) { int offset = Range.FindOffset(range); // Out of range. if (offset < 0) { firstLayer = 0; firstLevel = 0; return TextureViewCompatibility.Incompatible; } if (!_sizeInfo.FindView(offset, out firstLayer, out firstLevel)) { return TextureViewCompatibility.Incompatible; } if (!TextureCompatibility.ViewLayoutCompatible(Info, info, firstLevel)) { return TextureViewCompatibility.Incompatible; } if (info.GetSlices() > 1 && LayerSize != layerSize) { return TextureViewCompatibility.Incompatible; } TextureViewCompatibility result = TextureViewCompatibility.Full; result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel)); if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat) { // AMD and Intel have a bug where the view format is always ignored; // they use the parent format instead. // Create a copy dependency to avoid this issue. result = TextureViewCompatibility.CopyOnly; } return (Info.SamplesInX == info.SamplesInX && Info.SamplesInY == info.SamplesInY) ? result : TextureViewCompatibility.Incompatible; } /// /// Gets a texture of the specified target type from this texture. /// This can be used to get an array texture from a non-array texture and vice-versa. /// If this texture and the requested targets are equal, then this texture Host texture is returned directly. /// /// The desired target type /// A view of this texture with the requested target, or null if the target is invalid for this texture public ITexture GetTargetTexture(Target target) { if (target == Target) { return HostTexture; } if (_arrayViewTexture == null && IsSameDimensionsTarget(target)) { TextureCreateInfo createInfo = new TextureCreateInfo( Info.Width, Info.Height, target == Target.CubemapArray ? 6 : 1, Info.Levels, Info.Samples, Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockHeight, Info.FormatInfo.BytesPerPixel, Info.FormatInfo.Format, Info.DepthStencilMode, target, Info.SwizzleR, Info.SwizzleG, Info.SwizzleB, Info.SwizzleA); ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0); _arrayViewTexture = viewTexture; _arrayViewTarget = target; return viewTexture; } else if (_arrayViewTarget == target) { return _arrayViewTexture; } return null; } /// /// Check if this texture and the specified target have the same number of dimensions. /// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have /// the same number of dimensions. Same for Cubemap and 3D textures. /// /// The target to compare with /// True if both targets have the same number of dimensions, false otherwise private bool IsSameDimensionsTarget(Target target) { switch (Info.Target) { case Target.Texture1D: case Target.Texture1DArray: return target == Target.Texture1D || target == Target.Texture1DArray; case Target.Texture2D: case Target.Texture2DArray: return target == Target.Texture2D || target == Target.Texture2DArray; case Target.Cubemap: case Target.CubemapArray: return target == Target.Cubemap || target == Target.CubemapArray; case Target.Texture2DMultisample: case Target.Texture2DMultisampleArray: return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray; case Target.Texture3D: return target == Target.Texture3D; } return false; } /// /// Replaces view texture information. /// This should only be used for child textures with a parent. /// /// The parent texture /// The new view texture information /// The new host texture /// The first layer of the view /// The first level of the view public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel) { IncrementReferenceCount(); parent._viewStorage.SynchronizeMemory(); // If this texture has views, they must be given to the new parent. if (_views.Count > 0) { Texture[] viewCopy = _views.ToArray(); foreach (Texture view in viewCopy) { TextureCreateInfo createInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor); ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel); view.ReplaceView(parent, view.Info, newView, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel); } } ReplaceStorage(hostTexture); if (_viewStorage != this) { _viewStorage.RemoveView(this); } FirstLayer = parent.FirstLayer + firstLayer; FirstLevel = parent.FirstLevel + firstLevel; parent._viewStorage.AddView(this); SetInfo(info); DecrementReferenceCount(); } /// /// Sets the internal texture information structure. /// /// The new texture information private void SetInfo(TextureInfo info) { Info = info; Target = info.Target; _depth = info.GetDepth(); _layers = info.GetLayers(); } /// /// Signals that the texture has been modified. /// public void SignalModified() { bool wasModified = IsModified; if (!wasModified || Group.HasCopyDependencies) { IsModified = true; Group.SignalModified(this, !wasModified); } } /// /// Signals that a texture has been bound, or has been unbound. /// During this time, lazy copies will not clear the dirty flag. /// /// True if the texture has been bound, false if it has been unbound public void SignalModifying(bool bound) { bool wasModified = IsModified; if (!wasModified || Group.HasCopyDependencies) { IsModified = true; Group.SignalModifying(this, bound, !wasModified); } if (bound) { IncrementReferenceCount(); } else { DecrementReferenceCount(); } } /// /// Replaces the host texture, while disposing of the old one if needed. /// /// The new host texture private void ReplaceStorage(ITexture hostTexture) { DisposeTextures(); HostTexture = hostTexture; } /// /// Determine if any of our child textures are compaible as views of the given texture. /// /// The texture to check against /// True if any child is view compatible, false otherwise public bool HasViewCompatibleChild(Texture texture) { if (_viewStorage != this || _views.Count == 0) { return false; } foreach (Texture view in _views) { if (texture.IsViewCompatible(view.Info, view.Range, view.LayerSize, out _, out _) != TextureViewCompatibility.Incompatible) { return true; } } return false; } /// /// Increments the texture reference count. /// public void IncrementReferenceCount() { _referenceCount++; } /// /// Increments the reference count and records the given texture pool and ID as a pool owner. /// /// The texture pool this texture has been added to /// The ID of the reference to this texture in the pool public void IncrementReferenceCount(TexturePool pool, int id) { lock (_poolOwners) { _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id }); } _referenceCount++; } /// /// Decrements the texture reference count. /// When the reference count hits zero, the texture may be deleted and can't be used anymore. /// /// True if the texture is now referenceless, false otherwise public bool DecrementReferenceCount() { int newRefCount = --_referenceCount; if (newRefCount == 0) { if (_viewStorage != this) { _viewStorage.RemoveView(this); } _context.Methods.TextureManager.RemoveTextureFromCache(this); } Debug.Assert(newRefCount >= 0); DeleteIfNotUsed(); return newRefCount <= 0; } /// /// Decrements the texture reference count, also removing an associated pool owner reference. /// When the reference count hits zero, the texture may be deleted and can't be used anymore. /// /// The texture pool this texture is being removed from /// The ID of the reference to this texture in the pool /// True if the texture is now referenceless, false otherwise public bool DecrementReferenceCount(TexturePool pool, int id = -1) { lock (_poolOwners) { int references = _poolOwners.RemoveAll(entry => entry.Pool == pool && entry.ID == id || id == -1); if (references == 0) { // This reference has already been removed. return _referenceCount <= 0; } Debug.Assert(references == 1); } return DecrementReferenceCount(); } /// /// Forcibly remove this texture from all pools that reference it. /// /// Indicates if the removal is being done from another thread. public void RemoveFromPools(bool deferred) { lock (_poolOwners) { foreach (var owner in _poolOwners) { owner.Pool.ForceRemove(this, owner.ID, deferred); } _poolOwners.Clear(); } } /// /// Delete the texture if it is not used anymore. /// The texture is considered unused when the reference count is zero, /// and it has no child views. /// private void DeleteIfNotUsed() { // We can delete the texture as long it is not being used // in any cache (the reference count is 0 in this case), and // also all views that may be created from this texture were // already deleted (views count is 0). if (_referenceCount == 0 && _views.Count == 0) { Dispose(); } } /// /// Performs texture disposal, deleting the texture. /// private void DisposeTextures() { _currentData = null; HostTexture.Release(); _arrayViewTexture?.Release(); _arrayViewTexture = null; _flushHostTexture?.Release(); _flushHostTexture = null; } /// /// Called when the memory for this texture has been unmapped. /// Calls are from non-gpu threads. /// public void Unmapped() { ChangedMapping = true; IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped. RemoveFromPools(true); } /// /// Performs texture disposal, deleting the texture. /// public void Dispose() { DisposeTextures(); Disposed?.Invoke(this); if (Group.Storage == this) { Group.Dispose(); } } } }