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); }
public static bool OnDraw(XSpriteBatch __instance, XTexture2D?texture, XVector2 position, XRectangle?sourceRectangle, XColor color) { return(ForwardDraw( @this: __instance, texture: texture, position: position, sourceRectangle: sourceRectangle, color: color )); }
public static bool OnDraw(XSpriteBatch __instance, XTexture2D?texture, XVector2 position, XRectangle?sourceRectangle, XColor color, float rotation, XVector2 origin, float scale, SpriteEffects effects, float layerDepth) { return(ForwardDraw( @this: __instance, texture: texture, position: position, sourceRectangle: sourceRectangle, color: color, rotation: rotation, origin: origin, scale: new XVector2(scale), effects: effects, layerDepth: layerDepth )); }
public static bool OnDraw(XSpriteBatch __instance, ref XTexture2D?texture, ref XVector2 position, ref XRectangle?sourceRectangle, ref XColor color, float rotation, ref XVector2 origin, ref XVector2 scale, SpriteEffects effects, float layerDepth) { if (!Config.IsEnabled) { return(true); } return(__instance.OnDraw( texture: ref texture, position: ref position, source: ref sourceRectangle, color: ref color, rotation: rotation, origin: ref origin, scale: ref scale, effects: effects, layerDepth: ref layerDepth )); }
public static bool OnDraw(XSpriteBatch __instance, XTexture2D?texture, XRectangle destinationRectangle, XRectangle?sourceRectangle, XColor color) { return(ForwardDraw( @this: __instance, texture: texture, destinationRectangle: destinationRectangle, sourceRectangle: sourceRectangle, color: color )); }
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 bool OnDraw( this XSpriteBatch @this, ref XTexture2D?texture, ref XVector2 position, ref XRectangle?source, ref XColor color, float rotation, ref XVector2 origin, ref XVector2 scale, SpriteEffects effects, ref float layerDepth ) { if (texture is null) { return(false); } GetDrawParameters( texture: texture, source: source, bounds: out var sourceRectangle, scaleFactor: out var scaleFactor ); var originalSourceRect = sourceRectangle; ManagedSpriteInstance?spriteInstance; ManagedTexture2D? resampledTexture; if (texture is ManagedTexture2D texture2D) { resampledTexture = texture2D; spriteInstance = resampledTexture.SpriteInstance; sourceRectangle = resampledTexture.Dimensions; } else if (texture.FetchScaledTexture( expectedScale: EstimateScale(scale, scaleFactor), source: ref sourceRectangle, spriteInstance: out spriteInstance, create: true )) { spriteInstance.UpdateReferenceFrame(); resampledTexture = spriteInstance.Texture !; } else { resampledTexture = null; } if (spriteInstance is null || resampledTexture is null) { return(Continue); } if (originalSourceRect.X < 0) { position.X -= originalSourceRect.X * scale.X; } if (originalSourceRect.Y < 0) { position.Y -= originalSourceRect.Y * scale.Y; } var adjustedScale = (Vector2F)scale / spriteInstance.Scale; var adjustedPosition = position; var adjustedOrigin = (Vector2F)origin; if (spriteInstance.TexType == TextureType.SlicedImage) { sourceRectangle = source ?? resampledTexture.Bounds; if (source is not null) { sourceRectangle = new Bounds( (Vector2I)source.Value.Location - spriteInstance.OriginalSourceRectangle.Offset, source.Value.Size ); sourceRectangle.Offset = (sourceRectangle.OffsetF * spriteInstance.Scale).NearestInt(); sourceRectangle.Extent = (sourceRectangle.ExtentF * spriteInstance.Scale).NearestInt(); } } if (!spriteInstance.Padding.IsZero) { var paddingX = spriteInstance.Padding.X; var paddingY = spriteInstance.Padding.Y; if (effects.HasFlag(SpriteEffects.FlipHorizontally)) { paddingX = (paddingX.Y, paddingX.X); } if (effects.HasFlag(SpriteEffects.FlipVertically)) { paddingY = (paddingY.Y, paddingY.X); } var padding = new PaddingQuad(paddingX, paddingY); var textureSize = new Vector2F(sourceRectangle.Extent); var innerSize = (Vector2F)spriteInstance.UnpaddedSize; // This is the scale factor to bring the inner size to the draw size. var innerRatio = textureSize / innerSize; // spriteInstance.InnerRatio; // Scale the... scale by the scale factor. adjustedScale *= innerRatio; adjustedOrigin *= spriteInstance.Scale; adjustedOrigin /= innerRatio; adjustedOrigin += (Vector2F)padding.Offset; } else { adjustedOrigin *= spriteInstance.Scale; } if (source.HasValue) { sourceRectangle.Invert.X = source.Value.Width < 0; sourceRectangle.Invert.Y = source.Value.Height < 0; } if (Debug.Mode.RegisterDrawForSelect( instance: spriteInstance, texture: texture, originalPosition: position, originalSource: source, position: adjustedPosition, source: sourceRectangle, color: color, rotation: rotation, originalOrigin: origin, origin: adjustedOrigin, scale: adjustedScale, effects: effects, layerDepth: layerDepth )) { color = XColor.Red; } texture = resampledTexture; source = sourceRectangle; origin = adjustedOrigin; scale = adjustedScale; position = adjustedPosition; return(Continue); }
// Takes the arguments, and checks to see if the texture is padded. If it is, it is forwarded to the correct draw call, avoiding // intervening mods altering the arguments first. internal static bool OnDrawFirst( this XSpriteBatch @this, ref XTexture2D?texture, ref XRectangle destination, ref XRectangle?source, XColor color, float rotation, ref XVector2 origin, ref SpriteEffects effects, float layerDepth, ref ManagedTexture2D?__state ) { if (texture is null) { return(false); } using var watchdogScoped = WatchDog.WatchDog.ScopedWorkingState; /* * if (destination.Width < 0 || destination.Height < 0) { * Debug.Trace("destination invert"); * } * if (source is XRectangle sourceRect && (sourceRect.Width < 0 || sourceRect.Height < 0)) { * Debug.Trace("source invert"); * } */ GetDrawParameters( texture: texture, source: source, bounds: out var sourceRectangle, scaleFactor: out var scaleFactor ); var referenceRectangle = sourceRectangle; Bounds destinationBounds = destination; var expectedScale2D = destinationBounds.ExtentF / sourceRectangle.ExtentF; var expectedScale = EstimateScale(expectedScale2D, scaleFactor); if (!texture.FetchScaledTexture( expectedScale: expectedScale, source: ref sourceRectangle, spriteInstance: out var spriteInstance, create: true )) { return(Continue); } spriteInstance.UpdateReferenceFrame(); if (referenceRectangle.X < 0) { destinationBounds.Left -= referenceRectangle.X; } if (referenceRectangle.Y < 0) { destinationBounds.Top -= referenceRectangle.Y; } var resampledTexture = spriteInstance.Texture !; if (!spriteInstance.Padding.IsZero) { // Convert the draw into the other draw style. This has to be done because the padding potentially has // subpixel accuracy when scaled to the destination rectangle. var originalSize = referenceRectangle.ExtentF; var destinationSize = destinationBounds.ExtentF; var newScale = destinationSize / originalSize; var newPosition = destinationBounds.OffsetF; if ((destinationBounds.Invert.X || destinationBounds.Invert.Y) && DrawState.CurrentRasterizerState.CullMode == CullMode.CullCounterClockwiseFace) { // Winding order is invalid return(Stop); } if (destinationBounds.Invert.X) { effects ^= SpriteEffects.FlipHorizontally; } if (destinationBounds.Invert.Y) { effects ^= SpriteEffects.FlipVertically; } // TODO handle culling here for inverted sprites? @this.Draw( texture: resampledTexture, position: newPosition, sourceRectangle: sourceRectangle, color: color, rotation: rotation, origin: origin, scale: newScale, effects: effects, layerDepth: layerDepth ); return(Stop); } __state = resampledTexture; return(Continue); }
internal static bool OnDraw( this XSpriteBatch @this, ref XTexture2D?texture, ref XRectangle destination, ref XRectangle?source, ref XColor color, float rotation, ref XVector2 origin, ref SpriteEffects effects, ref float layerDepth, ref ManagedTexture2D?__state ) { if (texture is null) { return(false); } Bounds sourceRectangle; ManagedSpriteInstance?spriteInstance; ManagedTexture2D resampledTexture; Bounds destinationBounds = destination; var referenceSource = source.GetValueOrDefault(); if (__state is null) { GetDrawParameters( texture: texture, source: source, bounds: out sourceRectangle, scaleFactor: out var scaleFactor ); var expectedScale2D = new Vector2F(destinationBounds.Extent) / new Vector2F(sourceRectangle.Extent); var expectedScale = EstimateScale(expectedScale2D, scaleFactor); if (!texture.FetchScaledTexture( expectedScale: expectedScale, source: ref sourceRectangle, spriteInstance: out spriteInstance )) { return(Continue); } if (spriteInstance.TexType == TextureType.SlicedImage) { sourceRectangle = source ?? spriteInstance.Texture !.Bounds; } spriteInstance.UpdateReferenceFrame(); resampledTexture = spriteInstance.Texture !; } else { resampledTexture = __state; spriteInstance = resampledTexture.SpriteInstance; sourceRectangle = resampledTexture.Dimensions; if (spriteInstance.TexType == TextureType.SlicedImage) { sourceRectangle = source ?? resampledTexture.Bounds; if (source.HasValue) { sourceRectangle = new Bounds( (Vector2I)source.Value.Location - spriteInstance.OriginalSourceRectangle.Offset, source.Value.Size ); sourceRectangle.Offset = (sourceRectangle.OffsetF * spriteInstance.Scale).NearestInt(); sourceRectangle.Extent = (sourceRectangle.ExtentF * spriteInstance.Scale).NearestInt(); } } } if (referenceSource.X < 0) { destination.X -= referenceSource.X; } if (referenceSource.Y < 0) { destination.Y -= referenceSource.Y; } var scaledOrigin = (Vector2F)origin * spriteInstance.Scale; if (source.HasValue) { sourceRectangle.Invert.X = source.Value.Width < 0; sourceRectangle.Invert.Y = source.Value.Height < 0; } if (Debug.Mode.RegisterDrawForSelect( instance: spriteInstance, texture: texture, originalDestination: destinationBounds, destination: destination, source: sourceRectangle, color: color, rotation: rotation, originalOrigin: origin, origin: scaledOrigin, effects: effects, layerDepth: layerDepth )) { color = XColor.Red; } source = sourceRectangle; origin = scaledOrigin; texture = resampledTexture; return(Continue); }