private static void DebugValidate(Bounds sourceBounds, XTexture2D referenceTexture) { Bounds referenceBounds = referenceTexture.Bounds; if (!referenceBounds.Contains(sourceBounds)) { EmitOverlappingWarning(sourceBounds, referenceTexture); } if (sourceBounds.Right < sourceBounds.Left || sourceBounds.Bottom < sourceBounds.Top) { EmitInvertedWarning(sourceBounds, referenceTexture); } if (sourceBounds.Degenerate) { EmitDegenerateWarning(sourceBounds, referenceTexture); } #if false if (source.Left < 0 || source.Top < 0 || source.Right >= reference.Width || source.Bottom >= reference.Height) { if (source.Right - reference.Width > 1 || source.Bottom - reference.Height > 1) { Debug.Warning($"Out of range source '{source}' for texture '{reference.SafeName()}' ({reference.Width}, {reference.Height})"); } } if (source.Right < source.Left || source.Bottom < source.Top) { Debug.Warning($"Inverted range source '{source}' for texture '{reference.SafeName()}'"); } #endif }
internal Drawable(XTexture2D texture, Bounds?source = null, float rotation = 0.0f, int offset = 0) { Texture = texture; Source = source; Rotation = Math.Clamp(rotation, 0.0f, MathF.PI * 2.0f); Offset = offset; }
private static void EmitDegenerateWarning(Bounds sourceBounds, XTexture2D referenceTexture) { if (referenceTexture is not InternalTexture2D && referenceTexture.Meta().ShouldReportError(ReportOnceErrors.DegenerateSource)) { Debug.Warning($"Degenerate sprite source '{sourceBounds}' for texture '{referenceTexture.NormalizedName()}' ({referenceTexture.Extent()})"); } }
internal Initializer(XTexture2D reference, Bounds dimensions, uint expectedScale, TextureType textureType, bool animated) { Reference = reference; BlendState = DrawState.CurrentBlendState; SamplerState = DrawState.CurrentSamplerState; ExpectedScale = expectedScale; Bounds = dimensions; IsPreview = Configuration.Preview.Override.Instance is not null; Scaler = Configuration.Preview.Override.Instance?.Scaler ?? Config.Resample.Scaler; ScalerGradient = Configuration.Preview.Override.Instance?.ScalerGradient ?? Config.Resample.ScalerGradient; TextureType = textureType; // Truncate the bounds so that it fits if it wouldn't otherwise fit if (!Bounds.ClampToChecked(Reference.Bounds, out var clampedBounds)) { Debug.Warning($"SpriteInfo for '{reference.NormalizedName()}' bounds '{dimensions}' are not contained in reference bounds '{(Bounds)reference.Bounds}'"); Bounds = clampedBounds; } var refMeta = reference.Meta(); var refData = refMeta.CachedData; if (refData is null) { // TODO : Switch this around to use ReadOnlySequence so our hash is specific to the sprite refData = new byte[reference.SizeBytes()]; Debug.Trace($"Reloading Texture Data (not in cache): {reference.NormalizedName(DrawingColor.LightYellow)}"); reference.GetData(refData); reference.Meta().CachedRawData = refData; if (refMeta.IsCompressed) { refData = null; // we can only use uncompressed data at this stage. } WasCached = false; } else if (refData == Texture2DMeta.BlockedSentinel) { refData = null; WasCached = false; } else { WasCached = true; } ReferenceData = refData; Hash = null; DataHash = null; if (Config.SuspendedCache.Enabled && animated) { (Hash, DataHash) = GetHash(); } }
private AnimatedTexture( XTexture2D texture, Vector2I size, Vector2I[] spriteOffsets, int ticksPerFrame, uint currentTick ) : base(texture) { Size = size; SpriteOffsets = spriteOffsets; TicksPerFrame = ticksPerFrame; CurrentTick = currentTick; }
private static unsafe void SetDataPurge <T>( XTexture2D texture, XRectangle?rect, ReadOnlySpan <T> data, int startIndex, int elementCount, bool animated ) where T : unmanaged { TextureCache.Remove(texture); if (!ManagedSpriteInstance.Validate(texture, clean: true)) { return; } if (texture.Format.IsBlock()) { ManagedSpriteInstance.FullPurge(texture, animated: animated); return; } #if ASYNC_SETDATA var byteData = Cacheable(texture) ? GetByteArray(data, startIndex, elementCount, out int elementSize) : null; ThreadQueue.Queue((data) => Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; ScaledTexture.Purge( reference: texture, bounds: rect, data: new DataRef <byte>( data: data, offset: startIndex * elementSize, length: elementCount * elementSize ) ), byteData); #else var byteData = Cacheable(texture) ? data : default; var span = byteData.IsEmpty ? default : byteData.Slice(startIndex, elementCount).AsBytes(); ManagedSpriteInstance.Purge( reference: texture, bounds: rect, data: new(span), animated: animated ); #endif }
private static bool CheckIsDataChanged <T>( XTexture2D instance, int level, int arraySlice, XRectangle?inRect, T[] data, int startIndex, int elementCount ) where T : unmanaged { Bounds rect = inRect ?? instance.Bounds; if (instance.TryMeta(out var meta) && meta.CachedData is { } cachedData) { var dataSpan = data.AsReadOnlySpan().Cast <T, byte>(); unsafe { var inSpan = dataSpan; int inOffset = 0; int inRowLength = rect.Width * sizeof(T); var cachedSpan = new Span2D <byte>( array: cachedData, offset: startIndex * sizeof(T), width: rect.Width * sizeof(T), height: rect.Height, pitch: (instance.Width - rect.Width) * sizeof(T) ); for (int y = 0; y < rect.Height; ++y) { var inSpanRow = inSpan.SliceUnsafe(inOffset, inRowLength); var cachedSpanRow = cachedSpan.GetRowSpan(y); if (!inSpanRow.SequenceEqual(cachedSpanRow)) { return(true); } inOffset += inRowLength; } return(false); } } return(true); }
internal static Span <byte> Decode(ReadOnlySpan <byte> data, Vector2I size, SurfaceFormat format) { using var tempTexture = new DecodingTexture2D(DrawState.Device, size.Width, size.Height, false, format) { Name = "Decode Texture" }; tempTexture.SetData(data.ToArray()); using var pngStream = new MemoryStream(); tempTexture.SaveAsPng(pngStream, tempTexture.Width, tempTexture.Height); pngStream.Flush(); using var pngTexture = XTexture2D.FromStream(DrawState.Device, pngStream); var resultData = GC.AllocateUninitializedArray <byte>(pngTexture.Area() * sizeof(uint), pinned: true); pngTexture.GetData(resultData); return(resultData.AsSpan()); }
internal static bool IsFont(XTexture2D texture, Vector2I spriteSize, Vector2I sheetSize) { switch (texture.Format) { case SurfaceFormat.Dxt1: case SurfaceFormat.Dxt1SRgb: case SurfaceFormat.Dxt1a: case SurfaceFormat.Dxt3: case SurfaceFormat.Dxt3SRgb: case SurfaceFormat.Dxt5: case SurfaceFormat.Dxt5SRgb: return(Math.Min(spriteSize.MinOf, sheetSize.MinOf) >= 1); default: return(false); } }
public static void OnConstructTexture2D( XTexture2D __instance, GraphicsDevice graphicsDevice, int width, int height, bool mipmap, SurfaceFormat format, ref SurfaceType type, bool shared, int arraySize, ref bool __state ) { if (PlatformConstruct is not null && arraySize == 1 && type == SurfaceType.Texture) { type = SurfaceType.SwapChainRenderTarget; __state = true; }
internal ManagedTexture2D( ReadOnlyPinnedSpan <byte> .FixedSpan data, ManagedSpriteInstance instance, XTexture2D reference, Vector2I dimensions, SurfaceFormat format, string?name = null ) : base( graphicsDevice: reference.GraphicsDevice.IsDisposed ? DrawState.Device : reference.GraphicsDevice, width: dimensions.Width, height: dimensions.Height, mipmap: UseMips, format: format, type: PTexture2D.PlatformConstruct is null ? SurfaceType.Texture : SurfaceType.SwapChainRenderTarget, // this prevents the texture from being constructed immediately shared: UseShared, arraySize: 1 ) { if (PTexture2D.PlatformConstruct is not null && !GL.Texture2DExt.Construct(this, data, dimensions, UseMips, format, SurfaceType.Texture, UseShared)) { PTexture2D.PlatformConstruct(this, dimensions.X, dimensions.Y, UseMips, format, SurfaceType.Texture, UseShared); SetData(data.Array); } Name = name ?? $"{reference.NormalizedName()} [internal managed <{format}>]"; Reference = reference.MakeWeak(); SpriteInstance = instance; Dimensions = dimensions - instance.BlockPadding; reference.Disposing += OnParentDispose; Interlocked.Add(ref TotalAllocatedSize, (ulong)this.SizeBytes()); Interlocked.Increment(ref TotalManagedTextures); if (Configuration.Config.Garbage.ShouldCollectAccountOwnedTextures) { Garbage.MarkOwned(format, dimensions.Area); Marked = true; } }
private static bool Cacheable(XTexture2D texture) => texture.LevelCount <= 1;
internal Drawable(XTexture2D texture, Bounds?source, int rotationDegrees) : this(texture, source, DegreesToRadians(rotationDegrees)) { }
public static bool PatchImage(IAssetDataForImage __instance, XTexture2D source, XRectangle?sourceArea, XRectangle?targetArea, PatchMode patchMode) { if (!Config.SMAPI.ApplyPatchEnabled) { return(true); } // get texture if (source is null) { throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); } XTexture2D target = __instance.Data; // get areas sourceArea ??= new(0, 0, source.Width, source.Height); targetArea ??= new(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); // validate if (!source.Bounds.Contains(sourceArea.Value)) { throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); } if (!target.Bounds.Contains(targetArea.Value)) { throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); } if (sourceArea.Value.Size != targetArea.Value.Size) { throw new InvalidOperationException("The source and target areas must be the same size."); } if (GL.Texture2DExt.CopyTexture(source, sourceArea.Value, target, targetArea.Value, patchMode)) { return(false); } // get source data int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; var sourceData = GC.AllocateUninitializedArray <XColor>(pixelCount); source.GetData(0, sourceArea, sourceData, 0, pixelCount); // merge data in overlay mode if (patchMode == PatchMode.Overlay) { // get target data var targetData = GC.AllocateUninitializedArray <XColor>(pixelCount); target.GetData(0, targetArea, targetData, 0, pixelCount); // merge pixels for (int i = 0; i < sourceData.Length; i++) { var above = sourceData[i]; var below = targetData[i]; // shortcut transparency if (above.A < MinOpacity) { sourceData[i] = below; continue; } if (below.A < MinOpacity) { sourceData[i] = above; continue; } // merge pixels // This performs a conventional alpha blend for the pixels, which are already // premultiplied by the content pipeline. The formula is derived from // https://shawnhargreaves.com/blog/premultiplied-alpha.html. float alphaBelow = 1 - (above.A / 255f); sourceData[i] = new XColor( (int)(above.R + (below.R * alphaBelow)), // r (int)(above.G + (below.G * alphaBelow)), // g (int)(above.B + (below.B * alphaBelow)), // b Math.Max(above.A, below.A) // a ); } } // patch target texture target.SetData(0, targetArea, sourceData, 0, pixelCount); return(false); }
internal static void Unmark(XTexture2D texture) { texture.AssertNotNull(); Unmark(texture.SizeBytesLong()); }
private static bool GetIsSliced(Bounds bounds, XTexture2D reference, [NotNullWhen(true)] out Config.TextureRef?result) { return(reference.Meta().CheckSliced(bounds, out result)); }
internal static Vector2I Extent(this XTexture2D texture) => new(texture.Width, texture.Height);
internal static void PurgeHash(XTexture2D reference) { reference.Meta().CachedRawData = null; }
internal static int Area(this XTexture2D texture) => texture.Width * texture.Height;
protected MetaTexture(XTexture2D?texture) { Texture = texture ?? ThrowNullReferenceException <XTexture2D?>(nameof(texture)); }
internal static bool IsWater(Bounds bounds, XTexture2D texture) { return(bounds.Right <= 640 && bounds.Top >= 2000 && bounds.Extent.MinOf >= WaterBlock && texture.NormalizedName() == @"LooseSprites\Cursors"); }
internal static void Validate(Bounds sourceBounds, XTexture2D referenceTexture) { DebugValidate(sourceBounds, referenceTexture); }
internal static Span <byte> Decode(XTexture2D texture, ReadOnlySpan <byte> data) => Decoder.MonoBlockDecoder.Decode(data, texture.Extent(), texture.Format);
internal DrawInfo( ManagedSpriteInstance?instance, XTexture2D texture, Bounds destination, Bounds source, in XColor color,
protected MetaTexture(XTexture2D?texture) { Texture = texture ?? throw new NullReferenceException(nameof(texture)); }