public ManagedTexture2D( ScaledTexture texture, Texture2D reference, Vector2I dimensions, SurfaceFormat format, string name = null ) : base(reference.GraphicsDevice.IsDisposed ? DrawState.Device : reference.GraphicsDevice, dimensions.Width, dimensions.Height, UseMips, format) { this.Name = name ?? $"{reference.SafeName()} [RESAMPLED {(CompressionFormat)format}]"; Reference = reference.MakeWeak(); Texture = texture; Dimensions = dimensions - texture.BlockPadding; reference.Disposing += (_, _1) => OnParentDispose(); TotalAllocatedSize += this.SizeBytes(); ++TotalManagedTextures; Garbage.MarkOwned(format, dimensions.Area); Disposing += (_, _1) => { Garbage.UnmarkOwned(format, dimensions.Area); TotalAllocatedSize -= this.SizeBytes(); --TotalManagedTextures; }; }
internal bool TryGet(Texture2D texture, Bounds source, int expectedScale, out ScaledTexture result) { result = null; lock (texture.Meta()) { var Map = texture.Meta().SpriteTable; using (Lock.Shared) { var rectangleHash = SpriteHash(texture: texture, source: source, expectedScale: expectedScale); if (Map.TryGetValue(rectangleHash, out var scaledTexture)) { if (scaledTexture.Texture?.IsDisposed == true) { using (Lock.Promote) { Map.Clear(); } } else { if (scaledTexture.IsReady) { result = scaledTexture; } return(true); } } } } return(false); }
internal void Add(Texture2D reference, ScaledTexture texture, Bounds source, int expectedScale) { lock (reference.Meta()) { var Map = reference.Meta().SpriteTable; var rectangleHash = SpriteHash(texture: reference, source: source, expectedScale: expectedScale); using (Lock.Exclusive) { ScaledTextureReferences.Add(texture); Map.Add(rectangleHash, texture); } } }
internal void Add(Texture2D reference, ScaledTexture texture, Bounds source, uint expectedScale) { var rectangleHash = SpriteHash(texture: reference, source: source, expectedScale: expectedScale); var meta = reference.Meta(); using (Lock.Exclusive) { ScaledTextureReferences.Add(texture); using (meta.Lock.Exclusive) { meta.SpriteTable.Add(rectangleHash, texture); } } }
internal static void OnPresent() { if (TriggerGC) { ScaledTexture.PurgeTextures((Config.RequiredFreeMemory * Config.RequiredFreeMemoryHysterisis).NearestLong() * 1024 * 1024); //Garbage.Collect(); Garbage.Collect(compact: true, blocking: true, background: false); TriggerGC = false; } if (Config.AsyncScaling.CanFetchAndLoadSameFrame || !PushedUpdateThisFrame) { ScaledTexture.ProcessPendingActions(); } RemainingTexelFetchBudget = Config.AsyncScaling.ScalingBudgetPerFrameTexels; FetchedThisFrame = false; PushedUpdateThisFrame = false; ++CurrentFrame; }
internal void Remove(ScaledTexture scaledTexture, Texture2D texture) { try { lock (texture.Meta()) { var Map = texture.Meta().SpriteTable; using (Lock.Exclusive) { try { ScaledTextureReferences.Purge(); var removeElements = new List <ScaledTexture>(); foreach (var element in ScaledTextureReferences) { if (element == scaledTexture) { removeElements.Add(element); } } foreach (var element in removeElements) { ScaledTextureReferences.Remove(element); } } catch { } Map.Clear(); } } } finally { if (scaledTexture.Texture != null && !scaledTexture.Texture.IsDisposed) { Debug.TraceLn($"Disposing Active HD Texture: {scaledTexture.SafeName()}"); //scaledTexture.Texture.Dispose(); } } }
static internal ScaledTexture Get(Texture2D texture, Bounds source, int expectedScale) { if (!Validate(texture)) { return(null); } if (SpriteMap.TryGet(texture, source, expectedScale, out var scaleTexture)) { return(scaleTexture); } bool useAsync = (Config.AsyncScaling.EnabledForUnknownTextures || !texture.Name.IsBlank()) && (texture.Area() >= Config.AsyncScaling.MinimumSizeTexels); if (useAsync && Config.AsyncScaling.Enabled && !DrawState.GetUpdateToken(texture.Area()) && !texture.Meta().HasCachedData) { return(null); } if (Config.DiscardDuplicates) { // Check for duplicates with the same name. // TODO : We do have a synchronity issue here. We could purge before an asynchronous task adds the texture. // DiscardDuplicatesFrameDelay if (!texture.Name.IsBlank() && !Config.DiscardDuplicatesBlacklist.Contains(texture.SafeName())) { try { lock (DuplicateTable) { if (DuplicateTable.TryGetValue(texture.SafeName(), out var weakTexture)) { if (weakTexture.TryGetTarget(out var strongTexture)) { // Is it not the same texture, and the previous texture has not been accessed for at least 2 frames? if (strongTexture != texture && (DrawState.CurrentFrame - strongTexture.Meta().LastAccessFrame) > 2) { Debug.TraceLn($"Purging Duplicate Texture '{strongTexture.SafeName()}'"); DuplicateTable[texture.SafeName()] = texture.MakeWeak(); Purge(strongTexture); } } else { DuplicateTable[texture.SafeName()] = texture.MakeWeak(); } } else { DuplicateTable.Add(texture.SafeName(), texture.MakeWeak()); } } } catch (Exception ex) { ex.PrintWarning(); } } } bool isSprite = !ExcludeSprite(texture) && (!source.Offset.IsZero || source.Extent != texture.Extent()); var textureWrapper = new SpriteInfo(reference: texture, dimensions: source, expectedScale: expectedScale); ulong hash = Upscaler.GetHash(textureWrapper, isSprite); var newTexture = new ScaledTexture( assetName: texture.SafeName(), textureWrapper: textureWrapper, sourceRectangle: source, isSprite: isSprite, hash: hash, async: useAsync, expectedScale: expectedScale ); if (useAsync && Config.AsyncScaling.Enabled) { // It adds itself to the relevant maps. if (newTexture.IsReady && newTexture.Texture != null) { return(newTexture); } return(null); } else { return(newTexture); } }
static private ulong SpriteHash(Texture2D texture, Bounds source, int expectedScale) { return(Hashing.CombineHash(ScaledTexture.ExcludeSprite(texture) ? 0UL : source.Hash(), expectedScale.GetHashCode())); }
private static unsafe void CreateNewTexture( ScaledTexture texture, SpriteInfo input, bool desprite, bool isWater, in Bounds spriteBounds,
static internal ScaledTexture Get(Texture2D texture, Bounds source, uint expectedScale) { using var _ = Performance.Track(); if (SpriteMap.TryGet(texture, source, expectedScale, out var scaleTexture)) { return(scaleTexture); } if (!Validate(texture)) { return(null); } bool useAsync = (Config.AsyncScaling.EnabledForUnknownTextures || !texture.Anonymous()) && (texture.Area() >= Config.AsyncScaling.MinimumSizeTexels); // !texture.Meta().HasCachedData var estimatedDuration = GetTimer(texture: texture, async: useAsync).Estimate(texture.Area()); const float multiplier = 0.75f; var remainingTime = DrawState.RemainingFrameTime(multiplier: multiplier); if (DrawState.PushedUpdateWithin(1) && estimatedDuration >= remainingTime) { return(null); } // TODO : We should really only populate the average when we are performing an expensive operation like GetData. var begin = DateTime.Now; bool isSprite = (!source.Offset.IsZero || source.Extent != texture.Extent()); SpriteInfo textureWrapper; ulong hash; using (Performance.Track("new SpriteInfo")) textureWrapper = new SpriteInfo(reference: texture, dimensions: source, expectedScale: expectedScale); // If this is null, it can only happen due to something being blocked, so we should try again later. if (textureWrapper.Data == null) { return(null); } DrawState.PushedUpdateThisFrame = true; try { using (Performance.Track("Upscaler.GetHash")) hash = Upscaler.GetHash(textureWrapper, isSprite); var newTexture = new ScaledTexture( assetName: texture.SafeName(), textureWrapper: textureWrapper, sourceRectangle: source, isSprite: isSprite, hash: hash, async: useAsync, expectedScale: expectedScale ); if (useAsync && Config.AsyncScaling.Enabled) { // It adds itself to the relevant maps. if (newTexture.IsReady && newTexture.Texture != null) { return(newTexture); } return(null); } else { return(newTexture); } } finally { var averager = GetTimer(cached: textureWrapper.WasCached, async: useAsync); averager.Add(texture.Area(), DateTime.Now - begin); } }