stanford dragon rendering

This commit is contained in:
tylermurphy534 2022-09-18 21:20:51 -04:00
commit 8045b8ba04
41 changed files with 256370 additions and 0 deletions

1
.env.example Normal file
View file

@ -0,0 +1 @@
VULKAN_SDK=/home/tylerm/Documents/Vulkan

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.vscode
bin
.env
*.o

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "glfw"]
path = lib/glfw
url = https://github.com/glfw/glfw.git
[submodule "glm"]
path = lib/glm
url = https://github.com/g-truc/glm.git

59
Makefile Normal file
View file

@ -0,0 +1,59 @@
include .env
CC = clang++
INCFLAGS = -Isrc
INCFLAGS += -Iengine
INCFLAGS += -Ilib/glfw/include
INCFLAGS += -Ilib/glm
INCFLAGS += -L${VULKAN_SDK}/lib -lvulkan
CCFLAGS = -std=c++17 -O2 -g
CCFLAGS += $(INCFLAGS)
LDFLAGS = -lm
LDFLAGS += $(INCFLAGS)
LDFLAGS += lib/glfw/src/libglfw3.a
LDFLAGS += lib/glm/glm/libglm_static.a
SRC = $(shell find src -name "*.cpp")
SRC += $(shell find engine -name "*.cpp")
OBJ = $(SRC:.cpp=.o)
BIN = bin
VERTSRC = $(shell find ./res/shaders -type f -name "*.vert")
VERTOBJ = $(patsubst %.vert, %.vert.spv, $(VERTSRC))
FRAGSRC = $(shell find ./res/shaders -type f -name "*.frag")
FRAGOBJ = $(patsubst %.frag, %.frag.spv, $(FRAGSRC))
.PHONY: all clean
all: dirs libs shader build
libs:
cd lib/glfw && cmake . && make
cd lib/glm && cmake . && make
dirs:
mkdir -p ./$(BIN)
shader: $(VERTOBJ) $(FRAGOBJ)
run: build
$(RUN) $(BIN)/game
build: dirs shader ${OBJ}
${CC} -o $(BIN)/game $(filter %.o,$^) $(LDFLAGS)
%.spv: %
glslc -o $@ $<
%.o: %.cpp
$(CC) -o $@ -c $< $(CCFLAGS)
clean:
rm -rf app
rm -rf $(BIN) $(OBJ)
rm -rf res/shaders/*.spv
rm -rf lib/glfw/CMakeCache.txt

103
engine/xe_buffer.cpp Normal file
View file

@ -0,0 +1,103 @@
#include "xe_buffer.hpp"
#include <cassert>
#include <cstring>
namespace xe {
VkDeviceSize XeBuffer::getAlignment(VkDeviceSize instanceSize, VkDeviceSize minOffsetAlignment) {
if (minOffsetAlignment > 0) {
return (instanceSize + minOffsetAlignment - 1) & ~(minOffsetAlignment - 1);
}
return instanceSize;
}
XeBuffer::XeBuffer(
XeDevice &device,
VkDeviceSize instanceSize,
uint32_t instanceCount,
VkBufferUsageFlags usageFlags,
VkMemoryPropertyFlags memoryPropertyFlags,
VkDeviceSize minOffsetAlignment)
: xeDevice{device},
instanceSize{instanceSize},
instanceCount{instanceCount},
usageFlags{usageFlags},
memoryPropertyFlags{memoryPropertyFlags} {
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
bufferSize = alignmentSize * instanceCount;
device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer, memory);
}
XeBuffer::~XeBuffer() {
unmap();
vkDestroyBuffer(xeDevice.device(), buffer, nullptr);
vkFreeMemory(xeDevice.device(), memory, nullptr);
}
VkResult XeBuffer::map(VkDeviceSize size, VkDeviceSize offset) {
assert(buffer && memory && "Called map on buffer before create");
return vkMapMemory(xeDevice.device(), memory, offset, size, 0, &mapped);
}
void XeBuffer::unmap() {
if (mapped) {
vkUnmapMemory(xeDevice.device(), memory);
mapped = nullptr;
}
}
void XeBuffer::writeToBuffer(void *data, VkDeviceSize size, VkDeviceSize offset) {
assert(mapped && "Cannot copy to unmapped buffer");
if (size == VK_WHOLE_SIZE) {
memcpy(mapped, data, bufferSize);
} else {
char *memOffset = (char *)mapped;
memOffset += offset;
memcpy(memOffset, data, size);
}
}
VkResult XeBuffer::flush(VkDeviceSize size, VkDeviceSize offset) {
VkMappedMemoryRange mappedRange = {};
mappedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
mappedRange.memory = memory;
mappedRange.offset = offset;
mappedRange.size = size;
return vkFlushMappedMemoryRanges(xeDevice.device(), 1, &mappedRange);
}
VkResult XeBuffer::invalidate(VkDeviceSize size, VkDeviceSize offset) {
VkMappedMemoryRange mappedRange = {};
mappedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
mappedRange.memory = memory;
mappedRange.offset = offset;
mappedRange.size = size;
return vkInvalidateMappedMemoryRanges(xeDevice.device(), 1, &mappedRange);
}
VkDescriptorBufferInfo XeBuffer::descriptorInfo(VkDeviceSize size, VkDeviceSize offset) {
return VkDescriptorBufferInfo{
buffer,
offset,
size,
};
}
void XeBuffer::writeToIndex(void *data, int index) {
writeToBuffer(data, instanceSize, index * alignmentSize);
}
VkResult XeBuffer::flushIndex(int index) { return flush(alignmentSize, index * alignmentSize); }
VkDescriptorBufferInfo XeBuffer::descriptorInfoForIndex(int index) {
return descriptorInfo(alignmentSize, index * alignmentSize);
}
VkResult XeBuffer::invalidateIndex(int index) {
return invalidate(alignmentSize, index * alignmentSize);
}
}

60
engine/xe_buffer.hpp Normal file
View file

@ -0,0 +1,60 @@
#pragma once
#include "xe_device.hpp"
namespace xe {
class XeBuffer {
public:
XeBuffer(
XeDevice& device,
VkDeviceSize instanceSize,
uint32_t instanceCount,
VkBufferUsageFlags usageFlags,
VkMemoryPropertyFlags memoryPropertyFlags,
VkDeviceSize minOffsetAlignment = 1);
~XeBuffer();
XeBuffer(const XeBuffer&) = delete;
XeBuffer& operator=(const XeBuffer&) = delete;
VkResult map(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
void unmap();
void writeToBuffer(void* data, VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
VkResult flush(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
VkDescriptorBufferInfo descriptorInfo(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
VkResult invalidate(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
void writeToIndex(void* data, int index);
VkResult flushIndex(int index);
VkDescriptorBufferInfo descriptorInfoForIndex(int index);
VkResult invalidateIndex(int index);
VkBuffer getBuffer() const { return buffer; }
void* getMappedMemory() const { return mapped; }
uint32_t getInstanceCount() const { return instanceCount; }
VkDeviceSize getInstanceSize() const { return instanceSize; }
VkDeviceSize getAlignmentSize() const { return instanceSize; }
VkBufferUsageFlags getUsageFlags() const { return usageFlags; }
VkMemoryPropertyFlags getMemoryPropertyFlags() const { return memoryPropertyFlags; }
VkDeviceSize getBufferSize() const { return bufferSize; }
private:
static VkDeviceSize getAlignment(VkDeviceSize instanceSize, VkDeviceSize minOffsetAlignment);
XeDevice& xeDevice;
void* mapped = nullptr;
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkDeviceSize bufferSize;
uint32_t instanceCount;
VkDeviceSize instanceSize;
VkDeviceSize alignmentSize;
VkBufferUsageFlags usageFlags;
VkMemoryPropertyFlags memoryPropertyFlags;
};
}

79
engine/xe_camera.cpp Normal file
View file

@ -0,0 +1,79 @@
#include "xe_camera.hpp"
#include <cassert>
#include <limits>
namespace xe {
void XeCamera::setOrthographicProjection(
float left, float right, float top, float bottom, float near, float far) {
projectionMatrix = glm::mat4{1.0f};
projectionMatrix[0][0] = 2.f / (right - left);
projectionMatrix[1][1] = 2.f / (bottom - top);
projectionMatrix[2][2] = 1.f / (far - near);
projectionMatrix[3][0] = -(right + left) / (right - left);
projectionMatrix[3][1] = -(bottom + top) / (bottom - top);
projectionMatrix[3][2] = -near / (far - near);
}
void XeCamera::setPerspectiveProjection(float fovy, float aspect, float near, float far) {
assert(glm::abs(aspect - std::numeric_limits<float>::epsilon()) > 0.0f);
const float tanHalfFovy = tan(fovy / 2.f);
projectionMatrix = glm::mat4{0.0f};
projectionMatrix[0][0] = 1.f / (aspect * tanHalfFovy);
projectionMatrix[1][1] = 1.f / (tanHalfFovy);
projectionMatrix[2][2] = far / (far - near);
projectionMatrix[2][3] = 1.f;
projectionMatrix[3][2] = -(far * near) / (far - near);
}
void XeCamera::setViewDirection(glm::vec3 position, glm::vec3 direction, glm::vec3 up) {
const glm::vec3 w{glm::normalize(direction)};
const glm::vec3 u{glm::normalize(glm::cross(w, up))};
const glm::vec3 v{glm::cross(w, u)};
viewMatrix = glm::mat4{1.f};
viewMatrix[0][0] = u.x;
viewMatrix[1][0] = u.y;
viewMatrix[2][0] = u.z;
viewMatrix[0][1] = v.x;
viewMatrix[1][1] = v.y;
viewMatrix[2][1] = v.z;
viewMatrix[0][2] = w.x;
viewMatrix[1][2] = w.y;
viewMatrix[2][2] = w.z;
viewMatrix[3][0] = -glm::dot(u, position);
viewMatrix[3][1] = -glm::dot(v, position);
viewMatrix[3][2] = -glm::dot(w, position);
}
void XeCamera::setViewTarget(glm::vec3 position, glm::vec3 target, glm::vec3 up) {
setViewDirection(position, target - position, up);
}
void XeCamera::setViewYXZ(glm::vec3 position, glm::vec3 rotation) {
const float c3 = glm::cos(rotation.z);
const float s3 = glm::sin(rotation.z);
const float c2 = glm::cos(rotation.x);
const float s2 = glm::sin(rotation.x);
const float c1 = glm::cos(rotation.y);
const float s1 = glm::sin(rotation.y);
const glm::vec3 u{(c1 * c3 + s1 * s2 * s3), (c2 * s3), (c1 * s2 * s3 - c3 * s1)};
const glm::vec3 v{(c3 * s1 * s2 - c1 * s3), (c2 * c3), (c1 * c3 * s2 + s1 * s3)};
const glm::vec3 w{(c2 * s1), (-s2), (c1 * c2)};
viewMatrix = glm::mat4{1.f};
viewMatrix[0][0] = u.x;
viewMatrix[1][0] = u.y;
viewMatrix[2][0] = u.z;
viewMatrix[0][1] = v.x;
viewMatrix[1][1] = v.y;
viewMatrix[2][1] = v.z;
viewMatrix[0][2] = w.x;
viewMatrix[1][2] = w.y;
viewMatrix[2][2] = w.z;
viewMatrix[3][0] = -glm::dot(u, position);
viewMatrix[3][1] = -glm::dot(v, position);
viewMatrix[3][2] = -glm::dot(w, position);
}
}

37
engine/xe_camera.hpp Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include <glm/fwd.hpp>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
namespace xe {
class XeCamera {
public:
void setOrthographicProjection(
float left, float right, float top, float bottom, float near, float far);
void setPerspectiveProjection(
float fovy, float aspect, float near, float far);
void setViewDirection(
glm::vec3 position, glm::vec3 direction, glm::vec3 up = glm::vec3(0.f, -1.f, 0.f));
void setViewTarget(
glm::vec3 position, glm::vec3 target, glm::vec3 up = glm::vec3(0.f, -1.f, 0.f));
void setViewYXZ(
glm::vec3 position, glm::vec3 rotation);
const glm::mat4& getProjection() const { return projectionMatrix; }
const glm::mat4& getView() const { return viewMatrix; }
private:
glm::mat4 projectionMatrix{1.f};
glm::mat4 viewMatrix{1.f};
};
}

183
engine/xe_descriptors.cpp Normal file
View file

@ -0,0 +1,183 @@
#include "xe_descriptors.hpp"
#include <cassert>
#include <stdexcept>
namespace xe {
XeDescriptorSetLayout::Builder &XeDescriptorSetLayout::Builder::addBinding(
uint32_t binding,
VkDescriptorType descriptorType,
VkShaderStageFlags stageFlags,
uint32_t count) {
assert(bindings.count(binding) == 0 && "Binding already in use");
VkDescriptorSetLayoutBinding layoutBinding{};
layoutBinding.binding = binding;
layoutBinding.descriptorType = descriptorType;
layoutBinding.descriptorCount = count;
layoutBinding.stageFlags = stageFlags;
bindings[binding] = layoutBinding;
return *this;
}
std::unique_ptr<XeDescriptorSetLayout> XeDescriptorSetLayout::Builder::build() const {
return std::make_unique<XeDescriptorSetLayout>(xeDevice, bindings);
}
XeDescriptorSetLayout::XeDescriptorSetLayout(
XeDevice &xeDevice, std::unordered_map<uint32_t, VkDescriptorSetLayoutBinding> bindings)
: xeDevice{xeDevice}, bindings{bindings} {
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings{};
for (auto kv : bindings) {
setLayoutBindings.push_back(kv.second);
}
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{};
descriptorSetLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorSetLayoutInfo.bindingCount = static_cast<uint32_t>(setLayoutBindings.size());
descriptorSetLayoutInfo.pBindings = setLayoutBindings.data();
if (vkCreateDescriptorSetLayout(
xeDevice.device(),
&descriptorSetLayoutInfo,
nullptr,
&descriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create descriptor set layout!");
}
}
XeDescriptorSetLayout::~XeDescriptorSetLayout() {
vkDestroyDescriptorSetLayout(xeDevice.device(), descriptorSetLayout, nullptr);
}
XeDescriptorPool::Builder &XeDescriptorPool::Builder::addPoolSize(
VkDescriptorType descriptorType, uint32_t count) {
poolSizes.push_back({descriptorType, count});
return *this;
}
XeDescriptorPool::Builder &XeDescriptorPool::Builder::setPoolFlags(
VkDescriptorPoolCreateFlags flags) {
poolFlags = flags;
return *this;
}
XeDescriptorPool::Builder &XeDescriptorPool::Builder::setMaxSets(uint32_t count) {
maxSets = count;
return *this;
}
std::unique_ptr<XeDescriptorPool> XeDescriptorPool::Builder::build() const {
return std::make_unique<XeDescriptorPool>(xeDevice, maxSets, poolFlags, poolSizes);
}
XeDescriptorPool::XeDescriptorPool(
XeDevice &xeDevice,
uint32_t maxSets,
VkDescriptorPoolCreateFlags poolFlags,
const std::vector<VkDescriptorPoolSize> &poolSizes)
: xeDevice{xeDevice} {
VkDescriptorPoolCreateInfo descriptorPoolInfo{};
descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
descriptorPoolInfo.pPoolSizes = poolSizes.data();
descriptorPoolInfo.maxSets = maxSets;
descriptorPoolInfo.flags = poolFlags;
if (vkCreateDescriptorPool(xeDevice.device(), &descriptorPoolInfo, nullptr, &descriptorPool) !=
VK_SUCCESS) {
throw std::runtime_error("failed to create descriptor pool!");
}
}
XeDescriptorPool::~XeDescriptorPool() {
vkDestroyDescriptorPool(xeDevice.device(), descriptorPool, nullptr);
}
bool XeDescriptorPool::allocateDescriptor(
const VkDescriptorSetLayout descriptorSetLayout, VkDescriptorSet &descriptor) const {
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.pSetLayouts = &descriptorSetLayout;
allocInfo.descriptorSetCount = 1;
if (vkAllocateDescriptorSets(xeDevice.device(), &allocInfo, &descriptor) != VK_SUCCESS) {
return false;
}
return true;
}
void XeDescriptorPool::freeDescriptors(std::vector<VkDescriptorSet> &descriptors) const {
vkFreeDescriptorSets(
xeDevice.device(),
descriptorPool,
static_cast<uint32_t>(descriptors.size()),
descriptors.data());
}
void XeDescriptorPool::resetPool() {
vkResetDescriptorPool(xeDevice.device(), descriptorPool, 0);
}
XeDescriptorWriter::XeDescriptorWriter(XeDescriptorSetLayout &setLayout, XeDescriptorPool &pool)
: setLayout{setLayout}, pool{pool} {}
XeDescriptorWriter &XeDescriptorWriter::writeBuffer(
uint32_t binding, VkDescriptorBufferInfo *bufferInfo) {
assert(setLayout.bindings.count(binding) == 1 && "Layout does not contain specified binding");
auto &bindingDescription = setLayout.bindings[binding];
assert(
bindingDescription.descriptorCount == 1 &&
"Binding single descriptor info, but binding expects multiple");
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.descriptorType = bindingDescription.descriptorType;
write.dstBinding = binding;
write.pBufferInfo = bufferInfo;
write.descriptorCount = 1;
writes.push_back(write);
return *this;
}
XeDescriptorWriter &XeDescriptorWriter::writeImage(
uint32_t binding, VkDescriptorImageInfo *imageInfo) {
assert(setLayout.bindings.count(binding) == 1 && "Layout does not contain specified binding");
auto &bindingDescription = setLayout.bindings[binding];
assert(
bindingDescription.descriptorCount == 1 &&
"Binding single descriptor info, but binding expects multiple");
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.descriptorType = bindingDescription.descriptorType;
write.dstBinding = binding;
write.pImageInfo = imageInfo;
write.descriptorCount = 1;
writes.push_back(write);
return *this;
}
bool XeDescriptorWriter::build(VkDescriptorSet &set) {
bool success = pool.allocateDescriptor(setLayout.getDescriptorSetLayout(), set);
if (!success) {
return false;
}
overwrite(set);
return true;
}
void XeDescriptorWriter::overwrite(VkDescriptorSet &set) {
for (auto &write : writes) {
write.dstSet = set;
}
vkUpdateDescriptorSets(pool.xeDevice.device(), writes.size(), writes.data(), 0, nullptr);
}
}

104
engine/xe_descriptors.hpp Normal file
View file

@ -0,0 +1,104 @@
#pragma once
#include "xe_device.hpp"
// std
#include <memory>
#include <unordered_map>
#include <vector>
namespace xe {
class XeDescriptorSetLayout {
public:
class Builder {
public:
Builder(XeDevice &xeDevice) : xeDevice{xeDevice} {}
Builder &addBinding(
uint32_t binding,
VkDescriptorType descriptorType,
VkShaderStageFlags stageFlags,
uint32_t count = 1);
std::unique_ptr<XeDescriptorSetLayout> build() const;
private:
XeDevice &xeDevice;
std::unordered_map<uint32_t, VkDescriptorSetLayoutBinding> bindings{};
};
XeDescriptorSetLayout(
XeDevice &xeDevice, std::unordered_map<uint32_t, VkDescriptorSetLayoutBinding> bindings);
~XeDescriptorSetLayout();
XeDescriptorSetLayout(const XeDescriptorSetLayout &) = delete;
XeDescriptorSetLayout &operator=(const XeDescriptorSetLayout &) = delete;
VkDescriptorSetLayout getDescriptorSetLayout() const { return descriptorSetLayout; }
private:
XeDevice &xeDevice;
VkDescriptorSetLayout descriptorSetLayout;
std::unordered_map<uint32_t, VkDescriptorSetLayoutBinding> bindings;
friend class XeDescriptorWriter;
};
class XeDescriptorPool {
public:
class Builder {
public:
Builder(XeDevice &xeDevice) : xeDevice{xeDevice} {}
Builder &addPoolSize(VkDescriptorType descriptorType, uint32_t count);
Builder &setPoolFlags(VkDescriptorPoolCreateFlags flags);
Builder &setMaxSets(uint32_t count);
std::unique_ptr<XeDescriptorPool> build() const;
private:
XeDevice &xeDevice;
std::vector<VkDescriptorPoolSize> poolSizes{};
uint32_t maxSets = 1000;
VkDescriptorPoolCreateFlags poolFlags = 0;
};
XeDescriptorPool(
XeDevice &xeDevice,
uint32_t maxSets,
VkDescriptorPoolCreateFlags poolFlags,
const std::vector<VkDescriptorPoolSize> &poolSizes);
~XeDescriptorPool();
XeDescriptorPool(const XeDescriptorPool &) = delete;
XeDescriptorPool &operator=(const XeDescriptorPool &) = delete;
bool allocateDescriptor(
const VkDescriptorSetLayout descriptorSetLayout, VkDescriptorSet &descriptor) const;
void freeDescriptors(std::vector<VkDescriptorSet> &descriptors) const;
void resetPool();
private:
XeDevice &xeDevice;
VkDescriptorPool descriptorPool;
friend class XeDescriptorWriter;
};
class XeDescriptorWriter {
public:
XeDescriptorWriter(XeDescriptorSetLayout &setLayout, XeDescriptorPool &pool);
XeDescriptorWriter &writeBuffer(uint32_t binding, VkDescriptorBufferInfo *bufferInfo);
XeDescriptorWriter &writeImage(uint32_t binding, VkDescriptorImageInfo *imageInfo);
bool build(VkDescriptorSet &set);
void overwrite(VkDescriptorSet &set);
private:
XeDescriptorSetLayout &setLayout;
XeDescriptorPool &pool;
std::vector<VkWriteDescriptorSet> writes;
};
}

534
engine/xe_device.cpp Executable file
View file

@ -0,0 +1,534 @@
#include "xe_device.hpp"
// std headers
#include <cstring>
#include <iostream>
#include <set>
#include <unordered_set>
namespace xe {
// local callback functions
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,
void *pUserData) {
std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
return VK_FALSE;
}
VkResult CreateDebugUtilsMessengerEXT(
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkDebugUtilsMessengerEXT *pDebugMessenger) {
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
instance,
"vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
void DestroyDebugUtilsMessengerEXT(
VkInstance instance,
VkDebugUtilsMessengerEXT debugMessenger,
const VkAllocationCallbacks *pAllocator) {
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
instance,
"vkDestroyDebugUtilsMessengerEXT");
if (func != nullptr) {
func(instance, debugMessenger, pAllocator);
}
}
// class member functions
XeDevice::XeDevice(XeWindow &window) : window{window} {
createInstance();
setupDebugMessenger();
createSurface();
pickPhysicalDevice();
createLogicalDevice();
createCommandPool();
}
XeDevice::~XeDevice() {
vkDestroyCommandPool(device_, commandPool, nullptr);
vkDestroyDevice(device_, nullptr);
if (enableValidationLayers) {
DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
}
vkDestroySurfaceKHR(instance, surface_, nullptr);
vkDestroyInstance(instance, nullptr);
}
void XeDevice::createInstance() {
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("validation layers requested, but not available!");
}
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "LittleVulkanEngine App";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
populateDebugMessengerCreateInfo(debugCreateInfo);
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT *)&debugCreateInfo;
} else {
createInfo.enabledLayerCount = 0;
createInfo.pNext = nullptr;
}
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
hasGflwRequiredInstanceExtensions();
}
void XeDevice::pickPhysicalDevice() {
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) {
throw std::runtime_error("failed to find GPUs with Vulkan support!");
}
std::cout << "Device count: " << deviceCount << std::endl;
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
for (const auto &device : devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
break;
}
}
if (physicalDevice == VK_NULL_HANDLE) {
throw std::runtime_error("failed to find a suitable GPU!");
}
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
std::cout << "physical device: " << properties.deviceName << std::endl;
}
void XeDevice::createLogicalDevice() {
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily};
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkPhysicalDeviceFeatures deviceFeatures = {};
deviceFeatures.samplerAnisotropy = VK_TRUE;
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.pEnabledFeatures = &deviceFeatures;
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
// might not really be necessary anymore because device specific validation layers
// have been deprecated
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device_) != VK_SUCCESS) {
throw std::runtime_error("failed to create logical device!");
}
vkGetDeviceQueue(device_, indices.graphicsFamily, 0, &graphicsQueue_);
vkGetDeviceQueue(device_, indices.presentFamily, 0, &presentQueue_);
}
void XeDevice::createCommandPool() {
QueueFamilyIndices queueFamilyIndices = findPhysicalQueueFamilies();
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily;
poolInfo.flags =
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
if (vkCreateCommandPool(device_, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool!");
}
}
void XeDevice::createSurface() { window.createWindowSurface(instance, &surface_); }
bool XeDevice::isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
VkPhysicalDeviceFeatures supportedFeatures;
vkGetPhysicalDeviceFeatures(device, &supportedFeatures);
return indices.isComplete() && extensionsSupported && swapChainAdequate &&
supportedFeatures.samplerAnisotropy;
}
void XeDevice::populateDebugMessengerCreateInfo(
VkDebugUtilsMessengerCreateInfoEXT &createInfo) {
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr; // Optional
}
void XeDevice::setupDebugMessenger() {
if (!enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT createInfo;
populateDebugMessengerCreateInfo(createInfo);
if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!");
}
}
bool XeDevice::checkValidationLayerSupport() {
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
for (const char *layerName : validationLayers) {
bool layerFound = false;
for (const auto &layerProperties : availableLayers) {
if (strcmp(layerName, layerProperties.layerName) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
}
std::vector<const char *> XeDevice::getRequiredExtensions() {
uint32_t glfwExtensionCount = 0;
const char **glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char *> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
void XeDevice::hasGflwRequiredInstanceExtensions() {
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
std::cout << "available extensions:" << std::endl;
std::unordered_set<std::string> available;
for (const auto &extension : extensions) {
std::cout << "\t" << extension.extensionName << std::endl;
available.insert(extension.extensionName);
}
std::cout << "required extensions:" << std::endl;
auto requiredExtensions = getRequiredExtensions();
for (const auto &required : requiredExtensions) {
std::cout << "\t" << required << std::endl;
if (available.find(required) == available.end()) {
throw std::runtime_error("Missing required glfw extension");
}
}
}
bool XeDevice::checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(
device,
nullptr,
&extensionCount,
availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
for (const auto &extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
QueueFamilyIndices XeDevice::findQueueFamilies(VkPhysicalDevice device) {
QueueFamilyIndices indices;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
int i = 0;
for (const auto &queueFamily : queueFamilies) {
if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
indices.graphicsFamilyHasValue = true;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface_, &presentSupport);
if (queueFamily.queueCount > 0 && presentSupport) {
indices.presentFamily = i;
indices.presentFamilyHasValue = true;
}
if (indices.isComplete()) {
break;
}
i++;
}
return indices;
}
SwapChainSupportDetails XeDevice::querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface_, &details.capabilities);
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, details.formats.data());
}
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(
device,
surface_,
&presentModeCount,
details.presentModes.data());
}
return details;
}
VkFormat XeDevice::findSupportedFormat(
const std::vector<VkFormat> &candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
for (VkFormat format : candidates) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
return format;
} else if (
tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
return format;
}
}
throw std::runtime_error("failed to find supported format!");
}
uint32_t XeDevice::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) &&
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
throw std::runtime_error("failed to find suitable memory type!");
}
void XeDevice::createBuffer(
VkDeviceSize size,
VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties,
VkBuffer &buffer,
VkDeviceMemory &bufferMemory) {
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(device_, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
throw std::runtime_error("failed to create vertex buffer!");
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device_, buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
if (vkAllocateMemory(device_, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate vertex buffer memory!");
}
vkBindBufferMemory(device_, buffer, bufferMemory, 0);
}
VkCommandBuffer XeDevice::beginSingleTimeCommands() {
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(device_, &allocInfo, &commandBuffer);
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
return commandBuffer;
}
void XeDevice::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(graphicsQueue_, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue_);
vkFreeCommandBuffers(device_, commandPool, 1, &commandBuffer);
}
void XeDevice::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
VkCommandBuffer commandBuffer = beginSingleTimeCommands();
VkBufferCopy copyRegion{};
copyRegion.srcOffset = 0; // Optional
copyRegion.dstOffset = 0; // Optional
copyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
endSingleTimeCommands(commandBuffer);
}
void XeDevice::copyBufferToImage(
VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t layerCount) {
VkCommandBuffer commandBuffer = beginSingleTimeCommands();
VkBufferImageCopy region{};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = layerCount;
region.imageOffset = {0, 0, 0};
region.imageExtent = {width, height, 1};
vkCmdCopyBufferToImage(
commandBuffer,
buffer,
image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&region);
endSingleTimeCommands(commandBuffer);
}
void XeDevice::createImageWithInfo(
const VkImageCreateInfo &imageInfo,
VkMemoryPropertyFlags properties,
VkImage &image,
VkDeviceMemory &imageMemory) {
if (vkCreateImage(device_, &imageInfo, nullptr, &image) != VK_SUCCESS) {
throw std::runtime_error("failed to create image!");
}
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(device_, image, &memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
if (vkAllocateMemory(device_, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate image memory!");
}
if (vkBindImageMemory(device_, image, imageMemory, 0) != VK_SUCCESS) {
throw std::runtime_error("failed to bind image memory!");
}
}
}

105
engine/xe_device.hpp Executable file
View file

@ -0,0 +1,105 @@
#pragma once
#include "xe_window.hpp"
#include <string>
#include <vector>
namespace xe {
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
struct QueueFamilyIndices {
uint32_t graphicsFamily;
uint32_t presentFamily;
bool graphicsFamilyHasValue = false;
bool presentFamilyHasValue = false;
bool isComplete() { return graphicsFamilyHasValue && presentFamilyHasValue; }
};
class XeDevice {
public:
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
XeDevice(XeWindow &window);
~XeDevice();
XeDevice(const XeDevice &) = delete;
void operator=(const XeDevice &) = delete;
XeDevice(XeDevice &&) = delete;
XeDevice &operator=(XeDevice &&) = delete;
VkCommandPool getCommandPool() { return commandPool; }
VkDevice device() { return device_; }
VkSurfaceKHR surface() { return surface_; }
VkQueue graphicsQueue() { return graphicsQueue_; }
VkQueue presentQueue() { return presentQueue_; }
SwapChainSupportDetails getSwapChainSupport() { return querySwapChainSupport(physicalDevice); }
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
QueueFamilyIndices findPhysicalQueueFamilies() { return findQueueFamilies(physicalDevice); }
VkFormat findSupportedFormat(
const std::vector<VkFormat> &candidates, VkImageTiling tiling, VkFormatFeatureFlags features);
void createBuffer(
VkDeviceSize size,
VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties,
VkBuffer &buffer,
VkDeviceMemory &bufferMemory);
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer commandBuffer);
void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);
void copyBufferToImage(
VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t layerCount);
void createImageWithInfo(
const VkImageCreateInfo &imageInfo,
VkMemoryPropertyFlags properties,
VkImage &image,
VkDeviceMemory &imageMemory);
VkPhysicalDeviceProperties properties;
private:
void createInstance();
void setupDebugMessenger();
void createSurface();
void pickPhysicalDevice();
void createLogicalDevice();
void createCommandPool();
bool isDeviceSuitable(VkPhysicalDevice device);
std::vector<const char *> getRequiredExtensions();
bool checkValidationLayerSupport();
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT &createInfo);
void hasGflwRequiredInstanceExtensions();
bool checkDeviceExtensionSupport(VkPhysicalDevice device);
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);
VkInstance instance;
VkDebugUtilsMessengerEXT debugMessenger;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
XeWindow &window;
VkCommandPool commandPool;
VkDevice device_;
VkSurfaceKHR surface_;
VkQueue graphicsQueue_;
VkQueue presentQueue_;
const std::vector<const char *> validationLayers = {"VK_LAYER_KHRONOS_validation"};
const std::vector<const char *> deviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_MAINTENANCE1_EXTENSION_NAME};
};
}

17
engine/xe_frame_info.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "xe_camera.hpp"
#include <vulkan/vulkan.h>
namespace xe {
struct XeFrameInfo {
int frameIndex;
float frameTime;
VkCommandBuffer commandBuffer;
XeCamera &camera;
VkDescriptorSet globalDescriptorSet;
};
}

64
engine/xe_game_object.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "xe_game_object.hpp"
namespace xe {
glm::mat4 TransformComponent::mat4() {
const float c3 = glm::cos(rotation.z);
const float s3 = glm::sin(rotation.z);
const float c2 = glm::cos(rotation.x);
const float s2 = glm::sin(rotation.x);
const float c1 = glm::cos(rotation.y);
const float s1 = glm::sin(rotation.y);
return glm::mat4{
{
scale.x * (c1 * c3 + s1 * s2 * s3),
scale.x * (c2 * s3),
scale.x * (c1 * s2 * s3 - c3 * s1),
0.0f,
},
{
scale.y * (c3 * s1 * s2 - c1 * s3),
scale.y * (c2 * c3),
scale.y * (c1 * c3 * s2 + s1 * s3),
0.0f,
},
{
scale.z * (c2 * s1),
scale.z * (-s2),
scale.z * (c1 * c2),
0.0f,
},
{translation.x, translation.y, translation.z, 1.0f}
};
}
glm::mat3 TransformComponent::normalMatrix() {
const float c3 = glm::cos(rotation.z);
const float s3 = glm::sin(rotation.z);
const float c2 = glm::cos(rotation.x);
const float s2 = glm::sin(rotation.x);
const float c1 = glm::cos(rotation.y);
const float s1 = glm::sin(rotation.y);
const glm::vec3 invScale = 1.0f/ scale;
return glm::mat3{
{
invScale.x * (c1 * c3 + s1 * s2 * s3),
invScale.x * (c2 * s3),
invScale.x * (c1 * s2 * s3 - c3 * s1),
},
{
invScale.y * (c3 * s1 * s2 - c1 * s3),
invScale.y * (c2 * c3),
invScale.y * (c1 * c3 * s2 + s1 * s3),
},
{
invScale.z * (c2 * s1),
invScale.z * (-s2),
invScale.z * (c1 * c2),
}
};
}
}

50
engine/xe_game_object.hpp Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include "xe_model.hpp"
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/fwd.hpp>
#include <memory>
namespace xe {
struct TransformComponent {
glm::vec3 translation{};
glm::vec3 scale{1.f, 1.f, 1.f};
glm::vec3 rotation{};
glm::mat4 mat4();
glm::mat3 normalMatrix();
};
class XeGameObject {
public:
using id_t = unsigned int;
static XeGameObject createGameObject() {
static id_t currentId = 0;
return XeGameObject(currentId++);
}
XeGameObject(const XeGameObject &) = delete;
XeGameObject &operator=(const XeGameObject &) = delete;
XeGameObject(XeGameObject&&) = default;
XeGameObject &operator=(XeGameObject &&) = default;
id_t getId() { return id; }
std::shared_ptr<XeModel> model{};
glm::vec3 color{};
TransformComponent transform;
private:
XeGameObject(id_t objId) : id{objId} {}
id_t id;
};
}

193
engine/xe_model.cpp Normal file
View file

@ -0,0 +1,193 @@
#include "xe_model.hpp"
#include "xe_utils.hpp"
#define TINYOBJLOADER_IMPLEMENTATION
#include "xe_obj_loader.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>
#include <cassert>
#include <cstring>
#include <unordered_map>
namespace std {
template<>
struct hash<xe::XeModel::Vertex> {
size_t operator()(xe::XeModel::Vertex const &vertex) const {
size_t seed = 0;
xe::hashCombine(seed, vertex.position, vertex.normal, vertex.uv);
return seed;
}
};
}
namespace xe {
XeModel::XeModel(XeDevice &device, const XeModel::Builder &builder) : xeDevice{device} {
createVertexBuffers(builder.vertices);
createIndexBuffers(builder.indices);
}
XeModel::~XeModel() {}
std::unique_ptr<XeModel> XeModel::createModelFromFile(XeDevice &device, const std::string &filepath) {
Builder builder{};
builder.loadModel(filepath);
return std::make_unique<XeModel>(device, builder);
}
void XeModel::createVertexBuffers(const std::vector<Vertex> &vertices) {
vertexCount = static_cast<uint32_t>(vertices.size());
assert(vertexCount >= 3 && "Vertex count must be atleast 3");
VkDeviceSize bufferSize = sizeof(vertices[0]) * vertexCount;
uint32_t vertexSize = sizeof(vertices[0]);
XeBuffer stagingBuffer {
xeDevice,
vertexSize,
vertexCount,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
};
stagingBuffer.map();
stagingBuffer.writeToBuffer((void *)vertices.data());
vertexBuffer = std::make_unique<XeBuffer>(
xeDevice,
vertexSize,
vertexCount,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
xeDevice.copyBuffer(stagingBuffer.getBuffer(), vertexBuffer->getBuffer(), bufferSize);
}
void XeModel::createIndexBuffers(const std::vector<uint32_t> &indices) {
indexCount = static_cast<uint32_t>(indices.size());
hasIndexBuffer = indexCount > 0;
if (!hasIndexBuffer) {
return;
}
VkDeviceSize bufferSize = sizeof(indices[0]) * indexCount;
uint32_t indexSize = sizeof(indices[0]);
XeBuffer stagingBuffer {
xeDevice,
indexSize,
indexCount,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
};
stagingBuffer.map();
stagingBuffer.writeToBuffer((void *)indices.data());
indexBuffer = std::make_unique<XeBuffer>(
xeDevice,
indexSize,
indexCount,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
xeDevice.copyBuffer(stagingBuffer.getBuffer(), indexBuffer->getBuffer(), bufferSize);
}
void XeModel::bind(VkCommandBuffer commandBuffer) {
VkBuffer buffers[] = {vertexBuffer->getBuffer()};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, buffers, offsets);
if (hasIndexBuffer) {
vkCmdBindIndexBuffer(commandBuffer, indexBuffer->getBuffer(), 0, VK_INDEX_TYPE_UINT32);
}
}
void XeModel::draw(VkCommandBuffer commandBuffer) {
if (hasIndexBuffer) {
vkCmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0);
} else {
vkCmdDraw(commandBuffer, vertexCount, 1, 0, 0);
}
}
std::vector<VkVertexInputBindingDescription> XeModel::Vertex::getBindingDescriptions() {
std::vector<VkVertexInputBindingDescription> bindingDescriptions(1);
bindingDescriptions[0].binding = 0;
bindingDescriptions[0].stride = sizeof(Vertex);
bindingDescriptions[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingDescriptions;
}
std::vector<VkVertexInputAttributeDescription> XeModel::Vertex::getAttributeDescriptions() {
std::vector<VkVertexInputAttributeDescription> attributeDescptions{};
attributeDescptions.push_back({0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, position)});
attributeDescptions.push_back({1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, color)});
attributeDescptions.push_back({2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)});
attributeDescptions.push_back({3, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)});
return attributeDescptions;
}
void XeModel::Builder::loadModel(const std::string &filepath) {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn, err;
if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filepath.c_str())) {
throw std::runtime_error(warn + err);
}
vertices.clear();
indices.clear();
std::unordered_map<Vertex, uint32_t> uniqueVertices{};
for (const auto &shape : shapes) {
for (const auto &index : shape.mesh.indices) {
Vertex vertex{};
if(index.vertex_index >= 0) {
vertex.position = {
attrib.vertices[3 * index.vertex_index + 0],
attrib.vertices[3 * index.vertex_index + 1],
attrib.vertices[3 * index.vertex_index + 2]
};
vertex.color = {
attrib.colors[3 * index.vertex_index + 0],
attrib.colors[3 * index.vertex_index + 1],
attrib.colors[3 * index.vertex_index + 2]
};
}
if(index.normal_index >= 0) {
vertex.normal = {
attrib.normals[3 * index.normal_index + 0],
attrib.normals[3 * index.normal_index + 1],
attrib.normals[3 * index.normal_index + 2]
};
}
if(index.texcoord_index >= 0) {
vertex.uv = {
attrib.texcoords[2 * index.texcoord_index + 0],
attrib.texcoords[2 * index.texcoord_index + 1],
};
}
if (uniqueVertices.count(vertex) == 0) {
uniqueVertices[vertex] = static_cast<uint32_t>(vertices.size());
vertices.push_back(vertex);
}
indices.push_back(uniqueVertices[vertex]);
}
}
}
}

63
engine/xe_model.hpp Normal file
View file

@ -0,0 +1,63 @@
#pragma once
#include "xe_device.hpp"
#include "xe_buffer.hpp"
#include <vector>
#include <memory>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
namespace xe {
class XeModel {
public:
struct Vertex {
glm::vec3 position;
glm::vec3 color;
glm::vec3 normal;
glm::vec2 uv;
static std::vector<VkVertexInputBindingDescription> getBindingDescriptions();
static std::vector<VkVertexInputAttributeDescription> getAttributeDescriptions();
bool operator==(const Vertex &other) const {
return position == other.position && color == other.color && normal == other.normal && uv == other.uv;
}
};
struct Builder {
std::vector<Vertex> vertices{};
std::vector<uint32_t> indices{};
void loadModel(const std::string &filepath);
};
XeModel(XeDevice &device, const XeModel::Builder &builder);
~XeModel();
XeModel(const XeModel &) = delete;
XeModel operator=(const XeModel &) = delete;
static std::unique_ptr<XeModel> createModelFromFile(XeDevice &device, const std::string &filepath);
void bind(VkCommandBuffer commandBuffer);
void draw(VkCommandBuffer commandBuffer);
private:
void createVertexBuffers(const std::vector<Vertex> &vertices);
void createIndexBuffers(const std::vector<uint32_t> &indices);
XeDevice &xeDevice;
std::unique_ptr<XeBuffer> vertexBuffer;
uint32_t vertexCount;
bool hasIndexBuffer = false;
std::unique_ptr<XeBuffer> indexBuffer;
uint32_t indexCount;
};
}

3330
engine/xe_obj_loader.hpp Normal file

File diff suppressed because it is too large Load diff

204
engine/xe_pipeline.cpp Executable file
View file

@ -0,0 +1,204 @@
#include "xe_pipeline.hpp"
#include "xe_model.hpp"
#include <cstdint>
#include <fstream>
#include <stdexcept>
#include <vector>
#include <iostream>
#include <vulkan/vulkan_core.h>
#include <assert.h>
namespace xe {
XePipeline::XePipeline(
XeDevice &device,
const std::string& vertFilepath,
const std::string& fragFilepath,
const PipelineConfigInfo& configInfo)
: xeDevice{device} {
createGraphicsPipeline(vertFilepath, fragFilepath, configInfo);
}
XePipeline::~XePipeline() {
vkDestroyShaderModule(xeDevice.device(), vertShaderModule, nullptr);
vkDestroyShaderModule(xeDevice.device(), fragShaderModule, nullptr);
vkDestroyPipeline(xeDevice.device(), graphicsPipeline, nullptr);
}
std::vector<char> XePipeline::readFile(const std::string& filepath) {
std::ifstream file{filepath, std::ios::ate | std::ios::binary};
if (!file.is_open()) {
throw std::runtime_error("faile to open file " + filepath);
}
size_t fileSize = static_cast<size_t>(file.tellg());
std::vector<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
};
void XePipeline::createGraphicsPipeline(
const std::string& vertFilePath,
const std::string& fragFilepath,
const PipelineConfigInfo& configInfo) {
assert(
configInfo.pipelineLayout != VK_NULL_HANDLE &&
"Cannot create graphics pipeline:: no pipelineLayout provided in configInfo");
assert(
configInfo.renderPass != VK_NULL_HANDLE &&
"Cannot create graphics pipeline:: no renderPass provided in configInfo");
auto vertCode = readFile(vertFilePath);
auto fragCode = readFile(fragFilepath);
createShaderModule(vertCode, &vertShaderModule);
createShaderModule(fragCode, &fragShaderModule);
VkPipelineShaderStageCreateInfo shaderStages[2];
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
shaderStages[0]. module = vertShaderModule;
shaderStages[0].pName = "main";
shaderStages[0].flags = 0;
shaderStages[0].pNext = nullptr;
shaderStages[0].pSpecializationInfo = nullptr;
shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shaderStages[1]. module = fragShaderModule;
shaderStages[1].pName = "main";
shaderStages[1].flags = 0;
shaderStages[1].pNext = nullptr;
shaderStages[1].pSpecializationInfo = nullptr;
auto bindingDescriptions = XeModel::Vertex::getBindingDescriptions();
auto attributeDescptions = XeModel::Vertex::getAttributeDescriptions();
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescptions.size());
vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescriptions.size());
vertexInputInfo.pVertexAttributeDescriptions = attributeDescptions.data();
vertexInputInfo.pVertexBindingDescriptions = bindingDescriptions.data();
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo;
pipelineInfo.pViewportState = &configInfo.viewportInfo;
pipelineInfo.pRasterizationState = &configInfo.rasterizationInfo;
pipelineInfo.pMultisampleState = &configInfo.multisampleInfo;
pipelineInfo.pColorBlendState = &configInfo.colorBlendInfo;
pipelineInfo.pDepthStencilState = &configInfo.depthStencilInfo;
pipelineInfo.pDynamicState = &configInfo.dynamicStateInfo;
pipelineInfo.layout = configInfo.pipelineLayout;
pipelineInfo.renderPass = configInfo.renderPass;
pipelineInfo.subpass = configInfo.subpass;
pipelineInfo.basePipelineIndex = -1;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
if(vkCreateGraphicsPipelines(xeDevice.device(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS){
throw std::runtime_error("failed to create graphics pipeline");
}
}
void XePipeline::createShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule) {
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
if(vkCreateShaderModule(xeDevice.device(), &createInfo, nullptr, shaderModule) != VK_SUCCESS) {
throw std::runtime_error("failed to create shader module");
}
}
void XePipeline::bind(VkCommandBuffer commandBuffer) {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
}
void XePipeline::defaultPipelineConfigInfo(PipelineConfigInfo& configInfo) {
configInfo.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
configInfo.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
configInfo.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
configInfo.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
configInfo.viewportInfo.viewportCount = 1;
configInfo.viewportInfo.pViewports = nullptr;
configInfo.viewportInfo.scissorCount = 1;
configInfo.viewportInfo.pScissors = nullptr;
configInfo.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
configInfo.rasterizationInfo.depthClampEnable = VK_FALSE;
configInfo.rasterizationInfo.rasterizerDiscardEnable = VK_FALSE;
configInfo.rasterizationInfo.polygonMode = VK_POLYGON_MODE_FILL;
configInfo.rasterizationInfo.lineWidth = 1.0f;
configInfo.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
configInfo.rasterizationInfo.frontFace = VK_FRONT_FACE_CLOCKWISE;
configInfo.rasterizationInfo.depthBiasEnable = VK_FALSE;
configInfo.rasterizationInfo.depthBiasConstantFactor = 0.0f;
configInfo.rasterizationInfo.depthBiasClamp = 0.0f;
configInfo.rasterizationInfo.depthBiasSlopeFactor = 0.0f;
configInfo.multisampleInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
configInfo.multisampleInfo.sampleShadingEnable = VK_FALSE;
configInfo.multisampleInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
configInfo.multisampleInfo.minSampleShading = 1.0f;
configInfo.multisampleInfo.pSampleMask = nullptr;
configInfo.multisampleInfo.alphaToCoverageEnable = VK_FALSE;
configInfo.multisampleInfo.alphaToOneEnable = VK_FALSE;
configInfo.colorBlendAttachment.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
configInfo.colorBlendAttachment.blendEnable = VK_FALSE;
configInfo.colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
configInfo.colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
configInfo.colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
configInfo.colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
configInfo.colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
configInfo.colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
configInfo.colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
configInfo.colorBlendInfo.logicOpEnable = VK_FALSE;
configInfo.colorBlendInfo.logicOp = VK_LOGIC_OP_COPY;
configInfo.colorBlendInfo.attachmentCount = 1;
configInfo.colorBlendInfo.pAttachments = &configInfo.colorBlendAttachment;
configInfo.colorBlendInfo.blendConstants[0] = 0.0f;
configInfo.colorBlendInfo.blendConstants[1] = 0.0f;
configInfo.colorBlendInfo.blendConstants[2] = 0.0f;
configInfo.colorBlendInfo.blendConstants[3] = 0.0f;
configInfo.depthStencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
configInfo.depthStencilInfo.depthTestEnable = VK_TRUE;
configInfo.depthStencilInfo.depthWriteEnable = VK_TRUE;
configInfo.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
configInfo.depthStencilInfo.depthBoundsTestEnable = VK_FALSE;
configInfo.depthStencilInfo.minDepthBounds = 0.0f;
configInfo.depthStencilInfo.maxDepthBounds = 1.0f;
configInfo.depthStencilInfo.stencilTestEnable = VK_FALSE;
configInfo.depthStencilInfo.front = {};
configInfo.depthStencilInfo.back = {};
configInfo.dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
configInfo.dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
configInfo.dynamicStateInfo.pDynamicStates = configInfo.dynamicStateEnables.data();
configInfo.dynamicStateInfo.dynamicStateCount =
static_cast<uint32_t>(configInfo.dynamicStateEnables.size());
configInfo.dynamicStateInfo.flags = 0;
}
}

61
engine/xe_pipeline.hpp Executable file
View file

@ -0,0 +1,61 @@
#pragma once
#include "xe_device.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace xe {
struct PipelineConfigInfo {
PipelineConfigInfo(const PipelineConfigInfo&) = delete;
PipelineConfigInfo& operator=(const PipelineConfigInfo&) = delete;
VkPipelineViewportStateCreateInfo viewportInfo;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
VkPipelineRasterizationStateCreateInfo rasterizationInfo;
VkPipelineMultisampleStateCreateInfo multisampleInfo;
VkPipelineColorBlendAttachmentState colorBlendAttachment;
VkPipelineColorBlendStateCreateInfo colorBlendInfo;
VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
std::vector<VkDynamicState> dynamicStateEnables;
VkPipelineDynamicStateCreateInfo dynamicStateInfo;
VkPipelineLayout pipelineLayout = nullptr;
VkRenderPass renderPass = nullptr;
uint32_t subpass = 0;
};
class XePipeline {
public:
XePipeline(
XeDevice &device,
const std::string& vertFilepath,
const std::string& fragFilepath,
const PipelineConfigInfo& configInfo);
~XePipeline();
XePipeline(const XePipeline&) = delete;
XePipeline operator=(const XePipeline&) = delete;
void bind(VkCommandBuffer commandBuffer);
static void defaultPipelineConfigInfo(PipelineConfigInfo& configInfo);
private:
static std::vector<char> readFile(const std::string& filePath);
void createGraphicsPipeline(
const std::string& vertFilePath,
const std::string& fragFilepath,
const PipelineConfigInfo& configInfo);
void createShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule);
XeDevice& xeDevice;
VkPipeline graphicsPipeline;
VkShaderModule vertShaderModule;
VkShaderModule fragShaderModule;
};
}

156
engine/xe_renderer.cpp Normal file
View file

@ -0,0 +1,156 @@
#include "xe_renderer.hpp"
#include "xe_device.hpp"
#include "xe_game_object.hpp"
#include "xe_swap_chain.hpp"
#include "xe_window.hpp"
#include <memory>
#include <vulkan/vulkan_core.h>
#include <array>
#include <cassert>
#include <stdexcept>
namespace xe {
XeRenderer::XeRenderer(XeWindow& window, XeDevice& device) : xeWindow{window}, xeDevice{device} {
recreateSwapChain();
createCommandBuffers();
}
XeRenderer::~XeRenderer() { freeCommandBuffers(); }
void XeRenderer::recreateSwapChain() {
auto extent = xeWindow.getExtent();
while (extent.width == 0 || extent.height == 0) {
extent = xeWindow.getExtent();
glfwWaitEvents();
}
vkDeviceWaitIdle(xeDevice.device());
if(xeSwapChain == nullptr) {
xeSwapChain = std::make_unique<XeSwapChain>(xeDevice, extent);
} else {
std::shared_ptr<XeSwapChain> oldSwapChain = std::move(xeSwapChain);
xeSwapChain = std::make_unique<XeSwapChain>(xeDevice, extent, oldSwapChain);
if(!oldSwapChain->compareSwapFormats(*xeSwapChain.get())) {
throw std::runtime_error("Swap chain image (or depth) format has changed");
}
}
// we'll come back to this in just a moment
}
void XeRenderer::createCommandBuffers() {
commandBuffers.resize(XeSwapChain::MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = xeDevice.getCommandPool();
allocInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
if(vkAllocateCommandBuffers(xeDevice.device(), &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate command buffers!");
}
}
void XeRenderer::freeCommandBuffers() {
vkFreeCommandBuffers(
xeDevice.device(),
xeDevice.getCommandPool(),
static_cast<uint32_t>(commandBuffers.size()),
commandBuffers.data());
commandBuffers.clear();
}
VkCommandBuffer XeRenderer::beginFrame() {
assert(!isFrameStarted && "Can't acll beingFrame while already in progress");
auto result = xeSwapChain->acquireNextImage(&currentImageIndex);
if(result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return nullptr;
}
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("failed to acquire swap chain image");
}
isFrameStarted = true;
auto commandBuffer = getCurrentCommandBuffer();
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if(vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffers");
}
return commandBuffer;
}
void XeRenderer::endFrame() {
assert(isFrameStarted && "Can't call endFrame while frame is not in progress");
auto commandBuffer = getCurrentCommandBuffer();
if(vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer");
}
auto result = xeSwapChain->submitCommandBuffers(&commandBuffer, &currentImageIndex);
if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || xeWindow.wasWindowResized()) {
xeWindow.resetWindowResizedFlag();
recreateSwapChain();
}
isFrameStarted = false;
currentFrameIndex = (currentFrameIndex + 1) % XeSwapChain::MAX_FRAMES_IN_FLIGHT;
}
void XeRenderer::beginSwapChainRenderPass(VkCommandBuffer commandBuffer){
assert(isFrameStarted && "Can't call beginSwapChainRenderPass while frame is not in progress");
assert(commandBuffer == getCurrentCommandBuffer() && "Can't begin render pass on command buffer from a different frame");
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = xeSwapChain->getRenderPass();
renderPassInfo.framebuffer = xeSwapChain->getFrameBuffer(currentImageIndex);
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = xeSwapChain->getSwapChainExtent();
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {0.1f, 0.1f, 0.1f, 1.0f};
clearValues[1].depthStencil = {1.0f, 0};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = static_cast<float>(xeSwapChain->getSwapChainExtent().height);
viewport.width = static_cast<float>(xeSwapChain->getSwapChainExtent().width);
viewport.height = -static_cast<float>(xeSwapChain->getSwapChainExtent().height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor{{0, 0}, xeSwapChain->getSwapChainExtent()};
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
}
void XeRenderer::endSwapChainRenderPass(VkCommandBuffer commandBuffer){
assert(isFrameStarted && "Can't call endSwapChainRenderPass while frame is not in progress");
assert(commandBuffer == getCurrentCommandBuffer() && "Can't end render pass on command buffer from a different frame");
vkCmdEndRenderPass(commandBuffer);
}
}

55
engine/xe_renderer.hpp Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#include "xe_swap_chain.hpp"
#include "xe_window.hpp"
#include "xe_device.hpp"
#include "xe_model.hpp"
#include <memory>
#include <vector>
#include <cassert>
namespace xe {
class XeRenderer {
public:
XeRenderer(XeWindow &window, XeDevice &device);
~XeRenderer();
XeRenderer(const XeRenderer &) = delete;
XeRenderer operator=(const XeRenderer &) = delete;
VkRenderPass getSwapChainRenderPass() const { return xeSwapChain->getRenderPass(); }
float getAspectRatio() const { return xeSwapChain->extentAspectRatio(); }
bool isFrameInProgress() const { return isFrameStarted; }
VkCommandBuffer getCurrentCommandBuffer() const {
assert(isFrameStarted && "Cannot get command buffer when frame not in progress");
return commandBuffers[currentFrameIndex];
}
int getFrameIndex() const {
assert(isFrameStarted && "Cannot get frame index when frame not in progress");
return currentFrameIndex;
}
VkCommandBuffer beginFrame();
void endFrame();
void beginSwapChainRenderPass(VkCommandBuffer commandBuffer);
void endSwapChainRenderPass(VkCommandBuffer commandBuffer);
private:
void createCommandBuffers();
void freeCommandBuffers();
void recreateSwapChain();
XeWindow& xeWindow;
XeDevice& xeDevice;
std::unique_ptr<XeSwapChain> xeSwapChain;
std::vector<VkCommandBuffer> commandBuffers;
uint32_t currentImageIndex;
int currentFrameIndex{0};
bool isFrameStarted{false};
};
}

424
engine/xe_swap_chain.cpp Executable file
View file

@ -0,0 +1,424 @@
#include "xe_swap_chain.hpp"
#include <array>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <limits>
#include <memory>
#include <set>
#include <stdexcept>
namespace xe {
XeSwapChain::XeSwapChain(XeDevice &deviceRef, VkExtent2D extent)
: device{deviceRef}, windowExtent{extent} {
init();
}
XeSwapChain::XeSwapChain(XeDevice &deviceRef, VkExtent2D extent, std::shared_ptr<XeSwapChain> previous)
: device{deviceRef}, windowExtent{extent}, oldSwapChain{previous} {
init();
oldSwapChain = nullptr;
}
void XeSwapChain::init() {
createSwapChain();
createImageViews();
createRenderPass();
createDepthResources();
createFramebuffers();
createSyncObjects();
}
XeSwapChain::~XeSwapChain() {
for (auto imageView : swapChainImageViews) {
vkDestroyImageView(device.device(), imageView, nullptr);
}
swapChainImageViews.clear();
if (swapChain != nullptr) {
vkDestroySwapchainKHR(device.device(), swapChain, nullptr);
swapChain = nullptr;
}
for (int i = 0; i < depthImages.size(); i++) {
vkDestroyImageView(device.device(), depthImageViews[i], nullptr);
vkDestroyImage(device.device(), depthImages[i], nullptr);
vkFreeMemory(device.device(), depthImageMemorys[i], nullptr);
}
for (auto framebuffer : swapChainFramebuffers) {
vkDestroyFramebuffer(device.device(), framebuffer, nullptr);
}
vkDestroyRenderPass(device.device(), renderPass, nullptr);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(device.device(), renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(device.device(), imageAvailableSemaphores[i], nullptr);
vkDestroyFence(device.device(), inFlightFences[i], nullptr);
}
}
VkResult XeSwapChain::acquireNextImage(uint32_t *imageIndex) {
vkWaitForFences(
device.device(),
1,
&inFlightFences[currentFrame],
VK_TRUE,
std::numeric_limits<uint64_t>::max());
VkResult result = vkAcquireNextImageKHR(
device.device(),
swapChain,
std::numeric_limits<uint64_t>::max(),
imageAvailableSemaphores[currentFrame],
VK_NULL_HANDLE,
imageIndex);
return result;
}
VkResult XeSwapChain::submitCommandBuffers(
const VkCommandBuffer *buffers, uint32_t *imageIndex) {
if (imagesInFlight[*imageIndex] != VK_NULL_HANDLE) {
vkWaitForFences(device.device(), 1, &imagesInFlight[*imageIndex], VK_TRUE, UINT64_MAX);
}
imagesInFlight[*imageIndex] = inFlightFences[currentFrame];
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = buffers;
VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
vkResetFences(device.device(), 1, &inFlightFences[currentFrame]);
if (vkQueueSubmit(device.graphicsQueue(), 1, &submitInfo, inFlightFences[currentFrame]) !=
VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = {swapChain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = imageIndex;
auto result = vkQueuePresentKHR(device.presentQueue(), &presentInfo);
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
return result;
}
void XeSwapChain::createSwapChain() {
SwapChainSupportDetails swapChainSupport = device.getSwapChainSupport();
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 &&
imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = device.surface();
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
QueueFamilyIndices indices = device.findPhysicalQueueFamilies();
uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily};
if (indices.graphicsFamily != indices.presentFamily) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
createInfo.pQueueFamilyIndices = nullptr;
}
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = oldSwapChain == nullptr ? VK_NULL_HANDLE : oldSwapChain->swapChain;
if (vkCreateSwapchainKHR(device.device(), &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
}
vkGetSwapchainImagesKHR(device.device(), swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device.device(), swapChain, &imageCount, swapChainImages.data());
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
}
void XeSwapChain::createImageViews() {
swapChainImageViews.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); i++) {
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = swapChainImages[i];
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = swapChainImageFormat;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device.device(), &viewInfo, nullptr, &swapChainImageViews[i]) !=
VK_SUCCESS) {
throw std::runtime_error("failed to create texture image view!");
}
}
}
void XeSwapChain::createRenderPass() {
VkAttachmentDescription depthAttachment{};
depthAttachment.format = findDepthFormat();
depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthAttachmentRef{};
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = getSwapChainImageFormat();
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
subpass.pDepthStencilAttachment = &depthAttachmentRef;
VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.srcAccessMask = 0;
dependency.srcStageMask =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstSubpass = 0;
dependency.dstStageMask =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
std::array<VkAttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
if (vkCreateRenderPass(device.device(), &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("failed to create render pass!");
}
}
void XeSwapChain::createFramebuffers() {
swapChainFramebuffers.resize(imageCount());
for (size_t i = 0; i < imageCount(); i++) {
std::array<VkImageView, 2> attachments = {swapChainImageViews[i], depthImageViews[i]};
VkExtent2D swapChainExtent = getSwapChainExtent();
VkFramebufferCreateInfo framebufferInfo = {};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
framebufferInfo.pAttachments = attachments.data();
framebufferInfo.width = swapChainExtent.width;
framebufferInfo.height = swapChainExtent.height;
framebufferInfo.layers = 1;
if (vkCreateFramebuffer(
device.device(),
&framebufferInfo,
nullptr,
&swapChainFramebuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create framebuffer!");
}
}
}
void XeSwapChain::createDepthResources() {
VkFormat depthFormat = findDepthFormat();
swapChainDepthFormat = depthFormat;
VkExtent2D swapChainExtent = getSwapChainExtent();
depthImages.resize(imageCount());
depthImageMemorys.resize(imageCount());
depthImageViews.resize(imageCount());
for (int i = 0; i < depthImages.size(); i++) {
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = swapChainExtent.width;
imageInfo.extent.height = swapChainExtent.height;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = depthFormat;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.flags = 0;
device.createImageWithInfo(
imageInfo,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
depthImages[i],
depthImageMemorys[i]);
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = depthImages[i];
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = depthFormat;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device.device(), &viewInfo, nullptr, &depthImageViews[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create texture image view!");
}
}
}
void XeSwapChain::createSyncObjects() {
imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
imagesInFlight.resize(imageCount(), VK_NULL_HANDLE);
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
if (vkCreateSemaphore(device.device(), &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) !=
VK_SUCCESS ||
vkCreateSemaphore(device.device(), &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) !=
VK_SUCCESS ||
vkCreateFence(device.device(), &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create synchronization objects for a frame!");
}
}
}
VkSurfaceFormatKHR XeSwapChain::chooseSwapSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &availableFormats) {
for (const auto &availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM &&
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
return availableFormats[0];
}
VkPresentModeKHR XeSwapChain::chooseSwapPresentMode(
const std::vector<VkPresentModeKHR> &availablePresentModes) {
for (const auto &availablePresentMode : availablePresentModes) {
if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
std::cout << "Present mode: Mailbox" << std::endl;
return availablePresentMode;
}
}
for (const auto &availablePresentMode : availablePresentModes) {
if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
std::cout << "Present mode: Immediate" << std::endl;
return availablePresentMode;
}
}
std::cout << "Present mode: V-Sync" << std::endl;
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D XeSwapChain::chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities) {
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
return capabilities.currentExtent;
} else {
VkExtent2D actualExtent = windowExtent;
actualExtent.width = std::max(
capabilities.minImageExtent.width,
std::min(capabilities.maxImageExtent.width, actualExtent.width));
actualExtent.height = std::max(
capabilities.minImageExtent.height,
std::min(capabilities.maxImageExtent.height, actualExtent.height));
return actualExtent;
}
}
VkFormat XeSwapChain::findDepthFormat() {
return device.findSupportedFormat(
{VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT},
VK_IMAGE_TILING_OPTIMAL,
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
}
}

88
engine/xe_swap_chain.hpp Executable file
View file

@ -0,0 +1,88 @@
#pragma once
#include "xe_device.hpp"
#include <vulkan/vulkan.h>
#include <memory>
#include <string>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace xe {
class XeSwapChain {
public:
static constexpr int MAX_FRAMES_IN_FLIGHT = 2;
XeSwapChain(XeDevice &deviceRef, VkExtent2D windowExtent);
XeSwapChain(XeDevice &deviceRef, VkExtent2D windowExtent, std::shared_ptr<XeSwapChain> previous);
~XeSwapChain();
XeSwapChain(const XeSwapChain &) = delete;
XeSwapChain operator=(const XeSwapChain &) = delete;
VkFramebuffer getFrameBuffer(int index) { return swapChainFramebuffers[index]; }
VkRenderPass getRenderPass() { return renderPass; }
VkImageView getImageView(int index) { return swapChainImageViews[index]; }
size_t imageCount() { return swapChainImages.size(); }
VkFormat getSwapChainImageFormat() { return swapChainImageFormat; }
VkExtent2D getSwapChainExtent() { return swapChainExtent; }
uint32_t width() { return swapChainExtent.width; }
uint32_t height() { return swapChainExtent.height; }
float extentAspectRatio() {
return static_cast<float>(swapChainExtent.width) / static_cast<float>(swapChainExtent.height);
}
VkFormat findDepthFormat();
VkResult acquireNextImage(uint32_t *imageIndex);
VkResult submitCommandBuffers(const VkCommandBuffer *buffers, uint32_t *imageIndex);
bool compareSwapFormats(const XeSwapChain& swapChain) const {
return swapChain.swapChainDepthFormat == swapChainDepthFormat &&
swapChain.swapChainImageFormat == swapChainImageFormat;
}
private:
void init();
void createSwapChain();
void createImageViews();
void createDepthResources();
void createRenderPass();
void createFramebuffers();
void createSyncObjects();
VkSurfaceFormatKHR chooseSwapSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &availableFormats);
VkPresentModeKHR chooseSwapPresentMode(
const std::vector<VkPresentModeKHR> &availablePresentModes);
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities);
VkFormat swapChainImageFormat;
VkFormat swapChainDepthFormat;
VkExtent2D swapChainExtent;
std::vector<VkFramebuffer> swapChainFramebuffers;
VkRenderPass renderPass;
std::vector<VkImage> depthImages;
std::vector<VkDeviceMemory> depthImageMemorys;
std::vector<VkImageView> depthImageViews;
std::vector<VkImage> swapChainImages;
std::vector<VkImageView> swapChainImageViews;
XeDevice &device;
VkExtent2D windowExtent;
VkSwapchainKHR swapChain;
std::shared_ptr<XeSwapChain> oldSwapChain;
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences;
std::vector<VkFence> imagesInFlight;
size_t currentFrame = 0;
};
}

11
engine/xe_utils.hpp Normal file
View file

@ -0,0 +1,11 @@
#pragma once
namespace xe {
template <typename T, typename... Rest>
void hashCombine(std::size_t& seed, const T& v, const Rest&... rest) {
seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hashCombine(seed, rest), ...);
};
}

41
engine/xe_window.cpp Executable file
View file

@ -0,0 +1,41 @@
#include "xe_window.hpp"
#include <GLFW/glfw3.h>
#include <stdexcept>
#include <stdexcept>
namespace xe {
XeWindow::XeWindow(int w, int h, std::string name) : width{w}, height{h}, windowName{name} {
initWindow();
}
XeWindow::~XeWindow() {
glfwDestroyWindow(window);
glfwTerminate();
}
void XeWindow::initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
window = glfwCreateWindow(width, height, windowName.c_str(), nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
}
void XeWindow::createWindowSurface(VkInstance instance, VkSurfaceKHR *surface){
if (glfwCreateWindowSurface(instance, window, nullptr, surface) != VK_SUCCESS) {
throw std::runtime_error("failed to create window surface");
}
}
void XeWindow::framebufferResizeCallback(GLFWwindow *window, int width, int height){
auto xeWindow = reinterpret_cast<XeWindow *>(glfwGetWindowUserPointer(window));
xeWindow->frameBufferResized = true;
xeWindow->width = width;
xeWindow->height = height;
}
}

38
engine/xe_window.hpp Executable file
View file

@ -0,0 +1,38 @@
#pragma once
#include <vulkan/vulkan_core.h>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <string>
namespace xe {
class XeWindow {
public:
XeWindow(int w, int h, std::string name);
~XeWindow();
XeWindow(const XeWindow &) = delete;
XeWindow &operator=(const XeWindow &);
bool shouldClose() { return glfwWindowShouldClose(window); }
VkExtent2D getExtent() { return { static_cast<uint32_t>(width), static_cast<uint32_t>(height)}; }
bool wasWindowResized() { return frameBufferResized; }
void resetWindowResizedFlag() { frameBufferResized = false; }
GLFWwindow *getGLFWwindow() const { return window; }
void createWindowSurface(VkInstance instance, VkSurfaceKHR *surface);
private:
static void framebufferResizeCallback(GLFWwindow *window, int width, int height);
void initWindow();
int width;
int height;
bool frameBufferResized = false;
std::string windowName;
GLFWwindow *window;
};
}

1
lib/glfw Submodule

@ -0,0 +1 @@
Subproject commit d299d9f78857e921b66bdab42c7ea27fe2e31810

1
lib/glm Submodule

@ -0,0 +1 @@
Subproject commit cc98465e3508535ba8c7f6208df934c156a018dc

249866
res/models/stanford-dragon.obj Normal file

File diff suppressed because it is too large Load diff

19
res/shaders/simple_shader.frag Executable file
View file

@ -0,0 +1,19 @@
#version 450
layout (location = 0) in vec3 fragColor;
layout (location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform GlobalUbo {
mat4 projectionViewMatrix;
vec3 directionToLight;
} ubo;
layout(push_constant) uniform Push {
mat4 transform;
mat4 normalMatrix;
} push;
void main() {
outColor = vec4(fragColor, 1.0);
}

Binary file not shown.

30
res/shaders/simple_shader.vert Executable file
View file

@ -0,0 +1,30 @@
#version 450
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 2) in vec3 normal;
layout(location = 3) in vec2 uv;
layout(location = 0) out vec3 fragColor;
layout(set = 0, binding = 0) uniform GlobalUbo {
mat4 projectionViewMatrix;
vec3 directionToLight;
} ubo;
layout(push_constant) uniform Push {
mat4 modelMatrix;
mat4 normalMatrix;
} push;
const float AMBIENT = 0.02;
void main() {
gl_Position = ubo.projectionViewMatrix * push.modelMatrix * vec4(position, 1.0);
vec3 normalWorldSpace = normalize(mat3(push.normalMatrix) * normal);
float lightIntensity = AMBIENT + max(dot(normalWorldSpace, ubo.directionToLight), 0);
fragColor = lightIntensity * color;
}

Binary file not shown.

125
src/first_app.cpp Executable file
View file

@ -0,0 +1,125 @@
#include "first_app.hpp"
#include "xe_camera.hpp"
#include "xe_game_object.hpp"
#include "xe_model.hpp"
#include "simple_render_system.hpp"
#include "keyboard_movement_controller.hpp"
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <array>
#include <chrono>
#include <cassert>
#include <stdexcept>
namespace xe {
struct GlobalUbo {
glm::mat4 projectionView{1.f};
glm::vec3 lightDirection = glm::normalize(glm::vec3{-1.f, 3.f, 1.f});
};
FirstApp::FirstApp() {
globalPool = XeDescriptorPool::Builder(xeDevice)
.setMaxSets(XeSwapChain::MAX_FRAMES_IN_FLIGHT)
.addPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, XeSwapChain::MAX_FRAMES_IN_FLIGHT)
.addPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, XeSwapChain::MAX_FRAMES_IN_FLIGHT)
.build();
loadGameObjects();
}
FirstApp::~FirstApp() {}
void FirstApp::run() {
std::vector<std::unique_ptr<XeBuffer>> uboBuffers(XeSwapChain::MAX_FRAMES_IN_FLIGHT);
for (int i = 0; i < uboBuffers.size(); i++) {
uboBuffers[i] = std::make_unique<XeBuffer>(
xeDevice,
sizeof(GlobalUbo),
XeSwapChain::MAX_FRAMES_IN_FLIGHT,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
uboBuffers[i]->map();
}
auto globalSetLayout = XeDescriptorSetLayout::Builder(xeDevice)
.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT)
.build();
std::vector<VkDescriptorSet> globalDescriptorSets(XeSwapChain::MAX_FRAMES_IN_FLIGHT);
for (int i = 0; i < globalDescriptorSets.size(); i++) {
auto bufferInfo = uboBuffers[i]->descriptorInfo();
XeDescriptorWriter(*globalSetLayout, *globalPool)
.writeBuffer(0, &bufferInfo)
.build(globalDescriptorSets[i]);
}
SimpleRenderSystem simpleRenderSystem{xeDevice, xeRenderer.getSwapChainRenderPass(), globalSetLayout->getDescriptorSetLayout()};
XeCamera camera{};
camera.setViewTarget(glm::vec3(-1.f, -2.f, 20.f), glm::vec3(0.f, 0.f, 2.5f));
auto viewerObject = XeGameObject::createGameObject();
KeyboardMovementController cameraController{};
auto currentTime = std::chrono::high_resolution_clock::now();
while (!xeWindow.shouldClose()) {
glfwPollEvents();
auto newTime = std::chrono::high_resolution_clock::now();
float frameTime = std::chrono::duration<float, std::chrono::seconds::period>(newTime - currentTime).count();
currentTime = newTime;
cameraController.moveInPlaneXZ(xeWindow.getGLFWwindow(), frameTime, viewerObject);
camera.setViewYXZ(viewerObject.transform.translation, viewerObject.transform.rotation);
float aspect = xeRenderer.getAspectRatio();
camera.setPerspectiveProjection(glm::radians(50.f), aspect, 0.1f, 100.f);
if(auto commandBuffer = xeRenderer.beginFrame()) {
int frameIndex = xeRenderer.getFrameIndex();
XeFrameInfo frameInfo{
frameIndex,
frameTime,
commandBuffer,
camera,
globalDescriptorSets[frameIndex]
};
// update
GlobalUbo ubo{};
ubo.projectionView = camera.getProjection() * camera.getView();
uboBuffers[frameIndex]->writeToBuffer(&ubo);
uboBuffers[frameIndex]->flush();
// render
xeRenderer.beginSwapChainRenderPass(commandBuffer);
simpleRenderSystem.renderGameObjects(frameInfo, gameObjects);
xeRenderer.endSwapChainRenderPass(commandBuffer);
xeRenderer.endFrame();
}
}
vkDeviceWaitIdle(xeDevice.device());
}
void FirstApp::loadGameObjects() {
std::shared_ptr<XeModel> xeModel = XeModel::createModelFromFile(xeDevice, "res/models/stanford-dragon.obj");
auto cube = XeGameObject::createGameObject();
cube.model = xeModel;
cube.transform.translation = {.0f, .0f, 2.5f};
cube.transform.scale = {.5f, .5f, .5f};
gameObjects.push_back(std::move(cube));
}
}

36
src/first_app.hpp Executable file
View file

@ -0,0 +1,36 @@
#pragma once
#include "xe_renderer.hpp"
#include "xe_window.hpp"
#include "xe_device.hpp"
#include "xe_game_object.hpp"
#include "xe_descriptors.hpp"
#include <memory>
#include <vector>
namespace xe {
class FirstApp {
public:
static constexpr int WIDTH = 800;
static constexpr int HEIGHT = 600;
FirstApp();
~FirstApp();
FirstApp(const FirstApp &) = delete;
FirstApp operator=(const FirstApp &) = delete;
void run();
private:
void loadGameObjects();
XeWindow xeWindow{WIDTH, HEIGHT, "Hello Vulkan!"};
XeDevice xeDevice{xeWindow};
XeRenderer xeRenderer{xeWindow, xeDevice};
std::unique_ptr<XeDescriptorPool> globalPool{};
std::vector<XeGameObject> gameObjects;
};
}

View file

@ -0,0 +1,42 @@
#include "keyboard_movement_controller.hpp"
#include <glm/common.hpp>
#include <glm/fwd.hpp>
#include <glm/geometric.hpp>
#include <limits>
namespace xe {
void KeyboardMovementController::moveInPlaneXZ(GLFWwindow* window, float dt, XeGameObject& gameObject) {
glm::vec3 rotate{0};
if(glfwGetKey(window, keys.lookRight) == GLFW_PRESS) rotate.y += 1.f;
if(glfwGetKey(window, keys.lookLeft) == GLFW_PRESS) rotate.y -= 1.f;
if(glfwGetKey(window, keys.lookUp) == GLFW_PRESS) rotate.x -= 1.f;
if(glfwGetKey(window, keys.lookDown) == GLFW_PRESS) rotate.x += 1.f;
if (glm::dot(rotate, rotate) > std::numeric_limits<float>::epsilon()) {
gameObject.transform.rotation += lookSpeed * dt * glm::normalize(rotate);
}
gameObject.transform.rotation.x = glm::clamp(gameObject.transform.rotation.x, -1.5f, 1.5f);
gameObject.transform.rotation.y = glm::mod(gameObject.transform.rotation.y, glm::two_pi<float>());
float yaw = gameObject.transform.rotation.y;
const glm::vec3 forwardDir{sin(yaw), 0.f, cos(yaw)};
const glm::vec3 rightDir{forwardDir.z, 0.f, -forwardDir.x};
const glm::vec3 upDir{0.f, 01.f, 0.f};
glm::vec3 moveDir{0};
if(glfwGetKey(window, keys.moveForward) == GLFW_PRESS) moveDir += forwardDir;
if(glfwGetKey(window, keys.moveBackward) == GLFW_PRESS) moveDir -= forwardDir;
if(glfwGetKey(window, keys.moveRight) == GLFW_PRESS) moveDir += rightDir;
if(glfwGetKey(window, keys.moveLeft) == GLFW_PRESS) moveDir -= rightDir;
if(glfwGetKey(window, keys.moveUp) == GLFW_PRESS) moveDir += upDir;
if(glfwGetKey(window, keys.moveDown) == GLFW_PRESS) moveDir -= upDir;
if (glm::dot(moveDir, moveDir) > std::numeric_limits<float>::epsilon()) {
gameObject.transform.translation += moveSpeed * dt * glm::normalize(moveDir);
}
}
}

View file

@ -0,0 +1,31 @@
#pragma once
#include "xe_game_object.hpp"
#include "xe_window.hpp"
#include <GLFW/glfw3.h>
namespace xe {
class KeyboardMovementController {
public:
struct KeyMappings {
int moveLeft = GLFW_KEY_A;
int moveRight = GLFW_KEY_D;
int moveForward = GLFW_KEY_W;
int moveBackward = GLFW_KEY_S;
int moveUp = GLFW_KEY_E;
int moveDown = GLFW_KEY_Q;
int lookLeft = GLFW_KEY_LEFT;
int lookRight = GLFW_KEY_RIGHT;
int lookUp = GLFW_KEY_UP;
int lookDown = GLFW_KEY_DOWN;
};
void moveInPlaneXZ(GLFWwindow* window, float dt, XeGameObject& gameObject);
KeyMappings keys{};
float moveSpeed{3.f};
float lookSpeed{1.5f};
};
}

18
src/main.cpp Executable file
View file

@ -0,0 +1,18 @@
#include "first_app.hpp"
#include <cstdlib>
#include <iostream>
#include <stdexcept>
int main() {
xe::FirstApp app{};
try {
app.run();
} catch (const std::exception &e) {
std::cerr << e.what() << '\n';
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,94 @@
#include "simple_render_system.hpp"
#include "xe_device.hpp"
#include <vector>
#include <vulkan/vulkan_core.h>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <array>
#include <cassert>
#include <stdexcept>
namespace xe {
struct SimplePushConstantData {
glm::mat4 modelMatrix{1.f};
glm::mat4 normalMatrix{1.f};
};
SimpleRenderSystem::SimpleRenderSystem(XeDevice& device, VkRenderPass renderPass, VkDescriptorSetLayout globalSetLayout) : xeDevice{device} {
createPipelineLayout(globalSetLayout);
createPipeline(renderPass);
}
SimpleRenderSystem::~SimpleRenderSystem() { vkDestroyPipelineLayout(xeDevice.device(), pipelineLayout, nullptr); }
void SimpleRenderSystem::createPipelineLayout(VkDescriptorSetLayout globalSetLayout) {
VkPushConstantRange pushConstantRange;
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(SimplePushConstantData);
std::vector<VkDescriptorSetLayout> descriptorSetLayouts{globalSetLayout};
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = static_cast<uint32_t>(descriptorSetLayouts.size());
pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data();
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
if(vkCreatePipelineLayout(xeDevice.device(), &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
std::runtime_error("failed to create pipeline layout!");
}
}
void SimpleRenderSystem::createPipeline(VkRenderPass renderPass) {
assert(pipelineLayout != nullptr && "Canor create pipeline before pipeline layout");
PipelineConfigInfo pipelineConfig{};
XePipeline::defaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.renderPass = renderPass;
pipelineConfig.pipelineLayout = pipelineLayout;
xePipeline = std::make_unique<XePipeline>(
xeDevice,
"res/shaders/simple_shader.vert.spv",
"res/shaders/simple_shader.frag.spv",
pipelineConfig
);
}
void SimpleRenderSystem::renderGameObjects(XeFrameInfo &frameInfo, std::vector<XeGameObject> &gameObjects) {
xePipeline->bind(frameInfo.commandBuffer);
vkCmdBindDescriptorSets(
frameInfo.commandBuffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout,
0,
1,
&frameInfo.globalDescriptorSet,
0,
nullptr);
for (auto& obj: gameObjects) {
SimplePushConstantData push{};
push.modelMatrix = obj.transform.mat4();
push.normalMatrix = obj.transform.normalMatrix();
vkCmdPushConstants(
frameInfo.commandBuffer,
pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
0,
sizeof(SimplePushConstantData),
&push);
obj.model->bind(frameInfo.commandBuffer);
obj.model->draw(frameInfo.commandBuffer);
}
}
}

View file

@ -0,0 +1,37 @@
#pragma once
#include "xe_camera.hpp"
#include "xe_pipeline.hpp"
#include "xe_device.hpp"
#include "xe_game_object.hpp"
#include "xe_frame_info.hpp"
#include <memory>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace xe {
class SimpleRenderSystem {
public:
SimpleRenderSystem(XeDevice& device, VkRenderPass renderPass, VkDescriptorSetLayout globalSetLayout);
~SimpleRenderSystem();
SimpleRenderSystem(const SimpleRenderSystem &) = delete;
SimpleRenderSystem operator=(const SimpleRenderSystem &) = delete;
void renderGameObjects(
XeFrameInfo &frameInfo,
std::vector<XeGameObject> &gameObjects
);
private:
void createPipelineLayout(VkDescriptorSetLayout globalSetLayout);
void createPipeline(VkRenderPass renderPass);
XeDevice& xeDevice;
std::unique_ptr<XePipeline> xePipeline;
VkPipelineLayout pipelineLayout;
};
}