/*
 * Process Hacker Extended Tools -
 *   GPU monitoring
 *
 * Copyright (C) 2011-2015 wj32
 * Copyright (C) 2016 dmex
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

#define INITGUID
#include "exttools.h"
#include <cfgmgr32.h>
#include <devpkey.h>
#include "d3dkmt.h"
#include "gpumon.h"

static GUID GUID_DISPLAY_DEVICE_ARRIVAL_I = { 0x1ca05180, 0xa699, 0x450a, { 0x9a, 0x0c, 0xde, 0x4f, 0xbe, 0x3d, 0xdd, 0x89 } };

static PFND3DKMT_OPENADAPTERFROMDEVICENAME D3DKMTOpenAdapterFromDeviceName_I;
static PFND3DKMT_CLOSEADAPTER D3DKMTCloseAdapter_I;
static PFND3DKMT_QUERYSTATISTICS D3DKMTQueryStatistics_I;

BOOLEAN EtGpuEnabled;
static PPH_LIST EtpGpuAdapterList;
static PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration;

ULONG EtGpuTotalNodeCount;
ULONG EtGpuTotalSegmentCount;
ULONG64 EtGpuDedicatedLimit;
ULONG64 EtGpuSharedLimit;
ULONG EtGpuNextNodeIndex = 0;
RTL_BITMAP EtGpuNodeBitMap;
PULONG EtGpuNodeBitMapBuffer;
ULONG EtGpuNodeBitMapBitsSet;
PULONG EtGpuNewNodeBitMapBuffer;

PH_UINT64_DELTA EtClockTotalRunningTimeDelta;
LARGE_INTEGER EtClockTotalRunningTimeFrequency;
PH_UINT64_DELTA EtGpuTotalRunningTimeDelta;
PH_UINT64_DELTA EtGpuSystemRunningTimeDelta;
FLOAT EtGpuNodeUsage;
PH_CIRCULAR_BUFFER_FLOAT EtGpuNodeHistory;
PH_CIRCULAR_BUFFER_ULONG EtMaxGpuNodeHistory; // ID of max. GPU usage process
PH_CIRCULAR_BUFFER_FLOAT EtMaxGpuNodeUsageHistory;

PPH_UINT64_DELTA EtGpuNodesTotalRunningTimeDelta;
PPH_CIRCULAR_BUFFER_FLOAT EtGpuNodesHistory;

ULONG64 EtGpuDedicatedUsage;
ULONG64 EtGpuSharedUsage;
PH_CIRCULAR_BUFFER_ULONG EtGpuDedicatedHistory;
PH_CIRCULAR_BUFFER_ULONG EtGpuSharedHistory;

VOID EtGpuMonitorInitialization(
    VOID
    )
{
    if (PhGetIntegerSetting(SETTING_NAME_ENABLE_GPU_MONITOR) && WindowsVersion >= WINDOWS_7)
    {
        PVOID gdi32Handle;

        if (gdi32Handle = PhGetDllHandle(L"gdi32.dll"))
        {
            D3DKMTOpenAdapterFromDeviceName_I = PhGetProcedureAddress(gdi32Handle, "D3DKMTOpenAdapterFromDeviceName", 0);
            D3DKMTCloseAdapter_I = PhGetProcedureAddress(gdi32Handle, "D3DKMTCloseAdapter", 0);
            D3DKMTQueryStatistics_I = PhGetProcedureAddress(gdi32Handle, "D3DKMTQueryStatistics", 0);
        }

        if (
            D3DKMTOpenAdapterFromDeviceName_I &&
            D3DKMTCloseAdapter_I &&
            D3DKMTQueryStatistics_I
            )
        {
            EtpGpuAdapterList = PhCreateList(4);

            if (EtpInitializeD3DStatistics() && EtpGpuAdapterList->Count != 0)
                EtGpuEnabled = TRUE;
        }
    }

    if (EtGpuEnabled)
    {
        ULONG sampleCount;
        ULONG i;
        ULONG j;
        PPH_STRING bitmapString;
        D3DKMT_QUERYSTATISTICS queryStatistics;

        sampleCount = PhGetIntegerSetting(L"SampleCount");
        PhInitializeCircularBuffer_FLOAT(&EtGpuNodeHistory, sampleCount);
        PhInitializeCircularBuffer_ULONG(&EtMaxGpuNodeHistory, sampleCount);
        PhInitializeCircularBuffer_FLOAT(&EtMaxGpuNodeUsageHistory, sampleCount);
        PhInitializeCircularBuffer_ULONG(&EtGpuDedicatedHistory, sampleCount);
        PhInitializeCircularBuffer_ULONG(&EtGpuSharedHistory, sampleCount);

        EtGpuNodesTotalRunningTimeDelta = PhAllocate(sizeof(PH_UINT64_DELTA) * EtGpuTotalNodeCount);
        memset(EtGpuNodesTotalRunningTimeDelta, 0, sizeof(PH_UINT64_DELTA) * EtGpuTotalNodeCount);
        EtGpuNodesHistory = PhAllocate(sizeof(PH_CIRCULAR_BUFFER_FLOAT) * EtGpuTotalNodeCount);

        for (i = 0; i < EtGpuTotalNodeCount; i++)
        {
            PhInitializeCircularBuffer_FLOAT(&EtGpuNodesHistory[i], sampleCount);
        }

        PhRegisterCallback(
            &PhProcessesUpdatedEvent,
            EtGpuProcessesUpdatedCallback,
            NULL,
            &ProcessesUpdatedCallbackRegistration
            );

        // Load the node bitmap.

        bitmapString = PhGetStringSetting(SETTING_NAME_GPU_NODE_BITMAP);

        if (!(bitmapString->Length & 3) && bitmapString->Length / 4 <= BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount))
        {
            PhHexStringToBuffer(&bitmapString->sr, (PUCHAR)EtGpuNodeBitMapBuffer);
            EtGpuNodeBitMapBitsSet = RtlNumberOfSetBits(&EtGpuNodeBitMap);
        }

        PhDereferenceObject(bitmapString);

        // Fix up the node bitmap if the current node count differs from what we've seen.
        if (EtGpuTotalNodeCount != PhGetIntegerSetting(SETTING_NAME_GPU_LAST_NODE_COUNT))
        {
            ULONG maxContextSwitch = 0;
            ULONG maxContextSwitchNodeIndex = 0;

            RtlClearAllBits(&EtGpuNodeBitMap);
            EtGpuNodeBitMapBitsSet = 0;

            for (i = 0; i < EtpGpuAdapterList->Count; i++)
            {
                PETP_GPU_ADAPTER gpuAdapter = EtpGpuAdapterList->Items[i];

                for (j = 0; j < gpuAdapter->NodeCount; j++)
                {
                    memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));
                    queryStatistics.Type = D3DKMT_QUERYSTATISTICS_NODE;
                    queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid;
                    queryStatistics.QueryNode.NodeId = j;

                    if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics)))
                    {
                        // The numbers below are quite arbitrary.
                        if (queryStatistics.QueryResult.NodeInformation.GlobalInformation.RunningTime.QuadPart != 0 &&
                            queryStatistics.QueryResult.NodeInformation.GlobalInformation.ContextSwitch > 10000)
                        {
                            RtlSetBits(&EtGpuNodeBitMap, gpuAdapter->FirstNodeIndex + j, 1);
                            EtGpuNodeBitMapBitsSet++;
                        }

                        if (maxContextSwitch < queryStatistics.QueryResult.NodeInformation.GlobalInformation.ContextSwitch)
                        {
                            maxContextSwitch = queryStatistics.QueryResult.NodeInformation.GlobalInformation.ContextSwitch;
                            maxContextSwitchNodeIndex = gpuAdapter->FirstNodeIndex + j;
                        }
                    }
                }
            }

            // Just in case
            if (EtGpuNodeBitMapBitsSet == 0)
            {
                RtlSetBits(&EtGpuNodeBitMap, maxContextSwitchNodeIndex, 1);
                EtGpuNodeBitMapBitsSet = 1;
            }

            PhSetIntegerSetting(SETTING_NAME_GPU_LAST_NODE_COUNT, EtGpuTotalNodeCount);
        }
    }
}

BOOLEAN EtpInitializeD3DStatistics(
    VOID
    )
{
    PWSTR deviceInterfaceList = NULL;
    ULONG deviceInterfaceListLength = 0;
    PWSTR deviceInterface;
    D3DKMT_OPENADAPTERFROMDEVICENAME openAdapterFromDeviceName;
    D3DKMT_QUERYSTATISTICS queryStatistics;
    D3DKMT_CLOSEADAPTER closeAdapter;

    if (CM_Get_Device_Interface_List_Size(
        &deviceInterfaceListLength,
        &GUID_DISPLAY_DEVICE_ARRIVAL_I,
        NULL,
        CM_GET_DEVICE_INTERFACE_LIST_PRESENT
        ) != CR_SUCCESS)
    {
        return FALSE;
    }

    deviceInterfaceList = PhAllocate(deviceInterfaceListLength * sizeof(WCHAR));
    memset(deviceInterfaceList, 0, deviceInterfaceListLength * sizeof(WCHAR));

    if (CM_Get_Device_Interface_List(
        &GUID_DISPLAY_DEVICE_ARRIVAL_I,
        NULL,
        deviceInterfaceList,
        deviceInterfaceListLength,
        CM_GET_DEVICE_INTERFACE_LIST_PRESENT
        ) != CR_SUCCESS)
    {
        PhFree(deviceInterfaceList);
        return FALSE;
    }

    for (deviceInterface = deviceInterfaceList; *deviceInterface; deviceInterface += PhCountStringZ(deviceInterface) + 1)
    {
        memset(&openAdapterFromDeviceName, 0, sizeof(D3DKMT_OPENADAPTERFROMDEVICENAME));
        openAdapterFromDeviceName.pDeviceName = deviceInterface;

        if (NT_SUCCESS(D3DKMTOpenAdapterFromDeviceName_I(&openAdapterFromDeviceName)))
        {
            memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));
            queryStatistics.Type = D3DKMT_QUERYSTATISTICS_ADAPTER;
            queryStatistics.AdapterLuid = openAdapterFromDeviceName.AdapterLuid;

            if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics)))
            {
                PETP_GPU_ADAPTER gpuAdapter;
                ULONG i;

                gpuAdapter = EtpAllocateGpuAdapter(queryStatistics.QueryResult.AdapterInformation.NbSegments);
                gpuAdapter->AdapterLuid = openAdapterFromDeviceName.AdapterLuid;
                gpuAdapter->Description = EtpQueryDeviceDescription(deviceInterface);
                gpuAdapter->NodeCount = queryStatistics.QueryResult.AdapterInformation.NodeCount;
                gpuAdapter->SegmentCount = queryStatistics.QueryResult.AdapterInformation.NbSegments;
                RtlInitializeBitMap(&gpuAdapter->ApertureBitMap, gpuAdapter->ApertureBitMapBuffer, queryStatistics.QueryResult.AdapterInformation.NbSegments);

                PhAddItemList(EtpGpuAdapterList, gpuAdapter);
                EtGpuTotalNodeCount += gpuAdapter->NodeCount;
                EtGpuTotalSegmentCount += gpuAdapter->SegmentCount;

                gpuAdapter->FirstNodeIndex = EtGpuNextNodeIndex;
                EtGpuNextNodeIndex += gpuAdapter->NodeCount;

                for (i = 0; i < gpuAdapter->SegmentCount; i++)
                {
                    memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));
                    queryStatistics.Type = D3DKMT_QUERYSTATISTICS_SEGMENT;
                    queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid;
                    queryStatistics.QuerySegment.SegmentId = i;

                    if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics)))
                    {
                        ULONG64 commitLimit;
                        ULONG aperture;

                        if (WindowsVersion >= WINDOWS_8)
                        {
                            commitLimit = queryStatistics.QueryResult.SegmentInformation.CommitLimit;
                            aperture = queryStatistics.QueryResult.SegmentInformation.Aperture;
                        }
                        else
                        {
                            commitLimit = queryStatistics.QueryResult.SegmentInformationV1.CommitLimit;
                            aperture = queryStatistics.QueryResult.SegmentInformationV1.Aperture;
                        }

                        if (aperture)
                            EtGpuSharedLimit += commitLimit;
                        else
                            EtGpuDedicatedLimit += commitLimit;

                        if (aperture)
                            RtlSetBits(&gpuAdapter->ApertureBitMap, i, 1);
                    }
                }
            }

            memset(&closeAdapter, 0, sizeof(D3DKMT_CLOSEADAPTER));
            closeAdapter.hAdapter = openAdapterFromDeviceName.hAdapter;
            D3DKMTCloseAdapter_I(&closeAdapter);
        }
    }

    PhFree(deviceInterfaceList);

    EtGpuNodeBitMapBuffer = PhAllocate(BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount));
    RtlInitializeBitMap(&EtGpuNodeBitMap, EtGpuNodeBitMapBuffer, EtGpuTotalNodeCount);
    RtlSetBits(&EtGpuNodeBitMap, 0, 1);
    EtGpuNodeBitMapBitsSet = 1;

    return TRUE;
}

PETP_GPU_ADAPTER EtpAllocateGpuAdapter(
    _In_ ULONG NumberOfSegments
    )
{
    PETP_GPU_ADAPTER adapter;
    SIZE_T sizeNeeded;

    sizeNeeded = FIELD_OFFSET(ETP_GPU_ADAPTER, ApertureBitMapBuffer);
    sizeNeeded += BYTES_NEEDED_FOR_BITS(NumberOfSegments);

    adapter = PhAllocate(sizeNeeded);
    memset(adapter, 0, sizeNeeded);

    return adapter;
}

PPH_STRING EtpQueryDeviceDescription(
    _In_ PWSTR DeviceInterface
    )
{
    CONFIGRET result;
    PPH_STRING string;
    ULONG bufferSize;
    DEVPROPTYPE devicePropertyType;
    DEVINST deviceInstanceHandle;
    ULONG deviceInstanceIdLength = MAX_DEVICE_ID_LEN;
    WCHAR deviceInstanceId[MAX_DEVICE_ID_LEN];

    if (CM_Get_Device_Interface_Property(
        DeviceInterface,
        &DEVPKEY_Device_InstanceId,
        &devicePropertyType,
        (PBYTE)deviceInstanceId,
        &deviceInstanceIdLength,
        0
        ) != CR_SUCCESS)
    {
        return NULL;
    }

    if (CM_Locate_DevNode(
        &deviceInstanceHandle,
        deviceInstanceId,
        CM_LOCATE_DEVNODE_NORMAL
        ) != CR_SUCCESS)
    {
        return NULL;
    }

    bufferSize = 0x40;
    string = PhCreateStringEx(NULL, bufferSize);

    if ((result = CM_Get_DevNode_Property( // CM_Get_DevNode_Registry_Property with CM_DRP_DEVICEDESC??
        deviceInstanceHandle,
        &DEVPKEY_Device_DeviceDesc,
        &devicePropertyType,
        (PBYTE)string->Buffer,
        &bufferSize,
        0
        )) != CR_SUCCESS)
    {
        PhDereferenceObject(string);
        string = PhCreateStringEx(NULL, bufferSize);

        result = CM_Get_DevNode_Property(
            deviceInstanceHandle,
            &DEVPKEY_Device_DeviceDesc,
            &devicePropertyType,
            (PBYTE)string->Buffer,
            &bufferSize,
            0
            );
    }

    if (result != CR_SUCCESS)
    {
        PhDereferenceObject(string);
        return NULL;
    }

    PhTrimToNullTerminatorString(string);

    return string;
}

VOID EtpUpdateSegmentInformation(
    _In_opt_ PET_PROCESS_BLOCK Block
    )
{
    ULONG i;
    ULONG j;
    PETP_GPU_ADAPTER gpuAdapter;
    D3DKMT_QUERYSTATISTICS queryStatistics;
    ULONG64 dedicatedUsage;
    ULONG64 sharedUsage;

    if (Block && !Block->ProcessItem->QueryHandle)
        return;

    dedicatedUsage = 0;
    sharedUsage = 0;

    for (i = 0; i < EtpGpuAdapterList->Count; i++)
    {
        gpuAdapter = EtpGpuAdapterList->Items[i];

        for (j = 0; j < gpuAdapter->SegmentCount; j++)
        {
            memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));

            if (Block)
                queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT;
            else
                queryStatistics.Type = D3DKMT_QUERYSTATISTICS_SEGMENT;

            queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid;

            if (Block)
            {
                queryStatistics.hProcess = Block->ProcessItem->QueryHandle;
                queryStatistics.QueryProcessSegment.SegmentId = j;
            }
            else
            {
                queryStatistics.QuerySegment.SegmentId = j;
            }

            if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics)))
            {
                if (Block)
                {
                    ULONG64 bytesCommitted;

                    if (WindowsVersion >= WINDOWS_8)
                    {
                        bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted;
                    }
                    else
                    {
                        bytesCommitted = (ULONG)queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted;
                    }

                    if (RtlCheckBit(&gpuAdapter->ApertureBitMap, j))
                        sharedUsage += bytesCommitted;
                    else
                        dedicatedUsage += bytesCommitted;
                }
                else
                {
                    ULONG64 bytesCommitted;

                    if (WindowsVersion >= WINDOWS_8)
                    {
                        bytesCommitted = queryStatistics.QueryResult.SegmentInformation.BytesCommitted;
                    }
                    else
                    {
                        bytesCommitted = queryStatistics.QueryResult.SegmentInformationV1.BytesCommitted;
                    }

                    if (RtlCheckBit(&gpuAdapter->ApertureBitMap, j))
                        sharedUsage += bytesCommitted;
                    else
                        dedicatedUsage += bytesCommitted;
                }
            }
        }
    }

    if (Block)
    {
        Block->GpuDedicatedUsage = dedicatedUsage;
        Block->GpuSharedUsage = sharedUsage;
    }
    else
    {
        EtGpuDedicatedUsage = dedicatedUsage;
        EtGpuSharedUsage = sharedUsage;
    }
}

VOID EtpUpdateNodeInformation(
    _In_opt_ PET_PROCESS_BLOCK Block
    )
{
    ULONG i;
    ULONG j;
    PETP_GPU_ADAPTER gpuAdapter;
    D3DKMT_QUERYSTATISTICS queryStatistics;
    ULONG64 totalRunningTime;
    ULONG64 systemRunningTime;

    if (Block && !Block->ProcessItem->QueryHandle)
        return;

    totalRunningTime = 0;
    systemRunningTime = 0;

    for (i = 0; i < EtpGpuAdapterList->Count; i++)
    {
        gpuAdapter = EtpGpuAdapterList->Items[i];

        for (j = 0; j < gpuAdapter->NodeCount; j++)
        {
            if (Block && !RtlCheckBit(&EtGpuNodeBitMap, gpuAdapter->FirstNodeIndex + j))
                continue;

            memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));

            if (Block)
                queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_NODE;
            else
                queryStatistics.Type = D3DKMT_QUERYSTATISTICS_NODE;

            queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid;

            if (Block)
            {
                queryStatistics.hProcess = Block->ProcessItem->QueryHandle;
                queryStatistics.QueryProcessNode.NodeId = j;
            }
            else
            {
                queryStatistics.QueryNode.NodeId = j;
            }

            if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics)))
            {
                if (Block)
                {
                    totalRunningTime += queryStatistics.QueryResult.ProcessNodeInformation.RunningTime.QuadPart;
                }
                else
                {
                    ULONG nodeIndex;

                    nodeIndex = gpuAdapter->FirstNodeIndex + j;

                    PhUpdateDelta(&EtGpuNodesTotalRunningTimeDelta[nodeIndex], queryStatistics.QueryResult.NodeInformation.GlobalInformation.RunningTime.QuadPart);

                    if (RtlCheckBit(&EtGpuNodeBitMap, gpuAdapter->FirstNodeIndex + j))
                    {
                        totalRunningTime += queryStatistics.QueryResult.NodeInformation.GlobalInformation.RunningTime.QuadPart;
                        systemRunningTime += queryStatistics.QueryResult.NodeInformation.SystemInformation.RunningTime.QuadPart;
                    }
                }
            }
        }
    }

    if (Block)
    {
        PhUpdateDelta(&Block->GpuRunningTimeDelta, totalRunningTime);
    }
    else
    {
        LARGE_INTEGER performanceCounter;

        NtQueryPerformanceCounter(&performanceCounter, &EtClockTotalRunningTimeFrequency);
        PhUpdateDelta(&EtClockTotalRunningTimeDelta, performanceCounter.QuadPart);
        PhUpdateDelta(&EtGpuTotalRunningTimeDelta, totalRunningTime);
        PhUpdateDelta(&EtGpuSystemRunningTimeDelta, systemRunningTime);
    }
}

VOID NTAPI EtGpuProcessesUpdatedCallback(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    static ULONG runCount = 0; // MUST keep in sync with runCount in process provider

    DOUBLE elapsedTime; // total GPU node elapsed time in micro-seconds
    ULONG i;
    PLIST_ENTRY listEntry;
    FLOAT maxNodeValue = 0;
    PET_PROCESS_BLOCK maxNodeBlock = NULL;

    // Update global statistics.

    EtpUpdateSegmentInformation(NULL);
    EtpUpdateNodeInformation(NULL);

    elapsedTime = (DOUBLE)EtClockTotalRunningTimeDelta.Delta * 10000000 / EtClockTotalRunningTimeFrequency.QuadPart;

    if (elapsedTime != 0)
        EtGpuNodeUsage = (FLOAT)(EtGpuTotalRunningTimeDelta.Delta / (elapsedTime * EtGpuNodeBitMapBitsSet));
    else
        EtGpuNodeUsage = 0;

    if (EtGpuNodeUsage > 1)
        EtGpuNodeUsage = 1;

    // Do the update of the node bitmap if needed.
    if (EtGpuNewNodeBitMapBuffer)
    {
        PULONG newBuffer;

        newBuffer = _InterlockedExchangePointer(&EtGpuNewNodeBitMapBuffer, NULL);

        if (newBuffer)
        {
            PhFree(EtGpuNodeBitMap.Buffer);
            EtGpuNodeBitMap.Buffer = newBuffer;
            EtGpuNodeBitMapBuffer = newBuffer;
            EtGpuNodeBitMapBitsSet = RtlNumberOfSetBits(&EtGpuNodeBitMap);
            EtSaveGpuMonitorSettings();
        }
    }

    // Update per-process statistics.
    // Note: no lock is needed because we only ever modify the list on this same thread.

    listEntry = EtProcessBlockListHead.Flink;

    while (listEntry != &EtProcessBlockListHead)
    {
        PET_PROCESS_BLOCK block;

        block = CONTAINING_RECORD(listEntry, ET_PROCESS_BLOCK, ListEntry);

        EtpUpdateSegmentInformation(block);
        EtpUpdateNodeInformation(block);

        if (elapsedTime != 0)
        {
            block->GpuNodeUsage = (FLOAT)(block->GpuRunningTimeDelta.Delta / (elapsedTime * EtGpuNodeBitMapBitsSet));

            if (block->GpuNodeUsage > 1)
                block->GpuNodeUsage = 1;
        }

        if (maxNodeValue < block->GpuNodeUsage)
        {
            maxNodeValue = block->GpuNodeUsage;
            maxNodeBlock = block;
        }

        listEntry = listEntry->Flink;
    }

    // Update history buffers.

    if (runCount != 0)
    {
        PhAddItemCircularBuffer_FLOAT(&EtGpuNodeHistory, EtGpuNodeUsage);
        PhAddItemCircularBuffer_ULONG(&EtGpuDedicatedHistory, (ULONG)(EtGpuDedicatedUsage / PAGE_SIZE));
        PhAddItemCircularBuffer_ULONG(&EtGpuSharedHistory, (ULONG)(EtGpuSharedUsage / PAGE_SIZE));

        for (i = 0; i < EtGpuTotalNodeCount; i++)
        {
            FLOAT usage;

            usage = (FLOAT)(EtGpuNodesTotalRunningTimeDelta[i].Delta / elapsedTime);

            if (usage > 1)
                usage = 1;

            PhAddItemCircularBuffer_FLOAT(&EtGpuNodesHistory[i], usage);
        }

        if (maxNodeBlock)
        {
            PhAddItemCircularBuffer_ULONG(&EtMaxGpuNodeHistory, HandleToUlong(maxNodeBlock->ProcessItem->ProcessId));
            PhAddItemCircularBuffer_FLOAT(&EtMaxGpuNodeUsageHistory, maxNodeBlock->GpuNodeUsage);
            PhReferenceProcessRecordForStatistics(maxNodeBlock->ProcessItem->Record);
        }
        else
        {
            PhAddItemCircularBuffer_ULONG(&EtMaxGpuNodeHistory, 0);
            PhAddItemCircularBuffer_FLOAT(&EtMaxGpuNodeUsageHistory, 0);
        }
    }

    runCount++;
}

VOID EtSaveGpuMonitorSettings(
    VOID
    )
{
    PPH_STRING string;

    string = PhBufferToHexString((PUCHAR)EtGpuNodeBitMapBuffer, BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount));
    PhSetStringSetting2(SETTING_NAME_GPU_NODE_BITMAP, &string->sr);
    PhDereferenceObject(string);
}

ULONG EtGetGpuAdapterCount(
    VOID
    )
{
    return EtpGpuAdapterList->Count;
}

ULONG EtGetGpuAdapterIndexFromNodeIndex(
    _In_ ULONG NodeIndex
    )
{
    ULONG i;
    PETP_GPU_ADAPTER gpuAdapter;

    for (i = 0; i < EtpGpuAdapterList->Count; i++)
    {
        gpuAdapter = EtpGpuAdapterList->Items[i];

        if (NodeIndex >= gpuAdapter->FirstNodeIndex && NodeIndex < gpuAdapter->FirstNodeIndex + gpuAdapter->NodeCount)
            return i;
    }

    return -1;
}

PPH_STRING EtGetGpuAdapterDescription(
    _In_ ULONG Index
    )
{
    PPH_STRING description;

    if (Index >= EtpGpuAdapterList->Count)
        return NULL;

    description = ((PETP_GPU_ADAPTER)EtpGpuAdapterList->Items[Index])->Description;

    if (description)
    {
        PhReferenceObject(description);
        return description;
    }
    else
    {
        return NULL;
    }
}

VOID EtAllocateGpuNodeBitMap(
    _Out_ PRTL_BITMAP BitMap
    )
{
    SIZE_T numberOfBytes;

    numberOfBytes = BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount);

    BitMap->Buffer = PhAllocate(numberOfBytes);
    BitMap->SizeOfBitMap = EtGpuTotalNodeCount;
    memset(BitMap->Buffer, 0, numberOfBytes);
}

VOID EtUpdateGpuNodeBitMap(
    _In_ PRTL_BITMAP NewBitMap
    )
{
    PULONG buffer;

    buffer = _InterlockedExchangePointer(&EtGpuNewNodeBitMapBuffer, NewBitMap->Buffer);

    if (buffer)
        PhFree(buffer);
}

VOID EtQueryProcessGpuStatistics(
    _In_ HANDLE ProcessHandle,
    _Out_ PET_PROCESS_GPU_STATISTICS Statistics
    )
{
    NTSTATUS status;
    ULONG i;
    ULONG j;
    PETP_GPU_ADAPTER gpuAdapter;
    D3DKMT_QUERYSTATISTICS queryStatistics;

    memset(Statistics, 0, sizeof(ET_PROCESS_GPU_STATISTICS));

    for (i = 0; i < EtpGpuAdapterList->Count; i++)
    {
        gpuAdapter = EtpGpuAdapterList->Items[i];

        Statistics->SegmentCount += gpuAdapter->SegmentCount;
        Statistics->NodeCount += gpuAdapter->NodeCount;

        for (j = 0; j < gpuAdapter->SegmentCount; j++)
        {
            memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));
            queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT;
            queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid;
            queryStatistics.hProcess = ProcessHandle;
            queryStatistics.QueryProcessSegment.SegmentId = j;

            if (NT_SUCCESS(status = D3DKMTQueryStatistics_I(&queryStatistics)))
            {
                ULONG64 bytesCommitted;

                if (WindowsVersion >= WINDOWS_8)
                {
                    bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted;
                }
                else
                {
                    bytesCommitted = (ULONG)queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted;
                }

                if (RtlCheckBit(&gpuAdapter->ApertureBitMap, j))
                    Statistics->SharedCommitted += bytesCommitted;
                else
                    Statistics->DedicatedCommitted += bytesCommitted;
            }
        }

        for (j = 0; j < gpuAdapter->NodeCount; j++)
        {
            memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));
            queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_NODE;
            queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid;
            queryStatistics.hProcess = ProcessHandle;
            queryStatistics.QueryProcessNode.NodeId = j;

            if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics)))
            {
                Statistics->RunningTime += queryStatistics.QueryResult.ProcessNodeInformation.RunningTime.QuadPart;
                Statistics->ContextSwitches += queryStatistics.QueryResult.ProcessNodeInformation.ContextSwitch;
            }
        }

        memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS));
        queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS;
        queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid;
        queryStatistics.hProcess = ProcessHandle;

        if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics)))
        {
            Statistics->BytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.BytesAllocated;
            Statistics->BytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.BytesReserved;
            Statistics->WriteCombinedBytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.WriteCombinedBytesAllocated;
            Statistics->WriteCombinedBytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.WriteCombinedBytesReserved;
            Statistics->CachedBytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.CachedBytesAllocated;
            Statistics->CachedBytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.CachedBytesReserved;
            Statistics->SectionBytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.SectionBytesAllocated;
            Statistics->SectionBytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.SectionBytesReserved;
        }
    }
}
