Link

Criando o projeto Vulkan mínimo

Nós já criamos um projeto enquanto testamos o ambiente de desenvolvimento. Agora vamos adicionar algumas novas classes a esse projeto.

Primeiro, localizamos a pasta do projeto na árvore do projeto, clicamos com o botão direito do mouse no nome do projeto e selecione Add New …. Selecionamos C++ na lista da esquerda e C++ Class na lista central. Clicamos no botão Choose…, digitamos “VulkanWindow” no campo Class name e “QVulkanWindow” no campo Base class. Clicamos em Next e Finish. O Qt Creator irá criar arquivos de cabeçalho e fonte para nossa nova classe e adicioná-los ao projeto.

Este é o código original do cabeçalho gerado pelo Qt para a nossa nova classe:

#ifndef VULKANWINDOW_H
#define VULKANWINDOW_H

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

#endif // VULKANWINDOW_H

Modificamos o código do cabeçalho para:

#ifndef VULKANWINDOW_H
#define VULKANWINDOW_H

#include <QVulkanWindow>

class Renderer;

class VulkanWindow : public QVulkanWindow {
    Q_OBJECT

public:
    VulkanWindow(QWindow *parentWindow = nullptr);
    QVulkanWindowRenderer *createRenderer() override;

private:
    QVulkanInstance m_instance;
    Renderer *m_renderer = nullptr;
};

#endif // VULKANWINDOW_H

Em seguida, modificamos o arquivo vulkanwindow.cpp para implementar o construtor de QVulkanWindow e inicializamos o campo privado QVulkanInstance m_instance:

#include "vulkanwindow.h"

#include "renderer.h"

VulkanWindow::VulkanWindow(QWindow *parentWindow) : QVulkanWindow(parentWindow) {
  if (!m_instance.create())
      qFatal("Failed to create Vulkan instance: %d", m_instance.errorCode());
  setVulkanInstance(&m_instance);
}

Para podermos acessar vários recursos do Vulkan através do objeto VulkanWindow, um ponteiro para a janela é passado e armazenado através do construtor.

Optamos por tornar o objeto QVulkanInstance um membro da classe VulkanWindow, então temos que remover esse objeto de instância de main.cpp:

int main(int argc, char *argv[]){
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

Em seguida, implementamos a função virtual createRenderer() da janela para retornar seu objeto renderizador:

QVulkanWindowRenderer *VulkanWindow::createRenderer() {
    m_renderer = new Renderer(this);
    return m_renderer;
}

Isso simplesmente retorna uma nova instância da subclasse QVulkanWindowRenderer.

Quando a janela é mostrada, o Qt irá chamar a função createRenderer() e o novo objeto renderizador será criado na sua implementação desta função. O renderizador é anexado à janela e será excluído automaticamente junto com ela, portanto, não há necessidade de excluí-lo manualmente.

Agora precisamos criar uma segunda nova classe chamada Renderer que deve ser derivada de QVulkanWindowRenderer. Para isso, seguimos os mesmos passos descritos anteriormente, criamos uma nova classe chamada Renderer com a classe base QVulkanWindowRenderer e modificamos o arquivo render.h, da seguinte forma:

#ifndef RENDERER_H
#define RENDERER_H

#include <QVulkanWindowRenderer>

class VulkanWindow;

class Renderer : public QVulkanWindowRenderer {
public:
    Renderer(VulkanWindow *window);

    void startNextFrame() override;

private:

    VulkanWindow *m_window = nullptr;
};

#endif // RENDERER_H

Em renderer.cpp, implementamos o construtor para inicializar a variável m_window e sobrescrevemos a função virtual startNextFrame()`, como mostrado:

#include "renderer.h"

#include "vulkanwindow.h"

Renderer::Renderer(VulkanWindow *window) : m_window(window) {

}

void Renderer::startNextFrame() {
    m_window->frameReady();
    m_window->requestUpdate();
}

Para indicar que já fizemos tudo o que queremos para este frame, precisamos chamar a função frameReady(). Isso permite que o Qt avance o loop de renderização. Até que essa função seja chamada, o processamento do quadro não pode ser concluído. No entanto, não é necessário chamar essa função diretamente da função startNextFrame(). Podemos atrasar essa chamada se precisarmos, por exemplo, aguardar a conclusão dos cálculos em um thread separado. Como etapa final, precisamos solicitar uma atualização da janela com requestUpdate() para garantir que o novo quadro seja solicitado em breve.

Agora precisamos adicionar à janela principal um quadro que exibe os gráficos Vulkan manipulados pela nossa classe personalizada VulkanWindow. Para isso, damos um clique duplo em mainwindow.ui na árvore do projeto, arrastamos e soltamos um objeto Frame para a interface do usuário, e alteramos o campo objectName para vulkanFrame. Em seguida, selecionamos MainWindow no painel a direita e clicamos no botão lay_out_in_a_grid (Lay Out in a Grid) no canto superior.

Finalmente, editamos o construtor de MainWindow:

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "vulkanwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MainWindow) {
    ui->setupUi(this);
    m_vulkanWindow = new VulkanWindow();
    QWidget *vulkanWrapper = QWidget::createWindowContainer(m_vulkanWindow);
    QGridLayout *vulkanGrid = new QGridLayout(ui->vulkanFrame);
    vulkanGrid->addWidget(vulkanWrapper);
}

MainWindow::~MainWindow() {
    delete ui;
}

A função QWidget::createWindowConteiner() usa um objeto QWindow arbitrário e cria um objeto QWidget que mantém a janela dentro de seus limites. Esse widget pode ser colocado em outro widget e pode ser gerenciado por um layout. Embora a janela pareça estar incorporada em outra janela, ela ainda permanece como uma janela nativa da perspectiva do sistema operacional, e qualquer renderização usando Vulkan será executada diretamente na janela sem um impacto pesado no desempenho. Essa abordagem tem algumas limitações. Por exemplo, a janela incorporada sempre aparecerá sobre outros widgets. No entanto, é adequado na maioria dos casos.

Não podemos nos esquecer de adicionar o membro de classe privado VulkanWindow *m_vulkanWindow em mainwindow.h. Quando compilarmos e executarmos o projeto, uma janela em branco com um retângulo preto deve aparecer.


Anterior Próximo