Skip to content

A modern C# wrapper for Intel Graphics Control Library (IGCL), providing easy access to Intel GPU features and settings.

Notifications You must be signed in to change notification settings

terrymacdonald/IGCLWrapper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

127 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IGCLWrapper

A modern C# wrapper for Intel Graphics Control Library (IGCL), providing easy access to Intel GPU features and settings.

Features

  • IntPtr-based API surface; no custom handle types to manage
  • Automatic cleanup via IDisposable (SafeHandle-backed)
  • Strongly typed structs/enums matching IGCL headers
  • Helper methods for common adapter/display queries
  • Facade DTOs for bool-friendly helper results, with *Native() accessors when you need raw structs
  • ClangSharp-generated bindings kept in sync with the SDK
  • Tests skip gracefully when hardware is absent
  • Split test suites:
    • IGCLWrapper.NativeTests exercises only ClangSharp-generated APIs.
    • IGCLWrapper.FacadeTests exercises the new facade helpers.

Quick Start

Prerequisites

  • Intel GPU with IGCL support
  • Windows 10/11 x64
  • .NET 10.0 SDK
  • Intel Graphics drivers (25.20.100.6618+)

Build the wrapper

git clone https://github.com/terrymacdonald/IGCLWrapper.git
cd IGCLWrapper

./prepare_igcl.ps1   # pulls the IGCL SDK
./build_igcl.ps1     # restores, regenerates bindings, builds, tests

Install / consume the wrapper

  • Local build artifacts: after ./build_igcl.ps1, reference IGCLWrapper\bin\Debug\net10.0\IGCLWrapper.dll (or Release if you build release) from your project.
  • Add as a project reference: add IGCLWrapper/IGCLWrapper.csproj to your solution and reference it.
  • Requirements at runtime: Windows x64, Intel GPU/driver, and IGCL DLLs available (the wrapper dynamically loads ControlLib from the installed Intel drivers).

Basic usage (facade)

using IGCLWrapper;

using var api = IGCLApiHelper.Initialize();
var adapters = api.EnumerateAdapters();
Console.WriteLine($"Found {adapters.Count} Intel GPU(s)");

foreach (var adapter in adapters)
{
    var props = adapter.GetProperties();
    Console.WriteLine($"\nGPU: {adapter.Name}");
    Console.WriteLine($"Device ID: 0x{props.pci_device_id:X}");

    foreach (var display in adapter.EnumerateDisplayOutputs())
    {
        if (!display.IsActive()) continue;
        var (width, height) = display.GetResolution();
        var refresh = display.GetRefreshRateHz();
        Console.WriteLine($"  {width}x{height} @ {refresh:F2} Hz");
    }
}

Error handling

try
{
    using var igcl = IGCLApi.Initialize();
    // use the API
}
catch (IGCLException ex)
{
    Console.WriteLine($"IGCL Error: {ex.Result} - {ex.Message}");
}
catch (DllNotFoundException)
{
    Console.WriteLine("IGCL DLL not found. Install Intel Graphics drivers.");
}

Working with the facade helpers (IGCLApiHelper)

Use the facade helpers to avoid manual struct sizing/handle management. DTO-returning helpers use bool properties; call *Native() variants when you need the raw structs. Get/Set operations are split into Get*() and Set*() helpers; GetSet*Native() remains for direct IGCL calls.

List active display resolutions

using IGCLWrapper;

using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
    foreach (var display in adapter.EnumerateDisplayOutputs())
    {
        if (!display.IsActive()) continue;
        var (w, h) = display.GetResolution();
        var hz = display.GetRefreshRateHz();
        Console.WriteLine($"{adapter.Name}: {w}x{h} @ {hz:F2} Hz");
    }
}

List only combined displays

using IGCLWrapper;
using System.Linq;

using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
    var result = adapter.GetCombinedDisplay();
    if (result.IsSupported && result.NumOutputs > 0)
    {
        Console.WriteLine($"Adapter {adapter.Name} has a combined display with {result.NumOutputs} outputs.");
    }
}

Query combined display layout

using IGCLWrapper;
using System.Linq;

using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
    var combined = adapter.GetCombinedDisplay();
    if (combined.NumOutputs == 0 || combined.ChildInfos == null)
    {
        Console.WriteLine($"Adapter {adapter.Name} has no combined display configured.");
        continue;
    }

    Console.WriteLine($"Combined display: {combined.CombinedDesktopWidth}x{combined.CombinedDesktopHeight}");
    for (var i = 0; i < combined.NumOutputs; i++)
    {
        var child = combined.ChildInfos[i];
        Console.WriteLine(
            $"  Output {i}: handle={child.DisplayOutput}, " +
            $"FbSrc={child.FbSrc.Left},{child.FbSrc.Top},{child.FbSrc.Right},{child.FbSrc.Bottom}, " +
            $"FbPos={child.FbPos.Left},{child.FbPos.Top},{child.FbPos.Right},{child.FbPos.Bottom}, " +
            $"Orientation={child.DisplayOrientation}, " +
            $"Target={child.TargetMode.Width}x{child.TargetMode.Height}@{child.TargetMode.RefreshRate}");
    }
}

Get current temperature

using IGCLWrapper;

using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
    var tempHelper = api.GetTemperatureHelper(adapter);
    var sensor = tempHelper.EnumTemperatureSensors().FirstOrDefault();
    if (sensor != IntPtr.Zero)
    {
        var tempC = tempHelper.TemperatureGetState(sensor);
        Console.WriteLine($"{adapter.Name}: {tempC:F1} C");
    }
}

Wait for a property change on an adapter

using IGCLWrapper;

using var api = IGCLApiHelper.Initialize();
var adapter = api.EnumerateAdapters().First();
var args = new ctl_wait_property_change_args_t
{
    Size = (uint)sizeof(ctl_wait_property_change_args_t),
    Version = 0,
    PropertyType = ctl_property_type_flags_t.CTL_PROPERTY_TYPE_FLAG_DISPLAY,
    TimeOutMilliSec = 5_000 // 5 seconds
};
adapter.WaitForPropertyChange(args);
Console.WriteLine("Property change observed or timeout reached.");

Doing the same with the native API

If you prefer direct P/Invoke access, use IGCLApi and the generated structs/functions.

List active display resolutions (native)

using IGCLWrapper;

using var igcl = IGCLApi.Initialize();
foreach (var adapter in igcl.EnumerateAdapters())
{
    var displays = igcl.EnumerateDisplays(adapter);
    foreach (var display in displays)
    {
        var props = new ctl_display_properties_t { Size = (uint)sizeof(ctl_display_properties_t), Version = 0 };
        if (IGCL.ctlGetDisplayProperties((_ctl_display_output_handle_t*)display, &props) != ctl_result_t.CTL_RESULT_SUCCESS)
            continue;
        var timing = props.Display_Timing_Info;
        if (timing.HActive == 0 || timing.VActive == 0) continue;
        Console.WriteLine($"{timing.HActive}x{timing.VActive} @ {timing.RefreshRate / 1000.0:F2} Hz");
    }
}

List only combined displays (native)

using IGCLWrapper;

using var igcl = IGCLApi.Initialize();
foreach (var adapter in igcl.EnumerateAdapters())
{
    var args = new ctl_combined_display_args_t
    {
        Size = (uint)sizeof(ctl_combined_display_args_t),
        Version = 0,
        OpType = ctl_combined_display_optype_t.CTL_COMBINED_DISPLAY_OPTYPE_QUERY_CONFIG
    };
    var result = IGCL.ctlGetSetCombinedDisplay((_ctl_device_adapter_handle_t*)adapter, &args);
    if (result == ctl_result_t.CTL_RESULT_SUCCESS && args.IsSupported && args.NumOutputs > 0)
    {
        Console.WriteLine("Combined display present.");
    }
}

Get current temperature (native)

using IGCLWrapper;

using var igcl = IGCLApi.Initialize();
foreach (var adapter in igcl.EnumerateAdapters())
{
    uint count = 0;
    if (IGCL.ctlEnumTemperatureSensors((_ctl_device_adapter_handle_t*)adapter, &count, null) != ctl_result_t.CTL_RESULT_SUCCESS || count == 0)
        continue;

    var sensors = new IntPtr[count];
    unsafe
    {
        fixed (IntPtr* pSensors = sensors)
        {
            if (IGCL.ctlEnumTemperatureSensors((_ctl_device_adapter_handle_t*)adapter, &count, (_ctl_temp_handle_t**)pSensors) != ctl_result_t.CTL_RESULT_SUCCESS)
                continue;
        }

        double temp = 0;
        if (IGCL.ctlTemperatureGetState((_ctl_temp_handle_t*)sensors[0], &temp) == ctl_result_t.CTL_RESULT_SUCCESS)
        {
            Console.WriteLine($"{temp:F1} C");
        }
    }
}

Testing

Tests require Intel GPU hardware and the IGCL DLLs present. They skip gracefully if not available.

./test_igcl.ps1
# or
dotnet test IGCLWrapper.NativeTests/IGCLWrapper.NativeTests.csproj
dotnet test IGCLWrapper.FacadeTests/IGCLWrapper.FacadeTests.csproj

Updating bindings

When Intel releases a new IGCL:

./prepare_igcl.ps1   # update SDK bits
./build_igcl.ps1     # regenerates bindings via ClangSharp and rebuilds

Project structure

  • IGCLWrapper/ - main wrapper
    • cs_generated/ - ClangSharp output (auto-generated)
    • IGCLApi.cs - high-level API
    • IGCLExtensions.cs - helpers for common ops
  • IGCLWrapper.NativeTests/ - native test suite
  • IGCLWrapper.FacadeTests/ - facade test suite
  • Samples/ - sample apps
  • drivers.gpu.control-library/ - IGCL SDK payload (populated by prepare script)

Usage notes

  • Always dispose IGCLApi (use using); SafeHandle + finalizer backstops leaks.
  • Handles returned from enumerate calls are opaque; pass them back to IGCL or helper methods.
  • Facade helpers return DTOs with bool properties; use *Native() helper methods to access raw structs.
  • Struct Version fields are bytes in native structs; use (byte)0/1 in native code paths.

Contributing

PRs welcome-please add/keep tests passing and let the generator own cs_generated.

About

A modern C# wrapper for Intel Graphics Control Library (IGCL), providing easy access to Intel GPU features and settings.

Resources

Stars

Watchers

Forks

Packages

No packages published