Skip to content

Commit

Permalink
🔨 Preparation for the embedded monitor feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
hexawyz committed Jan 19, 2025
1 parent a8fd977 commit 4141491
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 128 deletions.
8 changes: 8 additions & 0 deletions src/Exo/Core/Exo.Core/Features/BaseFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,11 @@ public interface ICoolingDeviceFeature : IDeviceFeature
public interface IPowerManagementDeviceFeature : IDeviceFeature
{
}

/// <summary>Defines features for devices with embedded monitors.</summary>
/// <remarks>Devices providing embedded monitors of any form should expose this feature to allow the software to control them.</remarks>
[TypeId(0x7A02A05E, 0x958B, 0x4039, 0xAF, 0xBA, 0xC5, 0xD2, 0xF7, 0x87, 0x71, 0x4B)]
public interface IEmbeddedMonitorDeviceFeature : IDeviceFeature
{
}

122 changes: 122 additions & 0 deletions src/Exo/Core/Exo.Core/Features/EmbeddedMonitorFeatures.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System.Collections.Immutable;
using Exo.Images;
using Exo.Monitors;

namespace Exo.Features.EmbeddedMonitors;

public interface IEmbeddedMonitorFeature : IEmbeddedMonitor, IEmbeddedMonitorDeviceFeature
{
}

/// <summary>To be used for a device exposing multiple embedded monitors.</summary>
/// <remarks>
/// <para>This feature is necessary to support devices such as the various Elgato StreamDecks.</para>
/// <para>This feature is exclusive with <see cref="IEmbeddedMonitorFeature"/>.</para>
/// </remarks>
public interface IEmbeddedMonitorControllerFeature : IEmbeddedMonitorDeviceFeature
{
/// <summary>Gets a list of the embedded monitors exposed by this device.</summary>
ImmutableArray<IEmbeddedMonitor> EmbeddedMonitors { get; }
}

/// <summary>A feaure to implement for a device supporting an automatic screensaver.</summary>
/// <remarks></remarks>
public interface IEmbeddedMonitorScreenSaverFeature : IEmbeddedMonitor, IEmbeddedMonitorDeviceFeature
{
}

public interface IEmbeddedMonitor
{
/// <summary>Gets the monitor ID.</summary>
/// <remarks>This property is especially important for devices exposing multiple monitors.</remarks>
Guid MonitorId { get; }
/// <summary>Gets information about the embedded monitor.</summary>
EmbeddedMonitorInformation MonitorInformation { get; }
/// <summary>Sets the image of a monitor.</summary>
/// <remarks>
/// <para>
/// Images provided must strictly be in a format supported by the monitor.
/// Implementations are <em>not</em> requested to do any kind of conversion to a supported image format.
/// Implementations are <em>not</em> requested to do any kind of resizing.
/// </para>
/// <para>
/// JPEG and similar formats are a bit special in that they would generally always represent R8G8B8 data but will be decoded by the device in their format of choice.
/// No monitor is ever expected to support transparency, even if they do make use of an extra color channel. (e.g. for alignment purposes)
/// </para>
/// <para>
/// The image ID that is provided to this method can be used by the implementation to avoid unnecessary I/O if data for the same image ID has already been uploaded.
/// It is up to the caller to determine how to manage their image IDs, but the same ID should never be used for two different images.
/// An auto-incremented ID is a perfectly fine minimal implementation, in this case considering all images as different even while they might actually be identical.
/// To be noted though, collisions are likely to not be a real problem as long as caller can guarantee that they happen rarely enough.
/// For example, if a collision is guaranteed to not occur in a lifetime of 100 new ID generations, the devices are unlikely to remember the previous data associated with that ID.
/// A 128 bit value is used in order to easily allow for callers to implement a more complex hashing system, for example using XXH128, in order to optimize operations on their side.
/// This is intended to be done in the image service of Exo.
/// </para>
/// </remarks>
/// <param name="imageId">An opaque image ID used to identify an image.</param>
/// <param name="imageFormat">The image format of the specified data.</param>
/// <param name="data">Valid image data in the format specified by <paramref name="imageFormat"/>.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
ValueTask SetImageAsync(UInt128 imageId, ImageFormat imageFormat, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
}

public interface IDrawableEmbeddedMonitor
{
/// <summary>Draws the specified image in the specified region of a monitor.</summary>
/// <remarks>
/// <para>
/// This call is useful to update part of the framebuffer of a device supporting such partial updates.
/// It is especially desirable as images might be generated on-the-fly, as it will reduce CPU usage of said images.
/// </para>
/// <para>
/// Constraints on the drawn image are the same as for <see cref="IEmbeddedMonitor.SetImageAsync(UInt128, ImageFormat, ReadOnlyMemory{byte}, CancellationToken)"/>.
/// It is expected that embedded monitors that support partial drawing do not support any kind of animated image format, as the two purposes would conflict.
/// </para>
/// <para>
/// The provided image ID is likely to be less useful in this call, however it is provided for consistency with the
/// <see cref="IEmbeddedMonitor.SetImageAsync(UInt128, ImageFormat, ReadOnlyMemory{byte}, CancellationToken)"/> call.
/// Implementations can decide to make use of it as they wish, with exactly the same rules and expectations.
/// </para>
/// </remarks>
/// <param name="position">The position at which to draw the image.</param>
/// <param name="size">The size of the region where the image will be drawn.</param>
/// <param name="imageId">An opaque image ID used to identify an image.</param>
/// <param name="imageFormat">The image format of the specified data.</param>
/// <param name="data">Valid image data in the format specified by <paramref name="imageFormat"/>.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
ValueTask DrawImageAsync(Point position, Size size, UInt128 imageId, ImageFormat imageFormat, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
}

public readonly record struct EmbeddedMonitorInformation
{
public EmbeddedMonitorInformation(MonitorShape shape, Size imageSize, PixelFormat pixelFormat, ImageFormats supportedImageFormats, bool hasAnimationSupport)
{
Shape = shape;
ImageSize = imageSize;
PixelFormat = pixelFormat;
SupportedImageFormats = supportedImageFormats;
HasAnimationSupport = hasAnimationSupport;
}

/// <summary>Gets the shape of the monitor.</summary>
/// <remarks>
/// Some AIO devices will expose a circular screen, but most embedded monitors are expected to be of rectangular shape.
/// The shape of the monitor might mainly be used to optimize image compression if the monitor is non-rectangular.
/// </remarks>
public MonitorShape Shape { get; }
/// <summary>Gets the image size of the monitor.</summary>
public Size ImageSize { get; }
/// <summary>Gets the effective pixel format of the monitor.</summary>
/// <remarks>
/// Monitors should generally support a 32 bits RGB(A) format, but this information is needed in order to feed acceptable images to the device.
/// This is especially important in case of raw images, but it will matter in other situations, such as when only a reduced number of colors is supported.
/// </remarks>
public PixelFormat PixelFormat { get; }
/// <summary>Gets a description of the image formats that are directly supported by the embedded monitor.</summary>
public ImageFormats SupportedImageFormats { get; }
/// <summary>Indicates whether the monitor has hardware support for animated images.</summary>
/// <remarks>Animated images are only expected to be supported for full refreshes.</remarks>
public bool HasAnimationSupport { get; }
}
115 changes: 0 additions & 115 deletions src/Exo/Core/Exo.Core/Features/MonitorFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,118 +120,3 @@ public interface IMonitorResponseTimeFeature : IMonitorDeviceFeature, INonContin
public interface IMonitorInputLagFeature : IMonitorDeviceFeature, INonContinuousVcpFeature { }
public interface IMonitorBlueLightFilterLevelFeature : IMonitorDeviceFeature, IContinuousVcpFeature { }
public interface IMonitorPowerIndicatorToggleFeature : IMonitorDeviceFeature, IBooleanVcpFeature { }

public interface IEmbeddedMonitorFeature : IEmbeddedMonitor, IMonitorDeviceFeature
{
}

/// <summary>To be used for a device exposing multiple embedded monitors.</summary>
/// <remarks>
/// <para>This feature is necessary to support devices such as the various Elgato StreamDecks.</para>
/// <para>THis feature is exclusive with <see cref="IEmbeddedMonitorFeature"/>.</para>
/// </remarks>
public interface IEmbeddedMonitorControllerFeature : IMonitorDeviceFeature
{
/// <summary>Gets a list of the embedded monitors exposed by this device.</summary>
ImmutableArray<IEmbeddedMonitor> EmbeddedMonitors { get; }
}

public interface IEmbeddedMonitorScreenSaverFeature : IEmbeddedMonitor, IMonitorDeviceFeature
{
}

public interface IEmbeddedMonitor
{
/// <summary>Gets the monitor ID.</summary>
/// <remarks>This property is especially important for devices exposing multiple monitors.</remarks>
Guid MonitorId { get; }
/// <summary>Gets information about the embedded monitor.</summary>
EmbeddedMonitorInformation MonitorInformation { get; }
/// <summary>Sets the image of a monitor.</summary>
/// <remarks>
/// <para>
/// Images provided must strictly be in a format supported by the monitor.
/// Implementations are <em>not</em> requested to do any kind of conversion to a supported image format.
/// Implementations are <em>not</em> requested to do any kind of resizing.
/// </para>
/// <para>
/// JPEG and similar formats are a bit special in that they would generally always represent R8G8B8 data but will be decoded by the device in their format of choice.
/// No monitor is ever expected to support transparency, even if they do make use of an extra color channel. (e.g. for alignment purposes)
/// </para>
/// <para>
/// The image ID that is provided to this method can be used by the implementation to avoid unnecessary I/O if data for the same image ID has already been uploaded.
/// It is up to the caller to determine how to manage their image IDs, but the same ID should never be used for two different images.
/// An auto-incremented ID is a perfectly fine minimal implementation, in this case considering all images as different even while they might actually be identical.
/// To be noted though, collisions are likely to not be a real problem as long as caller can guarantee that they happen rarely enough.
/// For example, if a collision is guaranteed to not occur in a lifetime of 100 new ID generations, the devices are unlikely to remember the previous data associated with that ID.
/// A 128 bit value is used in order to easily allow for callers to implement a more complex hashing system, for example using XXH128, in order to optimize operations on their side.
/// This is intended to be done in the image service of Exo.
/// </para>
/// </remarks>
/// <param name="imageId">An opaque image ID used to identify an image.</param>
/// <param name="imageFormat">The image format of the specified data.</param>
/// <param name="data">Valid image data in the format specified by <paramref name="imageFormat"/>.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
ValueTask SetImageAsync(UInt128 imageId, ImageFormat imageFormat, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
}

public interface IDrawableEmbeddedMonitor
{
/// <summary>Draws the specified image in the specified region of a monitor.</summary>
/// <remarks>
/// <para>
/// This call is useful to update part of the framebuffer of a device supporting such partial updates.
/// It is especially desirable as images might be generated on-the-fly, as it will reduce CPU usage of said images.
/// </para>
/// <para>
/// Constraints on the drawn image are the same as for <see cref="IEmbeddedMonitor.SetImageAsync(UInt128, ImageFormat, ReadOnlyMemory{byte}, CancellationToken)"/>.
/// It is expected that embedded monitors that support partial drawing do not support any kind of animated image format, as the two purposes would conflict.
/// </para>
/// <para>
/// The provided image ID is likely to be less useful in this call, however it is provided for consistency with the
/// <see cref="IEmbeddedMonitor.SetImageAsync(UInt128, ImageFormat, ReadOnlyMemory{byte}, CancellationToken)"/> call.
/// Implementations can decide to make use of it as they wish, with exactly the same rules and expectations.
/// </para>
/// </remarks>
/// <param name="position">The position at which to draw the image.</param>
/// <param name="size">The size of the region where the image will be drawn.</param>
/// <param name="imageId">An opaque image ID used to identify an image.</param>
/// <param name="imageFormat">The image format of the specified data.</param>
/// <param name="data">Valid image data in the format specified by <paramref name="imageFormat"/>.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
ValueTask DrawImageAsync(Point position, Size size, UInt128 imageId, ImageFormat imageFormat, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
}

public readonly record struct EmbeddedMonitorInformation
{
public EmbeddedMonitorInformation(MonitorShape shape, Size imageSize, PixelFormat pixelFormat, ImageFormats supportedImageFormats, bool hasAnimationSupport)
{
Shape = shape;
ImageSize = imageSize;
PixelFormat = pixelFormat;
SupportedImageFormats = supportedImageFormats;
HasAnimationSupport = hasAnimationSupport;
}

/// <summary>Gets the shape of the monitor.</summary>
/// <remarks>
/// Some AIO devices will expose a circular screen, but most embedded monitors are expected to be of rectangular shape.
/// The shape of the monitor might mainly be used to optimize image compression if the monitor is non-rectangular.
/// </remarks>
public MonitorShape Shape { get; }
/// <summary>Gets the image size of the monitor.</summary>
public Size ImageSize { get; }
/// <summary>Gets the effective pixel format of the monitor.</summary>
/// <remarks>
/// Monitors should generally support a 32 bits RGB(A) format, but this information is needed in order to feed acceptable images to the device.
/// This is especially important in case of raw images, but it will matter in other situations, such as when only a reduced number of colors is supported.
/// </remarks>
public PixelFormat PixelFormat { get; }
/// <summary>Gets a description of the image formats that are directly supported by the embedded monitor.</summary>
public ImageFormats SupportedImageFormats { get; }
/// <summary>Indicates whether the monitor has hardware support for animated images.</summary>
/// <remarks>Animated images are only expected to be supported for full refreshes.</remarks>
public bool HasAnimationSupport { get; }
}
14 changes: 7 additions & 7 deletions src/Exo/Core/Exo.Core/Images/ImageFormats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ namespace Exo.Images;

/// <summary>Describe known supported image formats.</summary>
[Flags]
public enum ImageFormats
public enum ImageFormats : uint
{
// NB: Enum must be kept in sync with ImageFormats
Raw = 0x00000001,
Bitmap = 0x00000010,
Gif = 0x00000100,
Jpeg = 0x00001000,
Png = 0x00010000,
WebP = 0x00100000,
Raw = 0b00000001,
Bitmap = 0b00000010,
Gif = 0b00000100,
Jpeg = 0b00001000,
Png = 0b00010000,
WebP = 0b00100000,
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
using DeviceTools.HumanInterfaceDevices.Usages;
using Exo.Discovery;
using Exo.Features;
using Exo.Features.EmbeddedMonitors;
using Exo.Features.Monitors;
using Exo.Features.PowerManagement;
using Exo.Images;
using Exo.Monitors;
using Microsoft.Extensions.Logging;

namespace Exo.Devices.Elgato.StreamDeck;

Expand All @@ -37,6 +37,7 @@ public sealed class StreamDeckDeviceDriver :
Driver,
IDeviceDriver<IGenericDeviceFeature>,
IDeviceDriver<IMonitorDeviceFeature>,
IDeviceDriver<IEmbeddedMonitorDeviceFeature>,
IDeviceDriver<IPowerManagementDeviceFeature>,
IDeviceIdFeature,
IDeviceSerialNumberFeature,
Expand Down Expand Up @@ -180,10 +181,12 @@ CancellationToken cancellationToken
private readonly Button[] _buttons;
private readonly IDeviceFeatureSet<IGenericDeviceFeature> _genericFeatures;
private readonly IDeviceFeatureSet<IMonitorDeviceFeature> _monitorFeatures;
private readonly IDeviceFeatureSet<IEmbeddedMonitorDeviceFeature> _embeddedMonitorFeatures;
private readonly IDeviceFeatureSet<IPowerManagementDeviceFeature> _powerManagementFeatures;

IDeviceFeatureSet<IGenericDeviceFeature> IDeviceDriver<IGenericDeviceFeature>.Features => _genericFeatures;
IDeviceFeatureSet<IMonitorDeviceFeature> IDeviceDriver<IMonitorDeviceFeature>.Features => _monitorFeatures;
IDeviceFeatureSet<IEmbeddedMonitorDeviceFeature> IDeviceDriver<IEmbeddedMonitorDeviceFeature>.Features => _embeddedMonitorFeatures;
IDeviceFeatureSet<IPowerManagementDeviceFeature> IDeviceDriver<IPowerManagementDeviceFeature>.Features => _powerManagementFeatures;


Expand Down Expand Up @@ -214,7 +217,8 @@ DeviceConfigurationKey configurationKey
_buttons = buttons;

_genericFeatures = FeatureSet.Create<IGenericDeviceFeature, StreamDeckDeviceDriver, IDeviceIdFeature, IDeviceSerialNumberFeature>(this);
_monitorFeatures = FeatureSet.Create<IMonitorDeviceFeature, StreamDeckDeviceDriver, IEmbeddedMonitorControllerFeature>(this);
_monitorFeatures = FeatureSet.Empty<IMonitorDeviceFeature>();
_embeddedMonitorFeatures = FeatureSet.Create<IEmbeddedMonitorDeviceFeature, StreamDeckDeviceDriver, IEmbeddedMonitorControllerFeature>(this);
_powerManagementFeatures = FeatureSet.Create<IPowerManagementDeviceFeature, StreamDeckDeviceDriver, IIdleSleepTimerFeature>(this);
}

Expand Down
13 changes: 9 additions & 4 deletions src/Exo/Devices/Exo.Devices.Nzxt.Kraken/KrakenDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Exo.Discovery;
using Exo.Features;
using Exo.Features.Cooling;
using Exo.Features.EmbeddedMonitors;
using Exo.Features.Monitors;
using Exo.Features.Sensors;
using Exo.Images;
Expand All @@ -22,14 +23,15 @@ namespace Exo.Devices.Nzxt.Kraken;
public class KrakenDriver :
Driver,
IDeviceDriver<IGenericDeviceFeature>,
IDeviceDriver<ISensorDeviceFeature>,
IDeviceDriver<ICoolingDeviceFeature>,
IDeviceDriver<IMonitorDeviceFeature>,
IDeviceDriver<IEmbeddedMonitorDeviceFeature>,
IDeviceIdFeature,
IDeviceSerialNumberFeature,
IDeviceDriver<ISensorDeviceFeature>,
ISensorsFeature,
ISensorsGroupedQueryFeature,
IDeviceDriver<ICoolingDeviceFeature>,
ICoolingControllerFeature,
IDeviceDriver<IMonitorDeviceFeature>,
IEmbeddedMonitorFeature,
IMonitorBrightnessFeature
{
Expand Down Expand Up @@ -250,6 +252,7 @@ static uint GetColor(byte r, byte g, byte b)
private readonly IDeviceFeatureSet<ISensorDeviceFeature> _sensorFeatures;
private readonly IDeviceFeatureSet<ICoolingDeviceFeature> _coolingFeatures;
private readonly IDeviceFeatureSet<IMonitorDeviceFeature> _monitorFeatures;
private readonly IDeviceFeatureSet<IEmbeddedMonitorDeviceFeature> _embeddedMonitorFeatures;

private int _groupQueriedSensorCount;
private readonly ushort _productId;
Expand Down Expand Up @@ -279,6 +282,7 @@ static uint GetColor(byte r, byte g, byte b)
IDeviceFeatureSet<ISensorDeviceFeature> IDeviceDriver<ISensorDeviceFeature>.Features => _sensorFeatures;
IDeviceFeatureSet<ICoolingDeviceFeature> IDeviceDriver<ICoolingDeviceFeature>.Features => _coolingFeatures;
IDeviceFeatureSet<IMonitorDeviceFeature> IDeviceDriver<IMonitorDeviceFeature>.Features => _monitorFeatures;
IDeviceFeatureSet<IEmbeddedMonitorDeviceFeature> IDeviceDriver<IEmbeddedMonitorDeviceFeature>.Features => _embeddedMonitorFeatures;

private KrakenDriver
(
Expand Down Expand Up @@ -308,7 +312,8 @@ DeviceConfigurationKey configurationKey
FeatureSet.Create<IGenericDeviceFeature, KrakenDriver, IDeviceIdFeature>(this);
_sensorFeatures = FeatureSet.Create<ISensorDeviceFeature, KrakenDriver, ISensorsFeature, ISensorsGroupedQueryFeature>(this);
_coolingFeatures = FeatureSet.Create<ICoolingDeviceFeature, KrakenDriver, ICoolingControllerFeature>(this);
_monitorFeatures = FeatureSet.Create<IMonitorDeviceFeature, KrakenDriver, IEmbeddedMonitorFeature, IMonitorBrightnessFeature>(this);
_monitorFeatures = FeatureSet.Create<IMonitorDeviceFeature, KrakenDriver, IMonitorBrightnessFeature>(this);
_embeddedMonitorFeatures = FeatureSet.Create<IEmbeddedMonitorDeviceFeature, KrakenDriver, IEmbeddedMonitorFeature>(this);
}

public override async ValueTask DisposeAsync()
Expand Down
Loading

0 comments on commit 4141491

Please sign in to comment.