name: color-operations
description: FlatRedBall ColorOperation enum (Add, Modulate, Color, etc.) shared across visual object types. Triggers: ColorOperation, mColorOperation, Sprite.ColorOperation, RenderableIpso, RenderableSkiaObject, AnimationFrame.ColorOperation, UpdateVertexColorsAccordingToAlpha.
Where it lives
ColorOperation enum: Engines/FlatRedBallXNA/FlatRedBall/Graphics/GraphicalEmumerations.cs.
It's not Sprite-specific — consumers include:
| Type | File | Notes |
|---|
Sprite | Engines/FlatRedBallXNA/FlatRedBall/Sprite.cs | Full support, all enum values; setter calls UpdateVertexColorsAccordingToAlpha |
AnimationFrame | Engines/FlatRedBallXNA/FlatRedBall/Graphics/Animation/AnimationFrame.cs | Nullable per-frame override, applied in Sprite.UpdateToAnimationFrame |
RenderableSkiaObject (Gum/SkiaSharp text, SVG) | Engines/SkiaGum/Renderables/RenderableSkiaObject.cs | Hardcoded to Modulate, read-only — does not respect Add/Subtract/etc. |
Renderer global state | Engines/FlatRedBallXNA/FlatRedBall/Graphics/Renderer.cs | Static ColorOperation property drives shader technique selection via _effectManager.GetVertexColorTechniqueFromColorOperation |
Gotchas
- Skia-rendered objects (Text/Gum via
RenderableSkiaObject) silently ignore non-Modulate operations — the IRenderableIpso.ColorOperation getter always returns Modulate, there's no setter. Don't expect Add/Subtract/etc. to work outside the XNA Sprite render path.
- Debug-only platform restriction: on iOS/Android/WinRT, setting
Sprite.ColorOperation to Add, Subtract, InterpolateColor, InverseTexture, Modulate2X, or Modulate4X throws in DEBUG builds (Sprite.cs ~line 410) — these ops aren't guaranteed cross-platform.
"AddSigned" is not ColorOperation.AddSubtract. It's a separate legacy string op-code handled only in GraphicalEnumerations.SetColors (GraphicalEmumerations.cs ~line 293), which biases desiredRed/Green/Blue by -127.5 and remaps to plain "Add" — no enum value, no VertexColorPacker involvement.
ColorOperation.Texture with a null Texture behaves like ColorOperation.Color (see Renderer.cs ~line 3349, Sprite.cs ~line 256) — a common "why does my untextured sprite still show color" trip-up.
Sprite.Red/Green/Blue clamp to [-1, 1], not [0, 1] (Sprite.cs ~line 301-304), and nothing downstream re-clamps before GPU upload. Packing (now VertexColorPacker.Pack, called from SpriteManager.cs ~line 2146) casts the float channel to uint via (uint)(255 * value). A negative float→uint cast here is undefined behavior per ECMA-335 (out-of-range float-to-unsigned conversion is unspecified) and is genuinely runtime-dependent, not just "clamps to 0": on .NET 8/x64 (this repo's actual net8.0 target) it wraps to byte 129; on .NET 10 the same code gives 0. Either way it is NOT a clean subtraction — never rely on negative Red/Green/Blue under ColorOperation.Add to produce predictable output; use ColorOperation.AddSubtract instead (bias/scale-encodes the signed value so it survives the UNORM byte round-trip predictably).