Implement actual game engine and rendering opengl and vulkan
This commit is contained in:
1 parent
543196d99e
commit
defb83c9c3
18 files changed
+1243
-981
No files matched your search
@@ -54,6 +54,7 @@ HEADERS += \
|
||||
src/editor/widgets/zoomablescrollarea.h \
|
||||
src/engine/gameengine.h \
|
||||
src/engine/glgamewindow.h \
|
||||
src/engine/gamescene.h \
|
||||
src/engine/vulkangamerenderer.h \
|
||||
src/engine/vulkangamewindow.h \
|
||||
src/futurecpp.h \
|
||||
@@ -132,6 +133,7 @@ SOURCES += \
|
||||
src/editor/widgets/zoomablescrollarea.cpp \
|
||||
src/engine/gameengine.cpp \
|
||||
src/engine/glgamewindow.cpp \
|
||||
src/engine/gamescene.cpp \
|
||||
src/engine/vulkangamerenderer.cpp \
|
||||
src/engine/vulkangamewindow.cpp \
|
||||
src/main.cpp \
|
||||
|
||||
+69
-41
@@ -1,68 +1,96 @@
|
||||
#include "gameengine.h"
|
||||
|
||||
#include <QVulkanInstance>
|
||||
#include <algorithm>
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
|
||||
#include "closeeventfilter.h"
|
||||
#include "projectcontainer.h"
|
||||
|
||||
GameEngine::GameEngine(const ProjectContainer &project, QObject *parent) :
|
||||
QObject{parent},
|
||||
m_project{project},
|
||||
m_glGameWindow{m_project, m_rotation},
|
||||
m_vulkanGameWindow{m_project, m_rotation},
|
||||
m_timerId(startTimer(1000/60, Qt::PreciseTimer))
|
||||
m_scene{project},
|
||||
m_glGameWindow{m_project, m_scene},
|
||||
m_vulkanGameWindow{m_project, m_scene}
|
||||
{
|
||||
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);
|
||||
m_vulkanAvailable = m_vulkanInstance.create();
|
||||
if (m_vulkanAvailable)
|
||||
m_vulkanGameWindow.setVulkanInstance(&m_vulkanInstance);
|
||||
else
|
||||
qWarning("Could not create Vulkan instance: %d", m_vulkanInstance.errorCode());
|
||||
|
||||
m_glGameWindow.setModality(Qt::WindowModal);
|
||||
m_vulkanGameWindow.setModality(Qt::WindowModal);
|
||||
|
||||
m_frameTimer.setTimerType(Qt::PreciseTimer);
|
||||
m_frameTimer.setSingleShot(true);
|
||||
connect(&m_frameTimer, &QTimer::timeout, this, [this]() {
|
||||
constexpr qint64 frameNanoseconds = 1000000000 / 60;
|
||||
m_scene.advanceTo(m_elapsed.elapsed());
|
||||
m_scene.draw();
|
||||
m_glGameWindow.requestUpdate();
|
||||
if (m_vulkanAvailable)
|
||||
m_vulkanGameWindow.requestUpdate();
|
||||
|
||||
const qint64 now = m_elapsed.nsecsElapsed();
|
||||
m_nextFrameNanoseconds = std::max(m_nextFrameNanoseconds + frameNanoseconds,
|
||||
(now / frameNanoseconds + 1) * frameNanoseconds);
|
||||
const qint64 remaining = m_nextFrameNanoseconds - m_elapsed.nsecsElapsed();
|
||||
m_frameTimer.start(int(std::max<qint64>(1, (remaining + 999999) / 1000000)));
|
||||
});
|
||||
}
|
||||
|
||||
void GameEngine::run()
|
||||
{
|
||||
m_glGameWindow.setTitle(tr("Game Window - OpenGL"));
|
||||
m_vulkanGameWindow.setTitle(tr("Game Window - Vulkan"));
|
||||
if (m_project.rooms.empty())
|
||||
return;
|
||||
|
||||
m_scene.start([this](ObjectInstance &target, const Action &action, ObjectInstance *) {
|
||||
if (target.applyAction(action))
|
||||
return;
|
||||
static constexpr std::array actionNames{
|
||||
"MoveFixed", "MoveFree", "MoveTowards", "SpeedHorizontal",
|
||||
"SpeedVertical", "SetGravity", "ExecuteCode"
|
||||
};
|
||||
const std::size_t type = action.index();
|
||||
if (!m_warnedActionTypes[type])
|
||||
{
|
||||
m_warnedActionTypes[type] = true;
|
||||
qWarning("Game action %s has no runtime implementation yet", actionNames[type]);
|
||||
}
|
||||
});
|
||||
|
||||
CloseEventFilter closeEventFilter;
|
||||
|
||||
m_glGameWindow.installEventFilter(&closeEventFilter);
|
||||
m_vulkanGameWindow.installEventFilter(&closeEventFilter);
|
||||
if (m_vulkanAvailable)
|
||||
m_vulkanGameWindow.installEventFilter(&closeEventFilter);
|
||||
|
||||
QEventLoop eventLoop;
|
||||
bool shuttingDown = false;
|
||||
connect(&closeEventFilter, &CloseEventFilter::closeEventReceived,
|
||||
&eventLoop, &QEventLoop::quit);
|
||||
&eventLoop, [this, &eventLoop, &shuttingDown]() {
|
||||
if (shuttingDown)
|
||||
return;
|
||||
shuttingDown = true;
|
||||
m_frameTimer.stop();
|
||||
|
||||
// Finish both QWindow close paths before their Vulkan instance is destroyed.
|
||||
if (m_vulkanAvailable)
|
||||
m_vulkanGameWindow.close();
|
||||
m_glGameWindow.close();
|
||||
QTimer::singleShot(0, &eventLoop, &QEventLoop::quit);
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
m_glGameWindow.setTitle(m_glGameWindow.title() + tr(" - OpenGL"));
|
||||
m_glGameWindow.show();
|
||||
m_vulkanGameWindow.show();
|
||||
|
||||
QTimer::singleShot(100, &m_glGameWindow, [this](){
|
||||
auto geometry = m_glGameWindow.geometry();
|
||||
qDebug() << geometry;
|
||||
geometry.moveLeft(geometry.left() - (geometry.width() / 2));
|
||||
m_glGameWindow.setGeometry(geometry);
|
||||
});
|
||||
QTimer::singleShot(100, &m_vulkanGameWindow, [this](){
|
||||
auto geometry = m_vulkanGameWindow.geometry();
|
||||
qDebug() << geometry;
|
||||
geometry.moveLeft(geometry.left() + (geometry.width() / 2));
|
||||
m_vulkanGameWindow.setGeometry(geometry);
|
||||
});
|
||||
|
||||
if (m_vulkanAvailable)
|
||||
{
|
||||
m_vulkanGameWindow.setTitle(m_vulkanGameWindow.title() + tr(" - Vulkan"));
|
||||
m_vulkanGameWindow.show();
|
||||
}
|
||||
m_elapsed.start();
|
||||
m_nextFrameNanoseconds = 1000000000 / 60;
|
||||
m_frameTimer.start(17);
|
||||
eventLoop.exec();
|
||||
}
|
||||
|
||||
void GameEngine::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
if (event->timerId() == m_timerId)
|
||||
{
|
||||
m_rotation += 1.f;
|
||||
}
|
||||
else
|
||||
QObject::timerEvent(event);
|
||||
}
|
||||
+13
-11
@@ -1,7 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <array>
|
||||
|
||||
#include <QObject>
|
||||
#include <QElapsedTimer>
|
||||
#include <QTimer>
|
||||
#include <QVulkanInstance>
|
||||
|
||||
#include "engine/gamescene.h"
|
||||
#include "engine/glgamewindow.h"
|
||||
#include "engine/vulkangamewindow.h"
|
||||
|
||||
@@ -13,21 +19,17 @@ class GameEngine : public QObject
|
||||
|
||||
public:
|
||||
explicit GameEngine(const ProjectContainer &project, QObject *parent = nullptr);
|
||||
|
||||
void run();
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *event) override;
|
||||
|
||||
private:
|
||||
const ProjectContainer &m_project;
|
||||
|
||||
GameScene m_scene;
|
||||
QVulkanInstance m_vulkanInstance;
|
||||
|
||||
float m_rotation{};
|
||||
|
||||
GlGameWindow m_glGameWindow;
|
||||
VulkanGameWindow m_vulkanGameWindow;
|
||||
|
||||
const int m_timerId;
|
||||
bool m_vulkanAvailable{};
|
||||
QTimer m_frameTimer;
|
||||
QElapsedTimer m_elapsed;
|
||||
qint64 m_nextFrameNanoseconds{};
|
||||
std::array<bool, std::variant_size_v<Action>> m_warnedActionTypes{};
|
||||
};
|
||||
@@ -0,0 +1,359 @@
|
||||
#include "gamescene.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <QHash>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
namespace {
|
||||
|
||||
std::size_t frameIndex(double imageIndex, std::size_t count)
|
||||
{
|
||||
const auto size = static_cast<qint64>(count);
|
||||
qint64 index = static_cast<qint64>(std::floor(imageIndex)) % size;
|
||||
if (index < 0)
|
||||
index += size;
|
||||
return static_cast<std::size_t>(index);
|
||||
}
|
||||
|
||||
QPointF motion(double degrees, double speed)
|
||||
{
|
||||
const double radians = degrees * std::numbers::pi / 180.0;
|
||||
return {speed * std::cos(radians), -speed * std::sin(radians)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ObjectInstance::executeActions(const ActionsContainer &actions, GameScene &scene,
|
||||
const ActionExecutor &executor, ObjectInstance *other)
|
||||
{
|
||||
if (!executor)
|
||||
return;
|
||||
|
||||
for (const Action &action : actions)
|
||||
{
|
||||
const AppliesTo &appliesTo = std::visit([](const auto &value) -> const AppliesTo & {
|
||||
return value.appliesTo;
|
||||
}, action);
|
||||
std::visit([&](const auto &target) {
|
||||
using Target = std::decay_t<decltype(target)>;
|
||||
if constexpr (std::is_same_v<Target, AppliesToSelf>)
|
||||
executor(*this, action, other);
|
||||
else if constexpr (std::is_same_v<Target, AppliesToOther>)
|
||||
{
|
||||
if (other)
|
||||
executor(*other, action, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ObjectInstance &instance : scene.instances)
|
||||
{
|
||||
if (instance.definition && instance.definition->name == target.objectName)
|
||||
executor(instance, action, this);
|
||||
}
|
||||
}
|
||||
}, appliesTo);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectInstance::executeEvent(Object::EventType event, GameScene &scene,
|
||||
const ActionExecutor &executor, ObjectInstance *other)
|
||||
{
|
||||
if (!definition)
|
||||
return;
|
||||
const auto found = definition->events.find(event);
|
||||
if (found != definition->events.end())
|
||||
executeActions(found->second, scene, executor, other);
|
||||
}
|
||||
|
||||
void ObjectInstance::executeCollisionEvent(const QString &objectName, GameScene &scene,
|
||||
const ActionExecutor &executor, ObjectInstance &other)
|
||||
{
|
||||
if (!definition)
|
||||
return;
|
||||
const auto found = definition->collisionEvents.find(objectName);
|
||||
if (found != definition->collisionEvents.end())
|
||||
executeActions(found->second, scene, executor, &other);
|
||||
}
|
||||
|
||||
void ObjectInstance::step()
|
||||
{
|
||||
const QPointF gravityMotion = motion(gravityDirection, gravity);
|
||||
hspeed += gravityMotion.x();
|
||||
vspeed += gravityMotion.y();
|
||||
position += QPointF{hspeed, vspeed};
|
||||
imageIndex += imageSpeed;
|
||||
}
|
||||
|
||||
bool ObjectInstance::setAlarm(std::size_t index, int steps)
|
||||
{
|
||||
if (index >= alarms.size())
|
||||
return false;
|
||||
alarms[index] = steps > 0 ? steps : -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ObjectInstance::applyAction(const Action &action)
|
||||
{
|
||||
return std::visit([this](const auto &value) -> bool {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, SpeedHorizontalAction>)
|
||||
hspeed = value.relative ? hspeed + value.speed : value.speed;
|
||||
else if constexpr (std::is_same_v<T, SpeedVerticalAction>)
|
||||
vspeed = value.relative ? vspeed + value.speed : value.speed;
|
||||
else if constexpr (std::is_same_v<T, SetGravityAction>)
|
||||
{
|
||||
gravityDirection = value.relative ? gravityDirection + value.direction : value.direction;
|
||||
gravity = value.relative ? gravity + value.gravity : value.gravity;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MoveFreeAction>)
|
||||
{
|
||||
const QPointF velocity = motion(value.direction, value.speed);
|
||||
hspeed = value.relative ? hspeed + velocity.x() : velocity.x();
|
||||
vspeed = value.relative ? vspeed + velocity.y() : velocity.y();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MoveTowardsAction>)
|
||||
{
|
||||
const QPointF offset{double(value.x), double(value.y)};
|
||||
const QPointF goal = value.relative ? position + offset : offset;
|
||||
const QPointF delta = goal - position;
|
||||
const double distance = std::hypot(delta.x(), delta.y());
|
||||
if (distance > 0)
|
||||
{
|
||||
hspeed = value.speed * delta.x() / distance;
|
||||
vspeed = value.speed * delta.y() / distance;
|
||||
}
|
||||
else
|
||||
hspeed = vspeed = 0;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MoveFixedAction>)
|
||||
{
|
||||
std::array<int, 9> choices{};
|
||||
int count = 0;
|
||||
for (int i = 0; i < int(choices.size()); ++i)
|
||||
if (value.directions[std::size_t(i)])
|
||||
choices[std::size_t(count++)] = i;
|
||||
if (count == 0)
|
||||
return true;
|
||||
const int direction = choices[QRandomGenerator::global()->bounded(count)];
|
||||
constexpr std::array<double, 9> angles{135, 90, 45, 180, 0, 0, 225, 270, 315};
|
||||
const QPointF velocity = direction == 4 ? QPointF{} : motion(angles[std::size_t(direction)], value.speed);
|
||||
hspeed = value.relative ? hspeed + velocity.x() : velocity.x();
|
||||
vspeed = value.relative ? vspeed + velocity.y() : velocity.y();
|
||||
}
|
||||
else
|
||||
return false; // ExecuteCode needs a GML interpreter.
|
||||
return true;
|
||||
}, action);
|
||||
}
|
||||
|
||||
GameScene::GameScene(const ProjectContainer &project)
|
||||
{
|
||||
if (project.rooms.empty())
|
||||
return;
|
||||
|
||||
const Room &room = project.rooms.front();
|
||||
roomSize = QSize{std::max(1, room.width), std::max(1, room.height)};
|
||||
caption = room.caption.isEmpty() ? room.name : room.caption;
|
||||
roomSpeed = std::max(1, room.speed);
|
||||
|
||||
QHash<QString, std::size_t> spriteIndices;
|
||||
for (const ::Sprite &source : project.sprites)
|
||||
{
|
||||
Sprite sprite;
|
||||
sprite.origin = QPoint{source.origin.x, source.origin.y};
|
||||
for (const QPixmap &pixmap : source.pixmaps)
|
||||
{
|
||||
if (pixmap.isNull())
|
||||
continue;
|
||||
sprite.frames.push_back(textures.size());
|
||||
textures.push_back(pixmap.toImage());
|
||||
sprite.collisionBounds.push_back(QRect{QPoint{}, textures.back().size()});
|
||||
}
|
||||
spriteIndices.insert(source.name, sprites.size());
|
||||
sprites.push_back(std::move(sprite));
|
||||
}
|
||||
|
||||
QHash<QString, const Object *> objects;
|
||||
for (const Object &object : project.objects)
|
||||
objects.insert(object.name, &object);
|
||||
|
||||
for (const Room::Object &placed : room.objects)
|
||||
{
|
||||
const Object *object = objects.value(placed.objectName, nullptr);
|
||||
if (!object)
|
||||
continue;
|
||||
const auto sprite = spriteIndices.constFind(object->spriteName);
|
||||
Instance instance;
|
||||
instance.definition = object;
|
||||
if (sprite != spriteIndices.cend() && !sprites.at(*sprite).frames.empty())
|
||||
instance.spriteIndex = *sprite;
|
||||
const auto mask = spriteIndices.constFind(object->maskSpriteName);
|
||||
if (!object->maskSpriteName.isEmpty() && mask != spriteIndices.cend() &&
|
||||
!sprites.at(*mask).frames.empty())
|
||||
instance.maskSpriteIndex = *mask;
|
||||
instance.position = QPointF{placed.pos};
|
||||
instance.depth = object->depth;
|
||||
instance.visible = object->visible;
|
||||
instances.push_back(instance);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t GameScene::textureFor(const Instance &instance) const
|
||||
{
|
||||
const Sprite &sprite = sprites.at(*instance.spriteIndex);
|
||||
return sprite.frames.at(frameIndex(instance.imageIndex, sprite.frames.size()));
|
||||
}
|
||||
|
||||
QRectF GameScene::collisionRect(const Instance &instance) const
|
||||
{
|
||||
const auto index = instance.maskSpriteIndex ? instance.maskSpriteIndex : instance.spriteIndex;
|
||||
if (!index)
|
||||
return {};
|
||||
const Sprite &sprite = sprites.at(*index);
|
||||
const QRect &bounds = sprite.collisionBounds.at(frameIndex(instance.imageIndex, sprite.frames.size()));
|
||||
if (bounds.isEmpty())
|
||||
return {};
|
||||
return {instance.position.x() - sprite.origin.x() + bounds.x(),
|
||||
instance.position.y() - sprite.origin.y() + bounds.y(),
|
||||
double(bounds.width()), double(bounds.height())};
|
||||
}
|
||||
|
||||
std::vector<const GameScene::Instance *> GameScene::drawOrder() const
|
||||
{
|
||||
std::vector<const Instance *> order;
|
||||
order.reserve(instances.size());
|
||||
for (const Instance &instance : instances)
|
||||
order.push_back(&instance);
|
||||
// Larger depths are behind smaller ones; equal depths keep room placement order.
|
||||
std::stable_sort(order.begin(), order.end(), [](const Instance *left, const Instance *right) {
|
||||
return left->depth > right->depth;
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
void GameScene::start(const ActionExecutor &executor)
|
||||
{
|
||||
m_executor = executor;
|
||||
m_stepsDone = 0;
|
||||
for (Instance &instance : instances)
|
||||
if (!instance.pendingDestroy)
|
||||
instance.executeEvent(Object::EventType::Create, *this, m_executor);
|
||||
removeDestroyed();
|
||||
}
|
||||
|
||||
void GameScene::destroyInstance(Instance &instance)
|
||||
{
|
||||
if (instance.pendingDestroy)
|
||||
return;
|
||||
instance.pendingDestroy = true;
|
||||
instance.executeEvent(Object::EventType::Destroy, *this, m_executor);
|
||||
}
|
||||
|
||||
void GameScene::removeDestroyed()
|
||||
{
|
||||
std::erase_if(instances, [](const Instance &instance) { return instance.pendingDestroy; });
|
||||
}
|
||||
|
||||
void GameScene::advanceTo(qint64 elapsedMilliseconds)
|
||||
{
|
||||
const qint64 targetStep = elapsedMilliseconds * roomSpeed / 1000;
|
||||
while (m_stepsDone < targetStep)
|
||||
{
|
||||
for (Instance &instance : instances)
|
||||
if (!instance.pendingDestroy)
|
||||
instance.executeEvent(Object::EventType::BeginStep, *this, m_executor);
|
||||
for (Instance &instance : instances)
|
||||
{
|
||||
if (instance.pendingDestroy)
|
||||
continue;
|
||||
for (std::size_t index = 0; index < instance.alarms.size(); ++index)
|
||||
{
|
||||
int &remaining = instance.alarms[index];
|
||||
if (remaining <= 0 || --remaining != 0)
|
||||
continue;
|
||||
remaining = -1;
|
||||
const auto event = static_cast<Object::EventType>(int(Object::EventType::Alarm0) + int(index));
|
||||
instance.executeEvent(event, *this, m_executor);
|
||||
if (instance.pendingDestroy)
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (Instance &instance : instances)
|
||||
if (!instance.pendingDestroy)
|
||||
instance.executeEvent(Object::EventType::Step, *this, m_executor);
|
||||
|
||||
std::vector<QPointF> previousPositions;
|
||||
previousPositions.reserve(instances.size());
|
||||
for (const Instance &instance : instances)
|
||||
previousPositions.push_back(instance.position);
|
||||
for (Instance &instance : instances)
|
||||
if (!instance.pendingDestroy)
|
||||
instance.step();
|
||||
|
||||
// Detect every pair at its moved position before collision actions can
|
||||
// change either instance. A sprite-size rectangle is our first mask.
|
||||
std::vector<std::pair<std::size_t, std::size_t>> collisions;
|
||||
for (std::size_t i = 0; i < instances.size(); ++i)
|
||||
{
|
||||
if (instances[i].pendingDestroy)
|
||||
continue;
|
||||
const QRectF first = collisionRect(instances[i]);
|
||||
if (first.isEmpty())
|
||||
continue;
|
||||
for (std::size_t j = i + 1; j < instances.size(); ++j)
|
||||
{
|
||||
if (instances[j].pendingDestroy)
|
||||
continue;
|
||||
const QRectF second = collisionRect(instances[j]);
|
||||
if (!second.isEmpty() && first.intersects(second))
|
||||
collisions.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
for (const auto &[i, j] : collisions)
|
||||
{
|
||||
Instance &first = instances[i];
|
||||
Instance &second = instances[j];
|
||||
if (first.pendingDestroy || second.pendingDestroy)
|
||||
continue;
|
||||
const QString &firstName = first.definition->name;
|
||||
const QString &secondName = second.definition->name;
|
||||
const auto firstEvent = first.definition->collisionEvents.find(secondName);
|
||||
const auto secondEvent = second.definition->collisionEvents.find(firstName);
|
||||
const bool firstHandles = firstEvent != first.definition->collisionEvents.end() &&
|
||||
!firstEvent->second.empty();
|
||||
const bool secondHandles = secondEvent != second.definition->collisionEvents.end() &&
|
||||
!secondEvent->second.empty();
|
||||
if (!firstHandles && !secondHandles)
|
||||
continue;
|
||||
|
||||
if (first.definition->solid || second.definition->solid)
|
||||
{
|
||||
first.position = previousPositions[i];
|
||||
second.position = previousPositions[j];
|
||||
}
|
||||
if (firstHandles)
|
||||
first.executeCollisionEvent(secondName, *this, m_executor, second);
|
||||
if (secondHandles && !second.pendingDestroy && !first.pendingDestroy)
|
||||
second.executeCollisionEvent(firstName, *this, m_executor, first);
|
||||
}
|
||||
for (Instance &instance : instances)
|
||||
if (!instance.pendingDestroy)
|
||||
instance.executeEvent(Object::EventType::EndStep, *this, m_executor);
|
||||
removeDestroyed();
|
||||
++m_stepsDone;
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::draw()
|
||||
{
|
||||
for (Instance &instance : instances)
|
||||
if (instance.visible && !instance.pendingDestroy)
|
||||
instance.executeEvent(Object::EventType::Draw, *this, m_executor);
|
||||
removeDestroyed();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <QImage>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
|
||||
#include "projectcontainer.h"
|
||||
|
||||
struct GameScene;
|
||||
struct ObjectInstance;
|
||||
|
||||
// The interpreter supplies action behavior. `other` is the collision counterpart
|
||||
// for self actions, or the event source when an action targets another instance.
|
||||
using ActionExecutor = std::function<void(ObjectInstance &target, const Action &action,
|
||||
ObjectInstance *other)>;
|
||||
|
||||
struct ObjectInstance
|
||||
{
|
||||
ObjectInstance() { alarms.fill(-1); }
|
||||
|
||||
const Object *definition{};
|
||||
std::optional<std::size_t> spriteIndex;
|
||||
std::optional<std::size_t> maskSpriteIndex;
|
||||
QPointF position;
|
||||
double hspeed{};
|
||||
double vspeed{};
|
||||
double gravity{};
|
||||
double gravityDirection{270.0};
|
||||
double imageIndex{};
|
||||
double imageSpeed{1.0};
|
||||
int depth{};
|
||||
bool visible{true};
|
||||
bool pendingDestroy{};
|
||||
std::array<int, 12> alarms;
|
||||
|
||||
void step();
|
||||
bool setAlarm(std::size_t index, int steps);
|
||||
bool applyAction(const Action &action);
|
||||
void executeActions(const ActionsContainer &actions, GameScene &scene,
|
||||
const ActionExecutor &executor, ObjectInstance *other = nullptr);
|
||||
void executeEvent(Object::EventType event, GameScene &scene,
|
||||
const ActionExecutor &executor, ObjectInstance *other = nullptr);
|
||||
void executeCollisionEvent(const QString &objectName, GameScene &scene,
|
||||
const ActionExecutor &executor, ObjectInstance &other);
|
||||
};
|
||||
|
||||
// Mutable runtime state shared by the OpenGL and Vulkan renderers.
|
||||
struct GameScene
|
||||
{
|
||||
struct Sprite
|
||||
{
|
||||
std::vector<std::size_t> frames;
|
||||
std::vector<QRect> collisionBounds;
|
||||
QPoint origin;
|
||||
};
|
||||
|
||||
using Instance = ObjectInstance;
|
||||
|
||||
explicit GameScene(const ProjectContainer &project);
|
||||
|
||||
std::size_t textureFor(const Instance &instance) const;
|
||||
QRectF collisionRect(const Instance &instance) const;
|
||||
std::vector<const Instance *> drawOrder() const;
|
||||
void start(const ActionExecutor &executor);
|
||||
void destroyInstance(Instance &instance);
|
||||
void advanceTo(qint64 elapsedMilliseconds);
|
||||
void draw();
|
||||
|
||||
QSize roomSize;
|
||||
QString caption;
|
||||
int roomSpeed{30};
|
||||
std::vector<QImage> textures;
|
||||
std::vector<Sprite> sprites;
|
||||
std::vector<Instance> instances;
|
||||
|
||||
private:
|
||||
qint64 m_stepsDone{};
|
||||
ActionExecutor m_executor;
|
||||
void removeDestroyed();
|
||||
};
|
||||
+178
-105
@@ -1,160 +1,233 @@
|
||||
#include "glgamewindow.h"
|
||||
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
#include <QKeyEvent>
|
||||
#include <QDebug>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
GlGameWindow::GlGameWindow(const ProjectContainer &project, const float &rotation, QWindow *parent) :
|
||||
#include <QKeyEvent>
|
||||
#include <QMatrix4x4>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QPlatformSurfaceEvent>
|
||||
|
||||
#include "gamescene.h"
|
||||
#include "projectcontainer.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
float x, y, z, u, v;
|
||||
};
|
||||
|
||||
constexpr std::array<Vertex, 4> quad {{
|
||||
{0.f, 0.f, 0.f, 0.f, 0.f},
|
||||
{0.f, 1.f, 0.f, 0.f, 1.f},
|
||||
{1.f, 0.f, 0.f, 1.f, 0.f},
|
||||
{1.f, 1.f, 0.f, 1.f, 1.f}
|
||||
}};
|
||||
|
||||
}
|
||||
|
||||
GlGameWindow::GlGameWindow(const ProjectContainer &project, const GameScene &scene, QWindow *parent) :
|
||||
QWindow{parent},
|
||||
m_project{project},
|
||||
m_rotation{rotation}
|
||||
m_scene{scene}
|
||||
{
|
||||
setSurfaceType(QWindow::OpenGLSurface);
|
||||
|
||||
setMinimumWidth(640);
|
||||
setMaximumWidth(640);
|
||||
setMinimumHeight(480);
|
||||
setMaximumHeight(480);
|
||||
resize(640, 480);
|
||||
if (!m_project.rooms.empty())
|
||||
{
|
||||
resize(m_scene.roomSize);
|
||||
if (!m_project.globalGameSettings.resizable)
|
||||
{
|
||||
setMinimumSize(m_scene.roomSize);
|
||||
setMaximumSize(m_scene.roomSize);
|
||||
}
|
||||
setTitle(m_scene.caption);
|
||||
}
|
||||
}
|
||||
|
||||
GlGameWindow::~GlGameWindow() = default;
|
||||
|
||||
void GlGameWindow::initialize()
|
||||
GlGameWindow::~GlGameWindow()
|
||||
{
|
||||
m_program = new QOpenGLShaderProgram{this};
|
||||
|
||||
|
||||
if (!m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/qtgameengine/opengl_shaders/color.vert")))
|
||||
qFatal("could not load vertex shader");
|
||||
if (!m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qtgameengine/opengl_shaders/color.frag")))
|
||||
qFatal("could not load fragment shader");
|
||||
|
||||
|
||||
m_program->link();
|
||||
m_posAttr = m_program->attributeLocation("posAttr");
|
||||
Q_ASSERT(m_posAttr != -1);
|
||||
m_colAttr = m_program->attributeLocation("colAttr");
|
||||
Q_ASSERT(m_colAttr != -1);
|
||||
m_matrixUniform = m_program->uniformLocation("matrix");
|
||||
Q_ASSERT(m_matrixUniform != -1);
|
||||
releaseResources();
|
||||
}
|
||||
|
||||
void GlGameWindow::render()
|
||||
bool GlGameWindow::initialize()
|
||||
{
|
||||
const qreal retinaScale = devicePixelRatio();
|
||||
glViewport(0, 0, width() * retinaScale, height() * retinaScale);
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
m_program = new QOpenGLShaderProgram;
|
||||
if (!m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
|
||||
QStringLiteral(":/qtgameengine/opengl_shaders/texture.vert")) ||
|
||||
!m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
|
||||
QStringLiteral(":/qtgameengine/opengl_shaders/texture.frag")) ||
|
||||
!m_program->link())
|
||||
{
|
||||
qWarning("Could not compile OpenGL sprite shaders: %s", qPrintable(m_program->log()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_vao.create())
|
||||
{
|
||||
qWarning("Could not create OpenGL vertex array");
|
||||
return false;
|
||||
}
|
||||
m_vao.bind();
|
||||
glGenBuffers(1, &m_vertexBuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad.data(), GL_STATIC_DRAW);
|
||||
|
||||
m_program->bind();
|
||||
|
||||
QMatrix4x4 matrix;
|
||||
matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
|
||||
matrix.translate(0, 0, -2);
|
||||
matrix.rotate(m_rotation, 0, 1, 0);
|
||||
|
||||
m_program->setUniformValue(m_matrixUniform, matrix);
|
||||
|
||||
static const GLfloat vertices[] {
|
||||
0.0f, 0.707f,
|
||||
-0.5f, -0.5f,
|
||||
0.5f, -0.5f
|
||||
};
|
||||
|
||||
static const GLfloat colors[] {
|
||||
1.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f
|
||||
};
|
||||
|
||||
glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
||||
glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors);
|
||||
|
||||
glEnableVertexAttribArray(m_posAttr);
|
||||
glEnableVertexAttribArray(m_colAttr);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
|
||||
glDisableVertexAttribArray(m_colAttr);
|
||||
glDisableVertexAttribArray(m_posAttr);
|
||||
|
||||
const int position = m_program->attributeLocation("position");
|
||||
const int texCoord = m_program->attributeLocation("texCoord");
|
||||
if (position < 0 || texCoord < 0)
|
||||
{
|
||||
qWarning("OpenGL sprite shader has missing attributes");
|
||||
return false;
|
||||
}
|
||||
glEnableVertexAttribArray(position);
|
||||
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
||||
reinterpret_cast<const void *>(offsetof(Vertex, x)));
|
||||
glEnableVertexAttribArray(texCoord);
|
||||
glVertexAttribPointer(texCoord, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
||||
reinterpret_cast<const void *>(offsetof(Vertex, u)));
|
||||
m_program->setUniformValue("tex", 0);
|
||||
m_program->release();
|
||||
m_vao.release();
|
||||
|
||||
m_textures.reserve(m_scene.textures.size());
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
for (const QImage &source : m_scene.textures)
|
||||
{
|
||||
const QImage image = source.convertToFormat(QImage::Format_RGBA8888);
|
||||
GLuint texture{};
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(),
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
m_textures.push_back(texture);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GlGameWindow::renderLater()
|
||||
void GlGameWindow::releaseResources()
|
||||
{
|
||||
requestUpdate();
|
||||
if (!m_context)
|
||||
return;
|
||||
|
||||
if (m_context->makeCurrent(this))
|
||||
{
|
||||
if (!m_textures.empty())
|
||||
glDeleteTextures(static_cast<GLsizei>(m_textures.size()), m_textures.data());
|
||||
if (m_vertexBuffer)
|
||||
glDeleteBuffers(1, &m_vertexBuffer);
|
||||
m_vao.destroy();
|
||||
delete m_program;
|
||||
m_program = nullptr;
|
||||
m_context->doneCurrent();
|
||||
}
|
||||
else
|
||||
delete m_program;
|
||||
|
||||
m_textures.clear();
|
||||
m_vertexBuffer = 0;
|
||||
m_program = nullptr;
|
||||
delete m_context;
|
||||
m_context = nullptr;
|
||||
}
|
||||
|
||||
void GlGameWindow::renderNow()
|
||||
{
|
||||
if (!isExposed())
|
||||
if (!isExposed() || m_project.rooms.empty())
|
||||
return;
|
||||
|
||||
bool needsInitialize = false;
|
||||
|
||||
if (!m_context)
|
||||
{
|
||||
m_context = new QOpenGLContext{this};
|
||||
m_context = new QOpenGLContext;
|
||||
m_context->setFormat(requestedFormat());
|
||||
m_context->create();
|
||||
|
||||
needsInitialize = true;
|
||||
if (!m_context->create())
|
||||
{
|
||||
qWarning("Could not create OpenGL context for the game window");
|
||||
delete m_context;
|
||||
m_context = nullptr;
|
||||
return;
|
||||
}
|
||||
if (!m_context->makeCurrent(this) || !initialize())
|
||||
{
|
||||
releaseResources();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!m_context->makeCurrent(this))
|
||||
return;
|
||||
|
||||
m_context->makeCurrent(this);
|
||||
const qreal scale = devicePixelRatio();
|
||||
glViewport(0, 0, qRound(width() * scale), qRound(height() * scale));
|
||||
glClearColor(1.f, 1.f, 1.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if (needsInitialize)
|
||||
QMatrix4x4 projection;
|
||||
projection.ortho(0.f, float(m_scene.roomSize.width()),
|
||||
float(m_scene.roomSize.height()), 0.f, -1.f, 1.f);
|
||||
m_program->bind();
|
||||
m_vao.bind();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
for (const GameScene::Instance *instance : m_scene.drawOrder())
|
||||
{
|
||||
initializeOpenGLFunctions();
|
||||
initialize();
|
||||
if (!instance->visible || !instance->spriteIndex)
|
||||
continue;
|
||||
const std::size_t textureIndex = m_scene.textureFor(*instance);
|
||||
const QImage &image = m_scene.textures.at(textureIndex);
|
||||
const QPoint &origin = m_scene.sprites.at(*instance->spriteIndex).origin;
|
||||
|
||||
QMatrix4x4 matrix = projection;
|
||||
matrix.translate(float(instance->position.x() - origin.x()),
|
||||
float(instance->position.y() - origin.y()));
|
||||
matrix.scale(float(image.width()), float(image.height()));
|
||||
m_program->setUniformValue("matrix", matrix);
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures.at(textureIndex));
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
m_vao.release();
|
||||
m_program->release();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
m_context->swapBuffers(this);
|
||||
|
||||
renderLater();
|
||||
}
|
||||
|
||||
bool GlGameWindow::event(QEvent *event)
|
||||
{
|
||||
switch (event->type())
|
||||
if (event->type() == QEvent::PlatformSurface &&
|
||||
static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() ==
|
||||
QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
|
||||
releaseResources();
|
||||
|
||||
if (event->type() == QEvent::UpdateRequest)
|
||||
{
|
||||
case QEvent::UpdateRequest:
|
||||
renderNow();
|
||||
return true;
|
||||
default:
|
||||
return QWindow::event(event);
|
||||
}
|
||||
return QWindow::event(event);
|
||||
}
|
||||
|
||||
void GlGameWindow::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
QWindow::keyPressEvent(event);
|
||||
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Escape:
|
||||
if (event->key() == Qt::Key_Escape && m_project.globalGameSettings.letEscEndGame)
|
||||
close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GlGameWindow::keyReleaseEvent(QKeyEvent *event)
|
||||
{
|
||||
QWindow::keyReleaseEvent(event);
|
||||
else
|
||||
QWindow::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void GlGameWindow::exposeEvent(QExposeEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QWindow::exposeEvent(event);
|
||||
if (isExposed())
|
||||
renderNow();
|
||||
}
|
||||
+14
-19
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWindow>
|
||||
#include <vector>
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QWindow>
|
||||
|
||||
class QOpenGLContext;
|
||||
class QOpenGLPaintDevice;
|
||||
class QOpenGLShaderProgram;
|
||||
struct GameScene;
|
||||
struct ProjectContainer;
|
||||
|
||||
class GlGameWindow : public QWindow, protected QOpenGLFunctions
|
||||
@@ -13,33 +16,25 @@ class GlGameWindow : public QWindow, protected QOpenGLFunctions
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GlGameWindow(const ProjectContainer &project, const float &m_rotation, QWindow *parent = nullptr);
|
||||
~GlGameWindow();
|
||||
explicit GlGameWindow(const ProjectContainer &project, const GameScene &scene, QWindow *parent = nullptr);
|
||||
~GlGameWindow() override;
|
||||
|
||||
void initialize();
|
||||
void render();
|
||||
|
||||
public slots:
|
||||
void renderLater();
|
||||
void renderNow();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void keyReleaseEvent(QKeyEvent *event) override;
|
||||
|
||||
void exposeEvent(QExposeEvent *event) override;
|
||||
|
||||
private:
|
||||
bool initialize();
|
||||
void releaseResources();
|
||||
|
||||
const ProjectContainer &m_project;
|
||||
|
||||
const GameScene &m_scene;
|
||||
QOpenGLContext *m_context{};
|
||||
QOpenGLPaintDevice *m_device{};
|
||||
|
||||
GLint m_posAttr = 0;
|
||||
GLint m_colAttr = 0;
|
||||
GLint m_matrixUniform = 0;
|
||||
|
||||
QOpenGLShaderProgram *m_program{};
|
||||
const float &m_rotation;
|
||||
QOpenGLVertexArrayObject m_vao;
|
||||
GLuint m_vertexBuffer{};
|
||||
std::vector<GLuint> m_textures;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
varying vec2 vTexCoord;
|
||||
uniform sampler2D tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(tex, vTexCoord);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
attribute vec3 position;
|
||||
attribute vec2 texCoord;
|
||||
varying vec2 vTexCoord;
|
||||
uniform mat4 matrix;
|
||||
|
||||
void main()
|
||||
{
|
||||
vTexCoord = texCoord;
|
||||
gl_Position = matrix * vec4(position, 1.0);
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
<qresource prefix="/qtgameengine">
|
||||
<file>opengl_shaders/color.frag</file>
|
||||
<file>opengl_shaders/color.vert</file>
|
||||
<file>opengl_shaders/texture.frag</file>
|
||||
<file>opengl_shaders/texture.vert</file>
|
||||
<file>vulkan_shader_modules/color_frag.spv</file>
|
||||
<file>vulkan_shader_modules/color_vert.spv</file>
|
||||
<file>vulkan_shader_modules/texture_frag.spv</file>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 v_texcoord;
|
||||
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 1) uniform sampler2D tex;
|
||||
layout(binding = 0) uniform sampler2D tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec4 position;
|
||||
layout(location = 0) in vec3 position;
|
||||
layout(location = 1) in vec2 texcoord;
|
||||
|
||||
layout(location = 0) out vec2 v_texcoord;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 mvp;
|
||||
} ubuf;
|
||||
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 matrix;
|
||||
} push;
|
||||
|
||||
void main()
|
||||
{
|
||||
v_texcoord = texcoord;
|
||||
gl_Position = ubuf.mvp * position;
|
||||
gl_Position = push.matrix * vec4(position, 1.0);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
+439
-747
File diff suppressed because it is too large.
Load diff
@@ -1,59 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <QVulkanWindow>
|
||||
#include <QImage>
|
||||
#include <vector>
|
||||
|
||||
struct ProjectContainer;
|
||||
#include <QImage>
|
||||
#include <QVulkanWindow>
|
||||
|
||||
struct GameScene;
|
||||
|
||||
class VulkanGameRenderer : public QVulkanWindowRenderer
|
||||
{
|
||||
public:
|
||||
VulkanGameRenderer(const ProjectContainer &project, const float &rotation, QVulkanWindow *w, bool msaa = false);
|
||||
VulkanGameRenderer(const GameScene &scene, QVulkanWindow *window);
|
||||
|
||||
void initResources() override;
|
||||
void initSwapChainResources() override;
|
||||
void releaseSwapChainResources() override;
|
||||
void releaseResources() override;
|
||||
|
||||
void startNextFrame() override;
|
||||
|
||||
protected:
|
||||
private:
|
||||
struct Texture
|
||||
{
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImage stagingImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
|
||||
QSize size;
|
||||
bool layoutPending{};
|
||||
bool stagingPending{};
|
||||
};
|
||||
|
||||
VkShaderModule createShader(const QString &name);
|
||||
bool createTextureFromFile(const QString &name);
|
||||
bool createTexture(const QImage &image);
|
||||
bool createTextureImage(const QSize &size, VkImage *image, VkDeviceMemory *mem,
|
||||
VkImageTiling tiling, VkImageUsageFlags usage, uint32_t memIndex);
|
||||
bool writeLinearImage(const QImage &img, VkImage image, VkDeviceMemory memory);
|
||||
void ensureTexture();
|
||||
bool createTexture(const QImage &image, Texture &texture);
|
||||
bool createTextureImage(const QSize &size, VkImageTiling tiling,
|
||||
VkImageUsageFlags usage, VkImage *image, VkDeviceMemory *memory);
|
||||
bool writeLinearImage(const QImage &image, VkImage target, VkDeviceMemory memory);
|
||||
void ensureTextures();
|
||||
|
||||
const GameScene &m_scene;
|
||||
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];
|
||||
QVulkanDeviceFunctions *m_devFuncs{};
|
||||
|
||||
VkBuffer m_vertexBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_vertexMemory = VK_NULL_HANDLE;
|
||||
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;
|
||||
|
||||
VkSampler m_sampler = VK_NULL_HANDLE;
|
||||
VkImage m_texImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_texMem = VK_NULL_HANDLE;
|
||||
bool m_texLayoutPending = false;
|
||||
VkImageView m_texView = VK_NULL_HANDLE;
|
||||
VkImage m_texStaging = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_texStagingMem = VK_NULL_HANDLE;
|
||||
bool m_texStagingPending = false;
|
||||
QSize m_texSize;
|
||||
VkFormat m_texFormat;
|
||||
|
||||
QMatrix4x4 m_proj;
|
||||
|
||||
const ProjectContainer &m_project;
|
||||
const float &m_rotation;
|
||||
VkFormat m_texFormat = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
std::vector<Texture> m_textures;
|
||||
QMatrix4x4 m_projection;
|
||||
};
|
||||
@@ -2,35 +2,39 @@
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "projectcontainer.h"
|
||||
#include "gamescene.h"
|
||||
#include "vulkangamerenderer.h"
|
||||
|
||||
VulkanGameWindow::VulkanGameWindow(const ProjectContainer &project, const float &rotation, QWindow *parent) :
|
||||
VulkanGameWindow::VulkanGameWindow(const ProjectContainer &project, const GameScene &scene, QWindow *parent) :
|
||||
QVulkanWindow{parent},
|
||||
m_project{project},
|
||||
m_rotation{rotation}
|
||||
m_scene{scene}
|
||||
{
|
||||
setMinimumWidth(640);
|
||||
setMaximumWidth(640);
|
||||
setMinimumHeight(480);
|
||||
setMaximumHeight(480);
|
||||
resize(640, 480);
|
||||
setPreferredColorFormats({VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM});
|
||||
if (!m_project.rooms.empty())
|
||||
{
|
||||
resize(m_scene.roomSize);
|
||||
if (!m_project.globalGameSettings.resizable)
|
||||
{
|
||||
setMinimumSize(m_scene.roomSize);
|
||||
setMaximumSize(m_scene.roomSize);
|
||||
}
|
||||
setTitle(m_scene.caption);
|
||||
}
|
||||
}
|
||||
|
||||
QVulkanWindowRenderer *VulkanGameWindow::createRenderer()
|
||||
{
|
||||
return new VulkanGameRenderer{m_project, m_rotation, this, true}; // try MSAA, when available
|
||||
return new VulkanGameRenderer{m_scene, this};
|
||||
}
|
||||
|
||||
void VulkanGameWindow::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
QVulkanWindow::keyPressEvent(event);
|
||||
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Escape:
|
||||
if (event->key() == Qt::Key_Escape && m_project.globalGameSettings.letEscEndGame)
|
||||
close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanGameWindow::keyReleaseEvent(QKeyEvent *event)
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#include <QVulkanWindowRenderer>
|
||||
|
||||
struct ProjectContainer;
|
||||
struct GameScene;
|
||||
|
||||
class VulkanGameWindow : public QVulkanWindow
|
||||
{
|
||||
public:
|
||||
explicit VulkanGameWindow(const ProjectContainer &project, const float &rotation, QWindow *parent = nullptr);
|
||||
explicit VulkanGameWindow(const ProjectContainer &project, const GameScene &scene, QWindow *parent = nullptr);
|
||||
|
||||
QVulkanWindowRenderer *createRenderer() override;
|
||||
|
||||
@@ -18,5 +19,5 @@ protected:
|
||||
|
||||
private:
|
||||
const ProjectContainer &m_project;
|
||||
const float &m_rotation;
|
||||
const GameScene &m_scene;
|
||||
};
|
||||
Reference in new issue
Block a user