diff options
author | tylermurphy534 <tylermurphy534@gmail.com> | 2022-09-18 21:20:51 -0400 |
---|---|---|
committer | tylermurphy534 <tylermurphy534@gmail.com> | 2022-09-18 21:20:51 -0400 |
commit | 8045b8ba04aae39a4cf9733e72413f648b6ebe2b (patch) | |
tree | f90a9bd50a2316d5077df99c9e8584afc76ed656 /engine | |
download | minecraftvulkan-8045b8ba04aae39a4cf9733e72413f648b6ebe2b.tar.gz minecraftvulkan-8045b8ba04aae39a4cf9733e72413f648b6ebe2b.tar.bz2 minecraftvulkan-8045b8ba04aae39a4cf9733e72413f648b6ebe2b.zip |
stanford dragon rendering
Diffstat (limited to 'engine')
-rw-r--r-- | engine/xe_buffer.cpp | 103 | ||||
-rw-r--r-- | engine/xe_buffer.hpp | 60 | ||||
-rw-r--r-- | engine/xe_camera.cpp | 79 | ||||
-rw-r--r-- | engine/xe_camera.hpp | 37 | ||||
-rw-r--r-- | engine/xe_descriptors.cpp | 183 | ||||
-rw-r--r-- | engine/xe_descriptors.hpp | 104 | ||||
-rwxr-xr-x | engine/xe_device.cpp | 534 | ||||
-rwxr-xr-x | engine/xe_device.hpp | 105 | ||||
-rw-r--r-- | engine/xe_frame_info.hpp | 17 | ||||
-rw-r--r-- | engine/xe_game_object.cpp | 64 | ||||
-rw-r--r-- | engine/xe_game_object.hpp | 50 | ||||
-rw-r--r-- | engine/xe_model.cpp | 193 | ||||
-rw-r--r-- | engine/xe_model.hpp | 63 | ||||
-rw-r--r-- | engine/xe_obj_loader.hpp | 3330 | ||||
-rwxr-xr-x | engine/xe_pipeline.cpp | 204 | ||||
-rwxr-xr-x | engine/xe_pipeline.hpp | 61 | ||||
-rw-r--r-- | engine/xe_renderer.cpp | 156 | ||||
-rw-r--r-- | engine/xe_renderer.hpp | 55 | ||||
-rwxr-xr-x | engine/xe_swap_chain.cpp | 424 | ||||
-rwxr-xr-x | engine/xe_swap_chain.hpp | 88 | ||||
-rw-r--r-- | engine/xe_utils.hpp | 11 | ||||
-rwxr-xr-x | engine/xe_window.cpp | 41 | ||||
-rwxr-xr-x | engine/xe_window.hpp | 38 |
23 files changed, 6000 insertions, 0 deletions
diff --git a/engine/xe_buffer.cpp b/engine/xe_buffer.cpp new file mode 100644 index 0000000..0c0006f --- /dev/null +++ b/engine/xe_buffer.cpp @@ -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); +} + +} + diff --git a/engine/xe_buffer.hpp b/engine/xe_buffer.hpp new file mode 100644 index 0000000..dbbb13f --- /dev/null +++ b/engine/xe_buffer.hpp @@ -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; +}; + +} + diff --git a/engine/xe_camera.cpp b/engine/xe_camera.cpp new file mode 100644 index 0000000..2174a24 --- /dev/null +++ b/engine/xe_camera.cpp @@ -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); +} + +}
\ No newline at end of file diff --git a/engine/xe_camera.hpp b/engine/xe_camera.hpp new file mode 100644 index 0000000..00711cd --- /dev/null +++ b/engine/xe_camera.hpp @@ -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}; + +}; + +}
\ No newline at end of file diff --git a/engine/xe_descriptors.cpp b/engine/xe_descriptors.cpp new file mode 100644 index 0000000..ef6dc2c --- /dev/null +++ b/engine/xe_descriptors.cpp @@ -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); +} + +}
\ No newline at end of file diff --git a/engine/xe_descriptors.hpp b/engine/xe_descriptors.hpp new file mode 100644 index 0000000..6e7950e --- /dev/null +++ b/engine/xe_descriptors.hpp @@ -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; +}; + +} + diff --git a/engine/xe_device.cpp b/engine/xe_device.cpp new file mode 100755 index 0000000..fb2bb7b --- /dev/null +++ b/engine/xe_device.cpp @@ -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!"); + } +} + +}
\ No newline at end of file diff --git a/engine/xe_device.hpp b/engine/xe_device.hpp new file mode 100755 index 0000000..0906123 --- /dev/null +++ b/engine/xe_device.hpp @@ -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}; +}; + +}
\ No newline at end of file diff --git a/engine/xe_frame_info.hpp b/engine/xe_frame_info.hpp new file mode 100644 index 0000000..d6cf7cb --- /dev/null +++ b/engine/xe_frame_info.hpp @@ -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; +}; + +}
\ No newline at end of file diff --git a/engine/xe_game_object.cpp b/engine/xe_game_object.cpp new file mode 100644 index 0000000..3a59019 --- /dev/null +++ b/engine/xe_game_object.cpp @@ -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), + } + }; +} + +}
\ No newline at end of file diff --git a/engine/xe_game_object.hpp b/engine/xe_game_object.hpp new file mode 100644 index 0000000..7adbbed --- /dev/null +++ b/engine/xe_game_object.hpp @@ -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; +}; + +}
\ No newline at end of file diff --git a/engine/xe_model.cpp b/engine/xe_model.cpp new file mode 100644 index 0000000..e6835a6 --- /dev/null +++ b/engine/xe_model.cpp @@ -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]); + } + } +} + +}
\ No newline at end of file diff --git a/engine/xe_model.hpp b/engine/xe_model.hpp new file mode 100644 index 0000000..2ffe298 --- /dev/null +++ b/engine/xe_model.hpp @@ -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; +}; + +}
\ No newline at end of file diff --git a/engine/xe_obj_loader.hpp b/engine/xe_obj_loader.hpp new file mode 100644 index 0000000..7938e16 --- /dev/null +++ b/engine/xe_obj_loader.hpp @@ -0,0 +1,3330 @@ +/* +The MIT License (MIT) + +Copyright (c) 2012-Present, Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// +// version 2.0.0 : Add new object oriented API. 1.x API is still provided. +// * Support line primitive. +// * Support points primitive. +// * Support multiple search path for .mtl(v1 API). +// * Support vertex weight `vw`(as an tinyobj extension) +// * Support escaped whitespece in mtllib +// * Add robust triangulation using Mapbox earcut(TINYOBJLOADER_USE_MAPBOX_EARCUT). +// version 1.4.0 : Modifed ParseTextureNameAndOption API +// version 1.3.1 : Make ParseTextureNameAndOption API public +// version 1.3.0 : Separate warning and error message(breaking API of LoadObj) +// version 1.2.3 : Added color space extension('-colorspace') to tex opts. +// version 1.2.2 : Parse multiple group names. +// version 1.2.1 : Added initial support for line('l') primitive(PR #178) +// version 1.2.0 : Hardened implementation(#175) +// version 1.1.1 : Support smoothing groups(#162) +// version 1.1.0 : Support parsing vertex color(#144) +// version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) +// version 1.0.7 : Support multiple tex options(#126) +// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) +// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) +// version 1.0.4 : Support multiple filenames for 'mtllib'(#112) +// version 1.0.3 : Support parsing texture options(#85) +// version 1.0.2 : Improve parsing speed by about a factor of 2 for large +// files(#105) +// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) +// version 1.0.0 : Change data structure. Change license from BSD to MIT. +// + +// +// Use this in *one* .cc +// #define TINYOBJLOADER_IMPLEMENTATION +// #include "tiny_obj_loader.h" +// + +#ifndef TINY_OBJ_LOADER_H_ +#define TINY_OBJ_LOADER_H_ + +#include <map> +#include <string> +#include <vector> + +namespace tinyobj { + +// TODO(syoyo): Better C++11 detection for older compiler +#if __cplusplus > 199711L +#define TINYOBJ_OVERRIDE override +#else +#define TINYOBJ_OVERRIDE +#endif + +#ifdef __clang__ +#pragma clang diagnostic push + +#pragma clang diagnostic ignored "-Wpadded" + +#endif + +// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... +// +// -blendu on | off # set horizontal texture blending +// (default on) +// -blendv on | off # set vertical texture blending +// (default on) +// -boost real_value # boost mip-map sharpness +// -mm base_value gain_value # modify texture map values (default +// 0 1) +// # base_value = brightness, +// gain_value = contrast +// -o u [v [w]] # Origin offset (default +// 0 0 0) +// -s u [v [w]] # Scale (default +// 1 1 1) +// -t u [v [w]] # Turbulence (default +// 0 0 0) +// -texres resolution # texture resolution to create +// -clamp on | off # only render texels in the clamped +// 0-1 range (default off) +// # When unclamped, textures are +// repeated across a surface, +// # when clamped, only texels which +// fall within the 0-1 +// # range are rendered. +// -bm mult_value # bump multiplier (for bump maps +// only) +// +// -imfchan r | g | b | m | l | z # specifies which channel of the file +// is used to +// # create a scalar or bump texture. +// r:red, g:green, +// # b:blue, m:matte, l:luminance, +// z:z-depth.. +// # (the default for bump is 'l' and +// for decal is 'm') +// bump -imfchan r bumpmap.tga # says to use the red channel of +// bumpmap.tga as the bumpmap +// +// For reflection maps... +// +// -type sphere # specifies a sphere for a "refl" +// reflection map +// -type cube_top | cube_bottom | # when using a cube map, the texture +// file for each +// cube_front | cube_back | # side of the cube is specified +// separately +// cube_left | cube_right +// +// TinyObjLoader extension. +// +// -colorspace SPACE # Color space of the texture. e.g. +// 'sRGB` or 'linear' +// + +#ifdef TINYOBJLOADER_USE_DOUBLE +//#pragma message "using double" +typedef double real_t; +#else +//#pragma message "using float" +typedef float real_t; +#endif + +typedef enum { + TEXTURE_TYPE_NONE, // default + TEXTURE_TYPE_SPHERE, + TEXTURE_TYPE_CUBE_TOP, + TEXTURE_TYPE_CUBE_BOTTOM, + TEXTURE_TYPE_CUBE_FRONT, + TEXTURE_TYPE_CUBE_BACK, + TEXTURE_TYPE_CUBE_LEFT, + TEXTURE_TYPE_CUBE_RIGHT +} texture_type_t; + +struct texture_option_t { + texture_type_t type; // -type (default TEXTURE_TYPE_NONE) + real_t sharpness; // -boost (default 1.0?) + real_t brightness; // base_value in -mm option (default 0) + real_t contrast; // gain_value in -mm option (default 1) + real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0) + real_t scale[3]; // -s u [v [w]] (default 1 1 1) + real_t turbulence[3]; // -t u [v [w]] (default 0 0 0) + int texture_resolution; // -texres resolution (No default value in the spec. + // We'll use -1) + bool clamp; // -clamp (default false) + char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') + bool blendu; // -blendu (default on) + bool blendv; // -blendv (default on) + real_t bump_multiplier; // -bm (for bump maps only, default 1.0) + + // extension + std::string colorspace; // Explicitly specify color space of stored texel + // value. Usually `sRGB` or `linear` (default empty). +}; + +struct material_t { + std::string name; + + real_t ambient[3]; + real_t diffuse[3]; + real_t specular[3]; + real_t transmittance[3]; + real_t emission[3]; + real_t shininess; + real_t ior; // index of refraction + real_t dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + int dummy; // Suppress padding warning. + + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, map_Bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + std::string reflection_texname; // refl + + texture_option_t ambient_texopt; + texture_option_t diffuse_texopt; + texture_option_t specular_texopt; + texture_option_t specular_highlight_texopt; + texture_option_t bump_texopt; + texture_option_t displacement_texopt; + texture_option_t alpha_texopt; + texture_option_t reflection_texopt; + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + real_t roughness; // [0, 1] default 0 + real_t metallic; // [0, 1] default 0 + real_t sheen; // [0, 1] default 0 + real_t clearcoat_thickness; // [0, 1] default 0 + real_t clearcoat_roughness; // [0, 1] default 0 + real_t anisotropy; // aniso. [0, 1] default 0 + real_t anisotropy_rotation; // anisor. [0, 1] default 0 + real_t pad0; + std::string roughness_texname; // map_Pr + std::string metallic_texname; // map_Pm + std::string sheen_texname; // map_Ps + std::string emissive_texname; // map_Ke + std::string normal_texname; // norm. For normal mapping. + + texture_option_t roughness_texopt; + texture_option_t metallic_texopt; + texture_option_t sheen_texopt; + texture_option_t emissive_texopt; + texture_option_t normal_texopt; + + int pad2; + + std::map<std::string, std::string> unknown_parameter; + +#ifdef TINY_OBJ_LOADER_PYTHON_BINDING + // For pybind11 + std::array<double, 3> GetDiffuse() { + std::array<double, 3> values; + values[0] = double(diffuse[0]); + values[1] = double(diffuse[1]); + values[2] = double(diffuse[2]); + + return values; + } + + std::array<double, 3> GetSpecular() { + std::array<double, 3> values; + values[0] = double(specular[0]); + values[1] = double(specular[1]); + values[2] = double(specular[2]); + + return values; + } + + std::array<double, 3> GetTransmittance() { + std::array<double, 3> values; + values[0] = double(transmittance[0]); + values[1] = double(transmittance[1]); + values[2] = double(transmittance[2]); + + return values; + } + + std::array<double, 3> GetEmission() { + std::array<double, 3> values; + values[0] = double(emission[0]); + values[1] = double(emission[1]); + values[2] = double(emission[2]); + + return values; + } + + std::array<double, 3> GetAmbient() { + std::array<double, 3> values; + values[0] = double(ambient[0]); + values[1] = double(ambient[1]); + values[2] = double(ambient[2]); + + return values; + } + + void SetDiffuse(std::array<double, 3> &a) { + diffuse[0] = real_t(a[0]); + diffuse[1] = real_t(a[1]); + diffuse[2] = real_t(a[2]); + } + + void SetAmbient(std::array<double, 3> &a) { + ambient[0] = real_t(a[0]); + ambient[1] = real_t(a[1]); + ambient[2] = real_t(a[2]); + } + + void SetSpecular(std::array<double, 3> &a) { + specular[0] = real_t(a[0]); + specular[1] = real_t(a[1]); + specular[2] = real_t(a[2]); + } + + void SetTransmittance(std::array<double, 3> &a) { + transmittance[0] = real_t(a[0]); + transmittance[1] = real_t(a[1]); + transmittance[2] = real_t(a[2]); + } + + std::string GetCustomParameter(const std::string &key) { + std::map<std::string, std::string>::const_iterator it = + unknown_parameter.find(key); + + if (it != unknown_parameter.end()) { + return it->second; + } + return std::string(); + } + +#endif +}; + +struct tag_t { + std::string name; + + std::vector<int> intValues; + std::vector<real_t> floatValues; + std::vector<std::string> stringValues; +}; + +struct joint_and_weight_t { + int joint_id; + real_t weight; +}; + +struct skin_weight_t { + int vertex_id; // Corresponding vertex index in `attrib_t::vertices`. + // Compared to `index_t`, this index must be positive and + // start with 0(does not allow relative indexing) + std::vector<joint_and_weight_t> weightValues; +}; + +// Index struct to support different indices for vtx/normal/texcoord. +// -1 means not used. +struct index_t { + int vertex_index; + int normal_index; + int texcoord_index; +}; + +struct mesh_t { + std::vector<index_t> indices; + std::vector<unsigned char> + num_face_vertices; // The number of vertices per + // face. 3 = triangle, 4 = quad, + // ... Up to 255 vertices per face. + std::vector<int> material_ids; // per-face material ID + std::vector<unsigned int> smoothing_group_ids; // per-face smoothing group + // ID(0 = off. positive value + // = group id) + std::vector<tag_t> tags; // SubD tag +}; + +// struct path_t { +// std::vector<int> indices; // pairs of indices for lines +//}; + +struct lines_t { + // Linear flattened indices. + std::vector<index_t> indices; // indices for vertices(poly lines) + std::vector<int> num_line_vertices; // The number of vertices per line. +}; + +struct points_t { + std::vector<index_t> indices; // indices for points +}; + +struct shape_t { + std::string name; + mesh_t mesh; + lines_t lines; + points_t points; +}; + +// Vertex attributes +struct attrib_t { + std::vector<real_t> vertices; // 'v'(xyz) + + // For backward compatibility, we store vertex weight in separate array. + std::vector<real_t> vertex_weights; // 'v'(w) + std::vector<real_t> normals; // 'vn' + std::vector<real_t> texcoords; // 'vt'(uv) + + // For backward compatibility, we store texture coordinate 'w' in separate + // array. + std::vector<real_t> texcoord_ws; // 'vt'(w) + std::vector<real_t> colors; // extension: vertex colors + + // + // TinyObj extension. + // + + // NOTE(syoyo): array index is based on the appearance order. + // To get a corresponding skin weight for a specific vertex id `vid`, + // Need to reconstruct a look up table: `skin_weight_t::vertex_id` == `vid` + // (e.g. using std::map, std::unordered_map) + std::vector<skin_weight_t> skin_weights; + + attrib_t() {} + + // + // For pybind11 + // + const std::vector<real_t> &GetVertices() const { return vertices; } + + const std::vector<real_t> &GetVertexWeights() const { return vertex_weights; } +}; + +struct callback_t { + // W is optional and set to 1 if there is no `w` item in `v` line + void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w); + void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z); + + // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in + // `vt` line. + void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z); + + // called per 'f' line. num_indices is the number of face indices(e.g. 3 for + // triangle, 4 for quad) + // 0 will be passed for undefined index in index_t members. + void (*index_cb)(void *user_data, index_t *indices, int num_indices); + // `name` material name, `material_id` = the array index of material_t[]. -1 + // if + // a material not found in .mtl + void (*usemtl_cb)(void *user_data, const char *name, int material_id); + // `materials` = parsed material data. + void (*mtllib_cb)(void *user_data, const material_t *materials, + int num_materials); + // There may be multiple group names + void (*group_cb)(void *user_data, const char **names, int num_names); + void (*object_cb)(void *user_data, const char *name); + + callback_t() + : vertex_cb(NULL), + normal_cb(NULL), + texcoord_cb(NULL), + index_cb(NULL), + usemtl_cb(NULL), + mtllib_cb(NULL), + group_cb(NULL), + object_cb(NULL) {} +}; + +class MaterialReader { + public: + MaterialReader() {} + virtual ~MaterialReader(); + + virtual bool operator()(const std::string &matId, + std::vector<material_t> *materials, + std::map<std::string, int> *matMap, std::string *warn, + std::string *err) = 0; +}; + +/// +/// Read .mtl from a file. +/// +class MaterialFileReader : public MaterialReader { + public: + // Path could contain separator(';' in Windows, ':' in Posix) + explicit MaterialFileReader(const std::string &mtl_basedir) + : m_mtlBaseDir(mtl_basedir) {} + virtual ~MaterialFileReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector<material_t> *materials, + std::map<std::string, int> *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::string m_mtlBaseDir; +}; + +/// +/// Read .mtl from a stream. +/// +class MaterialStreamReader : public MaterialReader { + public: + explicit MaterialStreamReader(std::istream &inStream) + : m_inStream(inStream) {} + virtual ~MaterialStreamReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector<material_t> *materials, + std::map<std::string, int> *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::istream &m_inStream; +}; + +// v2 API +struct ObjReaderConfig { + bool triangulate; // triangulate polygon? + + // Currently not used. + // "simple" or empty: Create triangle fan + // "earcut": Use the algorithm based on Ear clipping + std::string triangulation_method; + + /// Parse vertex color. + /// If vertex color is not present, its filled with default value. + /// false = no vertex color + /// This will increase memory of parsed .obj + bool vertex_color; + + /// + /// Search path to .mtl file. + /// Default = "" = search from the same directory of .obj file. + /// Valid only when loading .obj from a file. + /// + std::string mtl_search_path; + + ObjReaderConfig() + : triangulate(true), triangulation_method("simple"), vertex_color(true) {} +}; + +/// +/// Wavefront .obj reader class(v2 API) +/// +class ObjReader { + public: + ObjReader() : valid_(false) {} + + /// + /// Load .obj and .mtl from a file. + /// + /// @param[in] filename wavefront .obj filename + /// @param[in] config Reader configuration + /// + bool ParseFromFile(const std::string &filename, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// Parse .obj from a text string. + /// Need to supply .mtl text string by `mtl_text`. + /// This function ignores `mtllib` line in .obj text. + /// + /// @param[in] obj_text wavefront .obj filename + /// @param[in] mtl_text wavefront .mtl filename + /// @param[in] config Reader configuration + /// + bool ParseFromString(const std::string &obj_text, const std::string &mtl_text, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// .obj was loaded or parsed correctly. + /// + bool Valid() const { return valid_; } + + const attrib_t &GetAttrib() const { return attrib_; } + + const std::vector<shape_t> &GetShapes() const { return shapes_; } + + const std::vector<material_t> &GetMaterials() const { return materials_; } + + /// + /// Warning message(may be filled after `Load` or `Parse`) + /// + const std::string &Warning() const { return warning_; } + + /// + /// Error message(filled when `Load` or `Parse` failed) + /// + const std::string &Error() const { return error_; } + + private: + bool valid_; + + attrib_t attrib_; + std::vector<shape_t> shapes_; + std::vector<material_t> materials_; + + std::string warning_; + std::string error_; +}; + +/// ==>>========= Legacy v1 API ============================================= + +/// Loads .obj from a file. +/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data +/// 'shapes' will be filled with parsed shape data +/// Returns true when loading .obj become success. +/// Returns warning message into `warn`, and error message into `err` +/// 'mtl_basedir' is optional, and used for base directory for .mtl file. +/// In default(`NULL'), .mtl file is searched from an application's working +/// directory. +/// 'triangulate' is optional, and used whether triangulate polygon face in .obj +/// or not. +/// Option 'default_vcols_fallback' specifies whether vertex colors should +/// always be defined, even if no colors are given (fallback to white). +bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, + std::vector<material_t> *materials, std::string *warn, + std::string *err, const char *filename, + const char *mtl_basedir = NULL, bool triangulate = true, + bool default_vcols_fallback = true); + +/// Loads .obj from a file with custom user callback. +/// .mtl is loaded as usual and parsed material_t data will be passed to +/// `callback.mtllib_cb`. +/// Returns true when loading .obj/.mtl become success. +/// Returns warning message into `warn`, and error message into `err` +/// See `examples/callback_api/` for how to use this function. +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data = NULL, + MaterialReader *readMatFn = NULL, + std::string *warn = NULL, std::string *err = NULL); + +/// Loads object from a std::istream, uses `readMatFn` to retrieve +/// std::istream for materials. +/// Returns true when loading .obj become success. +/// Returns warning and error message into `err` +bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, + std::vector<material_t> *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn = NULL, bool triangulate = true, + bool default_vcols_fallback = true); + +/// Loads materials into std::map +void LoadMtl(std::map<std::string, int> *material_map, + std::vector<material_t> *materials, std::istream *inStream, + std::string *warning, std::string *err); + +/// +/// Parse texture name and texture option for custom texture parameter through +/// material::unknown_parameter +/// +/// @param[out] texname Parsed texture name +/// @param[out] texopt Parsed texopt +/// @param[in] linebuf Input string +/// +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf); + +/// =<<========== Legacy v1 API ============================================= + +} // namespace tinyobj + +#endif // TINY_OBJ_LOADER_H_ + +#ifdef TINYOBJLOADER_IMPLEMENTATION +#include <cassert> +#include <cctype> +#include <cmath> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <limits> +#include <set> +#include <sstream> +#include <utility> + +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + +#ifdef TINYOBJLOADER_DONOT_INCLUDE_MAPBOX_EARCUT +// Assume earcut.hpp is included outside of tiny_obj_loader.h +#else + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include <array> +#include "mapbox/earcut.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#endif // TINYOBJLOADER_USE_MAPBOX_EARCUT + +namespace tinyobj { + +MaterialReader::~MaterialReader() {} + +struct vertex_index_t { + int v_idx, vt_idx, vn_idx; + vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} + vertex_index_t(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} +}; + +// Internal data structure for face representation +// index + smoothing group. +struct face_t { + unsigned int + smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. + int pad_; + std::vector<vertex_index_t> vertex_indices; // face vertex indices. + + face_t() : smoothing_group_id(0), pad_(0) {} +}; + +// Internal data structure for line representation +struct __line_t { + // l v1/vt1 v2/vt2 ... + // In the specification, line primitrive does not have normal index, but + // TinyObjLoader allow it + std::vector<vertex_index_t> vertex_indices; +}; + +// Internal data structure for points representation +struct __points_t { + // p v1 v2 ... + // In the specification, point primitrive does not have normal index and + // texture coord index, but TinyObjLoader allow it. + std::vector<vertex_index_t> vertex_indices; +}; + +struct tag_sizes { + tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} + int num_ints; + int num_reals; + int num_strings; +}; + +struct obj_shape { + std::vector<real_t> v; + std::vector<real_t> vn; + std::vector<real_t> vt; +}; + +// +// Manages group of primitives(face, line, points, ...) +struct PrimGroup { + std::vector<face_t> faceGroup; + std::vector<__line_t> lineGroup; + std::vector<__points_t> pointsGroup; + + void clear() { + faceGroup.clear(); + lineGroup.clear(); + pointsGroup.clear(); + } + + bool IsEmpty() const { + return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); + } + + // TODO(syoyo): bspline, surface, ... +}; + +// See +// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf +static std::istream &safeGetline(std::istream &is, std::string &t) { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf *sb = is.rdbuf(); + + if (se) { + for (;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if (sb->sgetc() == '\n') sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if (t.empty()) is.setstate(std::ios::eofbit); + return is; + default: + t += static_cast<char>(c); + } + } + } + + return is; +} + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +// Make index zero-base, and also support relative index. +static inline bool fixIndex(int idx, int n, int *ret) { + if (!ret) { + return false; + } + + if (idx > 0) { + (*ret) = idx - 1; + return true; + } + + if (idx == 0) { + // zero is not allowed according to the spec. + return false; + } + + if (idx < 0) { + (*ret) = n + idx; // negative value = relative + return true; + } + + return false; // never reach here. +} + +static inline std::string parseString(const char **token) { + std::string s; + (*token) += strspn((*token), " \t"); + size_t e = strcspn((*token), " \t\r"); + s = std::string((*token), &(*token)[e]); + (*token) += e; + return s; +} + +static inline int parseInt(const char **token) { + (*token) += strspn((*token), " \t"); + int i = atoi((*token)); + (*token) += strcspn((*token), " \t\r"); + return i; +} + +// Tries to parse a floating point number located at s. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// The following situations triggers a failure: +// - s >= s_end. +// - parse failure. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) { + if (s >= s_end) { + return false; + } + + double mantissa = 0.0; + // This exponent is base 2 rather than 10. + // However the exponent we parse is supposed to be one of ten, + // thus we must take care to convert the exponent/and or the + // mantissa to a * 2^E, where a is the mantissa and E is the + // exponent. + // To get the final double we will use ldexp, it requires the + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // How many characters were read in a loop. + int read = 0; + // Tells whether a loop terminated due to reaching s_end. + bool end_not_reached = false; + bool leading_decimal_dots = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + if ((curr != s_end) && (*curr == '.')) { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else if (*curr == '.') { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + if (!leading_decimal_dots) { + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast<int>(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // We must make sure we actually got something. + if (read == 0) goto fail; + } + + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + static const double pow_lut[] = { + 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, + }; + const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; + + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast<int>(*curr - 0x30) * + (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') { + curr++; + // Figure out if a sign is present and if it is. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // To avoid annoying MSVC's min/max macro definiton, + // Use hardcoded int max value + if (exponent > (2147483647/10)) { // 2147483647 = std::numeric_limits<int>::max() + // Integer overflow + goto fail; + } + exponent *= 10; + exponent += static_cast<int>(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble: + *result = (sign == '+' ? 1 : -1) * + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) + : mantissa); + return true; +fail: + return false; +} + +static inline real_t parseReal(const char **token, double default_value = 0.0) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val = default_value; + tryParseDouble((*token), end, &val); + real_t f = static_cast<real_t>(val); + (*token) = end; + return f; +} + +static inline bool parseReal(const char **token, real_t *out) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val; + bool ret = tryParseDouble((*token), end, &val); + if (ret) { + real_t f = static_cast<real_t>(val); + (*out) = f; + } + (*token) = end; + return ret; +} + +static inline void parseReal2(real_t *x, real_t *y, const char **token, + const double default_x = 0.0, + const double default_y = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); +} + +static inline void parseReal3(real_t *x, real_t *y, real_t *z, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); +} + +static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0, + const double default_w = 1.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + (*w) = parseReal(token, default_w); +} + +// Extension: parse vertex with colors(6 items) +static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, + real_t *r, real_t *g, real_t *b, + const char **token, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + + const bool found_color = + parseReal(token, r) && parseReal(token, g) && parseReal(token, b); + + if (!found_color) { + (*r) = (*g) = (*b) = 1.0; + } + + return found_color; +} + +static inline bool parseOnOff(const char **token, bool default_value = true) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + + bool ret = default_value; + if ((0 == strncmp((*token), "on", 2))) { + ret = true; + } else if ((0 == strncmp((*token), "off", 3))) { + ret = false; + } + + (*token) = end; + return ret; +} + +static inline texture_type_t parseTextureType( + const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + texture_type_t ty = default_value; + + if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) { + ty = TEXTURE_TYPE_CUBE_TOP; + } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) { + ty = TEXTURE_TYPE_CUBE_BOTTOM; + } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) { + ty = TEXTURE_TYPE_CUBE_LEFT; + } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) { + ty = TEXTURE_TYPE_CUBE_RIGHT; + } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) { + ty = TEXTURE_TYPE_CUBE_FRONT; + } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) { + ty = TEXTURE_TYPE_CUBE_BACK; + } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) { + ty = TEXTURE_TYPE_SPHERE; + } + + (*token) = end; + return ty; +} + +static tag_sizes parseTagTriple(const char **token) { + tag_sizes ts; + + (*token) += strspn((*token), " \t"); + ts.num_ints = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + + (*token)++; // Skip '/' + + (*token) += strspn((*token), " \t"); + ts.num_reals = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + (*token)++; // Skip '/' + + ts.num_strings = parseInt(token); + + return ts; +} + +// Parse triples with index offsets: i, i/j/k, i//k, i/j +static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, + vertex_index_t *ret) { + if (!ret) { + return false; + } + + vertex_index_t vi(-1); + + if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + (*ret) = vi; + return true; + } + + // i/j/k or i/j + if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + + // i/j/k + (*token)++; // skip '/' + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + + (*ret) = vi; + + return true; +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index_t parseRawTriple(const char **token) { + vertex_index_t vi(static_cast<int>(0)); // 0 is an invalid index in OBJ + + vi.v_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; +} + +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf) { + // @todo { write more robust lexer and parser. } + bool found_texname = false; + std::string texture_name; + + const char *token = linebuf; // Assume line ends with NULL + + while (!IS_NEW_LINE((*token))) { + token += strspn(token, " \t"); // skip space + if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendu = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendv = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->clamp = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->sharpness = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { + token += 4; + texopt->bump_multiplier = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), + &(texopt->origin_offset[2]), &token); + } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), + &token, 1.0, 1.0, 1.0); + } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), + &(texopt->turbulence[2]), &token); + } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { + token += 5; + texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); + } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) { + token += 7; + // TODO(syoyo): Check if arg is int type. + texopt->texture_resolution = parseInt(&token); + } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) { + token += 9; + token += strspn(token, " \t"); + const char *end = token + strcspn(token, " \t\r"); + if ((end - token) == 1) { // Assume one char for -imfchan + texopt->imfchan = (*token); + } + token = end; + } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { + token += 4; + parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); + } else if ((0 == strncmp(token, "-colorspace", 11)) && + IS_SPACE((token[11]))) { + token += 12; + texopt->colorspace = parseString(&token); + } else { +// Assume texture filename +#if 0 + size_t len = strcspn(token, " \t\r"); // untile next space + texture_name = std::string(token, token + len); + token += len; + + token += strspn(token, " \t"); // skip space +#else + // Read filename until line end to parse filename containing whitespace + // TODO(syoyo): Support parsing texture option flag after the filename. + texture_name = std::string(token); + token += texture_name.length(); +#endif + + found_texname = true; + } + } + + if (found_texname) { + (*texname) = texture_name; + return true; + } else { + return false; + } +} + +static void InitTexOpt(texture_option_t *texopt, const bool is_bump) { + if (is_bump) { + texopt->imfchan = 'l'; + } else { + texopt->imfchan = 'm'; + } + texopt->bump_multiplier = static_cast<real_t>(1.0); + texopt->clamp = false; + texopt->blendu = true; + texopt->blendv = true; + texopt->sharpness = static_cast<real_t>(1.0); + texopt->brightness = static_cast<real_t>(0.0); + texopt->contrast = static_cast<real_t>(1.0); + texopt->origin_offset[0] = static_cast<real_t>(0.0); + texopt->origin_offset[1] = static_cast<real_t>(0.0); + texopt->origin_offset[2] = static_cast<real_t>(0.0); + texopt->scale[0] = static_cast<real_t>(1.0); + texopt->scale[1] = static_cast<real_t>(1.0); + texopt->scale[2] = static_cast<real_t>(1.0); + texopt->turbulence[0] = static_cast<real_t>(0.0); + texopt->turbulence[1] = static_cast<real_t>(0.0); + texopt->turbulence[2] = static_cast<real_t>(0.0); + texopt->texture_resolution = -1; + texopt->type = TEXTURE_TYPE_NONE; +} + +static void InitMaterial(material_t *material) { + InitTexOpt(&material->ambient_texopt, /* is_bump */ false); + InitTexOpt(&material->diffuse_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false); + InitTexOpt(&material->bump_texopt, /* is_bump */ true); + InitTexOpt(&material->displacement_texopt, /* is_bump */ false); + InitTexOpt(&material->alpha_texopt, /* is_bump */ false); + InitTexOpt(&material->reflection_texopt, /* is_bump */ false); + InitTexOpt(&material->roughness_texopt, /* is_bump */ false); + InitTexOpt(&material->metallic_texopt, /* is_bump */ false); + InitTexOpt(&material->sheen_texopt, /* is_bump */ false); + InitTexOpt(&material->emissive_texopt, /* is_bump */ false); + InitTexOpt(&material->normal_texopt, + /* is_bump */ false); // @fixme { is_bump will be true? } + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->reflection_texname = ""; + material->alpha_texname = ""; + for (int i = 0; i < 3; i++) { + material->ambient[i] = static_cast<real_t>(0.0); + material->diffuse[i] = static_cast<real_t>(0.0); + material->specular[i] = static_cast<real_t>(0.0); + material->transmittance[i] = static_cast<real_t>(0.0); + material->emission[i] = static_cast<real_t>(0.0); + } + material->illum = 0; + material->dissolve = static_cast<real_t>(1.0); + material->shininess = static_cast<real_t>(1.0); + material->ior = static_cast<real_t>(1.0); + + material->roughness = static_cast<real_t>(0.0); + material->metallic = static_cast<real_t>(0.0); + material->sheen = static_cast<real_t>(0.0); + material->clearcoat_thickness = static_cast<real_t>(0.0); + material->clearcoat_roughness = static_cast<real_t>(0.0); + material->anisotropy_rotation = static_cast<real_t>(0.0); + material->anisotropy = static_cast<real_t>(0.0); + material->roughness_texname = ""; + material->metallic_texname = ""; + material->sheen_texname = ""; + material->emissive_texname = ""; + material->normal_texname = ""; + + material->unknown_parameter.clear(); +} + +// code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html +template <typename T> +static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) { + int i, j, c = 0; + for (i = 0, j = nvert - 1; i < nvert; j = i++) { + if (((verty[i] > testy) != (verty[j] > testy)) && + (testx < + (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + + vertx[i])) + c = !c; + } + return c; +} + +// TODO(syoyo): refactor function. +static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, + const std::vector<tag_t> &tags, + const int material_id, const std::string &name, + bool triangulate, const std::vector<real_t> &v, + std::string *warn) { + if (prim_group.IsEmpty()) { + return false; + } + + shape->name = name; + + // polygon + if (!prim_group.faceGroup.empty()) { + // Flatten vertices and indices + for (size_t i = 0; i < prim_group.faceGroup.size(); i++) { + const face_t &face = prim_group.faceGroup[i]; + + size_t npolys = face.vertex_indices.size(); + + if (npolys < 3) { + // Face must have 3+ vertices. + if (warn) { + (*warn) += "Degenerated face found\n."; + } + continue; + } + + if (triangulate) { + if (npolys == 4) { + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1 = face.vertex_indices[1]; + vertex_index_t i2 = face.vertex_indices[2]; + vertex_index_t i3 = face.vertex_indices[3]; + + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + size_t vi3 = size_t(i3.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size()) || ((3 * vi3 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + if (warn) { + (*warn) += "Face with invalid vertex index found.\n"; + } + continue; + } + + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t v3x = v[vi3 * 3 + 0]; + real_t v3y = v[vi3 * 3 + 1]; + real_t v3z = v[vi3 * 3 + 2]; + + // There are two candidates to split the quad into two triangles. + // + // Choose the shortest edge. + // TODO: Is it better to determine the edge to split by calculating + // the area of each triangle? + // + // +---+ + // |\ | + // | \ | + // | \| + // +---+ + // + // +---+ + // | /| + // | / | + // |/ | + // +---+ + + real_t e02x = v2x - v0x; + real_t e02y = v2y - v0y; + real_t e02z = v2z - v0z; + real_t e13x = v3x - v1x; + real_t e13y = v3y - v1y; + real_t e13z = v3z - v1z; + + real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z; + real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z; + + index_t idx0, idx1, idx2, idx3; + + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + idx3.vertex_index = i3.v_idx; + idx3.normal_index = i3.vn_idx; + idx3.texcoord_index = i3.vt_idx; + + if (sqr02 < sqr13) { + // [0, 1, 2], [0, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } else { + // [0, 1, 3], [1, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx3); + + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } + + // Two triangle faces + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.num_face_vertices.push_back(3); + + shape->mesh.material_ids.push_back(material_id); + shape->mesh.material_ids.push_back(material_id); + + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + + } else { + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1(-1); + vertex_index_t i2 = face.vertex_indices[1]; + + // find the two axes to work in + size_t axes[2] = {1, 2}; + for (size_t k = 0; k < npolys; ++k) { + i0 = face.vertex_indices[(k + 0) % npolys]; + i1 = face.vertex_indices[(k + 1) % npolys]; + i2 = face.vertex_indices[(k + 2) % npolys]; + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + continue; + } + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t e0x = v1x - v0x; + real_t e0y = v1y - v0y; + real_t e0z = v1z - v0z; + real_t e1x = v2x - v1x; + real_t e1y = v2y - v1y; + real_t e1z = v2z - v1z; + real_t cx = std::fabs(e0y * e1z - e0z * e1y); + real_t cy = std::fabs(e0z * e1x - e0x * e1z); + real_t cz = std::fabs(e0x * e1y - e0y * e1x); + const real_t epsilon = std::numeric_limits<real_t>::epsilon(); + // std::cout << "cx " << cx << ", cy " << cy << ", cz " << cz << + // "\n"; + if (cx > epsilon || cy > epsilon || cz > epsilon) { + // std::cout << "corner\n"; + // found a corner + if (cx > cy && cx > cz) { + // std::cout << "pattern0\n"; + } else { + // std::cout << "axes[0] = 0\n"; + axes[0] = 0; + if (cz > cx && cz > cy) { + // std::cout << "axes[1] = 1\n"; + axes[1] = 1; + } + } + break; + } + } + +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + using Point = std::array<real_t, 2>; + + // first polyline define the main polygon. + // following polylines define holes(not used in tinyobj). + std::vector<std::vector<Point> > polygon; + + std::vector<Point> polyline; + + // Fill polygon data(facevarying vertices). + for (size_t k = 0; k < npolys; k++) { + i0 = face.vertex_indices[k]; + size_t vi0 = size_t(i0.v_idx); + + assert(((3 * vi0 + 2) < v.size())); + + real_t v0x = v[vi0 * 3 + axes[0]]; + real_t v0y = v[vi0 * 3 + axes[1]]; + + polyline.push_back({v0x, v0y}); + } + + polygon.push_back(polyline); + std::vector<uint32_t> indices = mapbox::earcut<uint32_t>(polygon); + // => result = 3 * faces, clockwise + + assert(indices.size() % 3 == 0); + + // Reconstruct vertex_index_t + for (size_t k = 0; k < indices.size() / 3; k++) { + { + index_t idx0, idx1, idx2; + idx0.vertex_index = face.vertex_indices[indices[3 * k + 0]].v_idx; + idx0.normal_index = + face.vertex_indices[indices[3 * k + 0]].vn_idx; + idx0.texcoord_index = + face.vertex_indices[indices[3 * k + 0]].vt_idx; + idx1.vertex_index = face.vertex_indices[indices[3 * k + 1]].v_idx; + idx1.normal_index = + face.vertex_indices[indices[3 * k + 1]].vn_idx; + idx1.texcoord_index = + face.vertex_indices[indices[3 * k + 1]].vt_idx; + idx2.vertex_index = face.vertex_indices[indices[3 * k + 2]].v_idx; + idx2.normal_index = + face.vertex_indices[indices[3 * k + 2]].vn_idx; + idx2.texcoord_index = + face.vertex_indices[indices[3 * k + 2]].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } + +#else // Built-in ear clipping triangulation + + + face_t remainingFace = face; // copy + size_t guess_vert = 0; + vertex_index_t ind[3]; + real_t vx[3]; + real_t vy[3]; + + // How many iterations can we do without decreasing the remaining + // vertices. + size_t remainingIterations = face.vertex_indices.size(); + size_t previousRemainingVertices = + remainingFace.vertex_indices.size(); + + while (remainingFace.vertex_indices.size() > 3 && + remainingIterations > 0) { + // std::cout << "remainingIterations " << remainingIterations << + // "\n"; + + npolys = remainingFace.vertex_indices.size(); + if (guess_vert >= npolys) { + guess_vert -= npolys; + } + + if (previousRemainingVertices != npolys) { + // The number of remaining vertices decreased. Reset counters. + previousRemainingVertices = npolys; + remainingIterations = npolys; + } else { + // We didn't consume a vertex on previous iteration, reduce the + // available iterations. + remainingIterations--; + } + + for (size_t k = 0; k < 3; k++) { + ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys]; + size_t vi = size_t(ind[k].v_idx); + if (((vi * 3 + axes[0]) >= v.size()) || + ((vi * 3 + axes[1]) >= v.size())) { + // ??? + vx[k] = static_cast<real_t>(0.0); + vy[k] = static_cast<real_t>(0.0); + } else { + vx[k] = v[vi * 3 + axes[0]]; + vy[k] = v[vi * 3 + axes[1]]; + } + } + + // + // area is calculated per face + // + real_t e0x = vx[1] - vx[0]; + real_t e0y = vy[1] - vy[0]; + real_t e1x = vx[2] - vx[1]; + real_t e1y = vy[2] - vy[1]; + real_t cross = e0x * e1y - e0y * e1x; + // std::cout << "axes = " << axes[0] << ", " << axes[1] << "\n"; + // std::cout << "e0x, e0y, e1x, e1y " << e0x << ", " << e0y << ", " + // << e1x << ", " << e1y << "\n"; + + real_t area = (vx[0] * vy[1] - vy[0] * vx[1]) * static_cast<real_t>(0.5); + // std::cout << "cross " << cross << ", area " << area << "\n"; + // if an internal angle + if (cross * area < static_cast<real_t>(0.0)) { + // std::cout << "internal \n"; + guess_vert += 1; + // std::cout << "guess vert : " << guess_vert << "\n"; + continue; + } + + // check all other verts in case they are inside this triangle + bool overlap = false; + for (size_t otherVert = 3; otherVert < npolys; ++otherVert) { + size_t idx = (guess_vert + otherVert) % npolys; + + if (idx >= remainingFace.vertex_indices.size()) { + // std::cout << "???0\n"; + // ??? + continue; + } + + size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx); + + if (((ovi * 3 + axes[0]) >= v.size()) || + ((ovi * 3 + axes[1]) >= v.size())) { + // std::cout << "???1\n"; + // ??? + continue; + } + real_t tx = v[ovi * 3 + axes[0]]; + real_t ty = v[ovi * 3 + axes[1]]; + if (pnpoly(3, vx, vy, tx, ty)) { + // std::cout << "overlap\n"; + overlap = true; + break; + } + } + + if (overlap) { + // std::cout << "overlap2\n"; + guess_vert += 1; + continue; + } + + // this triangle is an ear + { + index_t idx0, idx1, idx2; + idx0.vertex_index = ind[0].v_idx; + idx0.normal_index = ind[0].vn_idx; + idx0.texcoord_index = ind[0].vt_idx; + idx1.vertex_index = ind[1].v_idx; + idx1.normal_index = ind[1].vn_idx; + idx1.texcoord_index = ind[1].vt_idx; + idx2.vertex_index = ind[2].v_idx; + idx2.normal_index = ind[2].vn_idx; + idx2.texcoord_index = ind[2].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + + // remove v1 from the list + size_t removed_vert_index = (guess_vert + 1) % npolys; + while (removed_vert_index + 1 < npolys) { + remainingFace.vertex_indices[removed_vert_index] = + remainingFace.vertex_indices[removed_vert_index + 1]; + removed_vert_index += 1; + } + remainingFace.vertex_indices.pop_back(); + } + + // std::cout << "remainingFace.vi.size = " << + // remainingFace.vertex_indices.size() << "\n"; + if (remainingFace.vertex_indices.size() == 3) { + i0 = remainingFace.vertex_indices[0]; + i1 = remainingFace.vertex_indices[1]; + i2 = remainingFace.vertex_indices[2]; + { + index_t idx0, idx1, idx2; + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } +#endif + } // npolys + } else { + for (size_t k = 0; k < npolys; k++) { + index_t idx; + idx.vertex_index = face.vertex_indices[k].v_idx; + idx.normal_index = face.vertex_indices[k].vn_idx; + idx.texcoord_index = face.vertex_indices[k].vt_idx; + shape->mesh.indices.push_back(idx); + } + + shape->mesh.num_face_vertices.push_back( + static_cast<unsigned char>(npolys)); + shape->mesh.material_ids.push_back(material_id); // per face + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); // per face + } + } + + shape->mesh.tags = tags; + } + + // line + if (!prim_group.lineGroup.empty()) { + // Flatten indices + for (size_t i = 0; i < prim_group.lineGroup.size(); i++) { + for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->lines.indices.push_back(idx); + } + + shape->lines.num_line_vertices.push_back( + int(prim_group.lineGroup[i].vertex_indices.size())); + } + } + + // points + if (!prim_group.pointsGroup.empty()) { + // Flatten & convert indices + for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) { + for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->points.indices.push_back(idx); + } + } + } + + return true; +} + +// Split a string with specified delimiter character and escape character. +// https://rosettacode.org/wiki/Tokenize_a_string_with_escaping#C.2B.2B +static void SplitString(const std::string &s, char delim, char escape, + std::vector<std::string> &elems) { + std::string token; + + bool escaping = false; + for (size_t i = 0; i < s.size(); ++i) { + char ch = s[i]; + if (escaping) { + escaping = false; + } else if (ch == escape) { + escaping = true; + continue; + } else if (ch == delim) { + if (!token.empty()) { + elems.push_back(token); + } + token.clear(); + continue; + } + token += ch; + } + + elems.push_back(token); +} + +static std::string JoinPath(const std::string &dir, + const std::string &filename) { + if (dir.empty()) { + return filename; + } else { + // check '/' + char lastChar = *dir.rbegin(); + if (lastChar != '/') { + return dir + std::string("/") + filename; + } else { + return dir + filename; + } + } +} + +void LoadMtl(std::map<std::string, int> *material_map, + std::vector<material_t> *materials, std::istream *inStream, + std::string *warning, std::string *err) { + (void)err; + + // Create a default material anyway. + material_t material; + InitMaterial(&material); + + // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification. + bool has_d = false; + bool has_tr = false; + + // has_kd is used to set a default diffuse value when map_Kd is present + // and Kd is not. + bool has_kd = false; + + std::stringstream warn_ss; + + size_t line_no = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + line_no++; + + // Trim trailing whitespace. + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map->insert(std::pair<std::string, int>( + material.name, static_cast<int>(materials->size()))); + materials->push_back(material); + } + + // initial temporary material + InitMaterial(&material); + + has_d = false; + has_tr = false; + + // set new mtl name + token += 7; + { + std::stringstream sstr; + sstr << token; + material.name = sstr.str(); + } + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + // diffuse + if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + has_kd = true; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + // transmittance + if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || + (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseReal(&token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + // shininess + if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseReal(&token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + // dissolve + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseReal(&token); + + if (has_tr) { + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } + has_d = true; + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + if (has_d) { + // `d` wins. Ignore `Tr` value. + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } else { + // We invert value of Tr(assume Tr is in range [0, 1]) + // NOTE: Interpretation of Tr is application(exporter) dependent. For + // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) + material.dissolve = static_cast<real_t>(1.0) - parseReal(&token); + } + has_tr = true; + continue; + } + + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseReal(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseReal(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseReal(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseReal(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseReal(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseReal(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseReal(&token); + continue; + } + + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.ambient_texname), + &(material.ambient_texopt), token); + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.diffuse_texname), + &(material.diffuse_texopt), token); + + // Set a decent diffuse default value if a diffuse texture is specified + // without a matching Kd value. + if (!has_kd) { + material.diffuse[0] = static_cast<real_t>(0.6); + material.diffuse[1] = static_cast<real_t>(0.6); + material.diffuse[2] = static_cast<real_t>(0.6); + } + + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_texname), + &(material.specular_texopt), token); + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_highlight_texname), + &(material.specular_highlight_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = token; + ParseTextureNameAndOption(&(material.alpha_texname), + &(material.alpha_texopt), token); + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.displacement_texname), + &(material.displacement_texopt), token); + continue; + } + + // reflection map + if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.reflection_texname), + &(material.reflection_texopt), token); + continue; + } + + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.roughness_texname), + &(material.roughness_texopt), token); + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.metallic_texname), + &(material.metallic_texopt), token); + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.sheen_texname), + &(material.sheen_texopt), token); + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.emissive_texname), + &(material.emissive_texopt), token); + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.normal_texname), + &(material.normal_texopt), token); + continue; + } + + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, static_cast<size_t>(len)); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair<std::string, std::string>(key, value)); + } + } + // flush last material. + material_map->insert(std::pair<std::string, int>( + material.name, static_cast<int>(materials->size()))); + materials->push_back(material); + + if (warning) { + (*warning) = warn_ss.str(); + } +} + +bool MaterialFileReader::operator()(const std::string &matId, + std::vector<material_t> *materials, + std::map<std::string, int> *matMap, + std::string *warn, std::string *err) { + if (!m_mtlBaseDir.empty()) { +#ifdef _WIN32 + char sep = ';'; +#else + char sep = ':'; +#endif + + // https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g + std::vector<std::string> paths; + std::istringstream f(m_mtlBaseDir); + + std::string s; + while (getline(f, s, sep)) { + paths.push_back(s); + } + + for (size_t i = 0; i < paths.size(); i++) { + std::string filepath = JoinPath(paths[i], matId); + + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + } + + std::stringstream ss; + ss << "Material file [ " << matId + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + + } else { + std::string filepath = matId; + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + + std::stringstream ss; + ss << "Material file [ " << filepath + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + + return false; + } +} + +bool MaterialStreamReader::operator()(const std::string &matId, + std::vector<material_t> *materials, + std::map<std::string, int> *matMap, + std::string *warn, std::string *err) { + (void)err; + (void)matId; + if (!m_inStream) { + std::stringstream ss; + ss << "Material stream in error state. \n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + } + + LoadMtl(matMap, materials, &m_inStream, warn, err); + + return true; +} + +bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, + std::vector<material_t> *materials, std::string *warn, + std::string *err, const char *filename, const char *mtl_basedir, + bool triangulate, bool default_vcols_fallback) { + attrib->vertices.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->colors.clear(); + shapes->clear(); + + std::stringstream errss; + + std::ifstream ifs(filename); + if (!ifs) { + errss << "Cannot open file [" << filename << "]\n"; + if (err) { + (*err) = errss.str(); + } + return false; + } + + std::string baseDir = mtl_basedir ? mtl_basedir : ""; + if (!baseDir.empty()) { +#ifndef _WIN32 + const char dirsep = '/'; +#else + const char dirsep = '\\'; +#endif + if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; + } + MaterialFileReader matFileReader(baseDir); + + return LoadObj(attrib, shapes, materials, warn, err, &ifs, &matFileReader, + triangulate, default_vcols_fallback); +} + +bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, + std::vector<material_t> *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn /*= NULL*/, bool triangulate, + bool default_vcols_fallback) { + std::stringstream errss; + + std::vector<real_t> v; + std::vector<real_t> vn; + std::vector<real_t> vt; + std::vector<real_t> vc; + std::vector<skin_weight_t> vw; + std::vector<tag_t> tags; + PrimGroup prim_group; + std::string name; + + // material + std::set<std::string> material_filenames; + std::map<std::string, int> material_map; + int material = -1; + + // smoothing group id + unsigned int current_smoothing_id = + 0; // Initial value. 0 means no smoothing. + + int greatest_v_idx = -1; + int greatest_vn_idx = -1; + int greatest_vt_idx = -1; + + shape_t shape; + + bool found_all_colors = true; + + size_t line_num = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + + line_num++; + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + real_t x, y, z; + real_t r, g, b; + + found_all_colors &= parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); + + v.push_back(x); + v.push_back(y); + v.push_back(z); + + if (found_all_colors || default_vcols_fallback) { + vc.push_back(r); + vc.push_back(g); + vc.push_back(b); + } + + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + vn.push_back(x); + vn.push_back(y); + vn.push_back(z); + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y; + parseReal2(&x, &y, &token); + vt.push_back(x); + vt.push_back(y); + continue; + } + + // skin weight. tinyobj extension + if (token[0] == 'v' && token[1] == 'w' && IS_SPACE((token[2]))) { + token += 3; + + // vw <vid> <joint_0> <weight_0> <joint_1> <weight_1> ... + // example: + // vw 0 0 0.25 1 0.25 2 0.5 + + // TODO(syoyo): Add syntax check + int vid = 0; + vid = parseInt(&token); + + skin_weight_t sw; + + sw.vertex_id = vid; + + while (!IS_NEW_LINE(token[0])) { + real_t j, w; + // joint_id should not be negative, weight may be negative + // TODO(syoyo): # of elements check + parseReal2(&j, &w, &token, -1.0); + + if (j < static_cast<real_t>(0)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `vw' line. joint_id is negative. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + joint_and_weight_t jw; + + jw.joint_id = int(j); + jw.weight = w; + + sw.weightValues.push_back(jw); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + vw.push_back(sw); + } + + // line + if (token[0] == 'l' && IS_SPACE((token[1]))) { + token += 2; + + __line_t line; + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast<int>(v.size() / 3), + static_cast<int>(vn.size() / 3), + static_cast<int>(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `l' line(e.g. zero value for vertex index. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + line.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.lineGroup.push_back(line); + + continue; + } + + // points + if (token[0] == 'p' && IS_SPACE((token[1]))) { + token += 2; + + __points_t pts; + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast<int>(v.size() / 3), + static_cast<int>(vn.size() / 3), + static_cast<int>(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `p' line(e.g. zero value for vertex index. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + pts.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.pointsGroup.push_back(pts); + + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + face_t face; + + face.smoothing_group_id = current_smoothing_id; + face.vertex_indices.reserve(3); + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast<int>(v.size() / 3), + static_cast<int>(vn.size() / 3), + static_cast<int>(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `f' line(e.g. zero value for face index. line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx; + greatest_vn_idx = + greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx; + greatest_vt_idx = + greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx; + + face.vertex_indices.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + // replace with emplace_back + std::move on C++11 + prim_group.faceGroup.push_back(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6))) { + token += 6; + std::string namebuf = parseString(&token); + + int newMaterialId = -1; + std::map<std::string, int>::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { error!! material not found } + if (warn) { + (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n"; + } + } + + if (newMaterialId != material) { + // Create per-face material. Thus we don't add `shape` to `shapes` at + // this time. + // just clear `faceGroup` after `exportGroupsToShape()` call. + exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + prim_group.faceGroup.clear(); + material = newMaterialId; + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector<std::string> filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + std::stringstream ss; + ss << "Looks like empty filename for mtllib. Use default " + "material (line " + << line_num << ".)\n"; + + (*warn) += ss.str(); + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), materials, + &material_map, &warn_mtl, &err_mtl); + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0) { + shapes->push_back(shape); + } + + shape = shape_t(); + + // material = -1; + prim_group.clear(); + + std::vector<std::string> names; + + while (!IS_NEW_LINE(token[0])) { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + // names[0] must be 'g' + + if (names.size() < 2) { + // 'g' with empty names + if (warn) { + std::stringstream ss; + ss << "Empty group name. line: " << line_num << "\n"; + (*warn) += ss.str(); + name = ""; + } + } else { + std::stringstream ss; + ss << names[1]; + + // tinyobjloader does not support multiple groups for a primitive. + // Currently we concatinate multiple group names with a space to get + // single group name. + + for (size_t i = 2; i < names.size(); i++) { + ss << " " << names[i]; + } + + name = ss.str(); + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 || + shape.points.indices.size() > 0) { + shapes->push_back(shape); + } + + // material = -1; + prim_group.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + token += 2; + std::stringstream ss; + ss << token; + name = ss.str(); + + continue; + } + + if (token[0] == 't' && IS_SPACE(token[1])) { + const int max_tag_nums = 8192; // FIXME(syoyo): Parameterize. + tag_t tag; + + token += 2; + + tag.name = parseString(&token); + + tag_sizes ts = parseTagTriple(&token); + + if (ts.num_ints < 0) { + ts.num_ints = 0; + } + if (ts.num_ints > max_tag_nums) { + ts.num_ints = max_tag_nums; + } + + if (ts.num_reals < 0) { + ts.num_reals = 0; + } + if (ts.num_reals > max_tag_nums) { + ts.num_reals = max_tag_nums; + } + + if (ts.num_strings < 0) { + ts.num_strings = 0; + } + if (ts.num_strings > max_tag_nums) { + ts.num_strings = max_tag_nums; + } + + tag.intValues.resize(static_cast<size_t>(ts.num_ints)); + + for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) { + tag.intValues[i] = parseInt(&token); + } + + tag.floatValues.resize(static_cast<size_t>(ts.num_reals)); + for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + } + + tag.stringValues.resize(static_cast<size_t>(ts.num_strings)); + for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) { + tag.stringValues[i] = parseString(&token); + } + + tags.push_back(tag); + + continue; + } + + if (token[0] == 's' && IS_SPACE(token[1])) { + // smoothing group id + token += 2; + + // skip space. + token += strspn(token, " \t"); // skip space + + if (token[0] == '\0') { + continue; + } + + if (token[0] == '\r' || token[1] == '\n') { + continue; + } + + if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' && + token[2] == 'f') { + current_smoothing_id = 0; + } else { + // assume number + int smGroupId = parseInt(&token); + if (smGroupId < 0) { + // parse error. force set to 0. + // FIXME(syoyo): Report warning. + current_smoothing_id = 0; + } else { + current_smoothing_id = static_cast<unsigned int>(smGroupId); + } + } + + continue; + } // smoothing group id + + // Ignore unknown command. + } + + // not all vertices have colors, no default colors desired? -> clear colors + if (!found_all_colors && !default_vcols_fallback) { + vc.clear(); + } + + if (greatest_v_idx >= static_cast<int>(v.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vn_idx >= static_cast<int>(vn.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex normal indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vt_idx >= static_cast<int>(vt.size() / 2)) { + if (warn) { + std::stringstream ss; + ss << "Vertex texcoord indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + // exportGroupsToShape return false when `usemtl` is called in the last + // line. + // we also add `shape` to `shapes` when `shape.mesh` has already some + // faces(indices) + if (ret || shape.mesh.indices + .size()) { // FIXME(syoyo): Support other prims(e.g. lines) + shapes->push_back(shape); + } + prim_group.clear(); // for safety + + if (err) { + (*err) += errss.str(); + } + + attrib->vertices.swap(v); + attrib->vertex_weights.swap(v); + attrib->normals.swap(vn); + attrib->texcoords.swap(vt); + attrib->texcoord_ws.swap(vt); + attrib->colors.swap(vc); + attrib->skin_weights.swap(vw); + + return true; +} + +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data /*= NULL*/, + MaterialReader *readMatFn /*= NULL*/, + std::string *warn, /* = NULL*/ + std::string *err /*= NULL*/) { + std::stringstream errss; + + // material + std::set<std::string> material_filenames; + std::map<std::string, int> material_map; + int material_id = -1; // -1 = invalid + + std::vector<index_t> indices; + std::vector<material_t> materials; + std::vector<std::string> names; + names.reserve(2); + std::vector<const char *> names_out; + + std::string linebuf; + while (inStream.peek() != -1) { + safeGetline(inStream, linebuf); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + // TODO(syoyo): Support parsing vertex color extension. + real_t x, y, z, w; // w is optional. default = 1.0 + parseV(&x, &y, &z, &w, &token); + if (callback.vertex_cb) { + callback.vertex_cb(user_data, x, y, z, w); + } + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + if (callback.normal_cb) { + callback.normal_cb(user_data, x, y, z); + } + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; // y and z are optional. default = 0.0 + parseReal3(&x, &y, &z, &token); + if (callback.texcoord_cb) { + callback.texcoord_cb(user_data, x, y, z); + } + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + indices.clear(); + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi = parseRawTriple(&token); + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + indices.push_back(idx); + size_t n = strspn(token, " \t\r"); + token += n; + } + + if (callback.index_cb && indices.size() > 0) { + callback.index_cb(user_data, &indices.at(0), + static_cast<int>(indices.size())); + } + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + std::stringstream ss; + ss << token; + std::string namebuf = ss.str(); + + int newMaterialId = -1; + std::map<std::string, int>::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { warn!! material not found } + if (warn && (!callback.usemtl_cb)) { + (*warn) += "material [ " + namebuf + " ] not found in .mtl\n"; + } + } + + if (newMaterialId != material_id) { + material_id = newMaterialId; + } + + if (callback.usemtl_cb) { + callback.usemtl_cb(user_data, namebuf.c_str(), material_id); + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector<std::string> filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + (*warn) += + "Looks like empty filename for mtllib. Use default " + "material. \n"; + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, + &material_map, &warn_mtl, &err_mtl); + + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; // This should be warn message. + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } else { + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast<int>(materials.size())); + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + names.clear(); + + while (!IS_NEW_LINE(token[0])) { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + if (callback.group_cb) { + if (names.size() > 1) { + // create const char* array. + names_out.resize(names.size() - 1); + for (size_t j = 0; j < names_out.size(); j++) { + names_out[j] = names[j + 1].c_str(); + } + callback.group_cb(user_data, &names_out.at(0), + static_cast<int>(names_out.size())); + + } else { + callback.group_cb(user_data, NULL, 0); + } + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + token += 2; + + std::stringstream ss; + ss << token; + std::string object_name = ss.str(); + + if (callback.object_cb) { + callback.object_cb(user_data, object_name.c_str()); + } + + continue; + } + +#if 0 // @todo + if (token[0] == 't' && IS_SPACE(token[1])) { + tag_t tag; + + token += 2; + std::stringstream ss; + ss << token; + tag.name = ss.str(); + + token += tag.name.size() + 1; + + tag_sizes ts = parseTagTriple(&token); + + tag.intValues.resize(static_cast<size_t>(ts.num_ints)); + + for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.floatValues.resize(static_cast<size_t>(ts.num_reals)); + for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.stringValues.resize(static_cast<size_t>(ts.num_strings)); + for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) { + std::stringstream ss; + ss << token; + tag.stringValues[i] = ss.str(); + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } +#endif + + // Ignore unknown command. + } + + if (err) { + (*err) += errss.str(); + } + + return true; +} + +bool ObjReader::ParseFromFile(const std::string &filename, + const ObjReaderConfig &config) { + std::string mtl_search_path; + + if (config.mtl_search_path.empty()) { + // + // split at last '/'(for unixish system) or '\\'(for windows) to get + // the base directory of .obj file + // + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) { + mtl_search_path = filename.substr(0, pos); + } + } else { + mtl_search_path = config.mtl_search_path; + } + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + filename.c_str(), mtl_search_path.c_str(), + config.triangulate, config.vertex_color); + + return valid_; +} + +bool ObjReader::ParseFromString(const std::string &obj_text, + const std::string &mtl_text, + const ObjReaderConfig &config) { + std::stringbuf obj_buf(obj_text); + std::stringbuf mtl_buf(mtl_text); + + std::istream obj_ifs(&obj_buf); + std::istream mtl_ifs(&mtl_buf); + + MaterialStreamReader mtl_ss(mtl_ifs); + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color); + + return valid_; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} // namespace tinyobj + +#endif diff --git a/engine/xe_pipeline.cpp b/engine/xe_pipeline.cpp new file mode 100755 index 0000000..d06d09b --- /dev/null +++ b/engine/xe_pipeline.cpp @@ -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; + + } + +}
\ No newline at end of file diff --git a/engine/xe_pipeline.hpp b/engine/xe_pipeline.hpp new file mode 100755 index 0000000..06bcf0f --- /dev/null +++ b/engine/xe_pipeline.hpp @@ -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; +}; + +}
\ No newline at end of file diff --git a/engine/xe_renderer.cpp b/engine/xe_renderer.cpp new file mode 100644 index 0000000..2ed4f51 --- /dev/null +++ b/engine/xe_renderer.cpp @@ -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); + +} + +}
\ No newline at end of file diff --git a/engine/xe_renderer.hpp b/engine/xe_renderer.hpp new file mode 100644 index 0000000..321a4e3 --- /dev/null +++ b/engine/xe_renderer.hpp @@ -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}; +}; +}
\ No newline at end of file diff --git a/engine/xe_swap_chain.cpp b/engine/xe_swap_chain.cpp new file mode 100755 index 0000000..6e708b9 --- /dev/null +++ b/engine/xe_swap_chain.cpp @@ -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); +} + +} diff --git a/engine/xe_swap_chain.hpp b/engine/xe_swap_chain.hpp new file mode 100755 index 0000000..50aa03f --- /dev/null +++ b/engine/xe_swap_chain.hpp @@ -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; +}; + +} diff --git a/engine/xe_utils.hpp b/engine/xe_utils.hpp new file mode 100644 index 0000000..4ec88cf --- /dev/null +++ b/engine/xe_utils.hpp @@ -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), ...); +}; + +}
\ No newline at end of file diff --git a/engine/xe_window.cpp b/engine/xe_window.cpp new file mode 100755 index 0000000..2721279 --- /dev/null +++ b/engine/xe_window.cpp @@ -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; + } + +}
\ No newline at end of file diff --git a/engine/xe_window.hpp b/engine/xe_window.hpp new file mode 100755 index 0000000..bf80bd7 --- /dev/null +++ b/engine/xe_window.hpp @@ -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; +}; + +}
\ No newline at end of file |