vulkanstudy.github.io

Follow me on GitHub

ウィンドウサーフェス

デバイスを確保できたので、GPUに命令を送る準備が進んできました。 次に考えるのは、ディスプレイへの表示になります。

サーフェス

ディスプレイへの表示はOSが行います。 具体的には、「画面のどの場所にウィンドウを表示するのか?」、「全画面なのか?」、 「ウィンドウが後ろに隠れているので、今は画面に書き込まなくても良いんじゃない?」 等を気にしながら、描画結果を画面の一部に描き込む処理を管理します。

Vulkanでは、OSに表示して欲しい画像をサーフェスというオブジェクトで管理します。 OSとの画面表示のやり取りはサーフェスを通して行い、描画が終わった画面を表示するように指示していきます。

今回のプログラムを入力して、最終的に作られるコードはこちら。

サーフェスのオブジェクト

サーフェスの定義

サーフェスは、VkSurfaceKHRをハンドルとしてオブジェクト化されます。

class MyApplication
{
private:
  (中略)

  GLFWwindow* window_ = nullptr;
	VkInstance instance_;
	VkSurfaceKHR surface_;// ★追加

	VkPhysicalDevice physicalDevice_ = VK_NULL_HANDLE;
	VkDevice device_;

初期化は、物理デバイスを作成する前に行います。デバイスを生成する際に、サーフェスに対応しているのか調べながら選択することになるので、 インスタンスを作成した直後に初期化していきます。

片付けは、vkDestroySurfaceKHRを呼ぶだけでになります。対応する片付けメソッドに追加しましょう。

	// Vulkanの設定
	void initializeVulkan()
	{
		createInstance(&instance_);
		initializeDebugMessenger(instance_, debugMessenger_);
		surface_ = createSurface(instance_, window_);// ★追加
		physicalDevice_ = pickPhysicalDevice(instance_, surface_);// ★修正
		device_ = createLogicalDevice(physicalDevice_, graphicsQueue_, surface_);// ★修正
	}

	void finalizeVulkan()
	{
		vkDestroyDevice(device_, nullptr);
		finalizeDebugMessenger(instance_, debugMessenger_);
		vkDestroySurfaceKHR(instance_, surface_, nullptr);// ★追加
		vkDestroyInstance(instance_, nullptr);
	}

サーフェスの生成

さて、実際に生成するcreateSurfaceの中身です。

サーフェスはOSに密接にかかわるので、Windowsでは、アプリケーションインスタンスやウインドウハンドルといったような、 ウィンドウに関する情報が必要になってきます。

具体的には、VkWin32SurfaceCreateInfoKHR構造体というのが定義されているので、こちらにWindows特有の情報を設定して、 vkCreateWin32SurfaceKHR関数を呼び出します。

	static VkSurfaceKHR createSurface(VkInstance instance, GLFWwindow* window)
	{
		VkSurfaceKHR surface;

		VkWin32SurfaceCreateInfoKHR createInfo = {};
		createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
		createInfo.hwnd = glfwGetWin32Window(window);
		createInfo.hinstance = GetModuleHandle(nullptr);

		if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
			throw std::runtime_error("failed to create window surface!");
		}

		return surface;
	}

が、GLFWでは、OSの情報をGLFWwindowオブジェクトに隠ぺいして機種を問わず呼び出せるような仕組みが整っています。 ということで、実用上は、glfwCreateWindowSurfaceを呼び出すだけでサーフェスを作ることができます。

	static VkSurfaceKHR createSurface(VkInstance instance, GLFWwindow* window)
	{
		VkSurfaceKHR surface;
		if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
			throw std::runtime_error("failed to create window surface!");
		}

		return surface;
	}

プレゼントキュー

さて、画面に対するアクセスができたので、こちらに描画すればよいのですが、制限があるようです。 GPUに命令を伝えるqueueは、キューファミリー単位でサーフェスにアクセスできない場合があります。 例えばGPU汎用計算のキューファミリーは、そもそも画面に出力する目的ではないので、サーフェスにアクセスする必要はありません。 また、グラフィックスの描画は、2次元の画像として絵を生成することが多いですが、この書き込み先にサーフェスを直接には書けない(命令を出せない)場合もあるようです。

キューのサーフェスへのアクセス

プレゼントキューの定義

ということで、サーフェスに対して指示を行うキューを選別する必要があります。 今回は、presentQueueとして、描画命令用のキューとは別にキューを用意します。

class MyApplication
{
private:
	(中略)

	VkQueue graphicsQueue_;
	VkQueue presentQueue_;// ★追加

複数のqueueを扱うことになるので、今までのプログラムを拡張していきます。

プレゼントキューの取得

queueのインデックスを格納するQueueFamilyIndicesを複数のキューに拡張します。 こちら、presentQueueを保持するキューファミリーも保持できるようにします (このpresentFamilygraphicsFamilyと同じものかもしれません)。

	struct QueueFamilyIndices
	{
		std::optional<uint32_t> graphicsFamily;
		std::optional<uint32_t> presentFamily;// ★追加:OSの描画システムに結果を送るためのキューファミリー

		bool isComplete() {
			return graphicsFamily.has_value()
				&& presentFamily.has_value();// ★修正
		}
	};

キューファミリーは物理デバイスに対して含まれているかどうか確認します。 デバイスが持つキューファミリーが、サーフェスに命令を送れるかどうかは、 vkGetPhysicalDeviceSurfaceSupportKHR を呼び出すことで分かります。 キューを実際に持っていて、サーフェスに命令を送れるものがあるキューファミリーをQueueFamilyIndices::presentFamilyに登録します。

	static QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device, const VkSurfaceKHR surface)
	{
		(中略)

		int i = 0;
		QueueFamilyIndices indices;
		for (const auto& queueFamily : queueFamilies) 
		{
			// キューファミリーにキューがあり、グラフィックスキューとして使えるか調べる
			if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
				indices.graphicsFamily = i;
			}

			// ★追加:プレゼントキュー
			VkBool32 presentSupport = false;
			vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);// サーフェスに送れる?
			if (queueFamily.queueCount > 0 && presentSupport) {
				indices.presentFamily = i;
			}

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

			i++;
		}

		return indices;
	}

プレゼントキューのデバイス生成への追加

判明したプレゼントキューのキューファミリーをデバイスの生成時の情報に追加する。

前のプログラムでは、VkDeviceCreateInfo::pQueueCreateInfos にグラフィックスキューのVkDeviceQueueCreateInfoのポインタを指定した。 複数のキューファミリーをデバイスに追加する際は、VkDeviceCreateInfo::pQueueCreateInfosVkDeviceQueueCreateInfoの配列の先頭アドレスを指定して、VkDeviceCreateInfo::queueCreateInfoCountにキューファミリーの個数を設定します。 今回は、std::vector<VkDeviceQueueCreateInfo> queueCreateInfosとして、VkDeviceQueueCreateInfoを配列化しました。 他の変更点は、uniqueQueueFamiliesにプレゼントキューを追加して、その分だけforループでVkDeviceQueueCreateInfoを構築していきます。

	VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice, VkQueue& graphicsQueue, VkQueue& presentQueue, const VkSurfaceKHR surface)
	{
		QueueFamilyIndices indices = findQueueFamilies(physicalDevice, surface);

		// 使用するキューの情報を設定 (★複数キュー向けに拡張)
		std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
		std::set<uint32_t> uniqueQueueFamilies = {
			indices.graphicsFamily.value(),
			indices.presentFamily.value()
		};

		float queuePriority = 1.0f;// キューの優先度を設定
		for (uint32_t queueFamily : uniqueQueueFamilies) {
			VkDeviceQueueCreateInfo queueCreateInfo = {};
			queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
			queueCreateInfo.queueFamilyIndex = queueFamily;
			queueCreateInfo.queueCount = 1;
			queueCreateInfo.pQueuePriorities = &queuePriority;
			queueCreateInfos.push_back(queueCreateInfo);
		}

		// デバイスを生成するための情報を構築
		VkDeviceCreateInfo createInfo = {};
		createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

		//★複数キュー向けに拡張
		createInfo.pQueueCreateInfos = queueCreateInfos.data();
		createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());

		VkPhysicalDeviceFeatures deviceFeatures = {};// 使用する機能の情報(今回は特に無し)
		createInfo.pEnabledFeatures = &deviceFeatures;

		createInfo.enabledExtensionCount = 0;// 拡張機能(今回は無し)

		// 検証レイヤーの設定
		if (enableValidationLayers) {
			createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
			createInfo.ppEnabledLayerNames = validationLayers.data();
		}

		// 論理デバイスの作成
		VkDevice device;
		if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
			throw std::runtime_error("failed to create logical device!");
		}

		// キューの取得
		vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
		vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);//★追加

		return device;
	}

最後に、vkGetDeviceQueueでプレゼントキューのキューのハンドルVkQueueを取得しました。

少しずつ画面への表示に近づいてきました。