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) 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.