/// <summary> /// Constructs a new instance of the cached GPU texture. /// </summary> /// <param name="context">GPU context that the texture belongs to</param> /// <param name="info">Texture information</param> /// <param name="sizeInfo">Size information of the texture</param> /// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param> public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, TextureScaleMode scaleMode) { ScaleFactor = 1f; // Texture is first loaded at scale 1x. ScaleMode = scaleMode; InitializeTexture(context, info, sizeInfo); }
public static Texture Scale(this Texture texture, int width, int height, TextureScaleMode scaleMode) { Texture2D texture2D = (Texture2D)TextureUtils.ConvertToReadableTexture(texture); texture2D.Scale2D(width, height, scaleMode); texture = texture2D; return(texture); }
/// <summary> /// 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. /// </summary> /// <param name="scale">The new scale factor for this texture</param> 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); 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; } } }
/// <summary> /// Constructs a new instance of the cached GPU texture. /// </summary> /// <param name="context">GPU context that the texture belongs to</param> /// <param name="info">Texture information</param> /// <param name="sizeInfo">Size information of the texture</param> /// <param name="firstLayer">The first layer of the texture, or 0 if the texture has no parent</param> /// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param> /// <param name="scaleFactor">The floating point scale factor to initialize with</param> /// <param name="scaleMode">The scale mode to initialize with</param> private Texture( GpuContext context, TextureInfo info, SizeInfo sizeInfo, int firstLayer, int firstLevel, float scaleFactor, TextureScaleMode scaleMode) { InitializeTexture(context, info, sizeInfo); _firstLayer = firstLayer; _firstLevel = firstLevel; ScaleFactor = scaleFactor; ScaleMode = scaleMode; InitializeData(true); }
/// <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; } 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); } 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; lock (_textures) { 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) == TextureViewCompatibility.Full) { if (!isSamplerTexture) { info = AdjustSizes(overlap, info, firstLevel); } texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); if (IsTextureModified(overlap)) { texture.SignalModified(); } // 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); // 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; 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); 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 (IsTextureModified(overlap)) { 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) { 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 (IsTextureModified(overlap)) { 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); texture.Modified += CacheTextureModified; texture.Disposed += CacheTextureDisposed; } lock (_textures) { _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> /// <returns>The texture</returns> public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None) { bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 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 (!overlap.SizeMatches(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 (!overlap.SizeMatches(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> /// Scale Texture. /// </summary> /// <param name="source"></param> /// <param name="scale"></param> /// <param name="textureScaleMode"></param> /// <returns></returns> /// <exception cref="ArgumentOutOfRangeException"></exception> public static Texture2D ScaleTexture(Texture2D source, float scale, TextureScaleMode textureScaleMode) { int i; // Get All the source pixels var aSourceColor = source.GetPixels(0); var vSourceSize = new Vector2(source.width, source.height); // Calculate New Size float xWidth = Mathf.RoundToInt(source.width * scale); float xHeight = Mathf.RoundToInt(source.height * scale); // Make New var oNewTex = new Texture2D((int)xWidth, (int)xHeight, TextureFormat.RGBA32, false); // Make destination array var xLength = (int)xWidth * (int)xHeight; var aColor = new Color[xLength]; var vPixelSize = new Vector2(vSourceSize.x / xWidth, vSourceSize.y / xHeight); // Loop through destination pixels and process var vCenter = new Vector2(); for (i = 0; i < xLength; i++) { // Figure out x&y var xX = i % xWidth; var xY = Mathf.Floor(i / xWidth); // Calculate Center vCenter.x = xX / xWidth * vSourceSize.x; vCenter.y = xY / xHeight * vSourceSize.y; // Do Based on mode // Nearest neighbour (testing) switch (textureScaleMode) { case TextureScaleMode.Nearest: { // Nearest neighbour (testing) vCenter.x = Mathf.Round(vCenter.x); vCenter.y = Mathf.Round(vCenter.y); // Calculate source index var xSourceIndex = (int)(vCenter.y * vSourceSize.x + vCenter.x); // Copy Pixel aColor[i] = aSourceColor[xSourceIndex]; break; } case TextureScaleMode.Bilinear: { // Get Ratios var xRatioX = vCenter.x - Mathf.Floor(vCenter.x); var xRatioY = vCenter.y - Mathf.Floor(vCenter.y); // Get Pixel index's var xIndexTl = (int)(Mathf.Floor(vCenter.y) * vSourceSize.x + Mathf.Floor(vCenter.x)); var xIndexTr = (int)(Mathf.Floor(vCenter.y) * vSourceSize.x + Mathf.Ceil(vCenter.x)); var xIndexBl = (int)(Mathf.Ceil(vCenter.y) * vSourceSize.x + Mathf.Floor(vCenter.x)); var xIndexBr = (int)(Mathf.Ceil(vCenter.y) * vSourceSize.x + Mathf.Ceil(vCenter.x)); // Calculate Color aColor[i] = Color.Lerp( Color.Lerp(aSourceColor[xIndexTl], aSourceColor[xIndexTr], xRatioX), Color.Lerp(aSourceColor[xIndexBl], aSourceColor[xIndexBr], xRatioX), xRatioY ); break; } case TextureScaleMode.Average: { // Calculate grid around point var xXFrom = (int)Mathf.Max(Mathf.Floor(vCenter.x - vPixelSize.x * 0.5f), 0); var xXTo = (int)Mathf.Min(Mathf.Ceil(vCenter.x + vPixelSize.x * 0.5f), vSourceSize.x); var xYFrom = (int)Mathf.Max(Mathf.Floor(vCenter.y - vPixelSize.y * 0.5f), 0); var xYTo = (int)Mathf.Min(Mathf.Ceil(vCenter.y + vPixelSize.y * 0.5f), vSourceSize.y); // Loop and accumulate var oColorTemp = new Color(); float xGridCount = 0; for (var iy = xYFrom; iy < xYTo; iy++) { for (var ix = xXFrom; ix < xXTo; ix++) { // Get Color oColorTemp += aSourceColor[(int)(iy * vSourceSize.x + ix)]; // Sum xGridCount++; } } // Average Color aColor[i] = oColorTemp / xGridCount; break; } default: throw new ArgumentOutOfRangeException(nameof(textureScaleMode), textureScaleMode, null); } } oNewTex.SetPixels(aColor); oNewTex.Apply(); return(oNewTex); }
/// <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); } else if (flags.HasFlag(TextureSearchFlags.NoCreate)) { return(null); } // Calculate texture sizes, used to find all overlapping textures. SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); ulong size = (ulong)sizeInfo.TotalSize; bool partiallyMapped = false; if (range == null) { range = memoryManager.GetPhysicalRegions(info.GpuAddress, size); for (int i = 0; i < range.Value.Count; i++) { if (range.Value.GetSubRange(i).Address == MemoryManager.PteUnmapped) { partiallyMapped = true; break; } } } // 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, add temporary references for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, _context.Capabilities, 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); overlap.IncrementReferenceCount(); } // 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, new List <TextureIncompatibleOverlap>()); 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.LayoutIncompatible) { if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility)) { texture.Group.RegisterIncompatibleOverlap(new TextureIncompatibleOverlap(overlap.Group, oInfo.Compatibility), true); } } else if (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; var incompatibleOverlaps = new List <TextureIncompatibleOverlap>(); 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, _context.Capabilities, out int firstLayer, out int firstLevel); if (overlap.IsView && compatibility == TextureViewCompatibility.Full) { compatibility = TextureViewCompatibility.CopyOnly; } if (compatibility > TextureViewCompatibility.LayoutIncompatible) { _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[index] = _textureOverlaps[viewCompatible]; _textureOverlaps[viewCompatible] = overlap; if (compatibility == TextureViewCompatibility.Full) { if (viewCompatible != fullyCompatible) { // 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++; } viewCompatible++; hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices(); hasMipViews |= overlap.Info.Levels < texture.Info.Levels; } else { bool dataOverlaps = texture.DataOverlaps(overlap, compatibility); if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group)) { incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility)); } bool removeOverlap; bool modified = overlap.CheckModified(false); if (overlapInCache || !setData) { if (!dataOverlaps) { // 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. // The texture group will obtain copy dependencies for any subresources that are compatible between the two textures, // but sometimes its data must be flushed regardless. // 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 flush = overlapInCache && !modified && overlap.AlwaysFlushOnOverlap; setData |= modified || flush; if (overlapInCache) { _cache.Remove(overlap, flush); } removeOverlap = modified; } 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, incompatibleOverlaps); // 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); texture.Group.InitializeOverlaps(); 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); } if (partiallyMapped) { lock (_partiallyMappedTextures) { _partiallyMappedTextures.Add(texture); } } ShrinkOverlapsBufferIfNeeded(); for (int i = 0; i < overlapsCount; i++) { _textureOverlaps[i].DecrementReferenceCount(); } return(texture); }
public static Texture2D Scale2D(this Texture2D texture, int newWidth, int newHeight, TextureScaleMode scaleMode) { Color[] curColors = texture.GetPixels(); Color[] newColors = new Color[newWidth * newHeight]; switch (scaleMode) { case TextureScaleMode.Bilinear: newColors = BilinearScale(curColors, texture.width, texture.height, newWidth, newHeight); break; case TextureScaleMode.Point: newColors = PointScale(curColors, texture.width, texture.height, newWidth, newHeight); break; } texture.Resize(newWidth, newHeight); texture.SetPixels(newColors); texture.Apply(); return(texture); }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <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(TextureSearchFlags flags, TextureInfo info, int layerSize = 0, Size?sizeHint = null, MultiRange?range = 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; } ulong address; if (range != null) { address = range.Value.GetSubRange(0).Address; } else { address = _context.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]; bool rangeMatches = range != null?overlap.Range.Equals(range.Value) : overlap.Info.GpuAddress == info.GpuAddress; if (!rangeMatches) { continue; } TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); if (matchQuality == TextureMatchQuality.Perfect) { texture = overlap; break; } else if (matchQuality > bestQuality) { texture = overlap; bestQuality = matchQuality; } } if (texture != null) { 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(texture); } 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 = _context.MemoryManager.GetPhysicalRegions(info.GpuAddress, size); } // Find view compatible matches. int overlapsCount; lock (_textures) { overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); } for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, 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, range.Value, 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, 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; 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.Range, 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 && !texture.Range.Contains(overlap.Range) && 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> /// Draws a texture. /// </summary> /// <param name="texture">The texture to draw.</param> /// <param name="position">The position where the texture will be drawn.</param> /// <param name="color">The color to multiply with the colors of the texture. If unspecified the colors of the texture will be unchanged.</param> /// <param name="size">The destination size of the texture. If unspecified the original texture size will be used.</param> /// <param name="rotation">The amount the texture will be rotated clockwise (in degrees). If unspecified the texture will not be rotated.</param> /// <param name="pivot">The offset from position to the pivot that the texture will be rotated about. If unspecified the center of the destination bounds will be used.</param> /// <param name="mirror">The mirroring to apply to the texture. If unspecified the texture will not be mirrored.</param> /// <param name="source">The source bounds of the texture to draw. If unspecified the entire texture will be drawn.</param> /// <param name="blendMode">The blend mode to use when drawing the texture. If unspecified the texture will be drawn using the standard alpha-based blend mode.</param> /// <param name="scaleMode">The scale mode to use when drawing the texture. If unspecified the texture will be linearly interpolated.</param> public static void DrawTexture(Texture texture, Vector2 position, Color?color = null, Vector2?size = null, float rotation = 0, Vector2?pivot = null, TextureMirror mirror = TextureMirror.None, Bounds2?source = null, TextureBlendMode blendMode = TextureBlendMode.Normal, TextureScaleMode scaleMode = TextureScaleMode.Linear) { DrawTextureSetup(texture.Handle, color, blendMode, scaleMode); SDL.SDL_Rect src; SDL.SDL_Rect dest; if (source.HasValue) { // Use the specified source coordinates: src.x = (int)source.Value.Position.X; src.y = (int)source.Value.Position.Y; src.w = (int)source.Value.Size.X; src.h = (int)source.Value.Size.Y; dest.x = (int)position.X; dest.y = (int)position.Y; dest.w = src.w; dest.h = src.h; } else { // Use the full texture as the source: src.x = 0; src.y = 0; src.w = texture.Width; src.h = texture.Height; dest.x = (int)position.X; dest.y = (int)position.Y; dest.w = texture.Width; dest.h = texture.Height; } // Apply the size override, if specified: if (size.HasValue) { dest.w = (int)size.Value.X; dest.h = (int)size.Value.Y; } // Apply the pivot override, if specified: SDL.SDL_Point center; if (pivot.HasValue) { center.x = (int)pivot.Value.X; center.y = (int)pivot.Value.Y; } else { center.x = dest.w / 2; center.y = dest.h / 2; } SDL.SDL_RenderCopyEx(Renderer, texture.Handle, ref src, ref dest, rotation, ref center, (SDL.SDL_RendererFlip)mirror); }
// ====================================================================================== // Texture drawing // ====================================================================================== private static void DrawTextureSetup(IntPtr textureHandle, Color?color, TextureBlendMode blendMode, TextureScaleMode scaleMode) { Color finalColor = color ?? Color.White; SDL.SDL_SetTextureColorMod(textureHandle, finalColor.R, finalColor.G, finalColor.B); SDL.SDL_SetTextureAlphaMod(textureHandle, finalColor.A); SDL.SDL_SetTextureBlendMode(textureHandle, (SDL.SDL_BlendMode)blendMode); SDL.SDL_SetTextureScaleMode(textureHandle, (SDL.SDL_ScaleMode)scaleMode); }
/// <summary> /// Draws a resizable texture. /// See the documentation for an explanation of how resizable textures work. /// </summary> /// <param name="texture">The resizable texture to draw.</param> /// <param name="bounds">The bounds that the texture should be resized to.</param> /// <param name="color">The color to multiply with the colors of the texture. If unspecified the colors of the texture will be unchanged.</param> /// <param name="blendMode">The blend mode to use when drawing the texture. If unspecified the texture will be drawn using the standard alpha-based blend mode.</param> /// <param name="scaleMode">The scale mode to use when drawing the texture. If unspecified the texture will be linearly interpolated.</param> public static void DrawResizableTexture(ResizableTexture texture, Bounds2 bounds, Color?color = null, TextureBlendMode blendMode = TextureBlendMode.Normal, TextureScaleMode scaleMode = TextureScaleMode.Linear) { DrawTextureSetup(texture.Handle, color, blendMode, scaleMode); /* * 0 bxmin bxmax txmax * v v v v * +----|-------------|----+ < tymax * | 1 | 5 | 2 | * -----+-------------+----- < bymax * | | | | * | | | | * | 6 | 9 | 7 | * | | | | * | | | | * -----+-------------+----- < bymin * | 3 | 8 | 4 | * +----|-------------|----+ < 0 */ int bxmin = texture.LeftOffset; int bxmax = texture.RightOffset; int bymin = texture.TopOffset; int bymax = texture.BottomOffset; int txmax = texture.Width; int tymax = texture.Height; int px = (int)bounds.Position.X; int py = (int)bounds.Position.Y; // Don't let the overall size be so small that segment 9 has a negative size in either dimension: int sx = Math.Max((int)bounds.Size.X, txmax - bxmax + bxmin); int sy = Math.Max((int)bounds.Size.Y, tymax - bymax + bymin); // Draw each of the nine segments: DrawResizableTextureSegment(texture, 0, 0, bxmin, bymin, px, py, bxmin, bymin); DrawResizableTextureSegment(texture, bxmax, 0, txmax - bxmax, bymin, px + sx - (txmax - bxmax), py, txmax - bxmax, bymin); DrawResizableTextureSegment(texture, 0, bymax, bxmin, tymax - bymax, px, py + sy - (tymax - bymax), bxmin, tymax - bymax); DrawResizableTextureSegment(texture, bxmax, bymax, txmax - bxmax, tymax - bymax, px + sx - (txmax - bxmax), py + sy - (tymax - bymax), txmax - bxmax, tymax - bymax); DrawResizableTextureSegment(texture, bxmin, 0, bxmax - bxmin, bymin, px + bxmin, py, sx - bxmin - (txmax - bxmax), bymin); DrawResizableTextureSegment(texture, 0, bymin, bxmin, bymax - bymin, px, py + bymin, bxmin, sy - bymin - (tymax - bymax)); DrawResizableTextureSegment(texture, bxmax, bymin, txmax - bxmax, bymax - bymin, px + sx - (txmax - bxmax), py + bymin, txmax - bxmax, sy - bymin - (tymax - bymax)); DrawResizableTextureSegment(texture, bxmin, bymax, bxmax - bxmin, tymax - bymax, px + bxmin, py + sy - (tymax - bymax), sx - bxmin - (txmax - bxmax), tymax - bymax); DrawResizableTextureSegment(texture, bxmin, bymin, bxmax - bxmin, bymax - bymin, px + bxmin, py + bymin, sx - bxmin - (txmax - bxmax), sy - bymin - (tymax - bymax)); }