diff --git a/QtGameMaker.pro b/QtGameMaker.pro
index 9e84c8d..ff1e3d5 100644
--- a/QtGameMaker.pro
+++ b/QtGameMaker.pro
@@ -30,6 +30,10 @@ HEADERS += \
src/editor/widgets/qlineeditwithmenu.h \
src/editor/widgets/qscrollareawithmenu.h \
src/editor/widgets/roomeditwidget.h \
+ src/engine/gameengine.h \
+ src/engine/glgamewindow.h \
+ src/engine/vulkangamerenderer.h \
+ src/engine/vulkangamewindow.h \
src/futurecpp.h \
src/projectcontainer.h \
src/editor/jshighlighter.h \
@@ -72,8 +76,7 @@ HEADERS += \
src/editor/widgets/actionscontainerwidget.h \
src/editor/widgets/codeeditorwidget.h \
src/editor/widgets/drawingcanvaswidget.h \
- src/editor/widgets/pathpointswidget.h \
- src/engine/gamewindow.h
+ src/editor/widgets/pathpointswidget.h
SOURCES += \
src/closeeventfilter.cpp \
@@ -81,6 +84,10 @@ SOURCES += \
src/editor/widgets/qlineeditwithmenu.cpp \
src/editor/widgets/qscrollareawithmenu.cpp \
src/editor/widgets/roomeditwidget.cpp \
+ src/engine/gameengine.cpp \
+ src/engine/glgamewindow.cpp \
+ src/engine/vulkangamerenderer.cpp \
+ src/engine/vulkangamewindow.cpp \
src/main.cpp \
src/projectcontainer.cpp \
src/editor/jshighlighter.cpp \
@@ -123,8 +130,7 @@ SOURCES += \
src/editor/widgets/actionscontainerwidget.cpp \
src/editor/widgets/codeeditorwidget.cpp \
src/editor/widgets/drawingcanvaswidget.cpp \
- src/editor/widgets/pathpointswidget.cpp \
- src/engine/gamewindow.cpp
+ src/editor/widgets/pathpointswidget.cpp
FORMS += \
src/editor/mainwindow.ui \
@@ -155,4 +161,5 @@ FORMS += \
src/editor/widgets/actionscontainerwidget.ui
RESOURCES += \
- resources.qrc
+ resources_editor.qrc \
+ resources_engine.qrc
diff --git a/resources.qrc b/resources_editor.qrc
similarity index 100%
rename from resources.qrc
rename to resources_editor.qrc
diff --git a/resources_engine.qrc b/resources_engine.qrc
new file mode 100644
index 0000000..649f10c
--- /dev/null
+++ b/resources_engine.qrc
@@ -0,0 +1,6 @@
+
+
+ shader_modules/color_frag.spv
+ shader_modules/color_vert.spv
+
+
diff --git a/shader_modules/color.frag b/shader_modules/color.frag
new file mode 100644
index 0000000..3755876
--- /dev/null
+++ b/shader_modules/color.frag
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 v_color;
+
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(v_color, 1.0);
+}
diff --git a/shader_modules/color.vert b/shader_modules/color.vert
new file mode 100644
index 0000000..02492c0
--- /dev/null
+++ b/shader_modules/color.vert
@@ -0,0 +1,18 @@
+#version 440
+
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec3 color;
+
+layout(location = 0) out vec3 v_color;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+} ubuf;
+
+out gl_PerVertex { vec4 gl_Position; };
+
+void main()
+{
+ v_color = color;
+ gl_Position = ubuf.mvp * position;
+}
diff --git a/shader_modules/color_frag.spv b/shader_modules/color_frag.spv
new file mode 100644
index 0000000..30e33b7
Binary files /dev/null and b/shader_modules/color_frag.spv differ
diff --git a/shader_modules/color_vert.spv b/shader_modules/color_vert.spv
new file mode 100644
index 0000000..a1f42e3
Binary files /dev/null and b/shader_modules/color_vert.spv differ
diff --git a/src/editor/mainwindow.cpp b/src/editor/mainwindow.cpp
index 880ff04..d7010c5 100644
--- a/src/editor/mainwindow.cpp
+++ b/src/editor/mainwindow.cpp
@@ -10,7 +10,6 @@
#include
#include
#include
-#include
#include "models/projecttreemodel.h"
#include "dialogs/preferencesdialog.h"
@@ -30,8 +29,7 @@
#include "dialogs/userdefinedconstantsdialog.h"
#include "dialogs/triggersdialog.h"
#include "dialogs/includedfilesdialog.h"
-#include "engine/gamewindow.h"
-#include "closeeventfilter.h"
+#include "engine/gameengine.h"
namespace {
template struct PropertiesDialogForDetail;
@@ -659,20 +657,11 @@ void MainWindow::showIncludedFiles()
void MainWindow::runGame()
{
- GameWindow window{m_project};
- window.setTitle(tr("%0 - Game Window")
- .arg(m_filePath.isEmpty() ? "" : QFileInfo{m_filePath}.fileName()));
- window.setModality(Qt::ApplicationModal);
+ GameEngine engine{m_project};
- CloseEventFilter closeEventFilter;
- window.installEventFilter(&closeEventFilter);
-
- QEventLoop eventLoop;
- connect(&closeEventFilter, &CloseEventFilter::closeEventReceived,
- &eventLoop, &QEventLoop::quit);
-
- window.show();
- eventLoop.exec();
+ setEnabled(false);
+ engine.run();
+ setEnabled(true);
}
void MainWindow::debugGame()
diff --git a/src/engine/gameengine.cpp b/src/engine/gameengine.cpp
new file mode 100644
index 0000000..fd2ecb0
--- /dev/null
+++ b/src/engine/gameengine.cpp
@@ -0,0 +1,40 @@
+#include "gameengine.h"
+
+#include
+#include
+
+#include "closeeventfilter.h"
+
+GameEngine::GameEngine(const ProjectContainer &project, QObject *parent) :
+ QObject{parent},
+ m_project{project},
+ m_glGameWindow{m_project},
+ m_vulkanGameWindow{m_project}
+{
+ m_vulkanInstance.setLayers(QByteArrayList { "VK_LAYER_LUNARG_standard_validation" });
+
+ if (!m_vulkanInstance.create())
+ qFatal("Failed to create Vulkan instance: %d", m_vulkanInstance.errorCode());
+
+ m_vulkanGameWindow.setVulkanInstance(&m_vulkanInstance);
+}
+
+void GameEngine::run()
+{
+ m_glGameWindow.setTitle(tr("Game Window - OpenGL"));
+ m_vulkanGameWindow.setTitle(tr("Game Window - Vulkan"));
+
+ CloseEventFilter closeEventFilter;
+
+ m_glGameWindow.installEventFilter(&closeEventFilter);
+ m_vulkanGameWindow.installEventFilter(&closeEventFilter);
+
+ QEventLoop eventLoop;
+ connect(&closeEventFilter, &CloseEventFilter::closeEventReceived,
+ &eventLoop, &QEventLoop::quit);
+
+ m_glGameWindow.show();
+ m_vulkanGameWindow.show();
+
+ eventLoop.exec();
+}
diff --git a/src/engine/gameengine.h b/src/engine/gameengine.h
new file mode 100644
index 0000000..ad8cd14
--- /dev/null
+++ b/src/engine/gameengine.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include
+
+#include "engine/glgamewindow.h"
+#include "engine/vulkangamewindow.h"
+
+struct ProjectContainer;
+
+class GameEngine : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit GameEngine(const ProjectContainer &project, QObject *parent = nullptr);
+
+ void run();
+
+private:
+ const ProjectContainer &m_project;
+
+ QVulkanInstance m_vulkanInstance;
+
+ GlGameWindow m_glGameWindow;
+ VulkanGameWindow m_vulkanGameWindow;
+};
diff --git a/src/engine/gamewindow.cpp b/src/engine/glgamewindow.cpp
similarity index 87%
rename from src/engine/gamewindow.cpp
rename to src/engine/glgamewindow.cpp
index 4c68825..6cd1181 100644
--- a/src/engine/gamewindow.cpp
+++ b/src/engine/glgamewindow.cpp
@@ -1,4 +1,4 @@
-#include "gamewindow.h"
+#include "glgamewindow.h"
#include
#include
@@ -8,7 +8,7 @@
#include
#include
-GameWindow::GameWindow(const ProjectContainer &project, QWindow *parent) :
+GlGameWindow::GlGameWindow(const ProjectContainer &project, QWindow *parent) :
QWindow{parent},
m_project{project}
{
@@ -20,9 +20,9 @@ GameWindow::GameWindow(const ProjectContainer &project, QWindow *parent) :
setMaximumHeight(480);
}
-GameWindow::~GameWindow() = default;
+GlGameWindow::~GlGameWindow() = default;
-void GameWindow::initialize()
+void GlGameWindow::initialize()
{
m_program = new QOpenGLShaderProgram{this};
@@ -56,7 +56,7 @@ void GameWindow::initialize()
Q_ASSERT(m_matrixUniform != -1);
}
-void GameWindow::render()
+void GlGameWindow::render()
{
const qreal retinaScale = devicePixelRatio();
glViewport(0, 0, width() * retinaScale, height() * retinaScale);
@@ -100,12 +100,12 @@ void GameWindow::render()
++m_frame;
}
-void GameWindow::renderLater()
+void GlGameWindow::renderLater()
{
requestUpdate();
}
-void GameWindow::renderNow()
+void GlGameWindow::renderNow()
{
if (!isExposed())
return;
@@ -136,7 +136,7 @@ void GameWindow::renderNow()
renderLater();
}
-bool GameWindow::event(QEvent *event)
+bool GlGameWindow::event(QEvent *event)
{
switch (event->type())
{
@@ -148,21 +148,21 @@ bool GameWindow::event(QEvent *event)
}
}
-void GameWindow::keyPressEvent(QKeyEvent *event)
+void GlGameWindow::keyPressEvent(QKeyEvent *event)
{
QWindow::keyPressEvent(event);
qDebug() << (event->key() == Qt::Key_Escape);
}
-void GameWindow::keyReleaseEvent(QKeyEvent *event)
+void GlGameWindow::keyReleaseEvent(QKeyEvent *event)
{
QWindow::keyReleaseEvent(event);
qDebug() << (event->key() == Qt::Key_Escape);
}
-void GameWindow::exposeEvent(QExposeEvent *event)
+void GlGameWindow::exposeEvent(QExposeEvent *event)
{
Q_UNUSED(event);
diff --git a/src/engine/gamewindow.h b/src/engine/glgamewindow.h
similarity index 81%
rename from src/engine/gamewindow.h
rename to src/engine/glgamewindow.h
index eee5d3d..e577598 100644
--- a/src/engine/gamewindow.h
+++ b/src/engine/glgamewindow.h
@@ -8,13 +8,13 @@ class QOpenGLPaintDevice;
class QOpenGLShaderProgram;
struct ProjectContainer;
-class GameWindow : public QWindow, protected QOpenGLFunctions
+class GlGameWindow : public QWindow, protected QOpenGLFunctions
{
Q_OBJECT
public:
- explicit GameWindow(const ProjectContainer &project, QWindow *parent = nullptr);
- ~GameWindow();
+ explicit GlGameWindow(const ProjectContainer &project, QWindow *parent = nullptr);
+ ~GlGameWindow();
void initialize();
void render();
diff --git a/src/engine/vulkangamerenderer.cpp b/src/engine/vulkangamerenderer.cpp
new file mode 100644
index 0000000..1499349
--- /dev/null
+++ b/src/engine/vulkangamerenderer.cpp
@@ -0,0 +1,464 @@
+#include "vulkangamerenderer.h"
+
+#include
+#include
+
+// Note that the vertex data and the projection matrix assume OpenGL. With
+// Vulkan Y is negated in clip space and the near/far plane is at 0/1 instead
+// of -1/1. These will be corrected for by an extra transformation when
+// calculating the modelview-projection matrix.
+static float vertexData[] = { // Y up, front = CCW
+ 0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f
+};
+
+static const int UNIFORM_DATA_SIZE = 16 * sizeof(float);
+
+static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+VulkanGameRenderer::VulkanGameRenderer(QVulkanWindow *w, bool msaa) :
+ m_window(w)
+{
+ if (msaa) {
+ const QVector counts = w->supportedSampleCounts();
+ qDebug() << "Supported sample counts:" << counts;
+ for (int s = 16; s >= 4; s /= 2) {
+ if (counts.contains(s)) {
+ qDebug("Requesting sample count %d", s);
+ m_window->setSampleCount(s);
+ break;
+ }
+ }
+ }
+}
+
+VkShaderModule VulkanGameRenderer::createShader(const QString &name)
+{
+ QFile file(name);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning("Failed to read shader %s", qPrintable(name));
+ return VK_NULL_HANDLE;
+ }
+ QByteArray blob = file.readAll();
+ file.close();
+
+ VkShaderModuleCreateInfo shaderInfo;
+ memset(&shaderInfo, 0, sizeof(shaderInfo));
+ shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ shaderInfo.codeSize = blob.size();
+ shaderInfo.pCode = reinterpret_cast(blob.constData());
+ VkShaderModule shaderModule;
+ VkResult err = m_devFuncs->vkCreateShaderModule(m_window->device(), &shaderInfo, nullptr, &shaderModule);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create shader module: %d", err);
+ return VK_NULL_HANDLE;
+ }
+
+ return shaderModule;
+}
+
+void VulkanGameRenderer::initResources()
+{
+ qDebug("initResources");
+
+ VkDevice dev = m_window->device();
+ m_devFuncs = m_window->vulkanInstance()->deviceFunctions(dev);
+
+ // Prepare the vertex and uniform data. The vertex data will never
+ // change so one buffer is sufficient regardless of the value of
+ // QVulkanWindow::CONCURRENT_FRAME_COUNT. Uniform data is changing per
+ // frame however so active frames have to have a dedicated copy.
+
+ // Use just one memory allocation and one buffer. We will then specify the
+ // appropriate offsets for uniform buffers in the VkDescriptorBufferInfo.
+ // Have to watch out for
+ // VkPhysicalDeviceLimits::minUniformBufferOffsetAlignment, though.
+
+ // The uniform buffer is not strictly required in this example, we could
+ // have used push constants as well since our single matrix (64 bytes) fits
+ // into the spec mandated minimum limit of 128 bytes. However, once that
+ // limit is not sufficient, the per-frame buffers, as shown below, will
+ // become necessary.
+
+ const int concurrentFrameCount = m_window->concurrentFrameCount();
+ const VkPhysicalDeviceLimits *pdevLimits = &m_window->physicalDeviceProperties()->limits;
+ const VkDeviceSize uniAlign = pdevLimits->minUniformBufferOffsetAlignment;
+ qDebug("uniform buffer offset alignment is %u", (uint) uniAlign);
+ VkBufferCreateInfo bufInfo;
+ memset(&bufInfo, 0, sizeof(bufInfo));
+ bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ // Our internal layout is vertex, uniform, uniform, ... with each uniform buffer start offset aligned to uniAlign.
+ const VkDeviceSize vertexAllocSize = aligned(sizeof(vertexData), uniAlign);
+ const VkDeviceSize uniformAllocSize = aligned(UNIFORM_DATA_SIZE, uniAlign);
+ bufInfo.size = vertexAllocSize + concurrentFrameCount * uniformAllocSize;
+ bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+
+ VkResult err = m_devFuncs->vkCreateBuffer(dev, &bufInfo, nullptr, &m_buf);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to create buffer: %d", err);
+
+ VkMemoryRequirements memReq;
+ m_devFuncs->vkGetBufferMemoryRequirements(dev, m_buf, &memReq);
+
+ VkMemoryAllocateInfo memAllocInfo = {
+ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ nullptr,
+ memReq.size,
+ m_window->hostVisibleMemoryIndex()
+ };
+
+ err = m_devFuncs->vkAllocateMemory(dev, &memAllocInfo, nullptr, &m_bufMem);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to allocate memory: %d", err);
+
+ err = m_devFuncs->vkBindBufferMemory(dev, m_buf, m_bufMem, 0);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to bind buffer memory: %d", err);
+
+ quint8 *p;
+ err = m_devFuncs->vkMapMemory(dev, m_bufMem, 0, memReq.size, 0, reinterpret_cast(&p));
+ if (err != VK_SUCCESS)
+ qFatal("Failed to map memory: %d", err);
+ memcpy(p, vertexData, sizeof(vertexData));
+ QMatrix4x4 ident;
+ memset(m_uniformBufInfo, 0, sizeof(m_uniformBufInfo));
+ for (int i = 0; i < concurrentFrameCount; ++i) {
+ const VkDeviceSize offset = vertexAllocSize + i * uniformAllocSize;
+ memcpy(p + offset, ident.constData(), 16 * sizeof(float));
+ m_uniformBufInfo[i].buffer = m_buf;
+ m_uniformBufInfo[i].offset = offset;
+ m_uniformBufInfo[i].range = uniformAllocSize;
+ }
+ m_devFuncs->vkUnmapMemory(dev, m_bufMem);
+
+ VkVertexInputBindingDescription vertexBindingDesc = {
+ 0, // binding
+ 5 * sizeof(float),
+ VK_VERTEX_INPUT_RATE_VERTEX
+ };
+ VkVertexInputAttributeDescription vertexAttrDesc[] = {
+ { // position
+ 0, // location
+ 0, // binding
+ VK_FORMAT_R32G32_SFLOAT,
+ 0
+ },
+ { // color
+ 1,
+ 0,
+ VK_FORMAT_R32G32B32_SFLOAT,
+ 2 * sizeof(float)
+ }
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertexInputInfo;
+ vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+ vertexInputInfo.pNext = nullptr;
+ vertexInputInfo.flags = 0;
+ vertexInputInfo.vertexBindingDescriptionCount = 1;
+ vertexInputInfo.pVertexBindingDescriptions = &vertexBindingDesc;
+ vertexInputInfo.vertexAttributeDescriptionCount = 2;
+ vertexInputInfo.pVertexAttributeDescriptions = vertexAttrDesc;
+
+ // Set up descriptor set and its layout.
+ VkDescriptorPoolSize descPoolSizes = { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32_t(concurrentFrameCount) };
+ VkDescriptorPoolCreateInfo descPoolInfo;
+ memset(&descPoolInfo, 0, sizeof(descPoolInfo));
+ descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+ descPoolInfo.maxSets = concurrentFrameCount;
+ descPoolInfo.poolSizeCount = 1;
+ descPoolInfo.pPoolSizes = &descPoolSizes;
+ err = m_devFuncs->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, &m_descPool);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to create descriptor pool: %d", err);
+
+ VkDescriptorSetLayoutBinding layoutBinding = {
+ 0, // binding
+ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ 1,
+ VK_SHADER_STAGE_VERTEX_BIT,
+ nullptr
+ };
+ VkDescriptorSetLayoutCreateInfo descLayoutInfo = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ nullptr,
+ 0,
+ 1,
+ &layoutBinding
+ };
+ err = m_devFuncs->vkCreateDescriptorSetLayout(dev, &descLayoutInfo, nullptr, &m_descSetLayout);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to create descriptor set layout: %d", err);
+
+ for (int i = 0; i < concurrentFrameCount; ++i) {
+ VkDescriptorSetAllocateInfo descSetAllocInfo = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ nullptr,
+ m_descPool,
+ 1,
+ &m_descSetLayout
+ };
+ err = m_devFuncs->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_descSet[i]);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to allocate descriptor set: %d", err);
+
+ VkWriteDescriptorSet descWrite;
+ memset(&descWrite, 0, sizeof(descWrite));
+ descWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descWrite.dstSet = m_descSet[i];
+ descWrite.descriptorCount = 1;
+ descWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ descWrite.pBufferInfo = &m_uniformBufInfo[i];
+ m_devFuncs->vkUpdateDescriptorSets(dev, 1, &descWrite, 0, nullptr);
+ }
+
+ // Pipeline cache
+ VkPipelineCacheCreateInfo pipelineCacheInfo;
+ memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo));
+ pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+ err = m_devFuncs->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &m_pipelineCache);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to create pipeline cache: %d", err);
+
+ // Pipeline layout
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo;
+ memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo));
+ pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ pipelineLayoutInfo.setLayoutCount = 1;
+ pipelineLayoutInfo.pSetLayouts = &m_descSetLayout;
+ err = m_devFuncs->vkCreatePipelineLayout(dev, &pipelineLayoutInfo, nullptr, &m_pipelineLayout);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to create pipeline layout: %d", err);
+
+ // Shaders
+ VkShaderModule vertShaderModule = createShader(QStringLiteral(":/qtgameengine/shader_modules/color_vert.spv"));
+ VkShaderModule fragShaderModule = createShader(QStringLiteral(":/qtgameengine/shader_modules/color_frag.spv"));
+
+ // Graphics pipeline
+ VkGraphicsPipelineCreateInfo pipelineInfo;
+ memset(&pipelineInfo, 0, sizeof(pipelineInfo));
+ pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+
+ VkPipelineShaderStageCreateInfo shaderStages[2] = {
+ {
+ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ nullptr,
+ 0,
+ VK_SHADER_STAGE_VERTEX_BIT,
+ vertShaderModule,
+ "main",
+ nullptr
+ },
+ {
+ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ nullptr,
+ 0,
+ VK_SHADER_STAGE_FRAGMENT_BIT,
+ fragShaderModule,
+ "main",
+ nullptr
+ }
+ };
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = shaderStages;
+
+ pipelineInfo.pVertexInputState = &vertexInputInfo;
+
+ VkPipelineInputAssemblyStateCreateInfo ia;
+ memset(&ia, 0, sizeof(ia));
+ ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+ ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ pipelineInfo.pInputAssemblyState = &ia;
+
+ // The viewport and scissor will be set dynamically via vkCmdSetViewport/Scissor.
+ // This way the pipeline does not need to be touched when resizing the window.
+ VkPipelineViewportStateCreateInfo vp;
+ memset(&vp, 0, sizeof(vp));
+ vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+ vp.viewportCount = 1;
+ vp.scissorCount = 1;
+ pipelineInfo.pViewportState = &vp;
+
+ VkPipelineRasterizationStateCreateInfo rs;
+ memset(&rs, 0, sizeof(rs));
+ rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ rs.polygonMode = VK_POLYGON_MODE_FILL;
+ rs.cullMode = VK_CULL_MODE_NONE; // we want the back face as well
+ rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ rs.lineWidth = 1.0f;
+ pipelineInfo.pRasterizationState = &rs;
+
+ VkPipelineMultisampleStateCreateInfo ms;
+ memset(&ms, 0, sizeof(ms));
+ ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+ // Enable multisampling.
+ ms.rasterizationSamples = m_window->sampleCountFlagBits();
+ pipelineInfo.pMultisampleState = &ms;
+
+ VkPipelineDepthStencilStateCreateInfo ds;
+ memset(&ds, 0, sizeof(ds));
+ ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+ ds.depthTestEnable = VK_TRUE;
+ ds.depthWriteEnable = VK_TRUE;
+ ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
+ pipelineInfo.pDepthStencilState = &ds;
+
+ VkPipelineColorBlendStateCreateInfo cb;
+ memset(&cb, 0, sizeof(cb));
+ cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ // no blend, write out all of rgba
+ VkPipelineColorBlendAttachmentState att;
+ memset(&att, 0, sizeof(att));
+ att.colorWriteMask = 0xF;
+ cb.attachmentCount = 1;
+ cb.pAttachments = &att;
+ pipelineInfo.pColorBlendState = &cb;
+
+ VkDynamicState dynEnable[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+ VkPipelineDynamicStateCreateInfo dyn;
+ memset(&dyn, 0, sizeof(dyn));
+ dyn.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+ dyn.dynamicStateCount = sizeof(dynEnable) / sizeof(VkDynamicState);
+ dyn.pDynamicStates = dynEnable;
+ pipelineInfo.pDynamicState = &dyn;
+
+ pipelineInfo.layout = m_pipelineLayout;
+ pipelineInfo.renderPass = m_window->defaultRenderPass();
+
+ err = m_devFuncs->vkCreateGraphicsPipelines(dev, m_pipelineCache, 1, &pipelineInfo, nullptr, &m_pipeline);
+ if (err != VK_SUCCESS)
+ qFatal("Failed to create graphics pipeline: %d", err);
+
+ if (vertShaderModule)
+ m_devFuncs->vkDestroyShaderModule(dev, vertShaderModule, nullptr);
+ if (fragShaderModule)
+ m_devFuncs->vkDestroyShaderModule(dev, fragShaderModule, nullptr);
+}
+
+void VulkanGameRenderer::initSwapChainResources()
+{
+ qDebug("initSwapChainResources");
+
+ // Projection matrix
+ m_proj = m_window->clipCorrectionMatrix(); // adjust for Vulkan-OpenGL clip space differences
+ const QSize sz = m_window->swapChainImageSize();
+ m_proj.perspective(45.0f, sz.width() / (float) sz.height(), 0.01f, 100.0f);
+ m_proj.translate(0, 0, -4);
+}
+
+void VulkanGameRenderer::releaseSwapChainResources()
+{
+ qDebug("releaseSwapChainResources");
+}
+
+void VulkanGameRenderer::releaseResources()
+{
+ qDebug("releaseResources");
+
+ VkDevice dev = m_window->device();
+
+ if (m_pipeline) {
+ m_devFuncs->vkDestroyPipeline(dev, m_pipeline, nullptr);
+ m_pipeline = VK_NULL_HANDLE;
+ }
+
+ if (m_pipelineLayout) {
+ m_devFuncs->vkDestroyPipelineLayout(dev, m_pipelineLayout, nullptr);
+ m_pipelineLayout = VK_NULL_HANDLE;
+ }
+
+ if (m_pipelineCache) {
+ m_devFuncs->vkDestroyPipelineCache(dev, m_pipelineCache, nullptr);
+ m_pipelineCache = VK_NULL_HANDLE;
+ }
+
+ if (m_descSetLayout) {
+ m_devFuncs->vkDestroyDescriptorSetLayout(dev, m_descSetLayout, nullptr);
+ m_descSetLayout = VK_NULL_HANDLE;
+ }
+
+ if (m_descPool) {
+ m_devFuncs->vkDestroyDescriptorPool(dev, m_descPool, nullptr);
+ m_descPool = VK_NULL_HANDLE;
+ }
+
+ if (m_buf) {
+ m_devFuncs->vkDestroyBuffer(dev, m_buf, nullptr);
+ m_buf = VK_NULL_HANDLE;
+ }
+
+ if (m_bufMem) {
+ m_devFuncs->vkFreeMemory(dev, m_bufMem, nullptr);
+ m_bufMem = VK_NULL_HANDLE;
+ }
+}
+
+void VulkanGameRenderer::startNextFrame()
+{
+ VkDevice dev = m_window->device();
+ VkCommandBuffer cb = m_window->currentCommandBuffer();
+ const QSize sz = m_window->swapChainImageSize();
+
+ VkClearColorValue clearColor = {{ 0, 0, 0, 1 }};
+ VkClearDepthStencilValue clearDS = { 1, 0 };
+ VkClearValue clearValues[3];
+ memset(clearValues, 0, sizeof(clearValues));
+ clearValues[0].color = clearValues[2].color = clearColor;
+ clearValues[1].depthStencil = clearDS;
+
+ VkRenderPassBeginInfo rpBeginInfo;
+ memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
+ rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ rpBeginInfo.renderPass = m_window->defaultRenderPass();
+ rpBeginInfo.framebuffer = m_window->currentFramebuffer();
+ rpBeginInfo.renderArea.extent.width = sz.width();
+ rpBeginInfo.renderArea.extent.height = sz.height();
+ rpBeginInfo.clearValueCount = m_window->sampleCountFlagBits() > VK_SAMPLE_COUNT_1_BIT ? 3 : 2;
+ rpBeginInfo.pClearValues = clearValues;
+ VkCommandBuffer cmdBuf = m_window->currentCommandBuffer();
+ m_devFuncs->vkCmdBeginRenderPass(cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+ quint8 *p;
+ VkResult err = m_devFuncs->vkMapMemory(dev, m_bufMem, m_uniformBufInfo[m_window->currentFrame()].offset,
+ UNIFORM_DATA_SIZE, 0, reinterpret_cast(&p));
+ if (err != VK_SUCCESS)
+ qFatal("Failed to map memory: %d", err);
+ QMatrix4x4 m = m_proj;
+ m.rotate(m_rotation, 0, 1, 0);
+ memcpy(p, m.constData(), 16 * sizeof(float));
+ m_devFuncs->vkUnmapMemory(dev, m_bufMem);
+
+ // Not exactly a real animation system, just advance on every frame for now.
+ m_rotation += 1.0f;
+
+ m_devFuncs->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline);
+ m_devFuncs->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1,
+ &m_descSet[m_window->currentFrame()], 0, nullptr);
+ VkDeviceSize vbOffset = 0;
+ m_devFuncs->vkCmdBindVertexBuffers(cb, 0, 1, &m_buf, &vbOffset);
+
+ VkViewport viewport;
+ viewport.x = viewport.y = 0;
+ viewport.width = sz.width();
+ viewport.height = sz.height();
+ viewport.minDepth = 0;
+ viewport.maxDepth = 1;
+ m_devFuncs->vkCmdSetViewport(cb, 0, 1, &viewport);
+
+ VkRect2D scissor;
+ scissor.offset.x = scissor.offset.y = 0;
+ scissor.extent.width = viewport.width;
+ scissor.extent.height = viewport.height;
+ m_devFuncs->vkCmdSetScissor(cb, 0, 1, &scissor);
+
+ m_devFuncs->vkCmdDraw(cb, 3, 1, 0, 0);
+
+ m_devFuncs->vkCmdEndRenderPass(cmdBuf);
+
+ m_window->frameReady();
+ m_window->requestUpdate(); // render continuously, throttled by the presentation rate
+}
diff --git a/src/engine/vulkangamerenderer.h b/src/engine/vulkangamerenderer.h
new file mode 100644
index 0000000..83301a0
--- /dev/null
+++ b/src/engine/vulkangamerenderer.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include
+
+class VulkanGameRenderer : public QVulkanWindowRenderer
+{
+public:
+ VulkanGameRenderer(QVulkanWindow *w, bool msaa = false);
+
+ void initResources() override;
+ void initSwapChainResources() override;
+ void releaseSwapChainResources() override;
+ void releaseResources() override;
+
+ void startNextFrame() override;
+
+protected:
+ VkShaderModule createShader(const QString &name);
+
+ QVulkanWindow *m_window;
+ QVulkanDeviceFunctions *m_devFuncs;
+
+ VkDeviceMemory m_bufMem = VK_NULL_HANDLE;
+ VkBuffer m_buf = VK_NULL_HANDLE;
+ VkDescriptorBufferInfo m_uniformBufInfo[QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT];
+
+ VkDescriptorPool m_descPool = VK_NULL_HANDLE;
+ VkDescriptorSetLayout m_descSetLayout = VK_NULL_HANDLE;
+ VkDescriptorSet m_descSet[QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT];
+
+ VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
+ VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
+ VkPipeline m_pipeline = VK_NULL_HANDLE;
+
+ QMatrix4x4 m_proj;
+ float m_rotation = 0.0f;
+};
diff --git a/src/engine/vulkangamewindow.cpp b/src/engine/vulkangamewindow.cpp
new file mode 100644
index 0000000..c7af166
--- /dev/null
+++ b/src/engine/vulkangamewindow.cpp
@@ -0,0 +1,18 @@
+#include "vulkangamewindow.h"
+
+#include "vulkangamerenderer.h"
+
+VulkanGameWindow::VulkanGameWindow(const ProjectContainer &project, QWindow *parent) :
+ QVulkanWindow{parent},
+ m_project{project}
+{
+ setMinimumWidth(640);
+ setMaximumWidth(640);
+ setMinimumHeight(480);
+ setMaximumHeight(480);
+}
+
+QVulkanWindowRenderer *VulkanGameWindow::createRenderer()
+{
+ return new VulkanGameRenderer(this, true); // try MSAA, when available
+}
diff --git a/src/engine/vulkangamewindow.h b/src/engine/vulkangamewindow.h
new file mode 100644
index 0000000..65a3c4f
--- /dev/null
+++ b/src/engine/vulkangamewindow.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include
+#include
+
+struct ProjectContainer;
+
+class VulkanGameWindow : public QVulkanWindow
+{
+public:
+ explicit VulkanGameWindow(const ProjectContainer &project, QWindow *parent = nullptr);
+
+ QVulkanWindowRenderer *createRenderer() override;
+
+private:
+ const ProjectContainer &m_project;
+};
diff --git a/src/main.cpp b/src/main.cpp
index b09605c..58f25be 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -16,7 +16,8 @@ int main(int argc, char *argv[])
"%{function}(): "
"%{message}"));
- Q_INIT_RESOURCE(resources);
+ Q_INIT_RESOURCE(resources_editor);
+ Q_INIT_RESOURCE(resources_engine);
QApplication app(argc, argv);