stanford dragon rendering
This commit is contained in:
commit
8045b8ba04
41 changed files with 256370 additions and 0 deletions
1
.env.example
Normal file
1
.env.example
Normal file
|
@ -0,0 +1 @@
|
||||||
|
VULKAN_SDK=/home/tylerm/Documents/Vulkan
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.vscode
|
||||||
|
bin
|
||||||
|
.env
|
||||||
|
*.o
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal 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
59
Makefile
Normal 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
103
engine/xe_buffer.cpp
Normal 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
60
engine/xe_buffer.hpp
Normal 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
79
engine/xe_camera.cpp
Normal 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
37
engine/xe_camera.hpp
Normal 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
183
engine/xe_descriptors.cpp
Normal 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
104
engine/xe_descriptors.hpp
Normal 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
534
engine/xe_device.cpp
Executable 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, ©Region);
|
||||||
|
|
||||||
|
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,
|
||||||
|
®ion);
|
||||||
|
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
105
engine/xe_device.hpp
Executable 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
17
engine/xe_frame_info.hpp
Normal 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
64
engine/xe_game_object.cpp
Normal 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
50
engine/xe_game_object.hpp
Normal 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
193
engine/xe_model.cpp
Normal 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
63
engine/xe_model.hpp
Normal 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
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
204
engine/xe_pipeline.cpp
Executable 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
61
engine/xe_pipeline.hpp
Executable 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
156
engine/xe_renderer.cpp
Normal 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(¤tImageIndex);
|
||||||
|
|
||||||
|
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, ¤tImageIndex);
|
||||||
|
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
55
engine/xe_renderer.hpp
Normal 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
424
engine/xe_swap_chain.cpp
Executable 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
88
engine/xe_swap_chain.hpp
Executable 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
11
engine/xe_utils.hpp
Normal 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
41
engine/xe_window.cpp
Executable 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
38
engine/xe_window.hpp
Executable 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
1
lib/glfw
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit d299d9f78857e921b66bdab42c7ea27fe2e31810
|
1
lib/glm
Submodule
1
lib/glm
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit cc98465e3508535ba8c7f6208df934c156a018dc
|
249866
res/models/stanford-dragon.obj
Normal file
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
19
res/shaders/simple_shader.frag
Executable 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);
|
||||||
|
}
|
BIN
res/shaders/simple_shader.frag.spv
Normal file
BIN
res/shaders/simple_shader.frag.spv
Normal file
Binary file not shown.
30
res/shaders/simple_shader.vert
Executable file
30
res/shaders/simple_shader.vert
Executable 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;
|
||||||
|
}
|
BIN
res/shaders/simple_shader.vert.spv
Normal file
BIN
res/shaders/simple_shader.vert.spv
Normal file
Binary file not shown.
125
src/first_app.cpp
Executable file
125
src/first_app.cpp
Executable 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
36
src/first_app.hpp
Executable 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;
|
||||||
|
};
|
||||||
|
}
|
42
src/keyboard_movement_controller.cpp
Normal file
42
src/keyboard_movement_controller.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/keyboard_movement_controller.hpp
Normal file
31
src/keyboard_movement_controller.hpp
Normal 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
18
src/main.cpp
Executable 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;
|
||||||
|
}
|
94
src/simple_render_system.cpp
Normal file
94
src/simple_render_system.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/simple_render_system.hpp
Normal file
37
src/simple_render_system.hpp
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue