internal ScaledTexture(string assetName, SpriteInfo textureWrapper, Bounds sourceRectangle, ulong hash, bool isSprite, bool async, uint expectedScale) { using var _ = Performance.Track(); IsSprite = isSprite; Hash = hash; var source = textureWrapper.Reference; this.OriginalSourceRectangle = new Bounds(sourceRectangle); this.Reference = source.MakeWeak(); this.sourceRectangle = sourceRectangle; this.refScale = expectedScale; SpriteMap.Add(source, this, sourceRectangle, expectedScale); this.Name = source.Anonymous() ? assetName.SafeName() : source.SafeName(); originalSize = IsSprite ? sourceRectangle.Extent : new Vector2I(source); if (async && Config.AsyncScaling.Enabled) { ThreadQueue.Queue((SpriteInfo wrapper) => { Thread.CurrentThread.Priority = ThreadPriority.Lowest; using var _ = new AsyncTracker($"Resampling {Name} [{sourceRectangle}]"); Thread.CurrentThread.Name = "Texture Resampling Thread"; Upscaler.Upscale( texture: this, scale: ref refScale, input: wrapper, desprite: IsSprite, hash: Hash, wrapped: ref Wrapped, async: true ); // If the upscale fails, the asynchronous action on the render thread automatically cleans up the ScaledTexture. }, textureWrapper); } else { // TODO store the HD Texture in _this_ object instead. Will confuse things like subtexture updates, though. this.Texture = Upscaler.Upscale( texture: this, scale: ref refScale, input: textureWrapper, desprite: IsSprite, hash: Hash, wrapped: ref Wrapped, async: false ); if (this.Texture != null) { Finish(); } else { Dispose(); return; } } // TODO : I would love to dispose of this texture _now_, but we rely on it disposing to know if we need to dispose of ours. // There is a better way to do this using weak references, I just need to analyze it further. Memory leaks have been a pain so far. source.Disposing += (object sender, EventArgs args) => { OnParentDispose((Texture2D)sender); }; lock (MostRecentList) { CurrentRecentNode = MostRecentList.AddFirst(this.MakeWeak()); } }
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); } }