NVAPIって知っていますか? NVAPIとはNVIDIA社が提供しているAPIです。

NVIDIA社が提供している有名なAPIとしてはCUDAが存在しますが、CUDAとは違いNVAPIは搭載されているハードウェアの情報を取得することが主な使用用途だと思われます。

さて、私はGPUを酷使する機会が多少ながらもあり、その時にVRAMがどの程度使用されているかどうかを取得するためのNVAPIのコードを紹介します。

NVAPIをダウンロード

まずは、NVAPIを以下のリンクからダウンロードしてきます。

今回この記事で利用しているNVAPIのバージョンは「R450」です。

Gameworks Download Center - NVIDIA Developer

ダウンロードが終えたファイルの中には nvapi.h などのヘッタファイルがあり、ライブラリが入っているファイルがあることが分かります。

NVAPIファイルを、プログラムを作成する作業フォルダ内に配置してください。

VRAMの取得を行うコード

C言語
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include "NVAPI/nvapi.h"
#if _M_AMD64
#pragma comment(lib, "NVAPI/amd64/nvapi64.lib")
#else
#pragma comment(lib, "NVAPI/x86/nvapi.lib")
#endif

int main() {
  NvAPI_Status nStatus = NvAPI_Initialize();
  if (nStatus == NVAPI_OK) {
    NvPhysicalGpuHandle hGPUHandles[NVAPI_MAX_PHYSICAL_GPUS] = {0};
    NvU32 count = 0;
    nStatus = NvAPI_EnumPhysicalGPUs(hGPUHandles, &count);
    if (nStatus == NVAPI_OK) {
      printf("GPUS:%d\n", count);
    } else {
      printf("ErrorCode:%d\n", nStatus);
    }
    NvPhysicalGpuHandle hGPUHandle = hGPUHandles[0];
    NV_DISPLAY_DRIVER_MEMORY_INFO_V1 MemoryInfo;
    MemoryInfo.version = NV_DISPLAY_DRIVER_MEMORY_INFO_VER_1;
    nStatus = NvAPI_GPU_GetMemoryInfo(hGPUHandle, &MemoryInfo);
    if (nStatus == NVAPI_OK) {
      printf("VRAM Total:%.5lf[GB]\n",
             (double)MemoryInfo.dedicatedVideoMemory / 1048576);
      printf("VRAM Can Use:%.5lf[GB]\n",
             (double)MemoryInfo.availableDedicatedVideoMemory / 1048576);
    } else {
      printf("ErrorCode:%d\n", nStatus);
    }
  }
  return 0;
}

今回使用するコンパイラはMSVC(cl.exe)を使用します。clangコンパイラでもコンパイル可能です。

gccコンパイラだとエラーが出て通りません。

コードの説明

C言語
1
2
3
4
5
6
7
#include <stdio.h>
#include "NVAPI/nvapi.h"
#if _M_AMD64
#pragma comment(lib, "NVAPI/amd64/nvapi64.lib")
#else
#pragma comment(lib, "NVAPI/x86/nvapi.lib")
#endif

まず、標準出力のための stdio.h VRAMを取得するための nvapi.h を追加します。

nvapi.h の下部にわけわからんコードになっていますが、これはおまじないだと思ってコピーしてください。

この部分ではコンパイラが32ビット版を使用してコンパイルを行うか64ビット版のコンパイラを使用してコンパイルを行うかの違いです。深く考えない方が良いでしょう()

C言語
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
NvAPI_Status nStatus = NvAPI_Initialize();
if (nRet == NVAPI_OK) {
  NvPhysicalGpuHandle hGPUHandles[NVAPI_MAX_PHYSICAL_GPUS] = {0};
  NvU32 count = 0;
  nStatus = NvAPI_EnumPhysicalGPUs(hGPUHandles, &count);
}
if (nStatus == NVAPI_OK) {
    printf("GPUS:%d\n", count);
  } else {
    printf("ErrorCode:%d\n", nStatus);
}
NvPhysicalGpuHandle hGPUHandle = hGPUHandles[0];//下に続く

ここから本題です。

まず、 NvAPI_Initialize 関数を呼び出して初期化を行います。

この際、 NvAPI_Initialize 関数が NvAPI_Status であるためステータスコードを取得しておくと後からエラーが出た時に対処がしやすいです。

無事に初期化が行われれば、 NVAPI_OK ステータスが戻ってくるので、if文で判別してエラーが無いことを確認できます。

つぎにGPU自体がコンピュータに何台搭載されていて、どのGPUのVRAMを取得したのかを選択します。

そして、 NvPhysicalGpuHandle hGPUHandles[NVAPI_MAX_PHYSICAL_GPUS] = {0}; で複数のGPUハンドルが格納するための変数を作ります。

NvAPI_EnumPhysicalGPUs 関数を使用してGPUハンドルを取得します。

後に、 NvPhysicalGpuHandle hGPUHandle = hGPUHandles[0]; を行うことにより、GPU0のハンドルが変数に格納されて、ようやくVRAMを取得できるような形となります。

C言語
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
NV_DISPLAY_DRIVER_MEMORY_INFO_V1 MemoryInfo;
MemoryInfo.version = NV_DISPLAY_DRIVER_MEMORY_INFO_VER_1;
nStatus = NvAPI_GPU_GetMemoryInfo(hGPUHandle, &MemoryInfo);
if (nStatus == NVAPI_OK) {
  printf("VRAM Total:%.5lf[GB]\n",
         (double)MemoryInfo.dedicatedVideoMemory / 1048576);
  printf("VRAM Can Use:%.5lf[GB]\n",
         (double)MemoryInfo.availableDedicatedVideoMemory / 1048576);
} else {
  printf("ErrorCode:%d\n", nStatus);
}

まず、使用する構造体を変数宣言(MemoryInfoのこと)を行い、 MemoryInfo.version = NV_DISPLAY_DRIVER_MEMORY_INFO_VER_1; で使用する構造体のドライバを当てます。

そして、やっとのことでVRAMを取得するための関数が呼び出されます。

NvAPI_GPU_GetMemoryInfo(hGPUHandle, &MemoryInfo); この関数ではGPUのハンドルと変数を引数として渡します。最後にこの構造体の値はすべてKBで取得されるためGBに変換して出力しています。

出力結果

output
1
2
3
GPUS:1
VRAM Total:8.00000[GB]
VRAM Can Use:7.89844[GB]

私のパソコンに搭載しているグラフィックボードは、ASUS製の「GTX1070-8G」1機だけなのGPUSは1と表示され、Totalの所が8GBとなり、Can Useの所は使用可能量となっているので出力結果はリソースの使用状況によって変わります。

参考URL

NVAPI Reference Documentation -

mullerdóm: NVAPIを使ってGPUの使用メモリ量を求める -