mirror of
https://github.com/ZuneDev/MicrosoftIris.git
synced 2026-07-27 13:13:29 -07:00
Start UIX.RenderApi OpenGL implementation
This commit is contained in:
@@ -28,6 +28,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIXrender.Interop.Tests", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIXrender.Engine.Tests", "Tests\UIXrender.Engine.Tests\UIXrender.Engine.Tests.csproj", "{568E8DCD-CAB3-499B-A264-C62B1E1082B7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIXrender.Engine.Tests", "Tests\UIXrender.Engine.Tests\UIXrender.Engine.Tests.csproj", "{568E8DCD-CAB3-499B-A264-C62B1E1082B7}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIX.RenderApi.OpenGL", "UIX.RenderApi.OpenGL\UIX.RenderApi.OpenGL.csproj", "{6FE88A00-7281-4571-BBA3-83AAD8690049}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -146,6 +148,18 @@ Global
|
|||||||
{568E8DCD-CAB3-499B-A264-C62B1E1082B7}.Release|x64.Build.0 = Release|x64
|
{568E8DCD-CAB3-499B-A264-C62B1E1082B7}.Release|x64.Build.0 = Release|x64
|
||||||
{568E8DCD-CAB3-499B-A264-C62B1E1082B7}.Release|x86.ActiveCfg = Release|x86
|
{568E8DCD-CAB3-499B-A264-C62B1E1082B7}.Release|x86.ActiveCfg = Release|x86
|
||||||
{568E8DCD-CAB3-499B-A264-C62B1E1082B7}.Release|x86.Build.0 = Release|x86
|
{568E8DCD-CAB3-499B-A264-C62B1E1082B7}.Release|x86.Build.0 = Release|x86
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Release|x64.Build.0 = Release|x64
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{6FE88A00-7281-4571-BBA3-83AAD8690049}.Release|x86.Build.0 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Silk.NET.Maths;
|
||||||
|
using Silk.NET.Windowing;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A monitor, described from a Silk.NET <see cref="IMonitor"/>. Resolution changes are
|
||||||
|
/// reported as unsupported (the in-process renderer runs windowed).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLDisplay : IDisplay
|
||||||
|
{
|
||||||
|
private readonly DisplayMode m_mode;
|
||||||
|
|
||||||
|
public GLDisplay(IMonitor monitor, bool isPrimary)
|
||||||
|
{
|
||||||
|
DeviceName = monitor.Name ?? $"Monitor{monitor.Index}";
|
||||||
|
Rectangle<int> b = monitor.Bounds;
|
||||||
|
ScreenArea = new Rectangle(b.Origin.X, b.Origin.Y, b.Size.X, b.Size.Y);
|
||||||
|
WorkArea = ScreenArea;
|
||||||
|
IsPrimary = isPrimary;
|
||||||
|
|
||||||
|
var size = new Size(b.Size.X, b.Size.Y);
|
||||||
|
LogicalFullScreenResolution = size;
|
||||||
|
m_mode = new DisplayMode
|
||||||
|
{
|
||||||
|
sizePhysicalPxl = size,
|
||||||
|
sizeLogicalPxl = size,
|
||||||
|
nRefreshRate = monitor.VideoMode.RefreshRate ?? 60,
|
||||||
|
fInterlaced = false,
|
||||||
|
fTvMode = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DeviceName { get; }
|
||||||
|
public Rectangle ScreenArea { get; }
|
||||||
|
public Rectangle WorkArea { get; }
|
||||||
|
public bool IsPrimary { get; }
|
||||||
|
public TvFormat TvFormat => TvFormat.None;
|
||||||
|
public bool TvMode => false;
|
||||||
|
public Size LogicalFullScreenResolution { get; }
|
||||||
|
|
||||||
|
public DisplayMode[] SupportedModes => new[] { m_mode };
|
||||||
|
public DisplayMode[] ExtraModes => DisplayMode.EmptyModes;
|
||||||
|
public DisplayMode[] AllModes => SupportedModes;
|
||||||
|
public DisplayMode CurrentMode => m_mode;
|
||||||
|
public DisplayMode DesktopMode => m_mode;
|
||||||
|
public string MonitorPnP => DeviceName;
|
||||||
|
|
||||||
|
public bool ValidateDisplayMode(
|
||||||
|
DisplayMode modeDesired,
|
||||||
|
DisplayModeFlags nCheck,
|
||||||
|
bool fAllowAllModes,
|
||||||
|
out DisplayMode modeComplete,
|
||||||
|
out DisplayModeFlags nCompleteCheck)
|
||||||
|
{
|
||||||
|
// We only expose the desktop mode, so echo it back as the completed mode.
|
||||||
|
modeComplete = m_mode;
|
||||||
|
nCompleteCheck = nCheck;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windowed in-process rendering cannot switch the monitor's resolution.
|
||||||
|
public bool ChangeFullScreenResolution(DisplayMode modeChanges, DisplayModeFlags nValid) => false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Silk.NET.Windowing;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates monitors via Silk.NET and exposes them as <see cref="IDisplay"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLDisplayManager : IDisplayManager
|
||||||
|
{
|
||||||
|
private readonly List<GLDisplay> m_displays = new List<GLDisplay>();
|
||||||
|
|
||||||
|
public GLDisplayManager(IView view)
|
||||||
|
{
|
||||||
|
IMonitor? primary = Monitor.GetMainMonitor(view);
|
||||||
|
foreach (IMonitor monitor in Monitor.GetMonitors(view))
|
||||||
|
{
|
||||||
|
bool isPrimary = primary != null && monitor.Index == primary.Index;
|
||||||
|
var display = new GLDisplay(monitor, isPrimary);
|
||||||
|
if (isPrimary)
|
||||||
|
m_displays.Insert(0, display);
|
||||||
|
else
|
||||||
|
m_displays.Add(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_displays.Count == 0 && primary != null)
|
||||||
|
m_displays.Add(new GLDisplay(primary, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisplay PrimaryDisplay => m_displays[0];
|
||||||
|
|
||||||
|
public DisplayMode[] ExtraModes { get; set; } = DisplayMode.EmptyModes;
|
||||||
|
|
||||||
|
public IDisplay DisplayFromDeviceName(string stDeviceName)
|
||||||
|
{
|
||||||
|
foreach (GLDisplay d in m_displays)
|
||||||
|
{
|
||||||
|
if (d.DeviceName == stDeviceName)
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
return PrimaryDisplay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using Silk.NET.OpenGL;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Graphics device wrapping the live OpenGL context.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLGraphicsDevice : IGraphicsDevice
|
||||||
|
{
|
||||||
|
private readonly GL m_gl;
|
||||||
|
private readonly Action m_renderNow;
|
||||||
|
private string? m_captureFileName;
|
||||||
|
|
||||||
|
public GLGraphicsDevice(GL gl, GraphicsRenderingQuality quality, Action renderNow)
|
||||||
|
{
|
||||||
|
m_gl = gl;
|
||||||
|
m_renderNow = renderNow;
|
||||||
|
RenderingQuality = quality;
|
||||||
|
|
||||||
|
m_gl.GetInteger(GetPName.MaxTextureSize, out int maxTex);
|
||||||
|
if (maxTex <= 0)
|
||||||
|
maxTex = 2048;
|
||||||
|
MaximumImageSize = new Size(maxTex, maxTex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The RenderApi enum has no OpenGL member; we present as the hardware-accelerated
|
||||||
|
// path (Direct3D9) since the UI branches on GDI-vs-accelerated, not the exact API.
|
||||||
|
// TODO(stage 3): extend GraphicsDeviceType if a distinct OpenGL identity is needed.
|
||||||
|
public GraphicsDeviceType DeviceType => GraphicsDeviceType.Direct3D9;
|
||||||
|
|
||||||
|
public Size MaximumImageSize { get; }
|
||||||
|
public bool IsVideoComposited => false;
|
||||||
|
public GraphicsRenderingQuality RenderingQuality { get; }
|
||||||
|
|
||||||
|
public event BackBufferCapturedHandler? BackBufferCapturedEvent;
|
||||||
|
|
||||||
|
public void RenderNowIfPossible() => m_renderNow();
|
||||||
|
|
||||||
|
public void BeginCaptureBackBuffer(string stFileName) => m_captureFileName = stFileName;
|
||||||
|
|
||||||
|
public void EndCaptureBackBuffer()
|
||||||
|
{
|
||||||
|
// TODO(stage 3): read back the framebuffer to m_captureFileName. For now we
|
||||||
|
// only signal completion so callers waiting on the event proceed.
|
||||||
|
m_captureFileName = null;
|
||||||
|
BackBufferCapturedEvent?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The context is not lost/reset in the windowed in-process model.
|
||||||
|
public void TriggerDeviceReset() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Placeholder host-window used to embed native HWND content inside the scene.
|
||||||
|
/// Cross-platform HWND hosting has no in-process GL equivalent, so this tracks
|
||||||
|
/// state only. Stage-3 TODO: platform-gated child-surface embedding.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLHwndHostWindow : IHwndHostWindow
|
||||||
|
{
|
||||||
|
public ColorF BackgroundColor { get; set; }
|
||||||
|
public Point ClientPosition { get; set; }
|
||||||
|
public IntPtr Hwnd => IntPtr.Zero;
|
||||||
|
public bool Visible { get; set; }
|
||||||
|
public Size WindowSize { get; set; }
|
||||||
|
|
||||||
|
public event EventHandler? OnHandleChanged;
|
||||||
|
|
||||||
|
public void Dispose() => OnHandleChanged = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holds the current raw-input callback sink. The window (see <see cref="GLRenderWindow"/>)
|
||||||
|
/// translates Silk.NET input events and dispatches them here.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLInputSystem : IInputSystem
|
||||||
|
{
|
||||||
|
public IRawInputCallbacks? Callbacks { get; private set; }
|
||||||
|
|
||||||
|
public void RegisterRawInputCallbacks(IRawInputCallbacks handlers) => Callbacks = handlers;
|
||||||
|
|
||||||
|
public void UnregisterRawInputCallbacks() => Callbacks = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Silk.NET.Maths;
|
||||||
|
using Silk.NET.OpenGL;
|
||||||
|
using Silk.NET.Windowing;
|
||||||
|
// Disambiguate from Microsoft.Iris.Render.WindowOptions (enum) and this class's Window property.
|
||||||
|
using SilkWindowOptions = Silk.NET.Windowing.WindowOptions;
|
||||||
|
using SilkWindowFactory = Silk.NET.Windowing.Window;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// In-process, OpenGL-backed render engine. Owns a single Silk.NET window and its GL
|
||||||
|
/// context, pumps native events on demand and renders the visual tree each frame.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLRenderEngine : IRenderEngine
|
||||||
|
{
|
||||||
|
private readonly IRenderHost m_host;
|
||||||
|
private readonly IWindow m_silkWindow;
|
||||||
|
private readonly GLRenderSession m_session;
|
||||||
|
private readonly GLRenderWindow m_window;
|
||||||
|
|
||||||
|
private GL? m_gl;
|
||||||
|
private SceneRenderer? m_renderer;
|
||||||
|
private GLDisplayManager? m_displayManager;
|
||||||
|
|
||||||
|
private GraphicsRenderingQuality m_quality;
|
||||||
|
private SoundDeviceType m_soundType;
|
||||||
|
private volatile bool m_wakeRequested;
|
||||||
|
|
||||||
|
public GLRenderEngine(IrisEngineInfo engineInfo, IRenderHost renderHost)
|
||||||
|
{
|
||||||
|
m_host = renderHost;
|
||||||
|
|
||||||
|
var options = SilkWindowOptions.Default;
|
||||||
|
options.Title = "Iris";
|
||||||
|
options.API = new GraphicsAPI(
|
||||||
|
ContextAPI.OpenGL, ContextProfile.Core, ContextFlags.Default, new APIVersion(3, 3));
|
||||||
|
options.IsVisible = true;
|
||||||
|
options.ShouldSwapAutomatically = true;
|
||||||
|
m_silkWindow = SilkWindowFactory.Create(options);
|
||||||
|
|
||||||
|
m_session = new GLRenderSession();
|
||||||
|
m_window = new GLRenderWindow(m_silkWindow, m_session);
|
||||||
|
|
||||||
|
m_silkWindow.Load += OnLoad;
|
||||||
|
m_silkWindow.Render += OnRender;
|
||||||
|
m_silkWindow.Resize += _ => m_window.RaiseResize();
|
||||||
|
m_silkWindow.Move += _ => m_window.RaiseMove();
|
||||||
|
m_silkWindow.Closing += m_window.RaiseClose;
|
||||||
|
m_silkWindow.FocusChanged += m_window.RaiseActivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRenderSession Session => m_session;
|
||||||
|
public IRenderWindow Window => m_window;
|
||||||
|
public IDisplayManager DisplayManager => m_displayManager ?? throw new InvalidOperationException("Engine not initialized");
|
||||||
|
|
||||||
|
public void Initialize(
|
||||||
|
GraphicsDeviceType typeGraphics,
|
||||||
|
GraphicsRenderingQuality renderingQuality,
|
||||||
|
SoundDeviceType typeSound)
|
||||||
|
{
|
||||||
|
m_quality = renderingQuality;
|
||||||
|
m_soundType = typeSound;
|
||||||
|
// Window/GL creation is completed when GLRenderWindow.Initialize() runs and
|
||||||
|
// Silk raises Load (see OnLoad); we only latch the requested settings here.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoad()
|
||||||
|
{
|
||||||
|
m_gl = GL.GetApi(m_silkWindow);
|
||||||
|
m_renderer = new SceneRenderer(m_gl);
|
||||||
|
m_displayManager = new GLDisplayManager(m_silkWindow);
|
||||||
|
m_session.GraphicsDevice = new GLGraphicsDevice(m_gl, m_quality, RenderNow);
|
||||||
|
m_session.SoundDevice = new GLSoundDevice(m_soundType);
|
||||||
|
m_window.RaiseLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRender(double deltaSeconds)
|
||||||
|
{
|
||||||
|
if (m_renderer == null)
|
||||||
|
return;
|
||||||
|
m_renderer.BeginFrame(m_window.Width, m_window.Height, m_window.BackgroundColor);
|
||||||
|
m_window.Root.Render(m_renderer, Matrix4X4<float>.Identity, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderNow()
|
||||||
|
{
|
||||||
|
if (m_window.IsLoaded && !m_silkWindow.IsClosing)
|
||||||
|
m_silkWindow.DoRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ProcessNativeEvents()
|
||||||
|
{
|
||||||
|
m_silkWindow.DoEvents();
|
||||||
|
return !m_silkWindow.IsClosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForWork(uint nTimeoutInMsecs)
|
||||||
|
{
|
||||||
|
// Cooperative wait: return promptly if another thread requested a wake.
|
||||||
|
uint waited = 0;
|
||||||
|
const uint slice = 5;
|
||||||
|
while (waited < nTimeoutInMsecs && !m_wakeRequested)
|
||||||
|
{
|
||||||
|
Thread.Sleep((int)Math.Min(slice, nTimeoutInMsecs - waited));
|
||||||
|
waited += slice;
|
||||||
|
}
|
||||||
|
m_wakeRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InterThreadWake() => m_wakeRequested = true;
|
||||||
|
|
||||||
|
public void FlushBatch() => RenderNow();
|
||||||
|
|
||||||
|
public bool IsGraphicsDeviceAvailable(GraphicsDeviceType type, bool fFilterRecommended)
|
||||||
|
=> type == GraphicsDeviceType.Direct3D9 || type == GraphicsDeviceType.Gdi;
|
||||||
|
|
||||||
|
public bool IsSoundDeviceAvailable(SoundDeviceType type)
|
||||||
|
=> type == SoundDeviceType.WaveAudio || type == SoundDeviceType.DirectSound8;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
m_renderer?.Dispose();
|
||||||
|
m_session.Dispose();
|
||||||
|
if (!m_silkWindow.IsClosing)
|
||||||
|
m_silkWindow.Close();
|
||||||
|
m_silkWindow.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Thin factory that produces the render objects (visuals, sprites, images, effects,
|
||||||
|
/// …) and exposes the animation/input/graphics/sound subsystems. All objects live in
|
||||||
|
/// the same process and share the engine's single GL context.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLRenderSession : IRenderSession
|
||||||
|
{
|
||||||
|
public GLRenderSession()
|
||||||
|
{
|
||||||
|
AnimationSystem = new GLAnimationSystem();
|
||||||
|
InputSystem = new GLInputSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAnimationSystem AnimationSystem { get; }
|
||||||
|
public IInputSystem InputSystem { get; }
|
||||||
|
public IGraphicsDevice GraphicsDevice { get; internal set; } = null!;
|
||||||
|
public ISoundDevice SoundDevice { get; internal set; } = null!;
|
||||||
|
|
||||||
|
internal GLInputSystem RawInput => (GLInputSystem)InputSystem;
|
||||||
|
|
||||||
|
public IEffectTemplate CreateEffectTemplate(object objUser, string stName)
|
||||||
|
{
|
||||||
|
var t = new GLEffectTemplate(stName);
|
||||||
|
t.RegisterUsage(objUser);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IVideoStream CreateVideoStream(object objUser)
|
||||||
|
{
|
||||||
|
var v = new GLVideoStream();
|
||||||
|
v.RegisterUsage(objUser);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IVisualContainer CreateVisualContainer(object objUser, object objOwnerData)
|
||||||
|
{
|
||||||
|
var c = new GLVisualContainer(this, objOwnerData, isRoot: false);
|
||||||
|
c.RegisterUsage(objUser);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICamera CreateCamera(object objUser)
|
||||||
|
{
|
||||||
|
var c = new GLCamera();
|
||||||
|
c.RegisterUsage(objUser);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IGradient CreateGradient(object objUser)
|
||||||
|
{
|
||||||
|
var g = new GLGradient();
|
||||||
|
g.RegisterUsage(objUser);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISprite CreateSprite(object objUser, object objOwnerData)
|
||||||
|
{
|
||||||
|
var s = new GLSprite(this, objOwnerData);
|
||||||
|
s.RegisterUsage(objUser);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IImage CreateImage(object objUser, string identifier, ContentNotifyHandler handler)
|
||||||
|
{
|
||||||
|
var img = new GLImage(identifier, handler);
|
||||||
|
img.RegisterUsage(objUser);
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Iris.Input;
|
||||||
|
using Silk.NET.Maths;
|
||||||
|
using SilkWindow = Silk.NET.Windowing.IWindow;
|
||||||
|
using SilkWindowState = Silk.NET.Windowing.WindowState;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="IRenderWindow"/> implemented over a Silk.NET window. Owns the root
|
||||||
|
/// visual container and re-raises the Iris window events from Silk callbacks.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLRenderWindow : IRenderWindow
|
||||||
|
{
|
||||||
|
private readonly SilkWindow m_window;
|
||||||
|
private readonly GLVisualContainer m_root;
|
||||||
|
private Size m_initialClientSize = new Size(1024, 768);
|
||||||
|
|
||||||
|
public GLRenderWindow(SilkWindow window, GLRenderSession session)
|
||||||
|
{
|
||||||
|
m_window = window;
|
||||||
|
m_root = new GLVisualContainer(session, null!, isRoot: true);
|
||||||
|
m_root.RegisterUsage(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal GLVisualContainer Root => m_root;
|
||||||
|
|
||||||
|
// ---- Geometry ------------------------------------------------------------
|
||||||
|
public int Left => m_window.Position.X;
|
||||||
|
public int Top => m_window.Position.Y;
|
||||||
|
public int Right => m_window.Position.X + m_window.Size.X;
|
||||||
|
public int Bottom => m_window.Position.Y + m_window.Size.Y;
|
||||||
|
public int Width => m_window.Size.X;
|
||||||
|
public int Height => m_window.Size.Y;
|
||||||
|
|
||||||
|
public HWND WindowHandle => new HWND(m_window.Native?.Win32?.Hwnd ?? IntPtr.Zero);
|
||||||
|
|
||||||
|
public Size ClientSize
|
||||||
|
{
|
||||||
|
get => new Size(m_window.Size.X, m_window.Size.Y);
|
||||||
|
set => m_window.Size = new Vector2D<int>(value.Width, value.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size InitialClientSize
|
||||||
|
{
|
||||||
|
get => m_initialClientSize;
|
||||||
|
set => m_initialClientSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormPlacement InitialPlacement
|
||||||
|
{
|
||||||
|
set { /* honored at creation time via InitialClientSize/Position */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormPlacement FinalPlacement => new FormPlacement
|
||||||
|
{
|
||||||
|
NormalPosition = new Rectangle(Left, Top, Width, Height),
|
||||||
|
MaximizedLocation = Point.Zero,
|
||||||
|
ShowState = (uint)m_window.WindowState,
|
||||||
|
};
|
||||||
|
|
||||||
|
public int MinResizeWidth { get; set; }
|
||||||
|
public int MaxResizeWidth { get; set; }
|
||||||
|
|
||||||
|
public Point Position
|
||||||
|
{
|
||||||
|
get => new Point(m_window.Position.X, m_window.Position.Y);
|
||||||
|
set => m_window.Position = new Vector2D<int>(value.X, value.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => m_window.Title;
|
||||||
|
set => m_window.Title = value ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cursor Cursor { get; set; } = Cursor.Default;
|
||||||
|
public Cursor IdleCursor { get; set; } = Cursor.Default;
|
||||||
|
|
||||||
|
public bool Visible
|
||||||
|
{
|
||||||
|
get => m_window.IsVisible;
|
||||||
|
set => m_window.IsVisible = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLoaded { get; internal set; }
|
||||||
|
public ColorF BackgroundColor { get; set; } = new ColorF(0f, 0f, 0f, 1f);
|
||||||
|
public bool EnableExternalDragDrop { get; set; }
|
||||||
|
public bool IsDragInProgress { get; set; }
|
||||||
|
public IDisplay? CurrentDisplay { get; set; }
|
||||||
|
public bool FullScreenExclusive { get; set; }
|
||||||
|
public bool ActivationState { get; internal set; } = true;
|
||||||
|
|
||||||
|
public WindowState WindowState
|
||||||
|
{
|
||||||
|
get => m_window.WindowState switch
|
||||||
|
{
|
||||||
|
SilkWindowState.Minimized => WindowState.Minimized,
|
||||||
|
SilkWindowState.Maximized => WindowState.Maximized,
|
||||||
|
_ => WindowState.Normal,
|
||||||
|
};
|
||||||
|
set => m_window.WindowState = value switch
|
||||||
|
{
|
||||||
|
WindowState.Minimized => SilkWindowState.Minimized,
|
||||||
|
WindowState.Maximized => SilkWindowState.Maximized,
|
||||||
|
_ => SilkWindowState.Normal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormStyleInfo Styles { get; set; }
|
||||||
|
public HWND AppNotifyWindow { set { /* native app-notify sink; unused in-process */ } }
|
||||||
|
|
||||||
|
public IVisualContainer VisualRoot => m_root;
|
||||||
|
|
||||||
|
// ---- Methods -------------------------------------------------------------
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
// Creates the OS window + GL context; Silk raises Load, which the engine
|
||||||
|
// handles to build the GL device and then re-raises LoadEvent.
|
||||||
|
m_window.Size = new Vector2D<int>(m_initialClientSize.Width, m_initialClientSize.Height);
|
||||||
|
m_window.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetIcon(string sModuleName, uint nResourceID, IconFlags nOptions) { /* TODO(stage 3): load icon */ }
|
||||||
|
public void SetEdgeImages(bool fActiveEdges, ShadowEdgePart[] edges) { /* TODO(stage 3): window shadow edges */ }
|
||||||
|
public void SetWindowOptions(WindowOptions options, bool enable) { /* TODO(stage 3): map to Silk window flags */ }
|
||||||
|
public void SetMouseIdleOptions(Size sizeMouseIdleTolerance, uint nMouseIdleDelay) { }
|
||||||
|
public void SetCapture(IRawInputSite captureSite, bool state) { }
|
||||||
|
public void SetDragDropResult(uint nDragOverResult, uint nDragDropResult) { }
|
||||||
|
|
||||||
|
public void ClientToScreen(ref Point point)
|
||||||
|
{
|
||||||
|
point = new Point(point.X + Left, point.Y + Top);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScreenToClient(ref Point point)
|
||||||
|
{
|
||||||
|
point = new Point(point.X - Left, point.Y - Top);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ForceMouseIdle(bool fIdle) => MouseIdleEvent?.Invoke(fIdle);
|
||||||
|
public void LockMouseActive(bool fActive) { }
|
||||||
|
public void RefreshHitTarget() { }
|
||||||
|
public void TakeFocus() { }
|
||||||
|
public void TakeForeground(bool fForce) { }
|
||||||
|
public void BringToTop() { }
|
||||||
|
public void Restore() => WindowState = WindowState.Normal;
|
||||||
|
public void TemporarilyExitExclusiveMode() { }
|
||||||
|
|
||||||
|
public void Close(FormCloseReason fcrCloseReason)
|
||||||
|
{
|
||||||
|
CloseRequestEvent?.Invoke();
|
||||||
|
m_window.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IHwndHostWindow CreateHwndHostWindow() => new GLHwndHostWindow();
|
||||||
|
|
||||||
|
// ---- Events --------------------------------------------------------------
|
||||||
|
public event LocationChangedHandler? LocationChangedEvent;
|
||||||
|
public event SizeChangedHandler? SizeChangedEvent;
|
||||||
|
public event MonitorChangedHandler? MonitorChangedEvent;
|
||||||
|
public event WindowStateChangedHandler? WindowStateChangedEvent;
|
||||||
|
public event SysCommandHandler? SysCommandEvent;
|
||||||
|
public event MouseIdleHandler? MouseIdleEvent;
|
||||||
|
public event ShowHandler? ShowEvent;
|
||||||
|
public event ActivationChangeHandler? ActivationChangeEvent;
|
||||||
|
public event SessionActivateHandler? SessionActivateEvent;
|
||||||
|
public event SessionConnectHandler? SessionConnectEvent;
|
||||||
|
public event SetFocusHandler? SetFocusEvent;
|
||||||
|
public event LoadHandler? LoadEvent;
|
||||||
|
public event CloseHandler? CloseEvent;
|
||||||
|
public event CloseRequestHandler? CloseRequestEvent;
|
||||||
|
public event ForwardMessageHandler? ForwardMessageEvent;
|
||||||
|
|
||||||
|
// Raisers invoked by the engine as it pumps the Silk window.
|
||||||
|
internal void RaiseLoad()
|
||||||
|
{
|
||||||
|
IsLoaded = true;
|
||||||
|
LoadEvent?.Invoke();
|
||||||
|
ShowEvent?.Invoke(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RaiseResize() => SizeChangedEvent?.Invoke();
|
||||||
|
internal void RaiseMove() => LocationChangedEvent?.Invoke(Position);
|
||||||
|
internal void RaiseClose() => CloseEvent?.Invoke();
|
||||||
|
|
||||||
|
internal void RaiseActivation(bool active)
|
||||||
|
{
|
||||||
|
ActivationState = active;
|
||||||
|
ActivationChangeEvent?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Entry point for the in-process, OpenGL-backed renderer. Mirrors
|
||||||
|
/// <see cref="Microsoft.Iris.Render.RenderApi.CreateEngine"/>; used instead of it
|
||||||
|
/// because <c>RenderApi</c> is fixed to the native Iris engine and must not change.
|
||||||
|
/// </summary>
|
||||||
|
public static class OpenGLRenderApi
|
||||||
|
{
|
||||||
|
/// <summary>Create an OpenGL render engine for the given Iris engine info.</summary>
|
||||||
|
public static IRenderEngine CreateEngine(IrisEngineInfo engineInfo, IRenderHost renderHost)
|
||||||
|
{
|
||||||
|
if (engineInfo == null) throw new ArgumentNullException(nameof(engineInfo));
|
||||||
|
if (renderHost == null) throw new ArgumentNullException(nameof(renderHost));
|
||||||
|
return new GLRenderEngine(engineInfo, renderHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenience overload matching <c>RenderApi.CreateEngine</c>'s signature.
|
||||||
|
/// Only <see cref="IrisEngineInfo"/> is supported (the sole EngineType).
|
||||||
|
/// </summary>
|
||||||
|
public static IRenderEngine CreateEngine(EngineInfo engineInfo, IRenderHost renderHost)
|
||||||
|
{
|
||||||
|
if (engineInfo is not IrisEngineInfo iris)
|
||||||
|
throw new ArgumentException("Only IrisEngineInfo is supported.", nameof(engineInfo));
|
||||||
|
return CreateEngine(iris, renderHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using System;
|
||||||
|
using Silk.NET.Maths;
|
||||||
|
using Silk.NET.OpenGL;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Immediate-mode style GL renderer for the visual tree. Draws each sprite as a quad
|
||||||
|
/// in an orthographic, top-left-origin, pixel-space projection with alpha blending.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed unsafe class SceneRenderer : IDisposable
|
||||||
|
{
|
||||||
|
private const string VertexSource = @"#version 330 core
|
||||||
|
layout(location = 0) in vec2 aPos;
|
||||||
|
layout(location = 1) in vec2 aTex;
|
||||||
|
uniform mat4 uModel;
|
||||||
|
uniform mat4 uProj;
|
||||||
|
uniform vec2 uSize;
|
||||||
|
out vec2 vTex;
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vTex = aTex;
|
||||||
|
gl_Position = uProj * uModel * vec4(aPos * uSize, 0.0, 1.0);
|
||||||
|
}";
|
||||||
|
|
||||||
|
private const string FragmentSource = @"#version 330 core
|
||||||
|
in vec2 vTex;
|
||||||
|
out vec4 fragColor;
|
||||||
|
uniform sampler2D uTex;
|
||||||
|
uniform int uUseTexture;
|
||||||
|
uniform vec4 uColor;
|
||||||
|
uniform float uAlpha;
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
if (uUseTexture == 1)
|
||||||
|
{
|
||||||
|
vec4 t = texture(uTex, vTex);
|
||||||
|
fragColor = vec4(t.rgb, t.a * uAlpha);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fragColor = vec4(uColor.rgb, uColor.a * uAlpha);
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly GL m_gl;
|
||||||
|
private readonly uint m_program;
|
||||||
|
private readonly uint m_vao;
|
||||||
|
private readonly uint m_vbo;
|
||||||
|
|
||||||
|
private readonly int m_locModel;
|
||||||
|
private readonly int m_locProj;
|
||||||
|
private readonly int m_locSize;
|
||||||
|
private readonly int m_locUseTexture;
|
||||||
|
private readonly int m_locColor;
|
||||||
|
private readonly int m_locAlpha;
|
||||||
|
private readonly int m_locTex;
|
||||||
|
|
||||||
|
private Matrix4X4<float> m_projection = Matrix4X4<float>.Identity;
|
||||||
|
|
||||||
|
public SceneRenderer(GL gl)
|
||||||
|
{
|
||||||
|
m_gl = gl;
|
||||||
|
|
||||||
|
m_program = BuildProgram(gl);
|
||||||
|
m_locModel = gl.GetUniformLocation(m_program, "uModel");
|
||||||
|
m_locProj = gl.GetUniformLocation(m_program, "uProj");
|
||||||
|
m_locSize = gl.GetUniformLocation(m_program, "uSize");
|
||||||
|
m_locUseTexture = gl.GetUniformLocation(m_program, "uUseTexture");
|
||||||
|
m_locColor = gl.GetUniformLocation(m_program, "uColor");
|
||||||
|
m_locAlpha = gl.GetUniformLocation(m_program, "uAlpha");
|
||||||
|
m_locTex = gl.GetUniformLocation(m_program, "uTex");
|
||||||
|
|
||||||
|
// Unit quad: interleaved position (xy) + texcoord (uv). Texcoords are
|
||||||
|
// y-flipped so BGRA image rows (top-down) map upright in our y-down space.
|
||||||
|
float[] verts =
|
||||||
|
{
|
||||||
|
0f, 0f, 0f, 0f,
|
||||||
|
1f, 0f, 1f, 0f,
|
||||||
|
1f, 1f, 1f, 1f,
|
||||||
|
0f, 0f, 0f, 0f,
|
||||||
|
1f, 1f, 1f, 1f,
|
||||||
|
0f, 1f, 0f, 1f,
|
||||||
|
};
|
||||||
|
|
||||||
|
m_vao = gl.GenVertexArray();
|
||||||
|
gl.BindVertexArray(m_vao);
|
||||||
|
m_vbo = gl.GenBuffer();
|
||||||
|
gl.BindBuffer(BufferTargetARB.ArrayBuffer, m_vbo);
|
||||||
|
fixed (float* v = verts)
|
||||||
|
{
|
||||||
|
gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(verts.Length * sizeof(float)), v, BufferUsageARB.StaticDraw);
|
||||||
|
}
|
||||||
|
gl.EnableVertexAttribArray(0);
|
||||||
|
gl.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), (void*)0);
|
||||||
|
gl.EnableVertexAttribArray(1);
|
||||||
|
gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
||||||
|
gl.BindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginFrame(int widthPixels, int heightPixels, ColorF clear)
|
||||||
|
{
|
||||||
|
m_projection = Matrix4X4.CreateOrthographicOffCenter(0f, widthPixels, heightPixels, 0f, -1f, 1f);
|
||||||
|
|
||||||
|
m_gl.Viewport(0, 0, (uint)Math.Max(1, widthPixels), (uint)Math.Max(1, heightPixels));
|
||||||
|
m_gl.Disable(EnableCap.DepthTest);
|
||||||
|
m_gl.Enable(EnableCap.Blend);
|
||||||
|
m_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||||
|
m_gl.ClearColor(clear.R, clear.G, clear.B, clear.A);
|
||||||
|
m_gl.Clear((uint)ClearBufferMask.ColorBufferBit);
|
||||||
|
|
||||||
|
m_gl.UseProgram(m_program);
|
||||||
|
UploadMatrix(m_locProj, m_projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawColoredQuad(Matrix4X4<float> model, float width, float height, ColorF color, float alpha)
|
||||||
|
{
|
||||||
|
m_gl.UseProgram(m_program);
|
||||||
|
UploadMatrix(m_locModel, model);
|
||||||
|
m_gl.Uniform2(m_locSize, width, height);
|
||||||
|
m_gl.Uniform1(m_locUseTexture, 0);
|
||||||
|
m_gl.Uniform4(m_locColor, color.R, color.G, color.B, color.A);
|
||||||
|
m_gl.Uniform1(m_locAlpha, alpha);
|
||||||
|
DrawQuad();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawTexturedQuad(Matrix4X4<float> model, float width, float height, GLImage image, float alpha)
|
||||||
|
{
|
||||||
|
image.EnsureUploaded(m_gl);
|
||||||
|
if (image.TextureId == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_gl.UseProgram(m_program);
|
||||||
|
UploadMatrix(m_locModel, model);
|
||||||
|
m_gl.Uniform2(m_locSize, width, height);
|
||||||
|
m_gl.Uniform1(m_locUseTexture, 1);
|
||||||
|
m_gl.Uniform1(m_locAlpha, alpha);
|
||||||
|
m_gl.ActiveTexture(TextureUnit.Texture0);
|
||||||
|
m_gl.BindTexture(TextureTarget.Texture2D, image.TextureId);
|
||||||
|
m_gl.Uniform1(m_locTex, 0);
|
||||||
|
DrawQuad();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawQuad()
|
||||||
|
{
|
||||||
|
m_gl.BindVertexArray(m_vao);
|
||||||
|
m_gl.DrawArrays(PrimitiveType.Triangles, 0, 6);
|
||||||
|
m_gl.BindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Silk.NET.Maths stores matrices row-major; uploading with transpose=false makes
|
||||||
|
// GLSL read the transpose, so the shader uses column-vector order uProj*uModel*v.
|
||||||
|
private void UploadMatrix(int location, Matrix4X4<float> m)
|
||||||
|
{
|
||||||
|
float[] a =
|
||||||
|
{
|
||||||
|
m.M11, m.M12, m.M13, m.M14,
|
||||||
|
m.M21, m.M22, m.M23, m.M24,
|
||||||
|
m.M31, m.M32, m.M33, m.M34,
|
||||||
|
m.M41, m.M42, m.M43, m.M44,
|
||||||
|
};
|
||||||
|
fixed (float* p = a)
|
||||||
|
m_gl.UniformMatrix4(location, 1, false, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint BuildProgram(GL gl)
|
||||||
|
{
|
||||||
|
uint vs = CompileShader(gl, ShaderType.VertexShader, VertexSource);
|
||||||
|
uint fs = CompileShader(gl, ShaderType.FragmentShader, FragmentSource);
|
||||||
|
uint program = gl.CreateProgram();
|
||||||
|
gl.AttachShader(program, vs);
|
||||||
|
gl.AttachShader(program, fs);
|
||||||
|
gl.LinkProgram(program);
|
||||||
|
gl.GetProgram(program, ProgramPropertyARB.LinkStatus, out int linked);
|
||||||
|
if (linked == 0)
|
||||||
|
throw new InvalidOperationException("Shader link failed: " + gl.GetProgramInfoLog(program));
|
||||||
|
gl.DetachShader(program, vs);
|
||||||
|
gl.DetachShader(program, fs);
|
||||||
|
gl.DeleteShader(vs);
|
||||||
|
gl.DeleteShader(fs);
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint CompileShader(GL gl, ShaderType type, string source)
|
||||||
|
{
|
||||||
|
uint shader = gl.CreateShader(type);
|
||||||
|
gl.ShaderSource(shader, source);
|
||||||
|
gl.CompileShader(shader);
|
||||||
|
gl.GetShader(shader, ShaderParameterName.CompileStatus, out int status);
|
||||||
|
if (status == 0)
|
||||||
|
throw new InvalidOperationException($"{type} compile failed: " + gl.GetShaderInfoLog(shader));
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
m_gl.DeleteBuffer(m_vbo);
|
||||||
|
m_gl.DeleteVertexArray(m_vao);
|
||||||
|
m_gl.DeleteProgram(m_program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Camera parameters for a visual container. Stored verbatim; the current renderer
|
||||||
|
/// composites in an orthographic screen space, so perspective cameras are recorded
|
||||||
|
/// but not yet applied (stage-3 TODO for true 3D containers).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLCamera : SharedRenderObject, ICamera
|
||||||
|
{
|
||||||
|
public Vector3 Eye { get; set; }
|
||||||
|
public Vector3 At { get; set; }
|
||||||
|
public Vector3 Up { get; set; } = new Vector3(0f, 1f, 0f);
|
||||||
|
public float Zn { get; set; } = 1f;
|
||||||
|
public bool Perspective { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Template describing an effect (pixel-shader program in the original renderer).
|
||||||
|
/// We treat effects as typed property bags; the built-in image path reads the first
|
||||||
|
/// image property. Custom shader compilation is a stage-3 TODO.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLEffectTemplate : SharedRenderObject, IEffectTemplate
|
||||||
|
{
|
||||||
|
private readonly List<string> m_properties = new List<string>();
|
||||||
|
|
||||||
|
public GLEffectTemplate(string name) => Name = name;
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
public bool IsBuilt { get; private set; }
|
||||||
|
|
||||||
|
public void AddEffectProperty(string stPath) => m_properties.Add(stPath);
|
||||||
|
|
||||||
|
public bool Build(EffectInput input)
|
||||||
|
{
|
||||||
|
// No shader compilation yet; mark as built so callers proceed. The image
|
||||||
|
// path in GLSprite does not depend on a compiled program.
|
||||||
|
IsBuilt = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEffect CreateInstance(object objUser) => new GLEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Instance of an <see cref="GLEffectTemplate"/> holding property values.</summary>
|
||||||
|
public sealed class GLEffect : SharedRenderObject, IEffect
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, object> m_values = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
public GLEffect(GLEffectTemplate template) => Template = template;
|
||||||
|
|
||||||
|
public string Name => Template.Name;
|
||||||
|
IEffectTemplate IEffect.Template => Template;
|
||||||
|
public GLEffectTemplate Template { get; }
|
||||||
|
|
||||||
|
public void SetProperty(string stPropertyName, int nValue) => m_values[stPropertyName] = nValue;
|
||||||
|
public void SetProperty(string stPropertyName, float flValue) => m_values[stPropertyName] = flValue;
|
||||||
|
public void SetProperty(string stPropertyName, Vector2 vValue) => m_values[stPropertyName] = vValue;
|
||||||
|
public void SetProperty(string stPropertyName, Vector3 vValue) => m_values[stPropertyName] = vValue;
|
||||||
|
public void SetProperty(string stPropertyName, Vector4 vValue) => m_values[stPropertyName] = vValue;
|
||||||
|
public void SetProperty(string stPropertyName, ColorF colorValue) => m_values[stPropertyName] = colorValue;
|
||||||
|
public void SetProperty(string stPropertyName, IImage imgValue) => m_values[stPropertyName] = imgValue;
|
||||||
|
public void SetProperty(string stPropertyName, IImage[] imgValue) => m_values[stPropertyName] = imgValue;
|
||||||
|
public void SetProperty(string stPropertyName, IVideoStream streamValue) => m_values[stPropertyName] = streamValue;
|
||||||
|
|
||||||
|
/// <summary>First image assigned to any property, used as the sprite's texture.</summary>
|
||||||
|
internal GLImage? PrimaryImage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
foreach (object v in m_values.Values)
|
||||||
|
{
|
||||||
|
if (v is GLImage img)
|
||||||
|
return img;
|
||||||
|
if (v is IImage[] arr && arr.Length > 0 && arr[0] is GLImage first)
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An alpha/color gradient that can be attached to a visual. We record the stops and
|
||||||
|
/// mask; applying them as an alpha ramp during compositing is left as a stage-3 TODO.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLGradient : SharedRenderObject, IGradient
|
||||||
|
{
|
||||||
|
internal readonly struct Stop
|
||||||
|
{
|
||||||
|
public readonly float Position;
|
||||||
|
public readonly float Value;
|
||||||
|
public readonly RelativeSpace Space;
|
||||||
|
public Stop(float position, float value, RelativeSpace space)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
Value = value;
|
||||||
|
Space = space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<Stop> m_stops = new List<Stop>();
|
||||||
|
|
||||||
|
public Orientation Orientation { get; set; }
|
||||||
|
public ColorF ColorMask { get; set; } = new ColorF(1f, 1f, 1f, 1f);
|
||||||
|
public float Offset { get; set; }
|
||||||
|
|
||||||
|
public void AddValue(float flPosition, float flValue, RelativeSpace rsSpace)
|
||||||
|
=> m_stops.Add(new Stop(flPosition, flValue, rsSpace));
|
||||||
|
|
||||||
|
public void Clear() => m_stops.Clear();
|
||||||
|
|
||||||
|
internal IReadOnlyList<Stop> Stops => m_stops;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Silk.NET.OpenGL;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An image backed by an OpenGL texture. Pixel content supplied through
|
||||||
|
/// <see cref="LoadContent"/> is copied into managed memory and uploaded lazily on
|
||||||
|
/// the render thread (GL calls are only valid there), then cached as a texture.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLImage : SharedRenderObject, IImage
|
||||||
|
{
|
||||||
|
private readonly ContentNotifyHandler? m_notify;
|
||||||
|
private byte[]? m_pixelsBgra; // always stored as tightly-packed BGRA (A8R8G8B8 little-endian)
|
||||||
|
private bool m_dirty;
|
||||||
|
private uint m_texture;
|
||||||
|
|
||||||
|
public GLImage(string identifier, ContentNotifyHandler? notify)
|
||||||
|
{
|
||||||
|
Identifier = identifier;
|
||||||
|
m_notify = notify;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size Size { get; private set; }
|
||||||
|
public ImageFormat Format { get; private set; } = ImageFormat.None;
|
||||||
|
public string Identifier { get; }
|
||||||
|
|
||||||
|
internal uint TextureId => m_texture;
|
||||||
|
|
||||||
|
public bool LoadContent(ImageFormat format, Size size, int Stride, IntPtr Data)
|
||||||
|
{
|
||||||
|
if (Data == IntPtr.Zero || size.Width <= 0 || size.Height <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int bpp = format == ImageFormat.A8 ? 1 : 4;
|
||||||
|
byte[] packed = new byte[size.Width * size.Height * 4];
|
||||||
|
|
||||||
|
for (int y = 0; y < size.Height; y++)
|
||||||
|
{
|
||||||
|
IntPtr row = Data + y * Stride;
|
||||||
|
for (int x = 0; x < size.Width; x++)
|
||||||
|
{
|
||||||
|
int dst = (y * size.Width + x) * 4;
|
||||||
|
if (format == ImageFormat.A8)
|
||||||
|
{
|
||||||
|
byte a = Marshal.ReadByte(row, x);
|
||||||
|
packed[dst + 0] = 255;
|
||||||
|
packed[dst + 1] = 255;
|
||||||
|
packed[dst + 2] = 255;
|
||||||
|
packed[dst + 3] = a;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int src = x * bpp;
|
||||||
|
byte b = Marshal.ReadByte(row, src + 0);
|
||||||
|
byte g = Marshal.ReadByte(row, src + 1);
|
||||||
|
byte r = Marshal.ReadByte(row, src + 2);
|
||||||
|
byte a = format == ImageFormat.X8R8G8B8 ? (byte)255 : Marshal.ReadByte(row, src + 3);
|
||||||
|
packed[dst + 0] = b;
|
||||||
|
packed[dst + 1] = g;
|
||||||
|
packed[dst + 2] = r;
|
||||||
|
packed[dst + 3] = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pixelsBgra = packed;
|
||||||
|
Size = size;
|
||||||
|
Format = format;
|
||||||
|
m_dirty = true;
|
||||||
|
m_notify?.Invoke(ContentNotification.Acquire, this, Data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Upload pending pixel content to the GPU. Must run on the GL thread.</summary>
|
||||||
|
internal unsafe void EnsureUploaded(GL gl)
|
||||||
|
{
|
||||||
|
if (!m_dirty || m_pixelsBgra == null)
|
||||||
|
return;
|
||||||
|
m_dirty = false;
|
||||||
|
|
||||||
|
if (m_texture == 0)
|
||||||
|
m_texture = gl.GenTexture();
|
||||||
|
|
||||||
|
gl.BindTexture(TextureTarget.Texture2D, m_texture);
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)GLEnum.Linear);
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)GLEnum.Linear);
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)GLEnum.ClampToEdge);
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)GLEnum.ClampToEdge);
|
||||||
|
|
||||||
|
fixed (byte* p = m_pixelsBgra)
|
||||||
|
{
|
||||||
|
gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba,
|
||||||
|
(uint)Size.Width, (uint)Size.Height, 0,
|
||||||
|
PixelFormat.Bgra, PixelType.UnsignedByte, p);
|
||||||
|
}
|
||||||
|
gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DeleteTexture(GL gl)
|
||||||
|
{
|
||||||
|
if (m_texture != 0)
|
||||||
|
{
|
||||||
|
gl.DeleteTexture(m_texture);
|
||||||
|
m_texture = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using Silk.NET.Maths;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A leaf visual that paints a textured/colored quad. Content comes from its
|
||||||
|
/// <see cref="IEffect"/> (typically an image effect); absent an image it fills
|
||||||
|
/// with the sprite's debug color, which keeps placeholder content visible.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLSprite : GLVisual, ISprite
|
||||||
|
{
|
||||||
|
private int m_nineGridLeft, m_nineGridTop, m_nineGridRight, m_nineGridBottom;
|
||||||
|
|
||||||
|
public GLSprite(GLRenderSession session, object ownerData)
|
||||||
|
: base(session, ownerData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEffect? Effect { get; set; }
|
||||||
|
public bool RelativeSize { get; set; }
|
||||||
|
|
||||||
|
public void SetCoordMap(int idxLayer, CoordMap coordMap)
|
||||||
|
{
|
||||||
|
// TODO(stage 3): honor per-layer coordinate remaps. Not required for the
|
||||||
|
// basic textured-quad path; stored intent is dropped for now.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNineGrid(int left, int top, int right, int bottom)
|
||||||
|
{
|
||||||
|
m_nineGridLeft = left;
|
||||||
|
m_nineGridTop = top;
|
||||||
|
m_nineGridRight = right;
|
||||||
|
m_nineGridBottom = bottom;
|
||||||
|
// TODO(stage 3): implement 9-slice stretching. Currently the sprite is
|
||||||
|
// drawn as a single stretched quad regardless of these insets.
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Render(SceneRenderer renderer, Matrix4X4<float> parentMatrix, float inheritedAlpha)
|
||||||
|
{
|
||||||
|
if (!Visible || Size.X <= 0f || Size.Y <= 0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Matrix4X4<float> matrix = LocalMatrix * parentMatrix;
|
||||||
|
float alpha = inheritedAlpha * Alpha;
|
||||||
|
|
||||||
|
GLImage? image = (Effect as GLEffect)?.PrimaryImage;
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
renderer.DrawTexturedQuad(matrix, Size.X, Size.Y, image, alpha);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ColorF c = DebugColor;
|
||||||
|
// A zeroed ColorF would be fully transparent black; only draw when the
|
||||||
|
// caller actually assigned a debug color.
|
||||||
|
if (c.A > 0f)
|
||||||
|
renderer.DrawColoredQuad(matrix, Size.X, Size.Y, c, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user