Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • vulkan2021/vkcv-framework
1 result
Show changes
Commits on Source (154)
Showing
with 1324 additions and 545 deletions
......@@ -23,7 +23,7 @@ build_ubuntu_gcc:
- mkdir debug
- cd debug
- cmake -DCMAKE_BUILD_TYPE=Debug ..
- cmake --build .
- cmake --build . -j 4
artifacts:
name: "Documentation - $CI_PIPELINE_ID"
paths:
......@@ -49,7 +49,7 @@ build_win10_msvc:
- mkdir debug
- cd debug
- cmake -DCMAKE_BUILD_TYPE=Debug ..
- cmake --build .
- cmake --build . -j 4
build_win10_mingw:
only:
......@@ -66,7 +66,7 @@ build_win10_mingw:
- mkdir debug
- cd debug
- cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_C_COMPILER:FILEPATH=C:\msys64\mingw64\bin\x86_64-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER:FILEPATH=C:\msys64\mingw64\bin\x86_64-w64-mingw32-g++.exe .. -G "Unix Makefiles"
- cmake --build . -j 8
- cmake --build . -j 4
build_mac_clang:
only:
......@@ -85,7 +85,7 @@ build_mac_clang:
- export LDFLAGS="-L/usr/local/opt/llvm/lib"
- export CPPFLAGS="-I/usr/local/opt/llvm/include"
- cmake -DCMAKE_C_COMPILER="/usr/local/opt/llvm/bin/clang" -DCMAKE_CXX_COMPILER="/usr/local/opt/llvm/bin/clang++" -DCMAKE_BUILD_TYPE=Debug ..
- cmake --build .
- cmake --build . -j 4
deploy_doc_develop:
only:
......
......@@ -22,9 +22,6 @@
[submodule "modules/gui/lib/imgui"]
path = modules/gui/lib/imgui
url = https://github.com/ocornut/imgui.git
[submodule "lib/VulkanMemoryAllocator"]
path = lib/VulkanMemoryAllocator
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
[submodule "lib/VulkanMemoryAllocator-Hpp"]
path = lib/VulkanMemoryAllocator-Hpp
url = https://github.com/malte-v/VulkanMemoryAllocator-Hpp.git
......
......@@ -65,7 +65,7 @@ add_library(vkcv STATIC ${vkcv_sources})
if(MSVC)
#enable multicore compilation on visual studio
target_compile_options(vkcv PRIVATE "/MP" "/openmp")
target_compile_options(vkcv PRIVATE "/MP" "/openmp" "/Zc:offsetof-")
#set source groups to create proper filters in visual studio
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${vkcv_sources})
......
# adding all source files and header files of the framework:
set(vkcv_sources
${vkcv_include}/vkcv/Features.hpp
${vkcv_source}/vkcv/Features.cpp
${vkcv_include}/vkcv/FeatureManager.hpp
${vkcv_source}/vkcv/FeatureManager.cpp
${vkcv_include}/vkcv/Context.hpp
${vkcv_source}/vkcv/Context.cpp
......
......@@ -76,8 +76,8 @@ namespace vkcv {
{}
[[nodiscard]]
static Buffer<T> create(BufferManager* manager, BufferType type, size_t count, BufferMemoryType memoryType) {
return Buffer<T>(manager, manager->createBuffer(type, count * sizeof(T), memoryType), type, count, memoryType);
static Buffer<T> create(BufferManager* manager, BufferType type, size_t count, BufferMemoryType memoryType, bool supportIndirect) {
return Buffer<T>(manager, manager->createBuffer(type, count * sizeof(T), memoryType, supportIndirect), type, count, memoryType);
}
};
......
......@@ -70,7 +70,7 @@ namespace vkcv
* @param memoryType Type of buffers memory
* @return New buffer handle
*/
BufferHandle createBuffer(BufferType type, size_t size, BufferMemoryType memoryType);
BufferHandle createBuffer(BufferType type, size_t size, BufferMemoryType memoryType, bool supportIndirect);
/**
* Returns the Vulkan buffer handle of a buffer
......
......@@ -5,6 +5,7 @@
#include "QueueManager.hpp"
#include "DrawcallRecording.hpp"
#include "Features.hpp"
namespace vkcv
{
......@@ -32,6 +33,9 @@ namespace vkcv
[[nodiscard]]
const vk::Device &getDevice() const;
[[nodiscard]]
const FeatureManager& getFeatureManager() const;
[[nodiscard]]
const QueueManager& getQueueManager() const;
......@@ -41,8 +45,8 @@ namespace vkcv
static Context create(const char *applicationName,
uint32_t applicationVersion,
const std::vector<vk::QueueFlagBits>& queueFlags,
const std::vector<const char *>& instanceExtensions,
const std::vector<const char *>& deviceExtensions);
const Features& features,
const std::vector<const char*>& instanceExtensions = {});
private:
/**
......@@ -53,11 +57,12 @@ namespace vkcv
* @param device Vulkan-Device
*/
Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device,
QueueManager&& queueManager, vma::Allocator&& allocator) noexcept;
FeatureManager&& featureManager, QueueManager&& queueManager, vma::Allocator&& allocator) noexcept;
vk::Instance m_Instance;
vk::PhysicalDevice m_PhysicalDevice;
vk::Device m_Device;
FeatureManager m_FeatureManager;
QueueManager m_QueueManager;
vma::Allocator m_Allocator;
......
......@@ -7,14 +7,14 @@
#include <memory>
#include <vulkan/vulkan.hpp>
#include "vkcv/Context.hpp"
#include "vkcv/Swapchain.hpp"
#include "vkcv/Window.hpp"
#include "vkcv/PassConfig.hpp"
#include "vkcv/Handles.hpp"
#include "vkcv/Buffer.hpp"
#include "vkcv/Image.hpp"
#include "vkcv/PipelineConfig.hpp"
#include "Context.hpp"
#include "Swapchain.hpp"
#include "Window.hpp"
#include "PassConfig.hpp"
#include "Handles.hpp"
#include "Buffer.hpp"
#include "Image.hpp"
#include "PipelineConfig.hpp"
#include "CommandResources.hpp"
#include "SyncResources.hpp"
#include "Result.hpp"
......@@ -139,8 +139,8 @@ namespace vkcv
const char *applicationName,
uint32_t applicationVersion,
const std::vector<vk::QueueFlagBits>& queueFlags = {},
const std::vector<const char*>& instanceExtensions = {},
const std::vector<const char*>& deviceExtensions = {});
const Features& features = {},
const std::vector<const char *>& instanceExtensions = {});
/**
* Creates a basic vulkan graphics pipeline using @p config from the pipeline config class and returns it using the @p handle.
......@@ -185,8 +185,8 @@ namespace vkcv
* return Buffer-Object
*/
template<typename T>
Buffer<T> createBuffer(vkcv::BufferType type, size_t count, BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL) {
return Buffer<T>::create(m_BufferManager.get(), type, count, memoryType);
Buffer<T> createBuffer(vkcv::BufferType type, size_t count, BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL, bool supportIndirect = false) {
return Buffer<T>::create(m_BufferManager.get(), type, count, memoryType, supportIndirect);
}
/**
......@@ -249,16 +249,16 @@ namespace vkcv
bool beginFrame(uint32_t& width, uint32_t& height);
void recordDrawcallsToCmdStream(
const CommandStreamHandle cmdStreamHandle,
const PassHandle renderpassHandle,
const CommandStreamHandle& cmdStreamHandle,
const PassHandle& renderpassHandle,
const PipelineHandle pipelineHandle,
const PushConstants &pushConstants,
const std::vector<DrawcallInfo> &drawcalls,
const std::vector<ImageHandle> &renderTargets);
void recordMeshShaderDrawcalls(
const CommandStreamHandle cmdStreamHandle,
const PassHandle renderpassHandle,
const CommandStreamHandle& cmdStreamHandle,
const PassHandle& renderpassHandle,
const PipelineHandle pipelineHandle,
const PushConstants& pushConstantData,
const std::vector<MeshShaderDrawcall>& drawcalls,
......@@ -270,6 +270,20 @@ namespace vkcv
const uint32_t dispatchCount[3],
const std::vector<DescriptorSetUsage> &descriptorSetUsages,
const PushConstants& pushConstants);
void recordBeginDebugLabel(const CommandStreamHandle &cmdStream,
const std::string& label,
const std::array<float, 4>& color);
void recordEndDebugLabel(const CommandStreamHandle &cmdStream);
void recordComputeIndirectDispatchToCmdStream(
const CommandStreamHandle cmdStream,
const PipelineHandle computePipeline,
const vkcv::BufferHandle buffer,
const size_t bufferArgOffset,
const std::vector<DescriptorSetUsage>& descriptorSetUsages,
const PushConstants& pushConstants);
/**
* @brief end recording and present image
......@@ -312,6 +326,14 @@ namespace vkcv
void recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst,
SamplerFilterType filterType);
void setDebugLabel(const BufferHandle &handle, const std::string &label);
void setDebugLabel(const PassHandle &handle, const std::string &label);
void setDebugLabel(const PipelineHandle &handle, const std::string &label);
void setDebugLabel(const DescriptorSetHandle &handle, const std::string &label);
void setDebugLabel(const SamplerHandle &handle, const std::string &label);
void setDebugLabel(const ImageHandle &handle, const std::string &label);
void setDebugLabel(const CommandStreamHandle &handle, const std::string &label);
};
}
#pragma once
#include <vulkan/vulkan.hpp>
#include "vkcv/Handles.hpp"
#include "vkcv/ShaderStage.hpp"
......@@ -41,12 +39,12 @@ namespace vkcv
uint32_t bindingID,
DescriptorType descriptorType,
uint32_t descriptorCount,
ShaderStage shaderStage
ShaderStages shaderStages
) noexcept;
uint32_t bindingID;
DescriptorType descriptorType;
uint32_t descriptorCount;
ShaderStage shaderStage;
ShaderStages shaderStages;
};
}
......@@ -29,8 +29,19 @@ namespace vkcv {
};
struct Mesh {
inline Mesh(std::vector<VertexBufferBinding> vertexBufferBindings, vk::Buffer indexBuffer, size_t indexCount, IndexBitCount indexBitCount = IndexBitCount::Bit16) noexcept
: vertexBufferBindings(vertexBufferBindings), indexBuffer(indexBuffer), indexCount(indexCount), indexBitCount(indexBitCount){}
inline Mesh(){}
inline Mesh(
std::vector<VertexBufferBinding> vertexBufferBindings,
vk::Buffer indexBuffer,
size_t indexCount,
IndexBitCount indexBitCount = IndexBitCount::Bit16) noexcept
:
vertexBufferBindings(vertexBufferBindings),
indexBuffer(indexBuffer),
indexCount(indexCount),
indexBitCount(indexBitCount) {}
std::vector<VertexBufferBinding> vertexBufferBindings;
vk::Buffer indexBuffer;
......
#pragma once
#include "Logger.hpp"
#include <functional>
#include <unordered_set>
#include <vector>
#include <vulkan/vulkan.hpp>
namespace vkcv {
class FeatureManager {
private:
vk::PhysicalDevice& m_physicalDevice;
std::vector<const char*> m_supportedExtensions;
std::vector<const char*> m_activeExtensions;
vk::PhysicalDeviceFeatures2 m_featuresBase;
std::vector<vk::BaseOutStructure*> m_featuresExtensions;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceFeatures& features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDevice16BitStorageFeatures& features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDevice8BitStorageFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceBufferDeviceAddressFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceDescriptorIndexingFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceHostQueryResetFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceImagelessFramebufferFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceMultiviewFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceProtectedMemoryFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceSamplerYcbcrConversionFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceScalarBlockLayoutFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceShaderAtomicInt64Features &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceShaderFloat16Int8Features& features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceTimelineSemaphoreFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceUniformBufferStandardLayoutFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceVariablePointersFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceVulkanMemoryModelFeatures &features, bool required) const;
[[nodiscard]]
bool checkSupport(const vk::PhysicalDeviceMeshShaderFeaturesNV& features, bool required) const;
vk::BaseOutStructure* findFeatureStructure(vk::StructureType type) const;
public:
explicit FeatureManager(vk::PhysicalDevice& physicalDevice);
FeatureManager(const FeatureManager& other) = delete;
FeatureManager(FeatureManager&& other) noexcept;
~FeatureManager();
FeatureManager& operator=(const FeatureManager& other) = delete;
FeatureManager& operator=(FeatureManager&& other) noexcept;
[[nodiscard]]
bool isExtensionSupported(const std::string& extension) const;
bool useExtension(const std::string& extension, bool required = true);
[[nodiscard]]
bool isExtensionActive(const std::string& extension) const;
[[nodiscard]]
const std::vector<const char*>& getActiveExtensions() const;
bool useFeatures(const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction, bool required = true);
template<typename T>
bool useFeatures(const std::function<void(T&)>& featureFunction, bool required = true) {
T features;
T* features_ptr = reinterpret_cast<T*>(findFeatureStructure(features.sType));
if (features_ptr) {
features = *features_ptr;
}
featureFunction(features);
if (!checkSupport(features, required)) {
return false;
}
if (features_ptr) {
*features_ptr = features;
return true;
}
features_ptr = new T(features);
if (m_featuresExtensions.empty()) {
m_featuresBase.setPNext(features_ptr);
} else {
m_featuresExtensions.back()->setPNext(
reinterpret_cast<vk::BaseOutStructure*>(features_ptr)
);
}
m_featuresExtensions.push_back(
reinterpret_cast<vk::BaseOutStructure*>(features_ptr)
);
return true;
}
[[nodiscard]]
const vk::PhysicalDeviceFeatures2& getFeatures() const;
};
}
#pragma once
#include <functional>
#include <vector>
#include <initializer_list>
#include "FeatureManager.hpp"
namespace vkcv {
typedef std::function<bool(FeatureManager&)> Feature;
class Features {
private:
std::vector<Feature> m_features;
public:
Features() = default;
Features(const std::initializer_list<std::string>& list);
Features(const Features& other) = default;
Features(Features&& other) = default;
~Features() = default;
Features& operator=(const Features& other) = default;
Features& operator=(Features&& other) = default;
void requireExtension(const std::string& extension);
void requireExtensionFeature(const std::string& extension,
const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction);
template<typename T>
void requireExtensionFeature(const std::string& extension, const std::function<void(T&)>& featureFunction) {
m_features.emplace_back([extension, featureFunction](FeatureManager& featureManager) {
if (featureManager.useExtension(extension, true)) {
return featureManager.template useFeatures<T>(featureFunction, true);
} else {
return false;
}
});
}
void requireFeature(const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction);
template<typename T>
void requireFeature(const std::function<void(T&)>& featureFunction) {
m_features.emplace_back([featureFunction](FeatureManager& featureManager) {
return featureManager.template useFeatures<T>(featureFunction, true);
});
}
void tryExtension(const std::string& extension);
void tryExtensionFeature(const std::string& extension,
const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction);
template<typename T>
void tryExtensionFeature(const std::string& extension, const std::function<void(T&)>& featureFunction) {
m_features.emplace_back([extension, featureFunction](FeatureManager& featureManager) {
if (featureManager.useExtension(extension, false)) {
return featureManager.template useFeatures<T>(featureFunction, false);
} else {
return false;
}
});
}
void tryFeature(const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction);
template<typename T>
void tryFeature(const std::function<void(T&)>& featureFunction) {
m_features.emplace_back([featureFunction](FeatureManager& featureManager) {
return featureManager.template useFeatures<T>(featureFunction, false);
});
}
[[nodiscard]]
const std::vector<Feature>& getList() const;
};
}
......@@ -53,9 +53,10 @@ namespace vkcv {
VKCV_DEBUG_MESSAGE_LEN, \
__VA_ARGS__ \
); \
auto output = getLogOutput(level); \
if (level != vkcv::LogLevel::RAW_INFO) { \
fprintf( \
getLogOutput(level), \
output, \
"[%s]: %s [%s, line %d: %s]\n", \
vkcv::getLogName(level), \
output_message, \
......@@ -65,12 +66,13 @@ namespace vkcv {
); \
} else { \
fprintf( \
getLogOutput(level), \
output, \
"[%s]: %s\n", \
vkcv::getLogName(level), \
output_message \
); \
} \
fflush(output); \
}
#else
......
......@@ -13,8 +13,8 @@
#include <vulkan/vulkan.hpp>
#include <spirv_cross.hpp>
#include "VertexLayout.hpp"
#include "ShaderStage.hpp"
#include "DescriptorConfig.hpp"
#include "ShaderStage.hpp"
namespace vkcv {
......
#pragma once
namespace vkcv {
enum class ShaderStage
{
VERTEX,
TESS_CONTROL,
TESS_EVAL,
GEOMETRY,
FRAGMENT,
COMPUTE,
TASK,
MESH
};
#include <vulkan/vulkan.hpp>
namespace vkcv {
enum class ShaderStage : VkShaderStageFlags {
VERTEX = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eVertex),
TESS_CONTROL = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTessellationControl),
TESS_EVAL = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTessellationEvaluation),
GEOMETRY = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eGeometry),
FRAGMENT = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eFragment),
COMPUTE = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eCompute),
TASK = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTaskNV),
MESH = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eMeshNV)
};
using ShaderStages = vk::Flags<ShaderStage>;
constexpr vk::ShaderStageFlags getShaderStageFlags(ShaderStages shaderStages) noexcept {
return vk::ShaderStageFlags(static_cast<VkShaderStageFlags>(shaderStages));
}
constexpr ShaderStages operator|(ShaderStage stage0, ShaderStage stage1) noexcept {
return ShaderStages(stage0) | stage1;
}
constexpr ShaderStages operator&(ShaderStage stage0, ShaderStage stage1) noexcept {
return ShaderStages(stage0) & stage1;
}
constexpr ShaderStages operator^(ShaderStage stage0, ShaderStage stage1) noexcept {
return ShaderStages(stage0) ^ stage1;
}
constexpr ShaderStages operator~(ShaderStage stage) noexcept {
return ~(ShaderStages(stage));
}
}
......@@ -31,10 +31,10 @@ include(config/FX_GLTF.cmake)
include(config/STB.cmake)
# link the required libraries to the module
target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv)
target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv ${vkcv_libraries})
# including headers of dependencies and the VkCV framework
target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes})
target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes} ${vkcv_includes})
# add the own include directory for public headers
target_include_directories(vkcv_asset_loader BEFORE PUBLIC ${vkcv_asset_loader_include})
......
......@@ -11,17 +11,7 @@
#include <cstdint>
#include <filesystem>
/** These macros define limits of the following structs. Implementations can
* test against these limits when performing sanity checks. The main constraint
* expressed is that of the data type: Material indices are identified by a
* uint8_t in the VertexGroup struct, so there can't be more than UINT8_MAX
* materials in the mesh. Should these limits be too narrow, the data type has
* to be changed, but the current ones should be generous enough for most use
* cases. */
#define MAX_MATERIALS_PER_MESH UINT8_MAX
#define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX
/** LOADING MESHES
/* LOADING MESHES
* The description of meshes is a hierarchy of structures with the Mesh at the
* top.
*
......@@ -46,53 +36,89 @@
namespace vkcv::asset {
/** This enum matches modes in fx-gltf, the library returns a standard mode
* (TRIANGLES) if no mode is given in the file. */
/**
* These return codes are limited to the asset loader module. If unified return
* codes are defined for the vkcv framework, these will be used instead.
*/
#define ASSET_ERROR 0
#define ASSET_SUCCESS 1
/**
* This enum matches modes in fx-gltf, the library returns a standard mode
* (TRIANGLES) if no mode is given in the file.
*/
enum class PrimitiveMode : uint8_t {
POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
TRIANGLEFAN
POINTS = 0,
LINES = 1,
LINELOOP = 2,
LINESTRIP = 3,
TRIANGLES = 4,
TRIANGLESTRIP = 5,
TRIANGLEFAN = 6
};
/** The indices in the index buffer can be of different bit width. */
enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 };
typedef struct {
// TODO define struct for samplers (low priority)
// NOTE: glTF defines samplers based on OpenGL, which can not be
// directly translated to Vulkan. Specifically, OpenGL (and glTF)
// define a different set of Min/Mag-filters than Vulkan.
} Sampler;
/** struct for defining the loaded texture */
typedef struct {
int sampler; // index into the sampler array of the Scene
uint8_t channels; // number of channels
uint16_t w, h; // width and height of the texture
std::vector<uint8_t> data; // binary data of the decoded texture
} Texture;
/**
* The indices in the index buffer can be of different bit width.
*/
enum class IndexType : uint8_t {
UNDEFINED=0,
UINT8=1,
UINT16=2,
UINT32=3
};
/** The asset loader module only supports the PBR-MetallicRoughness model for
* materials.*/
typedef struct {
uint16_t textureMask; // bit mask with active texture targets
// Indices into the Array.textures array
int baseColor, metalRough, normal, occlusion, emissive;
// Scaling factors for each texture target
struct { float r, g, b, a; } baseColorFactor;
float metallicFactor, roughnessFactor;
float normalScale;
float occlusionStrength;
struct { float r, g, b; } emissiveFactor;
} Material;
/**
* This struct defines a sampler for a texture object. All values here can
* directly be passed to VkSamplerCreateInfo.
* NOTE that glTF defines samplers based on OpenGL, which can not be directly
* translated to Vulkan. The vkcv::asset::Sampler struct defined here adheres
* to the Vulkan spec, having alerady translated the flags from glTF to Vulkan.
* Since glTF does not specify border sampling for more than two dimensions,
* the addressModeW is hardcoded to a default: VK_SAMPLER_ADDRESS_MODE_REPEAT.
*/
struct Sampler {
int minFilter, magFilter;
int mipmapMode;
float minLOD, maxLOD;
int addressModeU, addressModeV, addressModeW;
};
/** Flags for the bit-mask in the Material struct. To check if a material has a
/**
* This struct describes a (partially) loaded texture.
* The data member is not populated after calling probeScene() but only when
* calling loadMesh(), loadScene() or loadTexture(). Note that textures are
* currently always loaded with 4 channels as RGBA, even if the image has just
* RGB or is grayscale. In the case where the glTF-file does not provide a URI
* but references a buffer view for the raw data, the path member will be empty
* even though the rest is initialized properly.
* NOTE: Loading textures without URI is untested.
*/
struct Texture {
std::filesystem::path path; // URI to the encoded texture data
int sampler; // index into the sampler array of the Scene
union { int width; int w; };
union { int height; int h; };
int channels;
std::vector<uint8_t> data; // binary data of the decoded texture
};
/**
* Flags for the bit-mask in the Material struct. To check if a material has a
* certain texture target, you can use the hasTexture() function below, passing
* the material struct and the enum. */
* the material struct and the enum.
*/
enum class PBRTextureTarget {
baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16
baseColor=1,
metalRough=2,
normal=4,
occlusion=8,
emissive=16
};
/** This macro translates the index of an enum in the defined order to an
/**
* This macro translates the index of an enum in the defined order to an
* integer with a single bit set in the corresponding place. It is used for
* working with the bitmask of texture targets ("textureMask") in the Material
* struct:
......@@ -103,100 +129,196 @@ enum class PBRTextureTarget {
* contact with bit-level operations. */
#define bitflag(ENUM) (0x1u << ((unsigned)(ENUM)))
/** To signal that a certain texture target is active in a Material struct, its
* bit is set in the textureMask. You can use this function to check that:
* Material mat = ...;
* if (materialHasTexture(&mat, baseColor)) {...} */
bool materialHasTexture(const Material *const m, const PBRTextureTarget t);
/**
* The asset loader module only supports the PBR-MetallicRoughness model for
* materials.
*/
struct Material {
uint16_t textureMask; // bit mask with active texture targets
// Indices into the Scene.textures vector
int baseColor, metalRough, normal, occlusion, emissive;
// Scaling factors for each texture target
struct { float r, g, b, a; } baseColorFactor;
float metallicFactor, roughnessFactor;
float normalScale;
float occlusionStrength;
struct { float r, g, b; } emissiveFactor;
/**
* To signal that a certain texture target is active in this Material
* struct, its bit is set in the textureMask. You can use this function
* to check that:
* if (myMaterial.hasTexture(baseColor)) {...}
*
* @param t The target to query for
* @return Boolean to signal whether the texture target is active in
* the material.
*/
bool hasTexture(PBRTextureTarget target) const;
};
/** With these enums, 0 is reserved to signal uninitialized or invalid data. */
/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
enum class PrimitiveType : uint32_t {
UNDEFINED = 0,
POSITION = 1,
NORMAL = 2,
TEXCOORD_0 = 3,
TEXCOORD_1 = 4,
TANGENT = 5
TANGENT = 5,
COLOR_0 = 6,
COLOR_1 = 7,
JOINTS_0 = 8,
WEIGHTS_0 = 9
};
/** These integer values are used the same way in OpenGL, Vulkan and glTF. This
/**
* These integer values are used the same way in OpenGL, Vulkan and glTF. This
* enum is not needed for translation, it's only for the programmers
* convenience (easier to read in if/switch statements etc). While this enum
* exists in (almost) the same definition in the fx-gltf library, we want to
* avoid exposing that dependency, thus it is re-defined here. */
* avoid exposing that dependency, thus it is re-defined here.
*/
enum class ComponentType : uint16_t {
NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123,
UINT32 = 5125, FLOAT32 = 5126
NONE = 0,
INT8 = 5120,
UINT8 = 5121,
INT16 = 5122,
UINT16 = 5123,
UINT32 = 5125,
FLOAT32 = 5126
};
/** This struct describes one vertex attribute of a vertex buffer. */
typedef struct {
/**
* This struct describes one vertex attribute of a vertex buffer.
*/
struct VertexAttribute {
PrimitiveType type; // POSITION, NORMAL, ...
uint32_t offset; // offset in bytes
uint32_t length; // length of ... in bytes
uint32_t stride; // stride in bytes
ComponentType componentType; // eg. 5126 for float
uint8_t componentCount; // eg. 3 for vec3
} VertexAttribute;
ComponentType componentType; // eg. 5126 for float
uint8_t componentCount; // eg. 3 for vec3
};
/** This struct represents one (possibly the only) part of a mesh. There is
/**
* This struct represents one (possibly the only) part of a mesh. There is
* always one vertexBuffer and zero or one indexBuffer (indexed rendering is
* common but not always used). If there is no index buffer, this is indicated
* by indexBuffer.data being empty. Each vertex buffer can have one or more
* vertex attributes. */
typedef struct {
* vertex attributes.
*/
struct VertexGroup {
enum PrimitiveMode mode; // draw as points, lines or triangle?
size_t numIndices, numVertices;
size_t numIndices;
size_t numVertices;
struct {
enum IndexType type; // data type of the indices
std::vector<uint8_t> data; // binary data of the index buffer
} indexBuffer;
struct {
std::vector<uint8_t> data; // binary data of the vertex buffer
std::vector<VertexAttribute> attributes; // description of one
} vertexBuffer;
struct { float x, y, z; } min; // bounding box lower left
struct { float x, y, z; } max; // bounding box upper right
int materialIndex; // index to one of the materials
} VertexGroup;
};
/** This struct represents a single mesh as it was loaded from a glTF file. It
/**
* This struct represents a single mesh as it was loaded from a glTF file. It
* consists of at least one VertexGroup, which then references other resources
* such as Materials. */
typedef struct {
* such as Materials.
*/
struct Mesh {
std::string name;
std::array<float, 16> modelMatrix;
std::vector<int> vertexGroups;
} Mesh;
};
/** The scene struct is simply a collection of objects in the scene as well as
/**
* The scene struct is simply a collection of objects in the scene as well as
* the resources used by those objects.
* For now the only type of object are the meshes and they are represented in a
* flat array.
* Note that parent-child relations are not yet possible. */
typedef struct {
* Note that parent-child relations are not yet possible.
*/
struct Scene {
std::vector<Mesh> meshes;
std::vector<VertexGroup> vertexGroups;
std::vector<Material> materials;
std::vector<Texture> textures;
std::vector<Sampler> samplers;
} Scene;
std::vector<std::string> uris;
};
/**
* Load every mesh from the glTF file, as well as materials and textures.
* Parse the given glTF file and create a shallow description of the content.
* Only the meta-data of the objects in the scene is loaded, not the binary
* content. The rationale is to provide a means of probing the content of a
* glTF file without the costly process of loading and decoding large amounts
* of data. The returned Scene struct can be used to search for specific meshes
* in the scene, that can then be loaded on their own using the loadMesh()
* function. Note that the Scene struct received as output argument will be
* overwritten by this function.
* After this function completes, the returned Scene struct is completely
* initialized and all information is final, except for the missing binary
* data. This means that indices to vectors will remain valid even when the
* shallow scene struct is filled with data by loadMesh().
* Note that for URIs only (local) filesystem paths are supported, no
* URLs using network protocols etc.
*
* @param path must be the path to a glTF or glb file.
* @param path must be the path to a glTF- or glb-file.
* @param scene is a reference to a Scene struct that will be filled with the
* meta-data of all objects described in the glTF file.
* @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
*/
int probeScene(const std::filesystem::path &path, Scene &scene);
/**
* This function loads a single mesh from the given file and adds it to the
* given scene. The scene must already be initialized (via probeScene()).
* The mesh_index refers to the Scenes meshes array and identifies the mesh to
* load. To find the mesh you want, iterate over the probed scene and check the
* meshes details (eg. name).
* Besides the mesh, this function will also add any associated data to the
* Scene struct such as Materials and Textures required by the Mesh.
*
* @param path must be the path to a glTF- or glb-file.
* @param scene is the scene struct to which the results will be written.
* @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
*/
int loadMesh(Scene &scene, int mesh_index);
/**
* Load every mesh from the glTF file, as well as materials, textures and other
* associated objects.
*
* @param path must be the path to a glTF- or glb-file.
* @param scene is a reference to a Scene struct that will be filled with the
* content of the glTF file being loaded.
* */
int loadScene(const std::string &path, Scene &scene);
struct TextureData {
int width;
int height;
int componentCount;
std::vector<char*> data;
};
TextureData loadTexture(const std::filesystem::path& path);
* @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
*/
int loadScene(const std::filesystem::path &path, Scene &scene);
/**
* Simply loads a single image at the given path and returns a Texture
* struct describing it. This is for special use cases only (eg.
* loading a font atlas) and not meant to be used for regular assets.
* The sampler is set to -1, signalling that this Texture was loaded
* outside the context of a glTF-file.
* If there was an error loading or decoding the image, the returned struct
* will be cleared to all 0 with path and data being empty; make sure to always
* check that !data.empty() before using the struct.
*
* @param path must be the path to an image file.
* @return Texture struct describing the loaded image.
*/
Texture loadTexture(const std::filesystem::path& path);
}
} // end namespace vkcv::asset
#include "vkcv/asset/asset_loader.hpp"
#include <iostream>
#include <string.h> // memcpy(3)
#include <set>
#include <stdlib.h> // calloc(3)
#include <vulkan/vulkan.hpp>
#include <fx/gltf.h>
#include <stb_image.h>
#include <vkcv/Logger.hpp>
#include <algorithm>
namespace vkcv::asset {
/**
* convert the accessor type from the fx-gltf library to an unsigned int
* @param type
* @return unsigned integer representation
*/
// TODO Return proper error code (we need to define those as macros or enums,
// will discuss during the next core meeting if that should happen on the scope
// of the vkcv framework or just this module)
uint8_t convertTypeToInt(const fx::gltf::Accessor::Type type) {
switch (type) {
case fx::gltf::Accessor::Type::None :
return 0;
case fx::gltf::Accessor::Type::Scalar :
return 1;
case fx::gltf::Accessor::Type::Vec2 :
return 2;
case fx::gltf::Accessor::Type::Vec3 :
return 3;
case fx::gltf::Accessor::Type::Vec4 :
return 4;
default: return 10; // TODO add cases for matrices (or maybe change the type in the struct itself)
/**
* This function unrolls nested exceptions via recursion and prints them
* @param e The exception being thrown
* @param path The path to the file that was responsible for the exception
*/
static void recurseExceptionPrint(const std::exception& e, const std::string &path) {
vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what());
try {
std::rethrow_if_nested(e);
} catch (const std::exception& nested) {
recurseExceptionPrint(nested, path);
}
}
}
/**
* This function unrolls nested exceptions via recursion and prints them
* @param e error code
* @param path path to file that is responsible for error
*/
void print_what (const std::exception& e, const std::string &path) {
vkcv_log(LogLevel::ERROR, "Loading file %s: %s",
path.c_str(), e.what());
/**
* Returns the component count for an accessor type of the fx-gltf library.
* @param type The accessor type
* @return An unsigned integer count
*/
static uint32_t getComponentCount(const fx::gltf::Accessor::Type type) {
switch (type) {
case fx::gltf::Accessor::Type::Scalar:
return 1;
case fx::gltf::Accessor::Type::Vec2:
return 2;
case fx::gltf::Accessor::Type::Vec3:
return 3;
case fx::gltf::Accessor::Type::Vec4:
case fx::gltf::Accessor::Type::Mat2:
return 4;
case fx::gltf::Accessor::Type::Mat3:
return 9;
case fx::gltf::Accessor::Type::Mat4:
return 16;
case fx::gltf::Accessor::Type::None:
default:
return 0;
}
}
try {
std::rethrow_if_nested(e);
} catch (const std::exception& nested) {
print_what(nested, path);
static uint32_t getComponentSize(ComponentType type) {
switch (type) {
case ComponentType::INT8:
case ComponentType::UINT8:
return 1;
case ComponentType::INT16:
case ComponentType::UINT16:
return 2;
case ComponentType::UINT32:
case ComponentType::FLOAT32:
return 4;
default:
return 0;
}
}
}
/** Translate the component type used in the index accessor of fx-gltf to our
* enum for index type. The reason we have defined an incompatible enum that
* needs translation is that only a subset of component types is valid for
* indices and we want to catch these incompatibilities here. */
enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &t)
{
switch (t) {
case fx::gltf::Accessor::ComponentType::UnsignedByte:
return IndexType::UINT8;
case fx::gltf::Accessor::ComponentType::UnsignedShort:
return IndexType::UINT16;
case fx::gltf::Accessor::ComponentType::UnsignedInt:
return IndexType::UINT32;
default:
vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(t));
return IndexType::UNDEFINED;
/**
* Translate the component type used in the index accessor of fx-gltf to our
* enum for index type. The reason we have defined an incompatible enum that
* needs translation is that only a subset of component types is valid for
* indices and we want to catch these incompatibilities here.
* @param t The component type
* @return The vkcv::IndexType enum representation
*/
enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type) {
switch (type) {
case fx::gltf::Accessor::ComponentType::UnsignedByte:
return IndexType::UINT8;
case fx::gltf::Accessor::ComponentType::UnsignedShort:
return IndexType::UINT16;
case fx::gltf::Accessor::ComponentType::UnsignedInt:
return IndexType::UINT32;
default:
vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type));
return IndexType::UNDEFINED;
}
}
}
/**
* This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given.
* @param translation possible translation vector (default 0,0,0)
* @param scale possible scale vector (default 1,1,1)
* @param rotation possible rotation, given in quaternion (default 0,0,0,1)
* @param matrix possible modelmatrix (default identity)
* @return model Matrix as an array of floats
*/
std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){
std::array<float, 16> modelMatrix = {1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1};
if (matrix != modelMatrix){
return matrix;
} else {
// translation
modelMatrix[3] = translation[0];
modelMatrix[7] = translation[1];
modelMatrix[11] = translation[2];
// rotation and scale
auto a = rotation[0];
auto q1 = rotation[1];
auto q2 = rotation[2];
auto q3 = rotation[3];
modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
// flip y, because GLTF uses y up, but vulkan -y up
modelMatrix[5] *= -1;
return modelMatrix;
}
}
bool materialHasTexture(const Material *const m, const PBRTextureTarget t)
{
return m->textureMask & bitflag(t);
}
int loadScene(const std::string &path, Scene &scene){
fx::gltf::Document sceneObjects;
try {
if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
sceneObjects = fx::gltf::LoadFromBinary(path);
} else {
sceneObjects = fx::gltf::LoadFromText(path);
}
} catch (const std::system_error &err) {
print_what(err, path);
return 0;
} catch (const std::exception &e) {
print_what(e, path);
return 0;
}
size_t pos = path.find_last_of("/");
auto dir = path.substr(0, pos);
// file has to contain at least one mesh
if (sceneObjects.meshes.size() == 0) return 0;
fx::gltf::Accessor posAccessor;
std::vector<VertexAttribute> vertexAttributes;
std::vector<Material> materials;
std::vector<Texture> textures;
std::vector<Sampler> samplers;
std::vector<Mesh> meshes;
std::vector<VertexGroup> vertexGroups;
int groupCount = 0;
Mesh mesh = {};
for(int i = 0; i < sceneObjects.meshes.size(); i++){
std::vector<int> vertexGroupsIndices;
fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i];
for(int j = 0; j < objectMesh.primitives.size(); j++){
fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[j];
vertexAttributes.clear();
vertexAttributes.reserve(objectPrimitive.attributes.size());
for (auto const & attrib : objectPrimitive.attributes) {
fx::gltf::Accessor accessor = sceneObjects.accessors[attrib.second];
VertexAttribute attribute;
if (attrib.first == "POSITION") {
attribute.type = PrimitiveType::POSITION;
posAccessor = accessor;
} else if (attrib.first == "NORMAL") {
attribute.type = PrimitiveType::NORMAL;
} else if (attrib.first == "TEXCOORD_0") {
attribute.type = PrimitiveType::TEXCOORD_0;
}
else if (attrib.first == "TEXCOORD_1") {
attribute.type = PrimitiveType::TEXCOORD_1;
} else if (attrib.first == "TANGENT") {
attribute.type = PrimitiveType::TANGENT;
} else {
return 0;
}
attribute.offset = sceneObjects.bufferViews[accessor.bufferView].byteOffset;
attribute.length = sceneObjects.bufferViews[accessor.bufferView].byteLength;
attribute.stride = sceneObjects.bufferViews[accessor.bufferView].byteStride;
attribute.componentType = static_cast<ComponentType>(accessor.componentType);
if (convertTypeToInt(accessor.type) != 10) {
attribute.componentCount = convertTypeToInt(accessor.type);
} else {
return 0;
}
vertexAttributes.push_back(attribute);
}
IndexType indexType;
std::vector<uint8_t> indexBufferData = {};
if (objectPrimitive.indices >= 0){ // if there is no index buffer, -1 is returned from fx-gltf
const fx::gltf::Accessor &indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
const fx::gltf::BufferView &indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
const fx::gltf::Buffer &indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
indexBufferData.resize(indexBufferView.byteLength);
{
const size_t off = indexBufferView.byteOffset;
const void *const ptr = ((char*)indexBuffer.data.data()) + off;
if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
vkcv_log(LogLevel::ERROR, "Copying index buffer data");
return 0;
}
}
indexType = getIndexType(indexAccessor.componentType);
if (indexType == IndexType::UNDEFINED){
vkcv_log(LogLevel::ERROR, "Index Type undefined.");
return 0;
}
}
const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView];
const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer];
// only copy relevant part of vertex data
uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
uint32_t relevantBufferEnd = 0;
for (const auto &attribute : vertexAttributes) {
relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
const uint32_t attributeEnd = attribute.offset + attribute.length;
relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd); // TODO: need to incorporate stride?
}
const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
// FIXME: This only works when all vertex attributes are in one buffer
std::vector<uint8_t> vertexBufferData;
vertexBufferData.resize(relevantBufferSize);
{
const void *const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset;
if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) {
vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
return 0;
}
}
// make vertex attributes relative to copied section
for (auto &attribute : vertexAttributes) {
attribute.offset -= relevantBufferOffset;
}
const size_t numVertexGroups = objectMesh.primitives.size();
vertexGroups.reserve(numVertexGroups);
vertexGroups.push_back({
static_cast<PrimitiveMode>(objectPrimitive.mode),
sceneObjects.accessors[objectPrimitive.indices].count,
posAccessor.count,
{indexType, indexBufferData},
{vertexBufferData, vertexAttributes},
{posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
{posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
static_cast<uint8_t>(objectPrimitive.material)
});
vertexGroupsIndices.push_back(groupCount);
groupCount++;
}
mesh.name = sceneObjects.meshes[i].name;
mesh.vertexGroups = vertexGroupsIndices;
meshes.push_back(mesh);
}
for(int m = 0; m < sceneObjects.nodes.size(); m++) {
meshes[sceneObjects.nodes[m].mesh].modelMatrix = computeModelMatrix(sceneObjects.nodes[m].translation,
sceneObjects.nodes[m].scale,
sceneObjects.nodes[m].rotation,
sceneObjects.nodes[m].matrix);
}
if (sceneObjects.textures.size() > 0){
textures.reserve(sceneObjects.textures.size());
for(int k = 0; k < sceneObjects.textures.size(); k++){
const fx::gltf::Texture &tex = sceneObjects.textures[k];
const fx::gltf::Image &img = sceneObjects.images[tex.source];
std::string img_uri = dir + "/" + img.uri;
int w, h, c;
uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4);
c = 4; // FIXME hardcoded to always have RGBA channel layout
if (!data) {
vkcv_log(LogLevel::ERROR, "Loading texture image data.")
return 0;
}
const size_t byteLen = w * h * c;
std::vector<uint8_t> imgdata;
imgdata.resize(byteLen);
if (!memcpy(imgdata.data(), data, byteLen)) {
vkcv_log(LogLevel::ERROR, "Copying texture image data")
free(data);
return 0;
}
free(data);
/**
* This function fills the array of vertex attributes of a VertexGroup (usually
* part of a vkcv::asset::Mesh) object based on the description of attributes
* for a fx::gltf::Primitive.
*
* @param src The description of attribute objects from the fx-gltf library
* @param gltf The main glTF document
* @param dst The array of vertex attributes stored in an asset::Mesh object
* @return ASSET_ERROR when at least one VertexAttribute could not be
* constructed properly, otherwise ASSET_SUCCESS
*/
static int loadVertexAttributes(const fx::gltf::Attributes &src,
const std::vector<fx::gltf::Accessor> &accessors,
const std::vector<fx::gltf::BufferView> &bufferViews,
std::vector<VertexAttribute> &dst) {
for (const auto &attrib : src) {
VertexAttribute att {};
if (attrib.first == "POSITION") {
att.type = PrimitiveType::POSITION;
} else if (attrib.first == "NORMAL") {
att.type = PrimitiveType::NORMAL;
} else if (attrib.first == "TANGENT") {
att.type = PrimitiveType::TANGENT;
} else if (attrib.first == "TEXCOORD_0") {
att.type = PrimitiveType::TEXCOORD_0;
} else if (attrib.first == "TEXCOORD_1") {
att.type = PrimitiveType::TEXCOORD_1;
} else if (attrib.first == "COLOR_0") {
att.type = PrimitiveType::COLOR_0;
} else if (attrib.first == "COLOR_1") {
att.type = PrimitiveType::COLOR_1;
} else if (attrib.first == "JOINTS_0") {
att.type = PrimitiveType::JOINTS_0;
} else if (attrib.first == "WEIGHTS_0") {
att.type = PrimitiveType::WEIGHTS_0;
} else {
att.type = PrimitiveType::UNDEFINED;
}
if (att.type != PrimitiveType::UNDEFINED) {
const fx::gltf::Accessor &accessor = accessors[attrib.second];
const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView];
att.offset = buf.byteOffset;
att.length = buf.byteLength;
att.stride = buf.byteStride;
att.componentType = static_cast<ComponentType>(accessor.componentType);
att.componentCount = getComponentCount(accessor.type);
/* Assume tightly packed stride as not explicitly provided */
if (att.stride == 0) {
att.stride = att.componentCount * getComponentSize(att.componentType);
}
}
if ((att.type == PrimitiveType::UNDEFINED) ||
(att.componentCount == 0)) {
return ASSET_ERROR;
}
dst.push_back(att);
}
return ASSET_SUCCESS;
}
textures.push_back({
0,
static_cast<uint8_t>(c),
static_cast<uint16_t>(w),
static_cast<uint16_t>(h),
imgdata
});
/**
* This function calculates the modelMatrix out of the data given in the gltf file.
* It also checks, whether a modelMatrix was given.
*
* @param translation possible translation vector (default 0,0,0)
* @param scale possible scale vector (default 1,1,1)
* @param rotation possible rotation, given in quaternion (default 0,0,0,1)
* @param matrix possible modelmatrix (default identity)
* @return model Matrix as an array of floats
*/
static std::array<float, 16> calculateModelMatrix(const std::array<float, 3>& translation,
const std::array<float, 3>& scale,
const std::array<float, 4>& rotation,
const std::array<float, 16>& matrix){
std::array<float, 16> modelMatrix = {
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1
};
if (matrix != modelMatrix){
return matrix;
} else {
// translation
modelMatrix[3] = translation[0];
modelMatrix[7] = translation[1];
modelMatrix[11] = translation[2];
// rotation and scale
auto a = rotation[0];
auto q1 = rotation[1];
auto q2 = rotation[2];
auto q3 = rotation[3];
modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
// flip y, because GLTF uses y up, but vulkan -y up
modelMatrix[5] *= -1;
return modelMatrix;
}
}
}
}
bool Material::hasTexture(const PBRTextureTarget target) const {
return textureMask & bitflag(target);
}
if (sceneObjects.materials.size() > 0){
materials.reserve(sceneObjects.materials.size());
/**
* This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart.
* @param mode: wrapping mode of a sampler given as fx-gltf-enum
* @return int vulkan-enum representing the same wrapping mode
*/
static int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode) {
switch (mode) {
case fx::gltf::Sampler::WrappingMode::ClampToEdge:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case fx::gltf::Sampler::WrappingMode::MirroredRepeat:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case fx::gltf::Sampler::WrappingMode::Repeat:
default:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
}
}
for (int l = 0; l < sceneObjects.materials.size(); l++){
fx::gltf::Material material = sceneObjects.materials[l];
// TODO I think we shouldn't set the index for a texture target if
// it isn't defined. So we need to test first if there is a normal
// texture before assigning material.normalTexture.index.
// About the bitmask: If a normal texture is there, modify the
// materials textureMask like this:
// mat.textureMask |= bitflag(asset::normal);
materials.push_back({
0,
material.pbrMetallicRoughness.baseColorTexture.index,
material.pbrMetallicRoughness.metallicRoughnessTexture.index,
material.normalTexture.index,
material.occlusionTexture.index,
material.emissiveTexture.index,
{
material.pbrMetallicRoughness.baseColorFactor[0],
material.pbrMetallicRoughness.baseColorFactor[1],
material.pbrMetallicRoughness.baseColorFactor[2],
material.pbrMetallicRoughness.baseColorFactor[3]
},
material.pbrMetallicRoughness.metallicFactor,
material.pbrMetallicRoughness.roughnessFactor,
material.normalTexture.scale,
material.occlusionTexture.strength,
{
material.emissiveFactor[0],
material.emissiveFactor[1],
material.emissiveFactor[2]
}
/**
* If the glTF doesn't define samplers, we use the defaults defined by fx-gltf.
* The following are details about the glTF/OpenGL to Vulkan translation.
* magFilter (VkFilter?):
* GL_NEAREST -> VK_FILTER_NEAREST
* GL_LINEAR -> VK_FILTER_LINEAR
* minFilter (VkFilter?):
* mipmapMode (VkSamplerMipmapMode?):
* Vulkans minFilter and mipmapMode combined correspond to OpenGLs
* GL_minFilter_MIPMAP_mipmapMode:
* GL_NEAREST_MIPMAP_NEAREST:
* minFilter=VK_FILTER_NEAREST
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
* GL_LINEAR_MIPMAP_NEAREST:
* minFilter=VK_FILTER_LINEAR
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
* GL_NEAREST_MIPMAP_LINEAR:
* minFilter=VK_FILTER_NEAREST
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
* GL_LINEAR_MIPMAP_LINEAR:
* minFilter=VK_FILTER_LINEAR
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
* The modes of GL_LINEAR and GL_NEAREST have to be emulated using
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD:
* GL_LINEAR:
* minFilter=VK_FILTER_LINEAR
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
* minLOD=0, maxLOD=0.25
* GL_NEAREST:
* minFilter=VK_FILTER_NEAREST
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
* minLOD=0, maxLOD=0.25
* Setting maxLOD=0 causes magnification to always be performed (using
* the defined magFilter), this may be valid if the min- and magFilter
* are equal, otherwise it won't be the expected behaviour from OpenGL
* and glTF; instead using maxLod=0.25 allows the minFilter to be
* performed while still always rounding to the base level.
* With other modes, minLOD and maxLOD default to:
* minLOD=0
* maxLOD=VK_LOD_CLAMP_NONE
* wrapping:
* gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while
* Vulkan has addressModeU, addressModeV, addressModeW with values
* VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE,
* CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE}
* Translation from glTF to Vulkan is straight forward for the 3 existing
* modes, default is repeat, the other modes aren't available.
*/
static vkcv::asset::Sampler loadSampler(const fx::gltf::Sampler &src) {
Sampler dst {};
dst.minLOD = 0;
dst.maxLOD = VK_LOD_CLAMP_NONE;
switch (src.minFilter) {
case fx::gltf::Sampler::MinFilter::None:
case fx::gltf::Sampler::MinFilter::Nearest:
dst.minFilter = VK_FILTER_NEAREST;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
dst.maxLOD = 0.25;
break;
case fx::gltf::Sampler::MinFilter::Linear:
dst.minFilter = VK_FILTER_LINEAR;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
dst.maxLOD = 0.25;
break;
case fx::gltf::Sampler::MinFilter::NearestMipMapNearest:
dst.minFilter = VK_FILTER_NEAREST;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
break;
case fx::gltf::Sampler::MinFilter::LinearMipMapNearest:
dst.minFilter = VK_FILTER_LINEAR;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
break;
case fx::gltf::Sampler::MinFilter::NearestMipMapLinear:
dst.minFilter = VK_FILTER_NEAREST;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
break;
case fx::gltf::Sampler::MinFilter::LinearMipMapLinear:
dst.minFilter = VK_FILTER_LINEAR;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
break;
default:
break;
}
switch (src.magFilter) {
case fx::gltf::Sampler::MagFilter::None:
case fx::gltf::Sampler::MagFilter::Nearest:
dst.magFilter = VK_FILTER_NEAREST;
break;
case fx::gltf::Sampler::MagFilter::Linear:
dst.magFilter = VK_FILTER_LINEAR;
break;
default:
break;
}
dst.addressModeU = translateSamplerMode(src.wrapS);
dst.addressModeV = translateSamplerMode(src.wrapT);
// There is no information about wrapping for a third axis in glTF and
// we have to hardcode this value.
dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
return dst;
}
});
}
}
/**
* Initializes vertex groups of a Mesh, including copying the data to
* index- and vertex-buffers.
*/
static int loadVertexGroups(const fx::gltf::Mesh &objectMesh,
const fx::gltf::Document &sceneObjects,
Scene &scene, Mesh &mesh) {
mesh.vertexGroups.reserve(objectMesh.primitives.size());
for (const auto &objectPrimitive : objectMesh.primitives) {
VertexGroup vertexGroup;
vertexGroup.vertexBuffer.attributes.reserve(
objectPrimitive.attributes.size()
);
if (ASSET_SUCCESS != loadVertexAttributes(
objectPrimitive.attributes,
sceneObjects.accessors,
sceneObjects.bufferViews,
vertexGroup.vertexBuffer.attributes)) {
vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes of '%s'",
mesh.name.c_str());
return ASSET_ERROR;
}
// The accessor for the position attribute is used for
// 1) getting the vertex buffer view which is only needed to get
// the vertex buffer
// 2) getting the vertex count for the VertexGroup
// 3) getting the min/max of the bounding box for the VertexGroup
fx::gltf::Accessor posAccessor;
bool noPosition = true;
for (auto const& attrib : objectPrimitive.attributes) {
if (attrib.first == "POSITION") {
posAccessor = sceneObjects.accessors[attrib.second];
noPosition = false;
break;
}
}
if (noPosition) {
vkcv_log(LogLevel::ERROR, "Position attribute not found from '%s'",
mesh.name.c_str());
return ASSET_ERROR;
}
const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
int indexBufferURI;
if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf
const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
// Because the buffers are already preloaded into the memory by the gltf-library,
// it makes no sense to load them later on manually again into memory.
vertexGroup.indexBuffer.data.resize(indexBufferView.byteLength);
memcpy(vertexGroup.indexBuffer.data.data(),
indexBuffer.data.data() + indexBufferView.byteOffset,
indexBufferView.byteLength);
} else {
indexBufferURI = -1;
}
vertexGroup.indexBuffer.type = getIndexType(indexAccessor.componentType);
if (IndexType::UNDEFINED == vertexGroup.indexBuffer.type) {
vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported.");
return ASSET_ERROR;
}
if (posAccessor.bufferView >= sceneObjects.bufferViews.size()) {
vkcv_log(LogLevel::ERROR, "Access to bufferView out of bounds: %d",
posAccessor.bufferView);
return ASSET_ERROR;
}
const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView];
if (vertexBufferView.buffer >= sceneObjects.buffers.size()) {
vkcv_log(LogLevel::ERROR, "Access to buffer out of bounds: %d",
vertexBufferView.buffer);
return ASSET_ERROR;
}
const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer];
// only copy relevant part of vertex data
uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
uint32_t relevantBufferEnd = 0;
for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
relevantBufferEnd = std::max(relevantBufferEnd, attribute.offset + attribute.length);
}
const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
vertexGroup.vertexBuffer.data.resize(relevantBufferSize);
memcpy(vertexGroup.vertexBuffer.data.data(),
vertexBuffer.data.data() + relevantBufferOffset,
relevantBufferSize);
// make vertex attributes relative to copied section
for (auto& attribute : vertexGroup.vertexBuffer.attributes) {
attribute.offset -= relevantBufferOffset;
}
vertexGroup.mode = static_cast<PrimitiveMode>(objectPrimitive.mode);
vertexGroup.numIndices = sceneObjects.accessors[objectPrimitive.indices].count;
vertexGroup.numVertices = posAccessor.count;
memcpy(&(vertexGroup.min), posAccessor.min.data(), sizeof(vertexGroup.min));
memcpy(&(vertexGroup.max), posAccessor.max.data(), sizeof(vertexGroup.max));
vertexGroup.materialIndex = static_cast<uint8_t>(objectPrimitive.material);
mesh.vertexGroups.push_back(static_cast<int>(scene.vertexGroups.size()));
scene.vertexGroups.push_back(vertexGroup);
}
return ASSET_SUCCESS;
}
scene = {
meshes,
vertexGroups,
materials,
textures,
samplers
};
/**
* Returns an integer with specific bits set corresponding to the
* textures that appear in the given material. This mask is used in the
* vkcv::asset::Material struct and can be tested via the hasTexture
* method.
*/
static uint16_t generateTextureMask(fx::gltf::Material &material) {
uint16_t textureMask = 0;
if (material.pbrMetallicRoughness.baseColorTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::baseColor);
}
if (material.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::metalRough);
}
if (material.normalTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::normal);
}
if (material.occlusionTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::occlusion);
}
if (material.emissiveTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::emissive);
}
return textureMask;
}
return 1;
}
int probeScene(const std::filesystem::path& path, Scene& scene) {
fx::gltf::Document sceneObjects;
try {
if (path.extension() == ".glb") {
sceneObjects = fx::gltf::LoadFromBinary(path.string());
} else {
sceneObjects = fx::gltf::LoadFromText(path.string());
}
} catch (const std::system_error& err) {
recurseExceptionPrint(err, path.string());
return ASSET_ERROR;
} catch (const std::exception& e) {
recurseExceptionPrint(e, path.string());
return ASSET_ERROR;
}
const auto directory = path.parent_path();
scene.meshes.clear();
scene.vertexGroups.clear();
scene.materials.clear();
scene.textures.clear();
scene.samplers.clear();
// file has to contain at least one mesh
if (sceneObjects.meshes.empty()) {
vkcv_log(LogLevel::ERROR, "No meshes found! (%s)", path.c_str());
return ASSET_ERROR;
} else {
scene.meshes.reserve(sceneObjects.meshes.size());
for (size_t i = 0; i < sceneObjects.meshes.size(); i++) {
Mesh mesh;
mesh.name = sceneObjects.meshes[i].name;
if (loadVertexGroups(sceneObjects.meshes[i], sceneObjects, scene, mesh) != ASSET_SUCCESS) {
vkcv_log(LogLevel::ERROR, "Failed to load vertex groups of '%s'! (%s)",
mesh.name.c_str(), path.c_str());
return ASSET_ERROR;
}
scene.meshes.push_back(mesh);
}
// This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights
for (const auto& node : sceneObjects.nodes) {
if ((node.mesh >= 0) && (node.mesh < scene.meshes.size())) {
scene.meshes[node.mesh].modelMatrix = calculateModelMatrix(
node.translation,
node.scale,
node.rotation,
node.matrix
);
}
}
}
if (sceneObjects.samplers.empty()) {
vkcv_log(LogLevel::WARNING, "No samplers found! (%s)", path.c_str());
} else {
scene.samplers.reserve(sceneObjects.samplers.size());
for (const auto &samplerObject : sceneObjects.samplers) {
scene.samplers.push_back(loadSampler(samplerObject));
}
}
if (sceneObjects.textures.empty()) {
vkcv_log(LogLevel::WARNING, "No textures found! (%s)", path.c_str());
} else {
scene.textures.reserve(sceneObjects.textures.size());
for (const auto& textureObject : sceneObjects.textures) {
Texture texture;
if (textureObject.sampler < 0) {
texture.sampler = -1;
} else
if (static_cast<size_t>(textureObject.sampler) >= scene.samplers.size()) {
vkcv_log(LogLevel::ERROR, "Sampler of texture '%s' missing (%s)",
textureObject.name.c_str(), path.c_str());
return ASSET_ERROR;
} else {
texture.sampler = textureObject.sampler;
}
if ((textureObject.source < 0) ||
(static_cast<size_t>(textureObject.source) >= sceneObjects.images.size())) {
vkcv_log(LogLevel::ERROR, "Failed to load texture '%s' (%s)",
textureObject.name.c_str(), path.c_str());
return ASSET_ERROR;
}
const auto& image = sceneObjects.images[textureObject.source];
if (image.uri.empty()) {
const fx::gltf::BufferView bufferView = sceneObjects.bufferViews[image.bufferView];
texture.path.clear();
texture.data.resize(bufferView.byteLength);
memcpy(texture.data.data(),
sceneObjects.buffers[bufferView.buffer].data.data() + bufferView.byteOffset,
bufferView.byteLength);
} else {
texture.path = directory / image.uri;
}
scene.textures.push_back(texture);
}
}
if (sceneObjects.materials.empty()) {
vkcv_log(LogLevel::WARNING, "No materials found! (%s)", path.c_str());
} else {
scene.materials.reserve(sceneObjects.materials.size());
for (auto material : sceneObjects.materials) {
scene.materials.push_back({
generateTextureMask(material),
material.pbrMetallicRoughness.baseColorTexture.index,
material.pbrMetallicRoughness.metallicRoughnessTexture.index,
material.normalTexture.index,
material.occlusionTexture.index,
material.emissiveTexture.index,
{
material.pbrMetallicRoughness.baseColorFactor[0],
material.pbrMetallicRoughness.baseColorFactor[1],
material.pbrMetallicRoughness.baseColorFactor[2],
material.pbrMetallicRoughness.baseColorFactor[3]
},
material.pbrMetallicRoughness.metallicFactor,
material.pbrMetallicRoughness.roughnessFactor,
material.normalTexture.scale,
material.occlusionTexture.strength,
{
material.emissiveFactor[0],
material.emissiveFactor[1],
material.emissiveFactor[2]
}
});
}
}
return ASSET_SUCCESS;
}
/**
* Loads and decodes the textures data based on the textures file path.
* The path member is the only one that has to be initialized before
* calling this function, the others (width, height, channels, data)
* are set by this function and the sampler is of no concern here.
*/
static int loadTextureData(Texture& texture) {
if ((texture.width > 0) && (texture.height > 0) && (texture.channels > 0) &&
(!texture.data.empty())) {
return ASSET_SUCCESS; // Texture data was loaded already!
}
uint8_t* data;
if (texture.path.empty()) {
data = stbi_load_from_memory(
reinterpret_cast<uint8_t*>(texture.data.data()),
static_cast<int>(texture.data.size()),
&texture.width,
&texture.height,
&texture.channels, 4
);
} else {
data = stbi_load(
texture.path.string().c_str(),
&texture.width,
&texture.height,
&texture.channels, 4
);
}
if (!data) {
vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'",
texture.path.c_str());
texture.width = 0;
texture.height = 0;
texture.channels = 0;
return ASSET_ERROR;
}
texture.data.resize(texture.width * texture.height * 4);
memcpy(texture.data.data(), data, texture.data.size());
stbi_image_free(data);
return ASSET_SUCCESS;
}
TextureData loadTexture(const std::filesystem::path& path) {
TextureData texture;
uint8_t* data = stbi_load(path.string().c_str(), &texture.width, &texture.height, &texture.componentCount, 4);
if (!data) {
vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'", path.c_str());
texture.width = 0;
texture.height = 0;
texture.componentCount = 0;
return texture;
}
texture.data.resize(texture.width * texture.height * 4);
memcpy(texture.data.data(), data, texture.data.size());
return texture;
}
int loadMesh(Scene &scene, int index) {
if ((index < 0) || (static_cast<size_t>(index) >= scene.meshes.size())) {
vkcv_log(LogLevel::ERROR, "Mesh index out of range: %d", index);
return ASSET_ERROR;
}
const Mesh &mesh = scene.meshes[index];
for (const auto& vg : mesh.vertexGroups) {
const VertexGroup &vertexGroup = scene.vertexGroups[vg];
const Material& material = scene.materials[vertexGroup.materialIndex];
if (material.hasTexture(PBRTextureTarget::baseColor)) {
const int result = loadTextureData(scene.textures[material.baseColor]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading baseColor texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
if (material.hasTexture(PBRTextureTarget::metalRough)) {
const int result = loadTextureData(scene.textures[material.metalRough]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading metalRough texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
if (material.hasTexture(PBRTextureTarget::normal)) {
const int result = loadTextureData(scene.textures[material.normal]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading normal texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
if (material.hasTexture(PBRTextureTarget::occlusion)) {
const int result = loadTextureData(scene.textures[material.occlusion]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading occlusion texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
if (material.hasTexture(PBRTextureTarget::emissive)) {
const int result = loadTextureData(scene.textures[material.emissive]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading emissive texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
}
return ASSET_SUCCESS;
}
int loadScene(const std::filesystem::path &path, Scene &scene) {
int result = probeScene(path, scene);
if (result != ASSET_SUCCESS) {
vkcv_log(LogLevel::ERROR, "Loading scene failed '%s'",
path.c_str());
return result;
}
for (size_t i = 0; i < scene.meshes.size(); i++) {
result = loadMesh(scene, static_cast<int>(i));
if (result != ASSET_SUCCESS) {
vkcv_log(LogLevel::ERROR, "Loading mesh with index %d failed '%s'",
static_cast<int>(i), path.c_str());
return result;
}
}
return ASSET_SUCCESS;
}
Texture loadTexture(const std::filesystem::path& path) {
Texture texture;
texture.path = path;
texture.sampler = -1;
if (loadTextureData(texture) != ASSET_SUCCESS) {
texture.path.clear();
texture.w = texture.h = texture.channels = 0;
texture.data.clear();
}
return texture;
}
}
......@@ -29,42 +29,6 @@ namespace vkcv::camera {
float m_fov_min;
float m_fov_max;
/**
* @brief Indicates forward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveForward(int action);
/**
* @brief Indicates backward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveBackward(int action);
/**
* @brief Indicates left movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveLeft(int action);
/**
* @brief Indicates right movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveRight(int action);
/**
* @brief Indicates upward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveUpward(int action);
/**
* @brief Indicates downward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveDownward(int action);
public:
/**
......
......@@ -52,8 +52,8 @@ namespace vkcv::camera {
}
void CameraManager::mouseMoveCallback(double x, double y){
auto xoffset = static_cast<float>(x - m_lastX);
auto yoffset = static_cast<float>(y - m_lastY);
auto xoffset = static_cast<float>(x - m_lastX) / m_window.getWidth();
auto yoffset = static_cast<float>(y - m_lastY) / m_window.getHeight();
m_lastX = x;
m_lastY = y;
getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera());
......