2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Vulkan Tutorial (Drawing a triangle/Setup/Physical devices and queue families) 日本語訳

Last updated at Posted at 2022-07-12

Physical devices and queue families

Selecting a physical device

VkInstanceでVulkanライブラリを初期化したあと、私達が必要としている機能をサポートしたグラフィックスカードを、システムから探して選択なければなりません。実は、任意の数のグラフィックスカードを選択して同時に使うことも可能ですが、このチュートリアルでは、私達の要望を満たすグラフィックスカードのうち最初に見つかったものを使います。

pickPhysicalDevice関数を追加して、initVulkan関数の中でそれを呼び出します。

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    pickPhysicalDevice();
}

void pickPhysicalDevice() {

}

最終的に選択されたグラフィックスカードを格納するVkPhysicalDeviceハンドルを、新しいメンバ変数として追加します。このオブジェクトはVkInstanceが破棄されたときに暗黙のうちに破棄されるので、cleanup関数の中で何か新しいことをする必要はありません。

VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

グラフィックスカードをリストアップするのは拡張機能をリストアップするのとよく似ていて、まずは要素数を問い合わせます。

uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

もしVulkanをサポートしたデバイスが0個の場合、これ以上進めても意味がありません。

if (deviceCount == 0) {
    throw std::runtime_error("failed to find GPUs with Vulkan support!");
}

そうでなければ、全てのVkPhysicalDeviceハンドルを保持する配列を確保できます。

std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

ここで、全てのグラフィックスカードが同じに作られているわけではないので、それぞれを評価して、実行したい操作に適合しているかどうかチェックする必要があります。このために、新しい関数を追加します。

bool isDeviceSuitable(VkPhysicalDevice device) {
    return true;
}

そして、要求に合った物理デバイスがあるかどうかのチェックを、関数に追加します。

for (const auto& device : devices) {
    if (isDeviceSuitable(device)) {
        physicalDevice = device;
        break;
    }
}

if (physicalDevice == VK_NULL_HANDLE) {
    throw std::runtime_error("failed to find a suitable GPU!");
}

次の節では、isDeviceSuitable関数でチェックする最初の要件を追加します。あとの章ではより多くのVulkanの機能を使いはじめるので、この関数もより多くのチェックを含むように拡張していきます。

Base device suitability checks

デバイスの適合度を評価するために、いくつかの詳細を問い合わせるところから始めます。デバイスの名前、型、サポートされているVulkanのバージョン番号といったような基本的な属性は、vkGetPhysicalDevicePropertiesを使って問い合わせることができます。

VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);

圧縮テクスチャ、64ビットfloat、マルチビューポートレンダリング(VRで有用です)といったようなオプションの機能がサポートされているかどうかは、vkGetPhysicalDeviceFeaturesを使って問い合わせることができます。

VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

デバイスに問い合わせることができるさらに詳細な項目があり、デバイスメモリとキューファミリーについては後述します(次の節を参照)。

例えば、私達のアプリケーションが、ジオメトリシェーダをサポートしている専用グラフィックスカードのみ利用可能と決めたとしましょう。そうすると、isDeviceSuitable関数は次のようになります。

bool isDeviceSuitable(VkPhysicalDevice device) {
    VkPhysicalDeviceProperties deviceProperties;
    VkPhysicalDeviceFeatures deviceFeatures;
    vkGetPhysicalDeviceProperties(device, &deviceProperties);
    vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

    return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
           deviceFeatures.geometryShader;
}

デバイスが適合しているかどうかチェックして最初にみつかったものを使う代わりに、それぞれのデバイスに点数を与え、最も高いものを選択するということもできます。このやり方であれば、専用グラフィックスカードに高い点数を与えて優先しつつ、統合GPUしか利用可能なものがなければそれを使うといったことが可能です。これは以下のようにして実装できます。

#include <map>

...

void pickPhysicalDevice() {
    ...

    // Use an ordered map to automatically sort candidates by increasing score
    std::multimap<int, VkPhysicalDevice> candidates;

    for (const auto& device : devices) {
        int score = rateDeviceSuitability(device);
        candidates.insert(std::make_pair(score, device));
    }

    // Check if the best candidate is suitable at all
    if (candidates.rbegin()->first > 0) {
        physicalDevice = candidates.rbegin()->second;
    } else {
        throw std::runtime_error("failed to find a suitable GPU!");
    }
}

int rateDeviceSuitability(VkPhysicalDevice device) {
    ...

    int score = 0;

    // Discrete GPUs have a significant performance advantage
    if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
        score += 1000;
    }

    // Maximum possible size of textures affects graphics quality
    score += deviceProperties.limits.maxImageDimension2D;

    // Application can't function without geometry shaders
    if (!deviceFeatures.geometryShader) {
        return 0;
    }

    return score;
}

このチュートリアルの関数は、全て実装する必要はありませんが、デバイスを選択する方法についてどうやって設計するかのアイデアを与えます。もちろん、選択肢の名前を表示して、ユーザーが選択できるようにすることもできます。

私達はまだ始めたばかりなので、Vulkanがサポートされてさえいれば、どんなGPUでもそれに決定するようにします。

bool isDeviceSuitable(VkPhysicalDevice device) {
    return true;
}

次の節では、最初にチェックすべき必須の機能について解説します。

Queue families

以前にも手短に触れたように、描画からテクスチャのアップロードまで、ほとんど全てのVulkanの操作は、コマンドをキューにサブミットすることが必要です。異なるキューファミリーに由来する異なるタイプのキューが存在し、それぞれのキューファミリーはコマンドの一部だけを許可しています。例えば、コンピュートコマンドの処理だけを許可したキューファミリーや、メモリ転送に関連したコマンドだけを許可したキューファミリーがあるかもしれません。

どのキューファミリーがデバイスでサポートされているか、そして、そのうちのどれで私達が使いたいコマンドがサポートされているかをチェックする必要があります。この目的のために、必要な全てのキューファミリーを見つけ出す関数findQueueFamiliesを新たに追加します。

今はグラフィックスコマンドをサポートしたキューを探したいだけなので、関数は次のようになります。

uint32_t findQueueFamilies(VkPhysicalDevice device) {
    // Logic to find graphics queue family
}

しかし、次の章で、既に別のキューを探すことになるので、それに備えてインデックスを構造体にまとめておいたほうが良いでしょう。

struct QueueFamilyIndices {
    uint32_t graphicsFamily;
};

QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
    QueueFamilyIndices indices;
    // Logic to find queue family indices to populate struct with
    return indices;
}

しかし、仮にキューファミリーが見つからなかったら?findQueueFamilies関数内で例外を投げることもできますが、この関数はデバイスの適合度について判断をするのに正しい場所ではありません。例えば、専用の転送キューファミリーを持つデバイスを希望するけれども必須ではないかもしれません。そのため、特定のキューファミリーが見つかったかどうかを示す、何らかの方法が必要です。

キューファミリーのインデックスの有効な値として、理論上は0を含む全てのuint32_tの値を取りうるので、キューファミリーが存在しなかったことを示すのにマジックナンバーを使うことは不可能です。幸運なことに、C++17から、値が存在しているかどうかを識別するためのデータ構造が導入されました。

#include <optional>

...

std::optional<uint32_t> graphicsFamily;

std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // false

graphicsFamily = 0;

std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // true

std::optionalは、何かを代入するまで値を格納しないようなラッパーです。has_value()メンバ関数を呼び出すことで、値を格納しているかどうか、いつでも問い合わせることができます。ということは、次のようにロジックを変更することができるというわけです。

#include <optional>

...

struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;
};

QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
    QueueFamilyIndices indices;
    // Assign index to queue families that could be found
    return indices;
}

ようやく、findQueueFamiliesの実装を始めることができます。

QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
    QueueFamilyIndices indices;

    ...

    return indices;
}

キューファミリーのリストを集める手順は、あなたが期待しているとおり、vkGetPhysicalDeviceQueueFamilyPropertiesを使います。

uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

VkQueueFamilyProperties構造体は、サポートされている操作の種類や、そのファミリーから作ることができるキューの数など、キューファミリーに関する詳細な情報が格納されています。私達は、VK_QUEUE_GRAPHICS_BITをサポートするキューファミリーを、少なくとも1つは見つける必要があります。

int i = 0;
for (const auto& queueFamily : queueFamilies) {
    if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
        indices.graphicsFamily = i;
    }

    i++;
}

このキューファミリーを探索するイケてる関数もできたので、isDeviceSuitable関数の中で、私達が使いたいコマンドを処理できるデバイスかどうかチェックするのに使うことができます。

bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);

    return indices.graphicsFamily.has_value();
}

もう少し便利にするために、構造体自身にチェック関数を追加します。

struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;

    bool isComplete() {
        return graphicsFamily.has_value();
    }
};

...

bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);

    return indices.isComplete();
}

findQueueFamiliesからの早期exitにも使うことができます。

for (const auto& queueFamily : queueFamilies) {
    ...

    if (indices.isComplete()) {
        break;
    }

    i++;
}

グレイト!これで正しい物理デバイスを探すのに必要なものは全てそろいました。次のステップは、物理デバイスとのインターフェースとなる、論理デバイスの作成です。

C++ code

前の記事
次の記事

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?