Vulkan プログラミング: QVulkanWindow::graphicsCommandPool() を使って三角形を描画


QVulkanWindow::graphicsCommandPool()は、Vulkan APIでグラフィックスコマンドを記録するために使用されるコマンドプールを取得するための関数です。コマンドプールは、コマンドバッファを作成するために使用されるメモリプールであり、コマンドバッファは、描画コマンドやシェーダープログラムの実行などのグラフィックス操作を記録するために使用されます。

詳細

QVulkanWindow::graphicsCommandPool()は、VkCommandPool構造体を返します。この構造体は、コマンドプールの作成に使用される属性を定義します。これらの属性には、コマンドプールのタイプ、コマンドバッファを作成するために使用できる最大コマンド数、およびコマンドバッファを同期するために使用できるキューファミリーが含まれます。

// コマンドプールを取得する
VkCommandPool commandPool = window.graphicsCommandPool();

// コマンドバッファを作成する
VkCommandBuffer commandBuffer;
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool;
allocInfo.commandBufferCount = 1;

vkAllocateCommandBuffers(window.device(), &allocInfo, &commandBuffer);

// コマンドバッファにコマンドを記録する
...

// コマンドバッファを実行する
vkQueueSubmit(window.graphicsQueue(), 1, &commandBuffer, 0);

// コマンドバッファを解放する
vkFreeCommandBuffers(window.device(), commandPool, 1, &commandBuffer);

注意事項

QVulkanWindow::graphicsCommandPool()は、スレッドセーフではありません。複数のスレッドからこの関数を呼び出す場合は、同期メカニズムを使用して排他アクセスを確保する必要があります。



#include <QVulkanWindow>
#include <QtShader>
#include <QtCore/QCoreApplication>

class VulkanWindow : public QVulkanWindow
{
public:
    VulkanWindow();

private:
    void initialize();
    void render();

    // シェーダープログラム
    QVulkanShaderProgram program;

    // 頂点バッファ
    VkBuffer vertexBuffer;
    VkDeviceSize vertexBufferSize;

    // 描画コマンドバッファ
    VkCommandBuffer commandBuffer;

    // パイプラインレイアウト
    VkPipelineLayout pipelineLayout;

    // レンダーパス
    VkRenderPass renderPass;

    // フレームバッファ
    VkFramebuffer framebuffer;
};

VulkanWindow::VulkanWindow()
{
    setSurfaceType(VulkanSurfaceType::VulkanSurface);
    setFormat(QVulkanWindow::defaultFormat());
    setTitle("Vulkan Triangle");
}

void VulkanWindow::initialize()
{
    // シェーダープログラムを作成する
    program.addShaderFromSourceFile(QVulkanShader::Vertex, "vertex.vert");
    program.addShaderFromSourceFile(QVulkanShader::Fragment, "fragment.frag");
    program.link();

    // 頂点データを作成する
    float vertices[] = {
        0.0f, 0.5f, 0.0f,
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f
    };

    vertexBufferSize = sizeof(vertices);

    // 頂点バッファを作成する
    VkBufferCreateInfo bufferCreateInfo = {};
    bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferCreateInfo.size = vertexBufferSize;
    bufferCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
    bufferCreateInfo.flags = VK_BUFFER_CREATE_HOST_VISIBLE_BIT | VK_BUFFER_CREATE_HOST_COHERENT_BIT;

    VkMemoryRequirements memReqs;
    vkCreateBuffer(device(), &bufferCreateInfo, nullptr, &vertexBuffer);
    vkGetBufferMemoryRequirements(device(), vertexBuffer, &memReqs);

    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.size = memReqs.size;
    allocInfo.memoryTypeIndex = findMemoryType(memReqs.memoryFlags, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

    void* mappedMemory;
    vkMapMemory(device(), allocInfo.memory, 0, vertexBufferSize, 0, &mappedMemory);
    memcpy(mappedMemory, vertices, vertexBufferSize);
    vkUnmapMemory(device(), allocInfo.memory);

    VkPhysicalDeviceMemoryProperties memProps;
    vkGetPhysicalDeviceMemoryProperties(physicalDevice(), &memProps);

    VkDeviceSize offset = memReqs.alignment;
    if (offset < memReqs.size) {
        allocInfo.offset = offset;
    }

    vkAllocateMemory(device(), &allocInfo, nullptr, &allocInfo.memory);

    // 描画コマンドバッファを作成する
    commandPool = graphicsCommandPool();

    VkCommandBufferAllocateInfo commandBufferAllocInfo = {};
    commandBufferAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    commandBufferAllocInfo.commandPool = commandPool;
    commandBufferAllocInfo.commandBufferCount = 1;

    vkAllocateCommandBuffers(device(), &commandBufferAllocInfo, &commandBuffer);

    // パイプラインレイアウトを作成する
    VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
    pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    pipelineLayoutCreateInfo.setLayoutCount(1);
    pipelineLayoutCreateInfo.pSetLayouts = &program.layout();

    vkCreatePipelineLayout(device(), &pipelineLayoutCreateInfo, nullptr, &pipelineLayout);

    // レンダーパスを作成する
    VkAttachmentDescription colorAttachment = {};
    colorAttachment.format = format();
    colorAttachment.samples = VK_SAMPLE_


独自のコマンドプールを作成する

vkCreateCommandPool() 関数を使用して、独自のコマンドプールを作成できます。これにより、コマンドプールの属性をより詳細に制御できます。たとえば、コマンドプールのタイプ、コマンドバッファを作成するために使用できる最大コマンド数、およびコマンドバッファを同期するために使用できるキューファミリーを指定できます。

VkCommandPool commandPool;

VkCommandPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_BIT;
poolCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex;

vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool);

別の QVulkanWindow オブジェクトからコマンドプールを取得する

複数の QVulkanWindow オブジェクトを使用している場合は、1 つのオブジェクトからコマンドプールを取得して、他のオブジェクトで使用することができます。これを行うには、QVulkanWindow::graphicsCommandPool() 関数を呼び出してコマンドプールを取得し、VkCommandPool 構造体を返します。次に、vkGetDevicePtr() 関数を使用して、構造体のポインタを取得し、他の QVulkanWindow オブジェクトに渡します。

// コマンドプールを取得する
VkCommandPool commandPool = window1.graphicsCommandPool();

// ポインタを取得する
VkDevice* devicePtr;
vkGetDevicePtr(device, commandPool, &devicePtr);

// 別の QVulkanWindow オブジェクトに渡す
window2.setDevice(devicePtr);

コマンドバッファを直接作成する

vkAllocateCommandBuffers() 関数を使用して、コマンドバッファを直接作成できます。これにより、コマンドプールの必要性を完全に排除できます。ただし、この方法は、コマンドプールの管理がより複雑になるため、一般的には推奨されません。

VkCommandBuffer commandBuffer;

VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = VK_NULL_HANDLE;
allocInfo.commandBufferCount = 1;

vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);

注意事項

上記の方法を使用する場合は、コマンドプールのライフサイクルを適切に管理する必要があります。たとえば、独自のコマンドプールを作成した場合は、使用しなくなったときに解放する必要があります。