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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIXrender.Engine.Tests", "Tests\UIXrender.Engine.Tests\UIXrender.Engine.Tests.csproj", "{568E8DCD-CAB3-499B-A264-C62B1E1082B7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIX.RenderApi.OpenGL", "UIX.RenderApi.OpenGL\UIX.RenderApi.OpenGL.csproj", "{6FE88A00-7281-4571-BBA3-83AAD8690049}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x86.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
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