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