Start UIX.RenderApi OpenGL implementation

This commit is contained in:
Yoshi Askharoun
2026-07-25 15:24:59 -05:00
parent 651dcaba0b
commit 29ae596179
27 changed files with 1815 additions and 0 deletions
@@ -0,0 +1,67 @@
using System.Collections.Generic;
namespace Microsoft.Iris.Render.OpenGL
{
/// <summary>
/// Shared state machine for animations: play/pause/reset, repeat counting and the
/// async-notify event. Target property interpolation is intentionally minimal here
/// (see <see cref="GLKeyframeAnimation"/>); full per-frame evaluation is a stage-3 TODO.
/// </summary>
public abstract class GLAnimation : SharedRenderObject, IAnimation
{
public int RepeatCount { get; set; }
public bool IsPlaying { get; private set; }
public bool IsActive { get; private set; }
public bool AutoReset { get; set; }
public AnimationResetBehavior ResetBehavior { get; set; } = AnimationResetBehavior.LeaveCurrent;
public event AsyncNotifyHandler? AsyncNotifyEvent;
public virtual void Play()
{
IsPlaying = true;
IsActive = true;
}
public virtual void Pause() => IsPlaying = false;
public virtual void Reset()
{
IsPlaying = false;
IsActive = false;
}
public virtual void InstantAdvance(float advanceTime) { }
public virtual void InstantFinish()
{
IsPlaying = false;
IsActive = false;
}
protected void RaiseAsyncNotify(int cookie) => AsyncNotifyEvent?.Invoke(cookie);
/// <summary>Advance internal time. Called by the animation system each pulse.</summary>
internal virtual void Advance(int advanceMs) { }
}
public sealed class GLAnimationGroup : GLAnimation, IAnimationGroup
{
private readonly List<GLAnimation> m_members = new List<GLAnimation>();
public override void Play()
{
base.Play();
foreach (GLAnimation a in m_members)
a.Play();
}
internal void Add(GLAnimation animation) => m_members.Add(animation);
internal override void Advance(int advanceMs)
{
foreach (GLAnimation a in m_members)
a.Advance(advanceMs);
}
}
}
@@ -0,0 +1,60 @@
using System.Collections.Generic;
namespace Microsoft.Iris.Render.OpenGL
{
/// <summary>
/// Owns the set of live animations and advances them on each pulse. Pause/step/resume
/// gate whether time flows. Objects are created here so the session stays a thin factory.
/// </summary>
public sealed class GLAnimationSystem : IAnimationSystem
{
private readonly List<GLAnimation> m_animations = new List<GLAnimation>();
private bool m_paused;
public int UpdatesPerSecond { get; set; } = 60;
public float SpeedAdjustment { get; set; } = 1f;
public bool BackCompat { set { /* compatibility flag; no behavioral change */ } }
public IKeyframeAnimation CreateKeyframeAnimation(object objUser, AnimationInput initialValue)
{
var a = new GLKeyframeAnimation(initialValue);
m_animations.Add(a);
return a;
}
public IAnimationGroup CreateAnimationGroup(object objUser)
{
var g = new GLAnimationGroup();
m_animations.Add(g);
return g;
}
public IExternalAnimationInput CreateExternalAnimationInput(object objUser, IAnimationPropertyMap propertyMap)
=> new GLExternalAnimationInput(propertyMap);
public void PulseTimeAdvance(int nAdvanceMs)
{
if (m_paused)
return;
int scaled = (int)(nAdvanceMs * SpeedAdjustment);
foreach (GLAnimation a in m_animations)
{
if (a.IsPlaying)
a.Advance(scaled);
}
}
public void PauseAnimations() => m_paused = true;
public void StepAnimations(int nAdvanceMs)
{
foreach (GLAnimation a in m_animations)
{
if (a.IsPlaying)
a.Advance(nAdvanceMs);
}
}
public void ResumeAnimations() => m_paused = false;
}
}
@@ -0,0 +1,42 @@
using System.Collections.Generic;
namespace Microsoft.Iris.Render.OpenGL
{
/// <summary>
/// An externally-driven animation input. Providers publish named values that
/// animations can reference. We store the published values; wiring them into
/// keyframe evaluation is a stage-3 TODO alongside full animation support.
/// </summary>
public sealed class GLExternalAnimationInput : SharedRenderObject, IExternalAnimationInput
{
private static uint s_nextId = 1;
public GLExternalAnimationInput(IAnimationPropertyMap? propertyMap)
{
UniqueId = s_nextId++;
PropertyMap = propertyMap;
}
public uint UniqueId { get; }
internal IAnimationPropertyMap? PropertyMap { get; }
public IAnimationInputProvider CreateProvider(object objUser) => new GLAnimationInputProvider();
}
public sealed class GLAnimationInputProvider : SharedRenderObject, IAnimationInputProvider
{
private readonly Dictionary<string, object> m_values = new Dictionary<string, object>();
public void PublishFloat(string propertyName, float value) => m_values[propertyName] = value;
public void PublishVector2(string propertyName, Vector2 value) => m_values[propertyName] = value;
public void PublishVector3(string propertyName, Vector3 value) => m_values[propertyName] = value;
public void PublishVector4(string propertyName, Vector4 value) => m_values[propertyName] = value;
public void PublishQuaternion(string propertyName, Quaternion value) => m_values[propertyName] = value;
public void RevokeFloat(string propertyName) => m_values.Remove(propertyName);
public void RevokeVector2(string propertyName) => m_values.Remove(propertyName);
public void RevokeVector3(string propertyName) => m_values.Remove(propertyName);
public void RevokeVector4(string propertyName) => m_values.Remove(propertyName);
public void RevokeQuaternion(string propertyName) => m_values.Remove(propertyName);
}
}
@@ -0,0 +1,66 @@
using System.Collections.Generic;
namespace Microsoft.Iris.Render.OpenGL
{
/// <summary>
/// Keyframe animation. Stores keyframes, targets and events and runs the play-state
/// machine. Smooth per-frame evaluation and target property mutation are a stage-3
/// TODO; today it drives lifecycle/events so higher layers sequence correctly.
/// </summary>
public sealed class GLKeyframeAnimation : GLAnimation, IKeyframeAnimation
{
private readonly struct Target
{
public readonly IAnimatable Object;
public readonly string Property;
public readonly string? Mask;
public Target(IAnimatable o, string property, string? mask)
{
Object = o;
Property = property;
Mask = mask;
}
}
private readonly List<AnimationKeyframe> m_keyframes = new List<AnimationKeyframe>();
private readonly List<Target> m_targets = new List<Target>();
private readonly List<AnimationEvent> m_events = new List<AnimationEvent>();
public GLKeyframeAnimation(AnimationInput initialValue)
{
InitialValue = initialValue;
Type = initialValue.InputType;
}
public int KeyframeCount => m_keyframes.Count;
public AnimationInput InitialValue { get; }
public AnimationInput Reference { get; set; } = null!;
public AnimationInput Scale { get; set; } = null!;
public AnimationInputType Type { get; }
public void AddKeyframe(AnimationKeyframe keyframe) => m_keyframes.Add(keyframe);
public AnimationKeyframe GetKeyframe(int keyframeIndex) => m_keyframes[keyframeIndex];
public void SetKeyframe(int keyframeIndex, AnimationKeyframe keyframe) => m_keyframes[keyframeIndex] = keyframe;
public void AddTarget(IAnimatable targetObject, string targetProperty)
=> m_targets.Add(new Target(targetObject, targetProperty, null));
public void AddTarget(IAnimatable targetObject, string targetProperty, string targetPropertyMask)
=> m_targets.Add(new Target(targetObject, targetProperty, targetPropertyMask));
public void RemoveTarget(IAnimatable targetObject, string targetProperty, string targetPropertyMask)
=> m_targets.RemoveAll(t => ReferenceEquals(t.Object, targetObject)
&& t.Property == targetProperty && t.Mask == targetPropertyMask);
public void RemoveAllTargets() => m_targets.Clear();
public void AddStageEvent(AnimationStage animationStage, AnimationEvent animationEvent) => m_events.Add(animationEvent);
public void AddTimeEvent(float absoluteTime, AnimationEvent animationEvent) => m_events.Add(animationEvent);
public void AddProgressEvent(float progress, AnimationEvent animationEvent) => m_events.Add(animationEvent);
public void AddValueEvent(ValueEventCondition condition, AnimationInput reference, AnimationEvent animationEvent) => m_events.Add(animationEvent);
public void RemoveEvent(AnimationEvent animationEvent) => m_events.Remove(animationEvent);
public void RemoveAllEvents() => m_events.Clear();
}
}