/// <summary>
        /// Function to retrieve the outputs attached to a video adapter.
        /// </summary>
        /// <param name="device">The Direct 3D device used to filter display modes.</param>
        /// <param name="adapter">The adapter to retrieve the outputs from.</param>
        /// <param name="outputCount">The number of outputs for the device.</param>
        /// <param name="log">The logging interface used to capture debug messages.</param>
        /// <returns>A list if video output info values.</returns>
        private static Dictionary <string, VideoOutputInfo> GetOutputs(D3D11.Device5 device, Adapter4 adapter, int outputCount, IGorgonLog log)
        {
            var result = new Dictionary <string, VideoOutputInfo>(StringComparer.OrdinalIgnoreCase);

            // Devices created under RDP/TS do not support output selection.
            if (SystemInformation.TerminalServerSession)
            {
                log.Print("Devices under terminal services and software devices devices do not use outputs, no outputs enumerated.", LoggingLevel.Intermediate);
                return(result);
            }

            for (int i = 0; i < outputCount; ++i)
            {
                using (Output output = adapter.GetOutput(i))
                    using (Output6 output6 = output.QueryInterface <Output6>())
                    {
                        var outputInfo = new VideoOutputInfo(i, output6, GetVideoModes(device, output6));

                        if (outputInfo.VideoModes.Count == 0)
                        {
                            log.Print($"Output '{output.Description.DeviceName}' on adapter '{adapter.Description1.Description}' has no full screen video modes.",
                                      LoggingLevel.Intermediate);
                        }

                        result.Add(output.Description.DeviceName, outputInfo);
                    }
            }

            return(result);
        }
Esempio n. 2
0
        /// <summary>Imports the data.</summary>
        /// <param name="temporaryDirectory">The temporary directory for writing any transitory data.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <remarks>
        /// <para>
        /// The <paramref name="temporaryDirectory"/> should be used to write any working/temporary data used by the import.  Note that all data written into this directory will be deleted when the
        /// project is unloaded from memory.
        /// </para>
        /// </remarks>
        public FileInfo ImportData(DirectoryInfo temporaryDirectory, CancellationToken cancelToken)
        {
            var spriteCodec = new GorgonV3SpriteBinaryCodec(_renderer);

            _log.Print("Importing associated texture for sprite...", LoggingLevel.Simple);
            GorgonTexture2DView texture = GetTexture();

            try
            {
                _log.Print($"Importing file '{SourceFile.FullName}' (Codec: {_codec.Name})...", LoggingLevel.Verbose);
                GorgonSprite sprite = _codec.FromFile(SourceFile.FullName);

                if (sprite.Texture == null)
                {
                    sprite.Texture = texture;
                }

                var tempFile = new FileInfo(Path.Combine(temporaryDirectory.FullName, Path.GetFileNameWithoutExtension(SourceFile.Name)));

                _log.Print($"Converting '{SourceFile.FullName}' to Gorgon v3 Sprite file format.", LoggingLevel.Verbose);
                spriteCodec.Save(sprite, tempFile.FullName);

                return(tempFile);
            }
            finally
            {
                texture?.Dispose();
            }
        }
        /// <summary>Imports the data.</summary>
        /// <param name="temporaryDirectory">The temporary directory for writing any transitory data.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <remarks>
        /// <para>
        /// The <paramref name="temporaryDirectory"/> should be used to write any working/temporary data used by the import.  Note that all data written into this directory will be deleted when the
        /// project is unloaded from memory.
        /// </para>
        /// </remarks>
        public FileInfo ImportData(DirectoryInfo temporaryDirectory, CancellationToken cancelToken)
        {
            var ddsCodec = new GorgonCodecDds();

            _log.Print($"Importing file '{SourceFile.FullName}' (Codec: {_codec.Name})...", LoggingLevel.Verbose);
            using (IGorgonImage image = _codec.LoadFromFile(SourceFile.FullName))
            {
                var tempFile = new FileInfo(Path.Combine(temporaryDirectory.FullName, Path.GetFileNameWithoutExtension(SourceFile.Name)));

                _log.Print($"Converting '{SourceFile.FullName}' to DDS file format. Image format [{image.Format}].", LoggingLevel.Verbose);
                ddsCodec.SaveToFile(image, tempFile.FullName);

                return(tempFile);
            }
        }
Esempio n. 4
0
        /// <summary>Function to clear the undo/redo stacks.</summary>
        public void ClearStack()
        {
            Cancel();

            // Perform any clean up required for the undo/redo arguments.
            foreach (IDisposable command in _undoStack.OfType <IDisposable>())
            {
                command.Dispose();
            }

            _undoStack.Clear();
            _undoIndex = -1;

            _log.Print("Undo commands cleared.", LoggingLevel.Simple);
        }
Esempio n. 5
0
        /// <summary>
        /// Function to retrieve the description of the raw input device from the registry.
        /// </summary>
        /// <param name="deviceName">Path to the registry key that holds the device description.</param>
        /// <param name="log">The debug log file to use when logging issues.</param>
        /// <returns>The device description.</returns>
        public static string GetDeviceDescription(string deviceName, IGorgonLog log)
        {
            if (string.IsNullOrWhiteSpace(deviceName))
            {
                throw new ArgumentException(Resources.GORINP_RAW_ERR_CANNOT_READ_DEVICE_DATA, nameof(deviceName));
            }

            string[] regValue = deviceName.Split('#');

            regValue[0] = regValue[0].Substring(4);

            // Don't add RDP devices.
            if ((log != null) &&
                (regValue.Length > 0) &&
                (regValue[1].StartsWith("RDP_", StringComparison.OrdinalIgnoreCase)))
            {
                log.Print("WARNING: This is an RDP device.  Raw input in Gorgon is not supported under RDP.  Skipping this device.", LoggingLevel.Verbose);
                return(string.Empty);
            }

            using (RegistryKey deviceKey = Registry.LocalMachine.OpenSubKey($@"System\CurrentControlSet\Enum\{regValue[0]}\{regValue[1]}\{regValue[2]}",
                                                                            false))
            {
                if (deviceKey?.GetValue("DeviceDesc") == null)
                {
                    return(string.Empty);
                }

                regValue = deviceKey.GetValue("DeviceDesc").ToString().Split(';');

                return(regValue[regValue.Length - 1]);
            }
        }
        /// <summary>
        /// Function to add the WARP software device.
        /// </summary>
        /// <param name="index">Index of the device.</param>
        /// <param name="factory">The factory used to query the adapter.</param>
        /// <param name="log">The log interface used to send messages to a debug log.</param>
        /// <returns>The video adapter used for WARP software rendering.</returns>
        private static VideoAdapterInfo GetWARPSoftwareDevice(int index, Factory5 factory, IGorgonLog log)
        {
            D3D11.DeviceCreationFlags flags = D3D11.DeviceCreationFlags.None;

            if (GorgonGraphics.IsDebugEnabled)
            {
                flags = D3D11.DeviceCreationFlags.Debug;
            }

            using (Adapter warp = factory.GetWarpAdapter())
                using (Adapter4 warpAdapter4 = warp.QueryInterface <Adapter4>())
                    using (var D3DDevice = new D3D11.Device(warpAdapter4, flags))
                        using (D3D11.Device5 D3DDevice5 = D3DDevice.QueryInterface <D3D11.Device5>())
                        {
                            FeatureSet?featureSet = GetFeatureLevel(D3DDevice5);

                            if (featureSet == null)
                            {
                                log.Print("WARNING: The WARP software adapter does not support the minimum feature set of 12.0. This device will be excluded.", LoggingLevel.All);
                                return(null);
                            }

                            var result = new VideoAdapterInfo(index, warpAdapter4, featureSet.Value, new Dictionary <string, VideoOutputInfo>(), VideoDeviceType.Software);

                            PrintLog(result, log);

                            return(result);
                        }
        }
Esempio n. 7
0
        /// <summary>
        /// Function to return the class name for the device.
        /// </summary>
        /// <param name="deviceName">The name of the device from <see cref="RawInputApi.GetDeviceName"/>.</param>
        /// <param name="log">The debug log file to use when logging issues.</param>
        /// <returns>The device class name.</returns>
        public static string GetDeviceClass(string deviceName, IGorgonLog log)
        {
            if (string.IsNullOrWhiteSpace(deviceName))
            {
                throw new ArgumentException(Resources.GORINP_RAW_ERR_CANNOT_READ_DEVICE_DATA, nameof(deviceName));
            }

            string[] regValue = deviceName.Split('#');

            regValue[0] = regValue[0].Substring(4);

            // Don't add RDP devices.
            if ((log != null) &&
                (regValue.Length > 0) &&
                (regValue[1].StartsWith("RDP_", StringComparison.OrdinalIgnoreCase)))
            {
                log.Print("WARNING: This is an RDP device.  Raw input in Gorgon is not supported under RDP.  Skipping this device.", LoggingLevel.Verbose);
                return(string.Empty);
            }

            using (RegistryKey deviceKey = Registry.LocalMachine.OpenSubKey($@"System\CurrentControlSet\Enum\{regValue[0]}\{regValue[1]}\{regValue[2]}",
                                                                            false))
            {
                if (deviceKey?.GetValue("DeviceDesc") == null)
                {
                    return(string.Empty);
                }

                if (deviceKey.GetValue("Class") != null)
                {
                    return(deviceKey.GetValue("Class").ToString());
                }

                // Windows 8 no longer has a "Class" value in this area, so we need to go elsewhere to get it.
                if (deviceKey.GetValue("ClassGUID") == null)
                {
                    return(string.Empty);
                }

                string classGUID = deviceKey.GetValue("ClassGUID").ToString();

                if (string.IsNullOrWhiteSpace(classGUID))
                {
                    return(string.Empty);
                }

                using (RegistryKey classKey = Registry.LocalMachine.OpenSubKey($@"System\CurrentControlSet\Control\Class\{classGUID}"))
                {
                    return(classKey?.GetValue("Class") == null ? string.Empty : classKey.GetValue("Class").ToString());
                }
            }
        }
Esempio n. 8
0
        /// <summary>
        /// Function to unload a plugin by its name.
        /// </summary>
        /// <param name="plugin">The plugin to remove.</param>
        private void DisposePlugIn(GorgonPlugIn plugin)
        {
            if (!(plugin is IDisposable disposable))
            {
                return;
            }

            disposable.Dispose();
            _log.Print($"PlugIn '{plugin.Name}' disposed.", LoggingLevel.Verbose);
        }
Esempio n. 9
0
        /// <summary>
        /// Function to locate the application that can edit the specified file.
        /// </summary>
        /// <param name="workingFile">The file to edit.</param>
        /// <returns>The path to the executable.</returns>
        private string GetExecutable(IGorgonVirtualFile workingFile)
        {
            _log.Print($"Retrieving associated executable for files of type {workingFile.Extension}.", LoggingLevel.Verbose);

            string exePath = Win32API.GetAssociatedExecutable(workingFile.PhysicalFile.FullPath);

            if (string.IsNullOrWhiteSpace(exePath))
            {
                _log.Print($"No executable found for files of type {workingFile.Extension}.", LoggingLevel.Verbose);
                return(null);
            }

            _log.Print($"Found executable path {exePath}.", LoggingLevel.Verbose);

            return(exePath);
        }
        /// <summary>
        /// Function to create a new file system provider.
        /// </summary>
        /// <param name="providerPlugInName">The fully qualified type name of the plugin that contains the file system provider.</param>
        /// <returns>The new file system provider object, or if it was previously created, the previously created instance.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="providerPlugInName"/> is <b>null</b></exception>
        /// <exception cref="ArgumentEmptyException">Thrown when the <paramref name="providerPlugInName"/> is empty.</exception>
        /// <exception cref="GorgonException">Thrown when the plugin specified by the <paramref name="providerPlugInName"/> parameter was not found.</exception>
        public GorgonFileSystemProvider CreateProvider(string providerPlugInName)
        {
            if (providerPlugInName == null)
            {
                throw new ArgumentNullException(nameof(providerPlugInName));
            }

            if (string.IsNullOrWhiteSpace(providerPlugInName))
            {
                throw new ArgumentEmptyException(nameof(providerPlugInName));
            }

            _log.Print("Creating file system provider '{0}'.", LoggingLevel.Simple, providerPlugInName);

            GorgonFileSystemProvider plugin = _pluginService.GetPlugIn <GorgonFileSystemProvider>(providerPlugInName);

            if (plugin == null)
            {
                throw new GorgonException(GorgonResult.CannotCreate, string.Format(Resources.GORFS_ERR_NO_PROVIDER_PLUGIN, providerPlugInName));
            }

            return(plugin);
        }
Esempio n. 11
0
        /// <summary>
        /// Function to retrieve a list of mice.
        /// </summary>
        /// <returns>A read only list containing information about each mouse.</returns>
        public IReadOnlyList <IGorgonMouseInfo> EnumerateMice()
        {
            RAWINPUTDEVICELIST[] devices = RawInputApi.EnumerateRawInputDevices(RawInputType.Mouse);
            var result = new List <RawMouseInfo>();

            for (int i = 0; i < devices.Length; i++)
            {
                RawMouseInfo info = GetDeviceInfo <RawMouseInfo>(ref devices[i]);

                if (info == null)
                {
                    _log.Print("WARNING: Could not retrieve the class and device name.  Skipping this device.", LoggingLevel.Verbose);
                    continue;
                }

                _log.Print("Found mouse: '{0}' on HID path {1}, class {2}.", LoggingLevel.Verbose, info.Description, info.HIDPath, info.DeviceClass);

                result.Add(info);
            }

            return(result);
        }
Esempio n. 12
0
        /// <summary>
        /// Function that's called during idle time.
        /// </summary>
        /// <returns><b>true</b> to continue execution, <b>false</b> to stop.</returns>
        /// <remarks>This is the secondary default idle loop.</remarks>
        public static bool NewIdle()
        {
            if (_currentIdle != NewIdle)
            {
                _currentIdle = NewIdle;
                _log.Print("In new idle loop.", LoggingLevel.All);
            }

            var form = (formMain)GorgonApplication.ApplicationContext.MainForm;                         // Get our main form from the context.

            // Draw some bars every 16 ms.
            if ((GorgonTiming.MillisecondsSinceStart - _lastTime) >= 16.6f)
            {
                Color newColor = Color.Transparent;

                switch (_component)
                {
                case 0:
                    newColor = Color.FromArgb(_color, 0, 0);
                    break;

                case 1:
                    newColor = Color.FromArgb(0, _color, 0);
                    break;

                case 2:
                    newColor = Color.FromArgb(0, 0, _color);
                    break;
                }

                _lastTime = GorgonTiming.MillisecondsSinceStart;

                form.Draw(_lastX, _lastY, _lastX, (form.GraphicsSize.Height - 1) - (_lastY), newColor);

                _color += 3;
                _lastX++;

                if (_color >= 255)
                {
                    _color = 0;
                    _component++;
                    if (_component > 2)
                    {
                        _component = 0;
                    }
                }

                if (_lastX >= form.GraphicsSize.Width)
                {
                    _lastX = 0;
                }

                _lastY = _rnd.Next(0, form.GraphicsSize.Height / 4);
            }

            form.Flip();

            form.DrawFPS("Secondary Idle Loop - FPS: " + GorgonTiming.FPS.ToString("0.0"));

            return(true);
        }
Esempio n. 13
0
        /// <summary>
        /// Function to import an image file from the physical file system into the current image.
        /// </summary>
        /// <param name="codec">The codec used to open the file.</param>
        /// <param name="filePath">The path to the file to import.</param>
        /// <returns>The source file information, image data, the virtual file entry for the working file and the original pixel format of the file.</returns>
        public (FileInfo file, IGorgonImage image, IGorgonVirtualFile workingFile, BufferFormat originalFormat) ImportImage(IGorgonImageCodec codec, string filePath)
        {
            var file = new FileInfo(filePath);
            IGorgonImageCodec  importCodec  = codec;
            IGorgonImageInfo   metaData     = null;
            IGorgonVirtualFile workFile     = null;
            IGorgonImage       importImage  = null;
            string             workFilePath = $"{Path.GetFileNameWithoutExtension(filePath)}_import_{Guid.NewGuid().ToString("N")}";

            // Try to determine if we can actually read the file using an installed codec, if we can't, then try to find a suitable codec.
            using (FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                if ((importCodec == null) || (!importCodec.IsReadable(stream)))
                {
                    importCodec = null;

                    foreach (IGorgonImageCodec newCodec in InstalledCodecs.Codecs.Where(item => (item.CodecCommonExtensions.Count > 0) && (item.CanDecode)))
                    {
                        if (newCodec.IsReadable(stream))
                        {
                            importCodec = newCodec;
                            break;
                        }
                    }
                }

                if (importCodec == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_NO_CODEC, filePath));
                }

                metaData = importCodec.GetMetaData(stream);


                // We absolutely need to have an extension, or else the texconv tool will not work.
                var codecExtension = new GorgonFileExtension(importCodec.CodecCommonExtensions[0]);
                _log.Print($"Adding {codecExtension.Extension} extension to working file or else external tools may not be able to read it.", LoggingLevel.Verbose);
                workFilePath = $"{workFilePath}.{codecExtension.Extension}";

                using (Stream outStream = ScratchArea.OpenStream(workFilePath, FileMode.Create))
                {
                    stream.CopyTo(outStream);
                }
            }

            workFile = ScratchArea.FileSystem.GetFile(workFilePath);
            var formatInfo = new GorgonFormatInfo(metaData.Format);

            // This is always in DDS format.
            if (formatInfo.IsCompressed)
            {
                _log.Print($"Image is compressed using [{formatInfo.Format}] as its pixel format.", LoggingLevel.Intermediate);

                if (_compressor == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format));
                }

                _log.Print($"Loading image '{workFile.FullPath}'...", LoggingLevel.Simple);
                importImage = _compressor.Decompress(ref workFile, metaData);

                if (importImage == null)
                {
                    throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format));
                }

                _log.Print($"Loaded compressed ([{formatInfo.Format}]) image data as [{importImage.Format}]", LoggingLevel.Intermediate);
            }
            else
            {
                using (Stream workStream = workFile.OpenStream())
                {
                    importImage = importCodec.LoadFromStream(workStream);
                }
            }

            return(file, importImage, workFile, metaData.Format);
        }
Esempio n. 14
0
        /// <summary>Function to load all the specified plug in assemblies.</summary>
        /// <param name="pluginCache">The plugin cache that will hold the plug in assembies.</param>
        /// <param name="pluginAssemblyFiles">The list of plug in assembly paths to load.</param>
        /// <param name="log">The application logging interface.</param>
        /// <returns>A list of <see cref="PlugInAssemblyState"/> objects for each plug in assembly loaded.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="pluginAssemblyFiles" /> parameter is <b>null</b></exception>
        public static IReadOnlyList <PlugInAssemblyState> ValidateAndLoadAssemblies(this GorgonMefPlugInCache pluginCache, IEnumerable <FileInfo> pluginAssemblyFiles, IGorgonLog log)
        {
            if (pluginAssemblyFiles == null)
            {
                throw new ArgumentNullException(nameof(pluginAssemblyFiles));
            }

            var records = new List <PlugInAssemblyState>();

            // We use this to determine whether the plug in can be loaded into the current platform.
            AssemblyPlatformType currentPlatform = IntPtr.Size == 8 ? AssemblyPlatformType.x64 : AssemblyPlatformType.x86;

            foreach (FileInfo file in pluginAssemblyFiles)
            {
                // If we've already got the assembly loaded into this cache, then there's no need to try and load it.
                if (pluginCache.PlugInAssemblies?.Any(item => string.Equals(item, file.FullName, StringComparison.OrdinalIgnoreCase)) ?? false)
                {
                    continue;
                }

                if (!file.Exists)
                {
                    log.Print($"[ERROR] Plug in '{file.FullName}' was not found.", LoggingLevel.Verbose);
                    records.Add(new PlugInAssemblyState(file.FullName, Resources.GOREDIT_PLUGIN_LOAD_FAIL_NOT_FOUND, false));
                    continue;
                }

                (bool isManaged, AssemblyPlatformType platformType) = GorgonMefPlugInCache.IsManagedAssembly(file.FullName);

                if ((!isManaged) || (platformType == AssemblyPlatformType.Unknown))
                {
                    log.Print($"[WARNING] Skipping '{file.FullName}'. Not a valid .NET assembly.", LoggingLevel.Verbose);
                    records.Add(new PlugInAssemblyState(file.FullName, string.Format(Resources.GOREDIT_PLUGIN_LOAD_FAIL_NOT_DOT_NET, file.Name), false));
                    continue;
                }

                // Ensure that our platform type matches (AnyCPU is exempt and will always run, and DLLs don't allow Prefer 32 bit).
                if ((currentPlatform == AssemblyPlatformType.x86) && (platformType == AssemblyPlatformType.x64))
                {
                    log.Print($"[ERROR] Cannot load assembly '{file.FullName}', currently executing in an x86 environment, but the assembly is x64.", LoggingLevel.Simple);
                    records.Add(new PlugInAssemblyState(file.FullName, string.Format(Resources.GOREDIT_PLUGIN_LOAD_FAIL_PLATFORM_MISMATCH, file.Name, platformType, currentPlatform), true));
                    continue;
                }

                if ((currentPlatform == AssemblyPlatformType.x64) && (platformType == AssemblyPlatformType.x86))
                {
                    log.Print($"[ERROR] Cannot load assembly '{file.FullName}', currently executing in an x64 environment, but the assembly is x86.", LoggingLevel.Simple);
                    records.Add(new PlugInAssemblyState(file.FullName, string.Format(Resources.GOREDIT_PLUGIN_LOAD_FAIL_PLATFORM_MISMATCH, file.Name, platformType, currentPlatform), true));
                    continue;
                }

                try
                {
                    log.Print($"Loading plug in assembly '{file.FullName}'...", LoggingLevel.Simple);
                    pluginCache.LoadPlugInAssemblies(file.DirectoryName, file.Name);

                    records.Add(new PlugInAssemblyState(file.FullName, string.Empty, true));
                }
                catch (Exception ex)
                {
                    log.Print($"ERROR: Cannot load plug in assembly '{file.FullName}'.", LoggingLevel.Simple);
                    log.LogException(ex);
                    records.Add(new PlugInAssemblyState(file.FullName, string.Format(Resources.GOREDIT_PLUGIN_LOAD_FAIL_EXCEPTION, file.Name, ex.Message), true));
                }
            }

            return(records);
        }
        /// <summary>
        /// Function to print device log information.
        /// </summary>
        /// <param name="device">Device to print.</param>
        /// <param name="log">The log interface to output debug messages.</param>
        private static void PrintLog(VideoAdapterInfo device, IGorgonLog log)
        {
            log.Print($"Device found: {device.Name}", LoggingLevel.Simple);
            log.Print("===================================================================", LoggingLevel.Simple);
            log.Print($"Supported feature set: {device.FeatureSet}", LoggingLevel.Simple);
            log.Print($"Video memory: {(device.Memory.Video).FormatMemory()}", LoggingLevel.Simple);
            log.Print($"System memory: {(device.Memory.System).FormatMemory()}", LoggingLevel.Intermediate);
            log.Print($"Shared memory: {(device.Memory.Shared).FormatMemory()}", LoggingLevel.Intermediate);
            log.Print($"Device ID: 0x{device.PciInfo.DeviceID.FormatHex()}", LoggingLevel.Verbose);
            log.Print($"Sub-system ID: 0x{device.PciInfo.SubSystemID.FormatHex()}", LoggingLevel.Verbose);
            log.Print($"Vendor ID: 0x{device.PciInfo.VendorID.FormatHex()}", LoggingLevel.Verbose);
            log.Print($"Revision: {device.PciInfo.Revision}", LoggingLevel.Verbose);
            log.Print($"Unique ID: 0x{device.Luid.FormatHex()}", LoggingLevel.Verbose);
            log.Print("===================================================================", LoggingLevel.Simple);

            foreach (IGorgonVideoOutputInfo output in device.Outputs)
            {
                log.Print($"Found output '{output.Name}'.", LoggingLevel.Simple);
                log.Print("===================================================================", LoggingLevel.Verbose);
                log.Print($"Output bounds: ({output.DesktopBounds.Left}x{output.DesktopBounds.Top})-({output.DesktopBounds.Right}x{output.DesktopBounds.Bottom})",
                          LoggingLevel.Verbose);
                log.Print($"Monitor handle: 0x{output.MonitorHandle.FormatHex()}", LoggingLevel.Verbose);
                log.Print($"Attached to desktop: {output.IsAttachedToDesktop}", LoggingLevel.Verbose);
                log.Print($"Monitor rotation: {output.Rotation}", LoggingLevel.Verbose);
                log.Print("===================================================================", LoggingLevel.Simple);

                log.Print($"Retrieving video modes for output '{output.Name}'...", LoggingLevel.Simple);
                log.Print("===================================================================", LoggingLevel.Simple);

                foreach (GorgonVideoMode mode in output.VideoModes)
                {
                    log.Print($"{mode.ToString().PadRight(70)}\tScaling: {mode.Scaling.ToString().PadRight(20)}Scanline Order: {mode.ScanlineOrder.ToString().PadRight(25)}Stereo: {mode.SupportsStereo}",
                              LoggingLevel.Verbose);
                }

                log.Print("===================================================================", LoggingLevel.Verbose);
                log.Print($"Found {output.VideoModes.Count} video modes for output '{output.Name}'.", LoggingLevel.Simple);
                log.Print("===================================================================", LoggingLevel.Simple);
            }
        }
        /// <summary>
        /// Function to perform an enumeration of the video adapters attached to the system and populate this list.
        /// </summary>
        /// <param name="enumerateWARPDevice"><b>true</b> to enumerate the WARP software device, or <b>false</b> to exclude it.</param>
        /// <param name="log">The log that will capture debug logging messages.</param>
        /// <remarks>
        /// <para>
        /// Use this method to populate a list with information about the video adapters installed in the system.
        /// </para>
        /// <para>
        /// You may include the WARP device, which is a software based device that emulates most of the functionality of a video adapter, by setting the <paramref name="enumerateWARPDevice"/> to <b>true</b>.
        /// </para>
        /// <para>
        /// Gorgon requires a video adapter that is capable of supporting Direct 3D 12.0 at minimum. If no suitable devices are found installed in the computer, then the resulting list will be empty.
        /// </para>
        /// </remarks>
        public static IReadOnlyList <IGorgonVideoAdapterInfo> Enumerate(bool enumerateWARPDevice, IGorgonLog log)
        {
            var devices = new List <IGorgonVideoAdapterInfo>();

            if (log == null)
            {
                log = GorgonLog.NullLog;
            }

            using (var factory2 = new Factory2(GorgonGraphics.IsDebugEnabled))
                using (Factory5 factory5 = factory2.QueryInterface <Factory5>())
                {
                    int adapterCount = factory5.GetAdapterCount1();

                    log.Print("Enumerating video adapters...", LoggingLevel.Simple);

                    // Begin gathering device information.
                    for (int i = 0; i < adapterCount; i++)
                    {
                        // Get the video adapter information.
                        using (Adapter1 adapter1 = factory5.GetAdapter1(i))
                            using (Adapter4 adapter = adapter1.QueryInterface <Adapter4>())
                            {
                                // ReSharper disable BitwiseOperatorOnEnumWithoutFlags
                                if (((adapter.Desc3.Flags & AdapterFlags3.Remote) == AdapterFlags3.Remote) ||
                                    ((adapter.Desc3.Flags & AdapterFlags3.Software) == AdapterFlags3.Software))
                                {
                                    continue;
                                }
                                // ReSharper restore BitwiseOperatorOnEnumWithoutFlags

                                D3D11.DeviceCreationFlags flags = D3D11.DeviceCreationFlags.None;

                                if (GorgonGraphics.IsDebugEnabled)
                                {
                                    flags = D3D11.DeviceCreationFlags.Debug;
                                }

                                // We create a D3D device here to filter out unsupported video modes from the format list.
                                using (var D3DDevice = new D3D11.Device(adapter, flags, D3D.FeatureLevel.Level_12_1, D3D.FeatureLevel.Level_12_0))
                                    using (D3D11.Device5 D3DDevice5 = D3DDevice.QueryInterface <D3D11.Device5>())
                                    {
                                        D3DDevice5.DebugName = "Output enumerator device.";

                                        FeatureSet?featureSet = GetFeatureLevel(D3DDevice5);

                                        // Do not enumerate this device if its feature set is not supported.
                                        if (featureSet == null)
                                        {
                                            log.Print("This video adapter is not supported by Gorgon and will be skipped.", LoggingLevel.Verbose);
                                            continue;
                                        }

                                        Dictionary <string, VideoOutputInfo> outputs = GetOutputs(D3DDevice5, adapter, adapter.GetOutputCount(), log);

                                        if (outputs.Count <= 0)
                                        {
                                            log.Print($"WARNING: Video adapter {adapter.Description1.Description.Replace("\0", string.Empty)} has no outputs. Full screen mode will not be possible.",
                                                      LoggingLevel.Verbose);
                                        }

                                        var videoAdapter = new VideoAdapterInfo(i, adapter, featureSet.Value, outputs, VideoDeviceType.Hardware);

                                        devices.Add(videoAdapter);
                                        PrintLog(videoAdapter, log);
                                    }
                            }
                    }

                    // Get software devices.
                    if (!enumerateWARPDevice)
                    {
                        return(devices);
                    }

                    VideoAdapterInfo device = GetWARPSoftwareDevice(devices.Count, factory5, log);

                    if (device != null)
                    {
                        devices.Add(device);
                    }
                }

            log.Print("Found {0} video adapters.", LoggingLevel.Simple, devices.Count);

            return(devices);
        }