diff --git a/CMakePresets.json b/CMakePresets.json index 94effdfc..34465a50 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,11 +8,11 @@ "binaryDir": "${sourceDir}/out/build/${presetName}", "installDir": "${sourceDir}/out/install/${presetName}", "cacheVariables": { - "CMAKE_COMPILE_WARNING_AS_ERROR": "ON", - "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + "CMAKE_COMPILE_WARNING_AS_ERROR": "OFF" }, "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake" }, + { "name": "windows-base", "hidden": true, @@ -20,8 +20,8 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_C_FLAGS_INIT": "/W4 /wd4700", - "CMAKE_CXX_FLAGS_INIT": "/W4 /wd4700" + "CMAKE_C_FLAGS_INIT": "/W4", + "CMAKE_CXX_FLAGS_INIT": "/W4" }, "condition": { "type": "equals", @@ -29,6 +29,7 @@ "rhs": "Windows" } }, + { "name": "wx64-debug", "displayName": "x64 Debug", @@ -49,6 +50,26 @@ "CMAKE_BUILD_TYPE": "Release" } }, + { + "name": "wx86-debug", + "displayName": "x86 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "wx86-release", + "displayName": "x86 Release", + "inherits": "wx86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, { "name": "linux-base", "hidden": true, @@ -56,8 +77,8 @@ "cacheVariables": { "CMAKE_C_COMPILER": "clang", "CMAKE_CXX_COMPILER": "clang++", - "CMAKE_C_FLAGS_INIT": "-Wall -Wextra -pedantic -Wconversion -Wdouble-promotion -Wshadow -Wno-missing-field-initializers", - "CMAKE_CXX_FLAGS_INIT": "-Wall -Wextra -pedantic -Wconversion -Wdouble-promotion -Wshadow -Wno-missing-field-initializers" + "CMAKE_C_FLAGS_INIT": "-Wall -Wextra -pedantic -Wconversion -Wdouble-promotion -Wshadow -Wno-missing-field-initializers -Wno-nullability-extension", + "CMAKE_CXX_FLAGS_INIT": "-Wall -Wextra -pedantic -Wconversion -Wdouble-promotion -Wshadow -Wno-missing-field-initializers -Wno-nullability-extension" }, "condition": { "type": "equals", @@ -84,53 +105,26 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } - } - ], - "buildPresets": [ - { - "name": "wx64-debug", - "configurePreset": "wx64-debug" - }, - { - "name": "wx64-release", - "configurePreset": "wx64-release" - }, - { - "name": "lx64-debug", - "configurePreset": "lx64-debug" }, { - "name": "lx64-release", - "configurePreset": "lx64-release" - } - ], - "testPresets": [ - { - "name": "base", - "output": { - "outputOnFailure": true + "name": "lx86-debug", + "displayName": "x86 Debug", + "inherits": "linux-base", + "architecture": { + "value": "x86", + "strategy": "external" }, - "hidden": true - }, - { - "name": "wx64-debug", - "inherits": "base", - "configurePreset": "wx64-debug" - }, - { - "name": "wx64-release", - "inherits": "base", - "configurePreset": "wx64-release" - }, - { - "name": "lx64-debug", - "inherits": "base", - "configurePreset": "lx64-debug" + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } }, { - "name": "lx64-release", - "inherits": "base", - "configurePreset": "lx64-release" + "name": "lx86-release", + "displayName": "x86 Release", + "inherits": "lx86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } } ] } \ No newline at end of file diff --git a/cmake/compileShaders.cmake b/cmake/compileShaders.cmake index 3b09d959..44a5c967 100644 --- a/cmake/compileShaders.cmake +++ b/cmake/compileShaders.cmake @@ -1,7 +1,7 @@ set(BUILD_SHADERS_SCRIPT "${CMAKE_SOURCE_DIR}/scripts/build_shaders.py") set(SHADERS_SOURCE_DIR "${CMAKE_SOURCE_DIR}/shaders") -set(SHADERS_DEST_DIR "${CMAKE_BINARY_DIR}/shaders") +set(SHADERS_DEST_DIR "${CMAKE_BINARY_DIR}/demo/shaders") add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E echo "Running build_shaders.py script..." diff --git a/demo/main.cpp b/demo/main.cpp index 4585d801..2ff15de9 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,16 +1,100 @@ -#include -#include -#include +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include #include "IController.h" -int main([[maybe_unused]] int argc, [[maybe_unused]] char* args[]) { - try { - const auto controller = createInstance(); - controller->init(); - } catch (const std::runtime_error& e) { - std::cerr << "Unhandled exception: " << e.what() << '\n'; +void createCubes(const std::weak_ptr& mesh_controller) { + for (int i = 0; i < 5; i++) { + if (const auto sp = mesh_controller.lock()) { + sp->create_mesh("/basicmesh.glb"); + } else { + throw std::runtime_error("Cubes could not be created"); + } + } +} + +double getCurrentGlobalTime() { + // Get the current time point + const auto now = std::chrono::system_clock::now(); + + // Cast to a time duration since the epoch + const auto durationSinceEpoch = now.time_since_epoch(); + + // Convert to seconds in double precision + const std::chrono::duration seconds = durationSinceEpoch; + + // Return the double value + return seconds.count(); +} + +void updateCube(const std::shared_ptr& mesh_controller, Mesh::rid_t rid, int8_t i) { + const double sinValue = std::sin(getCurrentGlobalTime() + static_cast(i)) * 5.; + + const glm::mat4 scale = glm::scale(glm::vec3{0.2f}); + const glm::mat4 translation = glm::translate(glm::vec3{static_cast(i) - 2.5f, sinValue, 0}); + mesh_controller->set_transform(rid, scale * translation); +} + +void updateCubes(const std::weak_ptr& mesh_controller) { + if (const auto sp = mesh_controller.lock()) { + auto meshes = sp->get_meshes(); + for (int8_t i {}; const auto key : meshes) { + updateCube(sp, key, i); + ++i; + } + } else { + throw std::runtime_error("Cubes could not be updated"); } +} + +int main([[maybe_unused]] int argc, [[maybe_unused]] char* args[]) +{ + try + { + const auto controller = createInstance(); + const auto mesh_controller = controller->getMeshController(); + createCubes(mesh_controller); + + SDL_Event e; + bool bQuit = false; + bool stop_rendering = false; + + // main loop + while (!bQuit) { + // Handle events on queue + while (SDL_PollEvent(&e) != 0) { + // close the window when user alt-f4s or clicks the X button + if (e.type == SDL_QUIT) bQuit = true; + + if (e.type == SDL_WINDOWEVENT) { + if (e.window.event == SDL_WINDOWEVENT_MINIMIZED) { + stop_rendering = true; + } + if (e.window.event == SDL_WINDOWEVENT_RESTORED) { + stop_rendering = false; + } + } + + controller->process_event(e); + } + + // do not draw if we are minimized + if (stop_rendering) { + // throttle the speed to avoid the endless spinning + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + controller->run(); + controller->update(); + updateCubes(mesh_controller); + } + } catch (std::runtime_error const& e) + { + std::cerr << "Unhandled exception: " << e.what() << '\n'; + } - return 0; + return 0; } \ No newline at end of file diff --git a/include/IController.h b/include/IController.h index c469b8bb..28ea1879 100644 --- a/include/IController.h +++ b/include/IController.h @@ -6,6 +6,7 @@ #include #include "SDL2/SDL.h" +#include "core/MeshController.h" /*! \brief * Interface for interacting with the chip that allows changing keyboard @@ -23,57 +24,29 @@ class IController { * \brief Virtual destructor for the interface. */ virtual ~IController() = default; - - /*! - * \brief Initializes the controller. - * - * This method initializes the necessary components for the controller. - */ - virtual void init() const = 0; - /*! - * \brief Updates the controller status. - * - * This method updates the internal state or status of the controller. + * \brief Renders a new frame */ - virtual void update() const = 0; - + virtual void run() = 0; /*! - * \brief Processes incoming events. - * - * \param e Event to be processed. - * - * This method processes the incoming SDL event and performs necessary - * actions. + * \brief Applies updates */ - virtual void processEvent(SDL_Event &e) const = 0; - + virtual void update() = 0; /*! - * \brief Sends data to change the brightness of the backlight. - * - * \param lvl Level of brightness [0; 100]. - * - * This method sends data to the chip to change the brightness level of the - * backlight. The lvl parameter specifies the brightness level from 0 (off) - * to 100 (maximum brightness). + * \brief Gets a controller to interact with meshes + * \return Pointer to the MeshController */ - // virtual void setBrightness(uint16_t lvl) const = 0; - + [[nodiscard]] virtual std::weak_ptr + getMeshController() = 0; /*! - * \brief Sends data to change the color of the backlight. - * - * \param color Struct with RGB values (each [0; 255]). - * - * This method sends data to the chip to change the color of the backlight. - * The color parameter is a struct containing RGB values, each ranging from - * 0 to 255. + * \brief Handles user actions (SDL events) + * @param e SDL event */ - // virtual void setColor(Color color) const = 0; - + virtual void process_event(const SDL_Event& e) = 0; /*! * \brief Shared pointer type for IController. */ - using Ptr = std::shared_ptr; + using Ptr = std::shared_ptr; }; IController::Ptr createInstance(); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 987b4319..df62f0c3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,7 +2,7 @@ target_sources(${PROJECT_NAME} PRIVATE Controller.cpp ControllerImpl.cpp - Mesh.cpp + MeshController.cpp Model.cpp ModelImpl.cpp View.cpp diff --git a/src/core/Controller.cpp b/src/core/Controller.cpp index cfabd7e7..afbc8a0b 100644 --- a/src/core/Controller.cpp +++ b/src/core/Controller.cpp @@ -5,6 +5,6 @@ #include "core/ControllerImpl.h" -IController::Ptr createController(IModel::Ptr ptr) { - return std::make_shared(std::move(ptr)); +IController::Ptr createController(IModel::Ptr model, IView::Ptr view) { + return std::make_shared(std::move(model), std::move(view)); } \ No newline at end of file diff --git a/src/core/ControllerImpl.cpp b/src/core/ControllerImpl.cpp index d7ee3a5f..212c2d83 100644 --- a/src/core/ControllerImpl.cpp +++ b/src/core/ControllerImpl.cpp @@ -1,63 +1,31 @@ #include "core/ControllerImpl.h" -#include -#include -#include +#include #include -#define GLM_ENABLE_EXPERIMENTAL -#include - -#include "core/View.h" +#include "core/MeshController.h" #include "scene/Camera.h" -ControllerImpl::ControllerImpl(IModel::Ptr model) : _model(std::move(model)) {} - -double getCurrentGlobalTime() { - // Get the current time point - const auto now = std::chrono::system_clock::now(); - - // Cast to a time duration since the epoch - const auto durationSinceEpoch = now.time_since_epoch(); - - // Convert to seconds in double precision - const std::chrono::duration seconds = durationSinceEpoch; +ControllerImpl::ControllerImpl(IModel::Ptr model, IView::Ptr view) + : _model(std::move(model)), _view(std::move(view)) {} - // Return the double value - return seconds.count(); +void ControllerImpl::update() { + _model->get_engine().update(_model); + _model->getCamera()->update(); } -void updateCube(const std::shared_ptr &_model, int name) { - const double sinValue = std::sin(getCurrentGlobalTime() + name) * 5.0; - - const glm::mat4 scale = glm::scale(glm::vec3{0.2f}); - const glm::mat4 translation = glm::translate( - glm::vec3{static_cast(name) - 2.5f, sinValue, 0}); - - _model->setMeshTransform("cube" + std::to_string(name), - scale * translation); +void ControllerImpl::run() { + _view->run(); } -void updateCubes(const std::shared_ptr &_model) { - for (int i = 0; i < 5; i++) { - updateCube(_model, i); +std::weak_ptr ControllerImpl::getMeshController() { + if (!_mesh_controller) { + _mesh_controller = std::make_shared(_model); } + return _mesh_controller; } -void ControllerImpl::update() const { - _model->updateVulkan(); - - _model->getCamera()->update(); - - updateCubes(_model); -} - -void ControllerImpl::processEvent(SDL_Event &e) const { +void ControllerImpl::process_event(const SDL_Event& e) { _model->getCamera()->processSDLEvent(e); -} - -void ControllerImpl::init() const { - const auto controller = shared_from_this(); - const auto view = createView(controller, _model); - view->run(); + _view->process_event(e); } diff --git a/src/core/Mesh.cpp b/src/core/Mesh.cpp deleted file mode 100644 index f496c69d..00000000 --- a/src/core/Mesh.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Mesh.cpp -#include "core/Mesh.h" - -Mesh::Mesh(const std::string &filePath) : _currentModelPath(filePath) { - VulkanEngine &engine = VulkanEngine::Get(); - _rid = engine.registerMesh(filePath); -} - -Mesh::~Mesh() { - remove_model(); -} - -void Mesh::set_model(const std::string &filePath) { - VulkanEngine &engine = VulkanEngine::Get(); - - // Unregister current model if exists - if (!_currentModelPath.empty()) { - engine.unregisterMesh(_rid); - } - - // Register new model - _currentModelPath = filePath; - _rid = engine.registerMesh(filePath); - - // Reapply transform to new model - engine.setMeshTransform(_rid, _transform); -} - -void Mesh::remove_model() { - if (!_currentModelPath.empty()) { - VulkanEngine &engine = VulkanEngine::Get(); - engine.unregisterMesh(_rid); - _currentModelPath.clear(); - } -} - - -void Mesh::set_transform(glm::mat4 t) { - VulkanEngine &engine = VulkanEngine::Get(); - - _transform = t; - - engine.setMeshTransform(_rid, t); -} - -glm::mat4 Mesh::get_transform() { - return _transform; -} diff --git a/src/core/MeshController.cpp b/src/core/MeshController.cpp new file mode 100644 index 00000000..65f261c1 --- /dev/null +++ b/src/core/MeshController.cpp @@ -0,0 +1,36 @@ +#include "core/MeshController.h" +#include +#include +#include +#include +#include "scene/Mesh.h" + +MeshController::MeshController(IModel::Ptr model) : _model(std::move(model)) {} + +Mesh::rid_t MeshController::create_mesh( + const std::filesystem::path& file_path) const { + return _model->createMesh(file_path); +} + +void MeshController::delete_mesh(Mesh::rid_t id) const { + _model->delete_mesh(id); +} + +void MeshController::set_transform(Mesh::rid_t id, glm::mat4 t) const { + _model->setMeshTransform(id, t); +} + +glm::mat4 MeshController::get_transform(Mesh::rid_t id) const { + const auto transform = _model->get_mesh_transform(id); + return transform; +} + +std::vector MeshController::get_meshes() const { + const auto map = _model->get_meshes(); + std::vector v; + v.reserve(map.size()); + for (const auto rid : std::views::keys(map)) { + v.push_back(rid); + } + return v; +} diff --git a/src/core/Model.cpp b/src/core/Model.cpp index 0df2b63f..3afde71d 100644 --- a/src/core/Model.cpp +++ b/src/core/Model.cpp @@ -1,6 +1,6 @@ #include - #include "core/ModelImpl.h" +#include "interfaces/IModel.h" IModel::Ptr createModel() { return std::make_shared(); diff --git a/src/core/ModelImpl.cpp b/src/core/ModelImpl.cpp index f8e28caa..8d6a7642 100644 --- a/src/core/ModelImpl.cpp +++ b/src/core/ModelImpl.cpp @@ -1,8 +1,471 @@ #include "core/ModelImpl.h" #include +#include +#include +#include +#include +#include +#include -#include "core/Mesh.h" +#include "core/config.h" +#include "graphics/vulkan/MeshNode.h" +#define GLM_ENABLE_EXPERIMENTAL +#include + +namespace { + +VkFilter extract_filter(fastgltf::Filter filter) { + switch (filter) { + // nearest samplers + case fastgltf::Filter::Nearest: + case fastgltf::Filter::NearestMipMapNearest: + case fastgltf::Filter::NearestMipMapLinear: + return VK_FILTER_NEAREST; + + // linear samplers + case fastgltf::Filter::Linear: + case fastgltf::Filter::LinearMipMapNearest: + case fastgltf::Filter::LinearMipMapLinear: + default: + return VK_FILTER_LINEAR; + } +} + +VkSamplerMipmapMode extract_mipmap_mode(fastgltf::Filter filter) { + switch (filter) { + case fastgltf::Filter::NearestMipMapNearest: + case fastgltf::Filter::LinearMipMapNearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + + case fastgltf::Filter::NearestMipMapLinear: + case fastgltf::Filter::LinearMipMapLinear: + default: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + } +} + +constexpr auto kOptionsGLTF = fastgltf::Options::DontRequireValidAssetMember | + fastgltf::Options::AllowDouble | + fastgltf::Options::LoadGLBBuffers | + fastgltf::Options::LoadExternalBuffers; + +bool check_parser_result(fastgltf::Asset& gltf, + fastgltf::Expected& load) { + if (!load) { + std::cerr << "Failed to load glTF: " + << fastgltf::to_underlying(load.error()) << std::endl; + return false; + } + gltf = std::move(load.get()); + return true; +} + +bool load_file(fastgltf::Asset& gltf, std::string_view filePath) { + fastgltf::Parser parser{}; + + fastgltf::GltfDataBuffer data; + data.loadFromFile(filePath); + + std::filesystem::path path = filePath; + + const auto type = fastgltf::determineGltfFileType(&data); + if (type == fastgltf::GltfType::glTF) { + auto load = parser.loadGltf(&data, path.parent_path(), kOptionsGLTF); + return check_parser_result(gltf, load); + } + if (type == fastgltf::GltfType::GLB) { + auto load = + parser.loadGltfBinary(&data, path.parent_path(), kOptionsGLTF); + return check_parser_result(gltf, load); + } + std::cerr << "Failed to determine glTF container" << std::endl; + return false; +} + +void init_descriptor_pool(const VulkanEngine& engine, + Mesh::GLTF::LoadedGLTF& file, + const fastgltf::Asset& gltf) { + // we can stimate the descriptors we will need accurately + std::vector sizes = { + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}}; + + file.descriptorPool.init( + engine._device, + static_cast( + std::max(gltf.materials.size(), static_cast(1))), + sizes); +} + +void load_samplers(const VulkanEngine& engine, Mesh::GLTF::LoadedGLTF& file, + fastgltf::Asset& gltf) { + for (auto& [magFilter, minFilter, wrapS, wrapT, name] : gltf.samplers) { + VkSamplerCreateInfo sample = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .magFilter = extract_filter( + magFilter.value_or(fastgltf::Filter::Nearest)), + .minFilter = extract_filter( + minFilter.value_or(fastgltf::Filter::Nearest)), + .mipmapMode = extract_mipmap_mode( + minFilter.value_or(fastgltf::Filter::Nearest)), + .minLod = 0, + .maxLod = VK_LOD_CLAMP_NONE}; + + VkSampler newSampler; + vkCreateSampler(engine._device, &sample, nullptr, &newSampler); + + file.samplers.push_back(newSampler); + } +} + +void load_all_textures(const VulkanEngine& engine, fastgltf::Asset& gltf, + std::vector& images) { + images.reserve(gltf.images.size()); + for ([[maybe_unused]] fastgltf::Image& image : gltf.images) { + images.push_back(engine._errorCheckerboardImage); + } +} + +void create_material_data_buffer(const VulkanEngine& engine, + Mesh::GLTF::LoadedGLTF& file, + const fastgltf::Asset& gltf) { + const size_t material_сount = + !gltf.materials.empty() ? gltf.materials.size() : 1; + file.materialDataBuffer = engine.create_buffer( + sizeof(GLTFMetallic_Roughness::MaterialConstants) * material_сount, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); +} + +void grab_textures_from_GLTF( + const Mesh::GLTF::LoadedGLTF& file, fastgltf::Asset& gltf, + const fastgltf::Material& mat, + GLTFMetallic_Roughness::MaterialResources& materialResources, + const std::vector& images) { + if (mat.pbrData.baseColorTexture.has_value()) { + const size_t img = + gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex] + .imageIndex.value(); + const size_t sampler = + gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex] + .samplerIndex.value(); + + materialResources.colorImage = images[img]; + materialResources.colorSampler = file.samplers[sampler]; + } +} + +void load_material_data( + VulkanEngine& engine, Mesh::GLTF::LoadedGLTF& file, + fastgltf::Asset& gltf, + std::vector>& materials, + const std::vector& images) { + auto* sceneMaterialConstants = + static_cast( + file.materialDataBuffer.info.pMappedData); + + for (size_t data_index{}; fastgltf::Material & mat : gltf.materials) { + auto newMat = std::make_shared(); + materials.push_back(newMat); + file.materials[mat.name.c_str()] = newMat; + + // write material parameters to buffer + sceneMaterialConstants[data_index] = { + .colorFactors{ + mat.pbrData.baseColorFactor[0], + mat.pbrData.baseColorFactor[1], + mat.pbrData.baseColorFactor[2], + mat.pbrData.baseColorFactor[3], + }, + .metal_rough_factors{ + mat.pbrData.metallicFactor, + mat.pbrData.roughnessFactor, + {}, + {}, + }, + }; + + auto passType = MaterialPass::MainColor; + if (mat.alphaMode == fastgltf::AlphaMode::Blend) { + passType = MaterialPass::Transparent; + } + + GLTFMetallic_Roughness::MaterialResources materialResources{ + // default the material textures + engine._whiteImage, engine._defaultSamplerLinear, + engine._whiteImage, engine._defaultSamplerLinear, + // set the uniform buffer for the material data + file.materialDataBuffer.buffer, + static_cast( + data_index * + sizeof(GLTFMetallic_Roughness::MaterialConstants))}; + + grab_textures_from_GLTF(file, gltf, mat, materialResources, images); + // build material + newMat->data = engine.metalRoughMaterial.write_material( + engine._device, passType, materialResources, + file.descriptorPool); + + ++data_index; + } + + // Add a fallback material if no materials were defined in the GLTF + if (materials.empty()) { + auto default_mat = std::make_shared(); + materials.push_back(default_mat); + GLTFMetallic_Roughness::MaterialConstants constants = {}; + constants = { + .colorFactors = glm::vec4(1.0f), // White base color + .metal_rough_factors = glm::vec4(0.0f), // Non-metallic, smooth + }; + sceneMaterialConstants[0] = constants; + GLTFMetallic_Roughness::MaterialResources resources{}; + resources = {.colorImage = engine._whiteImage, + .colorSampler = engine._defaultSamplerLinear, + .metalRoughImage = engine._whiteImage, + .metalRoughSampler = engine._defaultSamplerLinear, + .dataBuffer = file.materialDataBuffer.buffer, + .dataBufferOffset = 0}; + default_mat->data = engine.metalRoughMaterial.write_material( + engine._device, MaterialPass::MainColor, resources, + file.descriptorPool); + } +} + +void load_indexes(const fastgltf::Asset& gltf, std::vector& indices, + fastgltf::Primitive& p, size_t initial_vtx) { + { + const fastgltf::Accessor& indexaccessor = + gltf.accessors[p.indicesAccessor.value()]; + indices.reserve(indices.size() + indexaccessor.count); + + fastgltf::iterateAccessor( + gltf, indexaccessor, [&](std::uint32_t idx) { + indices.push_back(idx + static_cast(initial_vtx)); + }); + } +} + +void load_vertex_positions(const fastgltf::Asset& gltf, + std::vector& vertices, + fastgltf::Primitive& p, size_t initial_vtx) { + const fastgltf::Accessor& posAccessor = + gltf.accessors[p.findAttribute("POSITION")->second]; + vertices.resize(vertices.size() + posAccessor.count); + + fastgltf::iterateAccessorWithIndex( + gltf, posAccessor, [&](const glm::vec3 v, size_t index) { + vertices[initial_vtx + index] = { + .position = v, + .normal = {1, 0, 0}, + .color = glm::vec4{1.}, + .uv_x = 0, + .uv_y = 0, + }; + }); +} + +void load_vertex_normals(const fastgltf::Asset& gltf, + std::vector& vertices, fastgltf::Primitive& p, + size_t initial_vtx) { + const auto normals = p.findAttribute("NORMAL"); + if (normals != p.attributes.end()) { + fastgltf::iterateAccessorWithIndex( + gltf, gltf.accessors[normals->second], + [&](const glm::vec3 v, size_t index) { + vertices[initial_vtx + index].normal = v; + }); + } +} + +void load_UVs(const fastgltf::Asset& gltf, std::vector& vertices, + fastgltf::Primitive& p, size_t initial_vtx) { + const auto uv = p.findAttribute("TEXCOORD_0"); + if (uv != p.attributes.end()) { + fastgltf::iterateAccessorWithIndex( + gltf, gltf.accessors[uv->second], + [&](glm::vec2 v, size_t index) { + vertices[initial_vtx + index].uv_x = v.x; + vertices[initial_vtx + index].uv_y = v.y; + }); + } +} + +void load_vertex_colors(const fastgltf::Asset& gltf, + std::vector& vertices, fastgltf::Primitive& p, + size_t initial_vtx) { + const auto colors = p.findAttribute("COLOR_0"); + if (colors != p.attributes.end()) { + fastgltf::iterateAccessorWithIndex( + gltf, gltf.accessors[colors->second], + [&](const glm::vec4 v, size_t index) { + vertices[initial_vtx + index].color = v; + }); + } +} + +void define_new_surface_material( + Mesh::GLTF::GeoSurface& newSurface, fastgltf::Primitive& p, + const std::vector>& + materials) { + if (p.materialIndex.has_value()) { + newSurface.material = materials[p.materialIndex.value()]; + } else { + newSurface.material = materials[0]; + } +} + +void upload_mesh_to_engine( + VulkanEngine& engine, Mesh::GLTF::LoadedGLTF& file, + fastgltf::Asset& gltf, + std::vector>& meshes, + const std::vector>& + materials) { + // use the same vectors for all meshes so that the memory doesn't reallocate + // as often + std::vector indices; + std::vector vertices; + + for (auto& [primitives, weights, name] : gltf.meshes) { + auto newmesh = std::make_shared(); + meshes.push_back(newmesh); + file.meshes[name.c_str()] = newmesh; + newmesh->name = name; + + // clear the mesh arrays each mesh, we don't want to merge them by error + indices.clear(); + vertices.clear(); + + for (auto&& p : primitives) { + Mesh::GLTF::GeoSurface newSurface; + newSurface.startIndex = static_cast(indices.size()); + newSurface.count = static_cast( + gltf.accessors[p.indicesAccessor.value()].count); + + const size_t initial_vtx = vertices.size(); + + load_indexes(gltf, indices, p, initial_vtx); + load_vertex_positions(gltf, vertices, p, initial_vtx); + load_vertex_normals(gltf, vertices, p, initial_vtx); + + load_UVs(gltf, vertices, p, initial_vtx); + load_vertex_colors(gltf, vertices, p, initial_vtx); + define_new_surface_material(newSurface, p, materials); + newmesh->surfaces.push_back(newSurface); + } + + newmesh->meshBuffers = engine.uploadMesh(indices, vertices); + } +} + +void load_nodes(Mesh::GLTF::LoadedGLTF& file, fastgltf::Asset& gltf, + std::vector>& meshes, + std::vector>& nodes) { + for (fastgltf::Node& node : gltf.nodes) { + std::shared_ptr newNode; + + // find if the node has a mesh, and if it does hook it to the mesh + // pointer and allocate it with the meshnode class + if (node.meshIndex.has_value()) { + newNode = std::make_shared(); + dynamic_cast(newNode.get())->mesh = + meshes[*node.meshIndex]; + } else { + newNode = std::make_shared(); + } + + nodes.push_back(newNode); + file.nodes[node.name.c_str()]; + + std::visit(fastgltf::visitor{ + [&](fastgltf::Node::TransformMatrix matrix) { + memcpy(&newNode->localTransform, matrix.data(), + sizeof(matrix)); + }, + [&](const fastgltf::TRS& transform) { + const glm::vec3 tl(transform.translation[0], + transform.translation[1], + transform.translation[2]); + const glm::quat rot(transform.rotation[3], + transform.rotation[0], + transform.rotation[1], + transform.rotation[2]); + const glm::vec3 sc(transform.scale[0], + transform.scale[1], + transform.scale[2]); + + const glm::mat4 tm = + glm::translate(glm::mat4(1.f), tl); + const glm::mat4 rm = glm::toMat4(rot); + const glm::mat4 sm = + glm::scale(glm::mat4(1.f), sc); + + newNode->localTransform = tm * rm * sm; + }}, + node.transform); + } +} + +void setup_nodes_relationships(Mesh::GLTF::LoadedGLTF& file, + fastgltf::Asset& gltf, + std::vector>& nodes) { + // run loop again to set up transform hierarchy + for (size_t i = 0; i < gltf.nodes.size(); i++) { + fastgltf::Node& node = gltf.nodes[i]; + const std::shared_ptr& sceneNode = nodes[i]; + + for (const auto& c : node.children) { + sceneNode->children.push_back(nodes[c]); + nodes[c]->parent = sceneNode; + } + } + + // find the top nodes, with no parents + for (auto& node : nodes) { + if (node->parent.lock() == nullptr) { + file.topNodes.push_back(node); + node->refreshTransform(glm::mat4{1.f}); + } + } +} + +std::optional> loadGLTF( + VulkanEngine& engine, std::string_view filePath) { + fmt::print("Loading GLTF: {}", filePath); + + auto scene = std::make_shared(); + scene->creator = &engine; + Mesh::GLTF::LoadedGLTF& file = *scene; + + fastgltf::Asset gltf; + + if (!load_file(gltf, filePath)) { + return {}; + } + + init_descriptor_pool(engine, file, gltf); + + load_samplers(engine, file, gltf); + + std::vector images; + load_all_textures(engine, gltf, images); + + create_material_data_buffer(engine, file, gltf); + + std::vector> materials; + load_material_data(engine, file, gltf, materials, images); + + std::vector> meshes; + upload_mesh_to_engine(engine, file, gltf, meshes, materials); + + std::vector> nodes; + load_nodes(file, gltf, meshes, nodes); + setup_nodes_relationships(file, gltf, nodes); + return scene; +} +} // unnamed namespace ModelImpl::~ModelImpl() { _engine.cleanup(); @@ -10,27 +473,66 @@ ModelImpl::~ModelImpl() { ModelImpl::ModelImpl() = default; -void ModelImpl::registerWindow(SDL_Window *window) { +void ModelImpl::registerWindow(SDL_Window* window) { _engine.mainCamera = &_camera; _engine.init(window); } -void ModelImpl::updateVulkan() { - _engine.update(); +VulkanEngine& ModelImpl::get_engine() { + assert(_engine._isInitialized); + return _engine; } -Camera *ModelImpl::getCamera() { +Camera* ModelImpl::getCamera() { return &_camera; } -void ModelImpl::createMesh(std::string name) { - const auto mesh = std::make_shared("/basicmesh.glb"); +Mesh::rid_t registerMesh(VulkanEngine& engine, ModelImpl::MeshMap& meshes, + const std::filesystem::path& filePath) { + std::random_device rd; + + // Use the Mersenne Twister engine for high-quality random numbers + std::mt19937_64 generator(rd()); + + // Create a uniform distribution for int64_t + std::uniform_int_distribution distribution; + + // Generate and print a random int64_t value + const Mesh::rid_t random_rid_t = distribution(generator); + + std::string structurePath = {std::string(ASSETS_DIR) + filePath.string()}; + auto structureFile = loadGLTF(engine, structurePath); + + assert(structureFile.has_value()); + + engine.loadedScenes["structure"] = *structureFile; + + meshes[random_rid_t] = {structureFile.value(), glm::mat4(1.)}; + + return random_rid_t; +} + +Mesh::rid_t ModelImpl::createMesh(const std::filesystem::path& file_path) { + assert(_engine._isInitialized); + const Mesh::rid_t rid = registerMesh(_engine, _meshes, file_path); + _meshes[rid].transform = 1.; + return rid; +} + +void ModelImpl::setMeshTransform(Mesh::rid_t rid, glm::mat4x4 transform) { + _meshes.at(rid).transform = transform; +} - mesh->set_transform(glm::mat4(1.0f)); +glm::mat4 ModelImpl::get_mesh_transform(Mesh::rid_t rid) { + return _meshes.at(rid).transform; +} - _meshes[name] = mesh; +void ModelImpl::delete_mesh(Mesh::rid_t rid) { + if (!_meshes.erase(rid)) { + throw std::invalid_argument("Invalid id of the mesh"); + } } -void ModelImpl::setMeshTransform(std::string name, glm::mat4x4 transform) { - _meshes[name]->set_transform(transform); +const ModelImpl::MeshMap& ModelImpl::get_meshes() { + return _meshes; } diff --git a/src/core/View.cpp b/src/core/View.cpp index 8751d9d1..4ed5a02d 100644 --- a/src/core/View.cpp +++ b/src/core/View.cpp @@ -1,10 +1,7 @@ #include "core/View.h" -#include -#include - #include "core/ViewImpl.h" -IView::Ptr createView(IController::Ptr controller, IModel::Ptr model) { - return std::make_unique(std::move(controller), std::move(model)); +IView::Ptr createView(IModel::Ptr m_ptr) { + return std::make_unique(std::move(m_ptr)); } \ No newline at end of file diff --git a/src/core/ViewImpl.cpp b/src/core/ViewImpl.cpp index 45fc8b76..c67b792d 100644 --- a/src/core/ViewImpl.cpp +++ b/src/core/ViewImpl.cpp @@ -4,16 +4,12 @@ #include #include -#ifdef __GNUC__ -#include -#else #include -#endif - #include #include #include #include +#include #include "graphics/vulkan/vk_engine.h" #include "imgui.h" @@ -25,76 +21,33 @@ constexpr auto kWindowFlags = static_cast(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); } -ViewImpl::ViewImpl(IController::Ptr controller, IModel::Ptr model) - : _controller(std::move(controller)), _model(std::move(model)) { +ViewImpl::ViewImpl(IModel::Ptr model) : _model(std::move(model)) { SDL_Init(SDL_INIT_VIDEO); + window = SDL_CreateWindow("engine", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1700, 900, kWindowFlags); -} - -void createCubes(const std::shared_ptr &_model) { - for (int i = 0; i < 5; i++) { - _model->createMesh("cube" + std::to_string(i)); - } + _model->registerWindow(window); } void ViewImpl::run() const { - _model->registerWindow(window); - - createCubes(_model); - - SDL_Event e; - bool bQuit = false; - - bool stop_rendering = false; - - // main loop - while (!bQuit) { - // Handle events on queue - while (SDL_PollEvent(&e) != 0) { - // close the window when user alt-f4s or clicks the X button - if (e.type == SDL_QUIT) bQuit = true; - - if (e.type == SDL_WINDOWEVENT) { - if (e.window.event == SDL_WINDOWEVENT_MINIMIZED) { - stop_rendering = true; - } - if (e.window.event == SDL_WINDOWEVENT_RESTORED) { - stop_rendering = false; - } - } - - _controller->processEvent(e); - ImGui_ImplSDL2_ProcessEvent(&e); - } - - // do not draw if we are minimized - if (stop_rendering) { - // throttle the speed to avoid the endless spinning - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - continue; - } - - // imgui new frame - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); - - if (ImGui::Begin("background")) { - VulkanEngine &engine = VulkanEngine::Get(); - ImGui::SliderFloat("Render Scale", &engine.renderScale, 0.3f, 1.f); - // other code - } - ImGui::End(); - - // make imgui calculate internal draw structures - ImGui::Render(); + // imgui new frame + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + if (ImGui::Begin("background")) { + VulkanEngine& engine = VulkanEngine::Get(); + ImGui::SliderFloat("Render Scale", &engine.renderScale, 0.3f, 1.f); + // other code + } + ImGui::End(); - _controller->update(); - //_model->updateVulkan(); + // make imgui calculate internal draw structures + ImGui::Render(); +} - // engine.update(); - } +void ViewImpl::process_event(const SDL_Event& e) { + ImGui_ImplSDL2_ProcessEvent(&e); } ViewImpl::~ViewImpl() { diff --git a/src/core/createInstance.cpp b/src/core/createInstance.cpp index fe1eb2bb..50b6cb2e 100644 --- a/src/core/createInstance.cpp +++ b/src/core/createInstance.cpp @@ -1,9 +1,11 @@ #include "IController.h" #include "core/Controller.h" #include "core/Model.h" +#include "core/View.h" IController::Ptr createInstance() { const auto model = createModel(); - const auto controller = createController(model); + const auto view = createView(model); + const auto controller = createController(model, view); return controller; } diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index e117fd76..ddbf57fa 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -1,15 +1,11 @@ target_sources(${PROJECT_NAME} PRIVATE - vulkan/vk_command_buffers.cpp - vulkan/vk_command_buffers_container.cpp + vulkan/MeshNode.cpp + vulkan/RenderableGLTF.cpp vulkan/vk_descriptors.cpp vulkan/vk_engine.cpp vulkan/vk_images.cpp vulkan/vk_initializers.cpp - vulkan/vk_loader.cpp vulkan/vk_pipelines.cpp - vulkan/pipelines.cpp - vulkan/ComputePipeline.cpp - vulkan/GraphicsPipeline.cpp Graphics.cpp ) \ No newline at end of file diff --git a/src/graphics/vulkan/MeshNode.cpp b/src/graphics/vulkan/MeshNode.cpp new file mode 100644 index 00000000..17075741 --- /dev/null +++ b/src/graphics/vulkan/MeshNode.cpp @@ -0,0 +1,24 @@ +#include + +#include "graphics/vulkan/MeshNode.h" +#include "graphics/vulkan/vk_engine.h" +#include "scene/Mesh.h" + +void MeshNode::Draw(const glm::mat4& topMatrix, DrawContext& ctx) { + const glm::mat4 nodeMatrix = topMatrix * worldTransform; + + for (const auto& [startIndex, count, material] : mesh->surfaces) { + RenderObject def{}; + def.indexCount = count; + def.firstIndex = startIndex; + def.indexBuffer = mesh->meshBuffers.indexBuffer.buffer; + def.material = &material->data; + + def.transform = nodeMatrix; + def.vertexBufferAddress = mesh->meshBuffers.vertexBufferAddress; + + ctx.OpaqueSurfaces.push_back(def); + } + + ENode::Draw(topMatrix, ctx); +} diff --git a/src/graphics/vulkan/RenderableGLTF.cpp b/src/graphics/vulkan/RenderableGLTF.cpp new file mode 100644 index 00000000..14be73da --- /dev/null +++ b/src/graphics/vulkan/RenderableGLTF.cpp @@ -0,0 +1,16 @@ +#include "graphics/vulkan/RenderableGLTF.h" +#include +#include +#include +#include "graphics/vulkan/vk_types.h" + +#define GLM_ENABLE_EXPERIMENTAL + +RenderableGLTF::RenderableGLTF(LoadedGltfPtr gltf) : _gltf(std::move(gltf)) {} + +void RenderableGLTF::Draw(const glm::mat4& topMatrix, DrawContext& ctx) { + // create renderables from the scenenodes + for (auto& n : _gltf->topNodes) { + n->Draw(topMatrix, ctx); + } +} diff --git a/src/graphics/vulkan/vk_engine.cpp b/src/graphics/vulkan/vk_engine.cpp index 99b94292..b227be8d 100644 --- a/src/graphics/vulkan/vk_engine.cpp +++ b/src/graphics/vulkan/vk_engine.cpp @@ -1,41 +1,41 @@ -#include "graphics/vulkan/vk_engine.h" +#include "graphics/vulkan/vk_engine.h" #include #include #include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include -#include -#include - +#include #include "core/Logging.h" -#include "core/config.h" #include "graphics/vulkan/vk_descriptors.h" #include "scene/Camera.h" -#define VMA_IMPLEMENTATION -#include -#include -#include -#include -#include -#include +#include +#include + +#include "core/config.h" -#define GLM_ENABLE_EXPERIMENTAL -#include +#include "SDL_vulkan.h" +#include "VkBootstrap.h" +#include "graphics/vulkan/RenderableGLTF.h" #include "graphics/vulkan/vk_images.h" #include "graphics/vulkan/vk_initializers.h" -#include "graphics/vulkan/vk_loader.h" #include "graphics/vulkan/vk_pipelines.h" #include "graphics/vulkan/vk_types.h" -#include "graphics/vulkan/vk_command_buffers.h" +#include "imgui.h" +#include "imgui_impl_sdl2.h" +#include "imgui_impl_vulkan.h" +#include "interfaces/IModel.h" +#define VMA_IMPLEMENTATION +#include "vk_mem_alloc.h" + +#define GLM_ENABLE_EXPERIMENTAL +#include "glm/gtx/transform.hpp" VulkanEngine* loadedEngine = nullptr; @@ -117,24 +117,23 @@ void VulkanEngine::init_default_data() { rect_indices[1] = 1; rect_indices[2] = 2; - const auto path_to_assets = std::string(ASSETS_DIR) + "/basicmesh.glb"; - testMeshes = loadGltfMeshes(this, path_to_assets).value(); + auto path_to_assets = std::string(ASSETS_DIR) + "/basicmesh.glb"; // 3 default textures, white, grey, black. 1 pixel each const uint32_t white = glm::packUnorm4x8(glm::vec4(1, 1, 1, 1)); - AllocatedImage whiteImageData = create_image(&white, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, + _whiteImage = + create_image(&white, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); - _whiteImage = std::make_unique(_allocator, _device, whiteImageData); const uint32_t grey = glm::packUnorm4x8(glm::vec4(0.66f, 0.66f, 0.66f, 1)); - AllocatedImage greyImageData = create_image(&grey, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, + _greyImage = + create_image(&grey, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); - _greyImage = std::make_unique(_allocator, _device, greyImageData); const uint32_t black = glm::packUnorm4x8(glm::vec4(0, 0, 0, 0)); - AllocatedImage blackImageData = create_image(&black, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, + _blackImage = + create_image(&black, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); - _blackImage = std::make_unique(_allocator, _device, blackImageData); // checkerboard image const uint32_t magenta = glm::packUnorm4x8(glm::vec4(1, 0, 1, 1)); @@ -144,9 +143,9 @@ void VulkanEngine::init_default_data() { pixels[y * 16 + x] = ((x % 2) ^ (y % 2)) ? magenta : black; } } - AllocatedImage errorImageData = create_image(pixels.data(), VkExtent3D{16, 16, 1}, + _errorCheckerboardImage = + create_image(pixels.data(), VkExtent3D{16, 16, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT); - _errorCheckerboardImage = std::make_unique(_allocator, _device, errorImageData); VkSamplerCreateInfo sampl = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; @@ -162,9 +161,9 @@ void VulkanEngine::init_default_data() { GLTFMetallic_Roughness::MaterialResources materialResources{}; // default the material textures - materialResources.colorImage = _whiteImage->get(); + materialResources.colorImage = _whiteImage; materialResources.colorSampler = _defaultSamplerLinear; - materialResources.metalRoughImage = _whiteImage->get(); + materialResources.metalRoughImage = _whiteImage; materialResources.metalRoughSampler = _defaultSamplerLinear; // set the uniform buffer for the material data @@ -174,13 +173,13 @@ void VulkanEngine::init_default_data() { // write the buffer auto* sceneUniformData = - (GLTFMetallic_Roughness::MaterialConstants*) - materialConstants.allocation->GetMappedData(); + static_cast( + materialConstants.allocation->GetMappedData()); sceneUniformData->colorFactors = glm::vec4{1, 1, 1, 1}; sceneUniformData->metal_rough_factors = glm::vec4{1, 0.5, 0, 0}; - // Store material constants buffer in managed buffers for automatic cleanup - _managedBuffers.push_back(std::make_unique(_allocator, materialConstants)); + _mainDeletionQueue.push_function( + [=, this] { destroy_buffer(materialConstants); }); materialResources.dataBuffer = materialConstants.buffer; materialResources.dataBufferOffset = 0; @@ -190,6 +189,135 @@ void VulkanEngine::init_default_data() { globalDescriptorAllocator); } +void VulkanEngine::init_mesh_pipeline() { + VkShaderModule triangleFragShader; + if (!vkutil::load_shader_module("./shaders/tex_image.frag.spv", _device, + &triangleFragShader)) { + fmt::println("Error when building the triangle fragment shader module"); + } else { + fmt::println("Triangle fragment shader successfully loaded"); + } + + VkShaderModule triangleVertexShader; + if (!vkutil::load_shader_module("./shaders/colored_triangle_mesh.vert.spv", + _device, &triangleVertexShader)) { + fmt::println("Error when building the triangle vertex shader module"); + } else { + fmt::println("Triangle vertex shader successfully loaded"); + } + + VkPushConstantRange bufferRange{}; + bufferRange.offset = 0; + bufferRange.size = sizeof(GPUDrawPushConstants); + bufferRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkPipelineLayoutCreateInfo pipeline_layout_info = + vkinit::pipeline_layout_create_info(); + pipeline_layout_info.pPushConstantRanges = &bufferRange; + pipeline_layout_info.pushConstantRangeCount = 1; + pipeline_layout_info.pSetLayouts = &_singleImageDescriptorLayout; + pipeline_layout_info.setLayoutCount = 1; + VK_CHECK(vkCreatePipelineLayout(_device, &pipeline_layout_info, nullptr, + &_meshPipelineLayout)); + + PipelineBuilder pipelineBuilder; + + // use the triangle layout we created + pipelineBuilder._pipelineLayout = _meshPipelineLayout; + // connecting the vertex and pixel shaders to the pipeline + pipelineBuilder.set_shaders(triangleVertexShader, triangleFragShader); + // it will draw triangles + pipelineBuilder.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + // filled triangles + pipelineBuilder.set_polygon_mode(VK_POLYGON_MODE_FILL); + // no backface culling + pipelineBuilder.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); + // no multisampling + pipelineBuilder.set_multisampling_none(); + // no blending + pipelineBuilder.disable_blending(); + + // pipelineBuilder.disable_depthtest(); + pipelineBuilder.enable_depthtest(true, VK_COMPARE_OP_GREATER); + + // connect the image format we will draw into, from draw image + pipelineBuilder.set_color_attachment_format(_drawImage.imageFormat); + pipelineBuilder.set_depth_format(VK_FORMAT_UNDEFINED); + + // finally build the pipeline + _meshPipeline = pipelineBuilder.build_pipeline(_device); + + // clean structures + vkDestroyShaderModule(_device, triangleFragShader, nullptr); + vkDestroyShaderModule(_device, triangleVertexShader, nullptr); + + _mainDeletionQueue.push_function([&] { + vkDestroyPipelineLayout(_device, _meshPipelineLayout, nullptr); + vkDestroyPipeline(_device, _meshPipeline, nullptr); + }); +} + +void VulkanEngine::init_triangle_pipeline() { + VkShaderModule triangleFragShader; + if (!vkutil::load_shader_module("./shaders/colored_triangle.frag.spv", + _device, &triangleFragShader)) { + fmt::println("Error when building the triangle fragment shader module"); + } else { + fmt::println("Triangle fragment shader successfully loaded"); + } + + VkShaderModule triangleVertexShader; + if (!vkutil::load_shader_module("./shaders/colored_triangle.vert.spv", + _device, &triangleVertexShader)) { + fmt::println("Error when building the triangle vertex shader module"); + } else { + fmt::println("Triangle vertex shader successfully loaded"); + } + + // build the pipeline layout that controls the inputs/outputs of the shader + // we are not using descriptor sets or other systems yet, so no need to use + // anything other than empty default + const VkPipelineLayoutCreateInfo pipeline_layout_info = + vkinit::pipeline_layout_create_info(); + VK_CHECK(vkCreatePipelineLayout(_device, &pipeline_layout_info, nullptr, + &_trianglePipelineLayout)); + + PipelineBuilder pipelineBuilder; + + // use the triangle layout we created + pipelineBuilder._pipelineLayout = _trianglePipelineLayout; + // connecting the vertex and pixel shaders to the pipeline + pipelineBuilder.set_shaders(triangleVertexShader, triangleFragShader); + // it will draw triangles + pipelineBuilder.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + // filled triangles + pipelineBuilder.set_polygon_mode(VK_POLYGON_MODE_FILL); + // no backface culling + pipelineBuilder.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); + // no multisampling + pipelineBuilder.set_multisampling_none(); + // no blending + pipelineBuilder.disable_blending(); + // no depth testing + pipelineBuilder.disable_depthtest(); + + // connect the image format we will draw into, from draw image + pipelineBuilder.set_color_attachment_format(_drawImage.imageFormat); + pipelineBuilder.set_depth_format(VK_FORMAT_UNDEFINED); + + // finally build the pipeline + _trianglePipeline = pipelineBuilder.build_pipeline(_device); + + // clean structures + vkDestroyShaderModule(_device, triangleFragShader, nullptr); + vkDestroyShaderModule(_device, triangleVertexShader, nullptr); + + _mainDeletionQueue.push_function([&] { + vkDestroyPipelineLayout(_device, _trianglePipelineLayout, nullptr); + vkDestroyPipeline(_device, _trianglePipeline, nullptr); + }); +} + void VulkanEngine::init_imgui() { // 1: create descriptor pool for IMGUI // the size of the pool is very oversize, but it's copied from imgui demo @@ -249,9 +377,11 @@ void VulkanEngine::init_imgui() { ImGui_ImplVulkan_CreateFontsTexture(); - // Store ImGui cleanup info - will be automatically handled when engine destructs - // Note: ImGui cleanup is now handled by storing the descriptor pool - // that will be automatically destroyed when the device is destroyed + // add destroy the imgui created structures + _mainDeletionQueue.push_function([=, this] { + ImGui_ImplVulkan_Shutdown(); + vkDestroyDescriptorPool(_device, imguiPool, nullptr); + }); } void VulkanEngine::init_descriptors() { @@ -290,13 +420,13 @@ void VulkanEngine::init_descriptors() { _device, _drawImageDescriptorLayout); DescriptorWriter writer; - writer.write_image(0, _drawImage->imageView(), VK_NULL_HANDLE, + writer.write_image(0, _drawImage.imageView, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE); writer.update_set(_device, _drawImageDescriptors); - for (auto& _frame : command_buffers_container._frames) { + for (auto& _frame : _frames) { // create a descriptor pool std::vector frame_sizes = { {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 3}, @@ -308,13 +438,59 @@ void VulkanEngine::init_descriptors() { _frame._frameDescriptors = DescriptorAllocatorGrowable{}; _frame._frameDescriptors.init(_device, 1000, frame_sizes); - // No need for deletion queue - frame descriptors will be cleaned up in cleanup() + _mainDeletionQueue.push_function( + [&] { _frame._frameDescriptors.destroy_pools(_device); }); } } +void VulkanEngine::init_background_pipelines() { + VkPipelineLayoutCreateInfo computeLayout{}; + computeLayout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + computeLayout.pNext = nullptr; + computeLayout.pSetLayouts = &_drawImageDescriptorLayout; + computeLayout.setLayoutCount = 1; + + VK_CHECK(vkCreatePipelineLayout(_device, &computeLayout, nullptr, + &_gradientPipelineLayout)); + + VkShaderModule computeDrawShader; + if (!vkutil::load_shader_module("./shaders/gradient.comp.spv", _device, + &computeDrawShader)) { + fmt::println("Error when building the compute shader \n"); + } + + VkPipelineShaderStageCreateInfo stageinfo{}; + stageinfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stageinfo.pNext = nullptr; + stageinfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; + stageinfo.module = computeDrawShader; + stageinfo.pName = "main"; + + VkComputePipelineCreateInfo computePipelineCreateInfo{}; + computePipelineCreateInfo.sType = + VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + computePipelineCreateInfo.pNext = nullptr; + computePipelineCreateInfo.layout = _gradientPipelineLayout; + computePipelineCreateInfo.stage = stageinfo; + + VK_CHECK(vkCreateComputePipelines(_device, VK_NULL_HANDLE, 1, + &computePipelineCreateInfo, nullptr, + &_gradientPipeline)); + + vkDestroyShaderModule(_device, computeDrawShader, nullptr); + + _mainDeletionQueue.push_function([&] { + vkDestroyPipelineLayout(_device, _gradientPipelineLayout, nullptr); + vkDestroyPipeline(_device, _gradientPipeline, nullptr); + }); +} + void VulkanEngine::init_pipelines() { - pipelines.init(_device, _singleImageDescriptorLayout, _drawImageDescriptorLayout, _drawImage->get()); - // Pipeline cleanup is handled automatically by the Pipelines object + init_background_pipelines(); + + init_triangle_pipeline(); + init_mesh_pipeline(); + metalRoughMaterial.build_pipelines(this); } @@ -324,12 +500,11 @@ void VulkanEngine::init(SDL_Window* window) { // only one engine initialization is allowed with the application. assert(loadedEngine == nullptr); loadedEngine = this; + init_vulkan(); init_swapchain(); - - command_buffers.init_commands(this); - - command_buffers_container.init_sync_structures(this); + init_commands(); + init_sync_structures(); init_descriptors(); init_pipelines(); init_imgui(); @@ -341,13 +516,6 @@ void VulkanEngine::init(SDL_Window* window) { mainCamera->pitch = 0; mainCamera->yaw = 0; - const std::string structurePath = {std::string(ASSETS_DIR) + - "/basicmesh.glb"}; - const auto structureFile = loadGltf(this, structurePath); - - assert(structureFile.has_value()); - loadedScenes["structure"] = *structureFile; - _isInitialized = true; } @@ -463,9 +631,65 @@ void VulkanEngine::init_vulkan() { allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; vmaCreateAllocator(&allocatorInfo, &_allocator); - // VMA allocator will be destroyed in cleanup() - no need for deletion queue + _mainDeletionQueue.push_function([&] { vmaDestroyAllocator(_allocator); }); } +void VulkanEngine::init_commands() { + // create a command pool for commands submitted to the graphics queue. + // we also want the pool to allow for resetting of individual command + // buffers + const VkCommandPoolCreateInfo commandPoolInfo = + vkinit::command_pool_create_info( + _graphicsQueueFamily, + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); + + for (auto& _frame : _frames) { + VK_CHECK(vkCreateCommandPool(_device, &commandPoolInfo, nullptr, + &_frame._commandPool)); + + // allocate the default command buffer that we will use for rendering + VkCommandBufferAllocateInfo cmdAllocInfo = + vkinit::command_buffer_allocate_info(_frame._commandPool, 1); + + VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, + &_frame._mainCommandBuffer)); + } + + VK_CHECK(vkCreateCommandPool(_device, &commandPoolInfo, nullptr, + &_immCommandPool)); + + // allocate the command buffer for immediate submits + const VkCommandBufferAllocateInfo cmdAllocInfo = + vkinit::command_buffer_allocate_info(_immCommandPool, 1); + + VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, + &_immCommandBuffer)); + + _mainDeletionQueue.push_function([=, this] { + vkDestroyCommandPool(_device, _immCommandPool, nullptr); + }); +} + +void VulkanEngine::init_sync_structures() { + const VkFenceCreateInfo fenceCreateInfo = + vkinit::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT); + const VkSemaphoreCreateInfo semaphoreCreateInfo = + vkinit::semaphore_create_info(); + + for (auto& _frame : _frames) { + VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, + &_frame._renderFence)); + + VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, + &_frame._swapchainSemaphore)); + VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, + &_frame._renderSemaphore)); + } + + VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, &_immFence)); + _mainDeletionQueue.push_function( + [=, this] { vkDestroyFence(_device, _immFence, nullptr); }); +} void VulkanEngine::create_swapchain(uint32_t width, uint32_t height) { vkb::SwapchainBuilder swapchainBuilder{_chosenGPU, _device, _surface}; @@ -506,7 +730,8 @@ void VulkanEngine::init_swapchain() { _windowExtent.height, 1}; // hardcoding the draw format to 32-bit float - VkFormat drawImageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + _drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + _drawImage.imageExtent = drawImageExtent; VkImageUsageFlags drawImageUsages{}; drawImageUsages |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; @@ -515,7 +740,7 @@ void VulkanEngine::init_swapchain() { drawImageUsages |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; const VkImageCreateInfo rimg_info = vkinit::image_create_info( - drawImageFormat, drawImageUsages, drawImageExtent); + _drawImage.imageFormat, drawImageUsages, drawImageExtent); // for the draw image, we want to allocate it from gpu local memory VmaAllocationCreateInfo rimg_allocinfo = {}; @@ -523,61 +748,23 @@ void VulkanEngine::init_swapchain() { rimg_allocinfo.requiredFlags = static_cast( VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - AllocatedImage drawImageData{}; - drawImageData.imageFormat = drawImageFormat; - drawImageData.imageExtent = drawImageExtent; - // allocate and create the image - vmaCreateImage(_allocator, &rimg_info, &rimg_allocinfo, &drawImageData.image, - &drawImageData.allocation, nullptr); + vmaCreateImage(_allocator, &rimg_info, &rimg_allocinfo, &_drawImage.image, + &_drawImage.allocation, nullptr); // build an image-view for the draw image to use for rendering const VkImageViewCreateInfo rview_info = vkinit::imageview_create_info( - drawImageFormat, drawImageData.image, + _drawImage.imageFormat, _drawImage.image, VK_IMAGE_ASPECT_COLOR_BIT); VK_CHECK(vkCreateImageView(_device, &rview_info, nullptr, - &drawImageData.imageView)); - - // Create smart pointer for automatic cleanup - _drawImage = std::make_unique(_allocator, _device, drawImageData); - - // Create depth image - VkExtent3D depthImageExtent = { - _windowExtent.width, - _windowExtent.height, - 1 - }; - - VkFormat depthFormat = VK_FORMAT_D32_SFLOAT; - - VkImageCreateInfo dimg_info = vkinit::image_create_info( - depthFormat, - VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - depthImageExtent); - - VmaAllocationCreateInfo dimg_allocinfo = {}; - dimg_allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - dimg_allocinfo.requiredFlags = static_cast( - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - - AllocatedImage depthImageData{}; - depthImageData.imageFormat = depthFormat; - depthImageData.imageExtent = depthImageExtent; - - // allocate and create the depth image - vmaCreateImage(_allocator, &dimg_info, &dimg_allocinfo, &depthImageData.image, - &depthImageData.allocation, nullptr); + &_drawImage.imageView)); - // build an image-view for the depth image - VkImageViewCreateInfo dview_info = vkinit::imageview_create_info( - depthFormat, depthImageData.image, VK_IMAGE_ASPECT_DEPTH_BIT); - - VK_CHECK(vkCreateImageView(_device, &dview_info, nullptr, - &depthImageData.imageView)); - - // Create smart pointer for automatic cleanup - _depthImage = std::make_unique(_allocator, _device, depthImageData); + // add to deletion queues + _mainDeletionQueue.push_function([=, this] { + vkDestroyImageView(_device, _drawImage.imageView, nullptr); + vmaDestroyImage(_allocator, _drawImage.image, _drawImage.allocation); + }); } void VulkanEngine::destroy_swapchain() { @@ -596,17 +783,16 @@ void VulkanEngine::cleanup() { loadedScenes.clear(); - // Smart pointers will automatically clean up resources + _mainDeletionQueue.flush(); - for (auto& _frame : command_buffers_container._frames) { - // Smart pointers automatically clean up sync objects - // Manual cleanup only for command pools and command buffers - if (_frame._commandPool) { - vkDestroyCommandPool(_device, _frame._commandPool->get(), nullptr); - } + for (const auto& _frame : _frames) { + // already written from before + vkDestroyCommandPool(_device, _frame._commandPool, nullptr); - // Destroy frame descriptors manually - _frame._frameDescriptors.destroy_pools(_device); + // destroy sync objects + vkDestroyFence(_device, _frame._renderFence, nullptr); + vkDestroySemaphore(_device, _frame._renderSemaphore, nullptr); + vkDestroySemaphore(_device, _frame._swapchainSemaphore, nullptr); } destroy_swapchain(); @@ -616,9 +802,6 @@ void VulkanEngine::cleanup() { vkb::destroy_debug_utils_messenger(_instance, _debug_messenger); vkDestroyInstance(_instance, nullptr); - - // VMA allocator cleanup - vmaDestroyAllocator(_allocator); } // clear engine pointer @@ -692,7 +875,7 @@ GPUMeshBuffers VulkanEngine::uploadMesh(std::span indices, // copy index buffer memcpy((char*)data + vertexBufferSize, indices.data(), indexBufferSize); - command_buffers.immediate_submit([&](VkCommandBuffer cmd) { + immediate_submit([&](VkCommandBuffer cmd) { VkBufferCopy vertexCopy{0}; vertexCopy.dstOffset = 0; vertexCopy.srcOffset = 0; @@ -708,12 +891,11 @@ GPUMeshBuffers VulkanEngine::uploadMesh(std::span indices, vkCmdCopyBuffer(cmd, staging.buffer, newSurface.indexBuffer.buffer, 1, &indexCopy); - }, - this); - - // Store mesh buffers in managed collections for automatic cleanup - _managedBuffers.push_back(std::make_unique(_allocator, newSurface.vertexBuffer)); - _managedBuffers.push_back(std::make_unique(_allocator, newSurface.indexBuffer)); + }); + _mainDeletionQueue.push_function( + [=, this] { destroy_buffer(newSurface.vertexBuffer); }); + _mainDeletionQueue.push_function( + [=, this] { destroy_buffer(newSurface.indexBuffer); }); destroy_buffer(staging); return newSurface; @@ -721,17 +903,19 @@ GPUMeshBuffers VulkanEngine::uploadMesh(std::span indices, void VulkanEngine::draw_background(VkCommandBuffer cmd) const { // bind the gradient drawing compute pipeline - - pipelines.gradientPipeline->bind(cmd); - - // bind descriptor sets - pipelines.gradientPipeline->bindDescriptorSets(cmd, &_drawImageDescriptors, 1); - - // dispatch the compute shader - pipelines.gradientPipeline->dispatch(cmd, - static_cast(std::ceil(_drawExtent.width / 16.0)), - static_cast(std::ceil(_drawExtent.height / 16.0)), - 1); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _gradientPipeline); + + // bind the descriptor set containing the draw image for the compute + // pipeline + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, + _gradientPipelineLayout, 0, 1, + &_drawImageDescriptors, 0, nullptr); + + // execute the compute pipeline dispatch. We are using 16x16 workgroup size, + // so we need to divide by it + vkCmdDispatch( + cmd, static_cast(std::ceil(_drawExtent.width / 16.0)), + static_cast(std::ceil(_drawExtent.height / 16.0)), 1); } void VulkanEngine::draw_imgui(VkCommandBuffer cmd, @@ -754,9 +938,10 @@ void VulkanEngine::draw_geometry(VkCommandBuffer cmd) { sizeof(GPUSceneData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); - // add it to the current frame's managed buffers for automatic cleanup - get_current_frame()._frameBuffers.push_back( - std::make_unique(_allocator, gpuSceneDataBuffer)); + // add it to the deletion queue of this frame, so it gets deleted once it's + // been used + get_current_frame()._deletionQueue.push_function( + [=, this] { destroy_buffer(gpuSceneDataBuffer); }); // write the buffer auto* sceneUniformData = @@ -774,13 +959,13 @@ void VulkanEngine::draw_geometry(VkCommandBuffer cmd) { writer.update_set(_device, globalDescriptor); VkRenderingAttachmentInfo colorAttachment = vkinit::attachment_info( - _drawImage->imageView(), nullptr, VK_IMAGE_LAYOUT_GENERAL); + _drawImage.imageView, nullptr, VK_IMAGE_LAYOUT_GENERAL); VkRenderingInfo renderInfo = vkinit::rendering_info(_drawExtent, &colorAttachment, nullptr); vkCmdBeginRendering(cmd, &renderInfo); - pipelines.trianglePipeline->bind(cmd); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _trianglePipeline); // set dynamic viewport and scissor VkViewport viewport = {}; @@ -805,13 +990,14 @@ void VulkanEngine::draw_geometry(VkCommandBuffer cmd) { VkDescriptorSet imageSet = get_current_frame()._frameDescriptors.allocate( _device, _singleImageDescriptorLayout); DescriptorWriter single_image_writer; - single_image_writer.write_image(0, _errorCheckerboardImage->imageView(), + single_image_writer.write_image(0, _errorCheckerboardImage.imageView, _defaultSamplerNearest, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); single_image_writer.update_set(_device, imageSet); - pipelines.meshPipeline->bindDescriptorSets(cmd, &imageSet, 1); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, + _meshPipelineLayout, 0, 1, &imageSet, 0, nullptr); for (const auto& [indexCount, firstIndex, indexBuffer, material, transform, vertexBufferAddress] : mainDrawContext.OpaqueSurfaces) { @@ -839,25 +1025,24 @@ void VulkanEngine::draw_geometry(VkCommandBuffer cmd) { vkCmdEndRendering(cmd); } -void VulkanEngine::draw() { - update_scene(); +void VulkanEngine::draw(const IModel::Ptr model) { + update_scene(model); // wait until the gpu has finished rendering the last frame. Timeout of 1 // second - VK_CHECK(vkWaitForFences(_device, 1, get_current_frame()._renderFence->getPtr(), + VK_CHECK(vkWaitForFences(_device, 1, &get_current_frame()._renderFence, true, 1000000000)); - // Clear frame buffers instead of flushing deletion queue - get_current_frame()._frameBuffers.clear(); + get_current_frame()._deletionQueue.flush(); get_current_frame()._frameDescriptors.clear_pools(_device); - VK_CHECK(vkResetFences(_device, 1, get_current_frame()._renderFence->getPtr())); + VK_CHECK(vkResetFences(_device, 1, &get_current_frame()._renderFence)); // request image from the swapchain uint32_t swapchainImageIndex; const VkResult e = vkAcquireNextImageKHR(_device, _swapchain, 1000000000, - get_current_frame()._swapchainSemaphore->get(), + get_current_frame()._swapchainSemaphore, nullptr, &swapchainImageIndex); if (e == VK_ERROR_OUT_OF_DATE_KHR) { resize_requested = true; @@ -879,11 +1064,11 @@ void VulkanEngine::draw() { _drawExtent.height = static_cast( (float)std::min(_swapchainExtent.height, - _drawImage->get().imageExtent.height) * + _drawImage.imageExtent.height) * renderScale); _drawExtent.width = static_cast( (float)std::min(_swapchainExtent.width, - _drawImage->get().imageExtent.width) * + _drawImage.imageExtent.width) * renderScale); VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo)); @@ -891,19 +1076,19 @@ void VulkanEngine::draw() { // transition our main draw image into general layout, so we can write into // it, we will overwrite it all, so we don't care about what was the older // layout - vkutil::transition_image(cmd, _drawImage->image(), VK_IMAGE_LAYOUT_UNDEFINED, + vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); draw_background(cmd); - vkutil::transition_image(cmd, _drawImage->image(), VK_IMAGE_LAYOUT_GENERAL, + vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); draw_geometry(cmd); // transition the draw image and the swapchain image into their correct // transfer layouts - vkutil::transition_image(cmd, _drawImage->image(), + vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex], @@ -911,7 +1096,7 @@ void VulkanEngine::draw() { VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); // execute a copy from the draw image into the swapchain - vkutil::copy_image_to_image(cmd, _drawImage->image(), + vkutil::copy_image_to_image(cmd, _drawImage.image, _swapchainImages[swapchainImageIndex], _drawExtent, _swapchainExtent); @@ -942,10 +1127,10 @@ void VulkanEngine::draw() { const VkSemaphoreSubmitInfo waitInfo = vkinit::semaphore_submit_info( VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR, - get_current_frame()._swapchainSemaphore->get()); + get_current_frame()._swapchainSemaphore); const VkSemaphoreSubmitInfo signalInfo = vkinit::semaphore_submit_info(VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT, - get_current_frame()._renderSemaphore->get()); + get_current_frame()._renderSemaphore); const VkSubmitInfo2 submit = vkinit::submit_info(&cmdinfo, &signalInfo, &waitInfo); @@ -953,7 +1138,7 @@ void VulkanEngine::draw() { // submit command buffer to the queue and execute it. // _renderFence will now block until the graphic commands finish execution VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, - get_current_frame()._renderFence->get())); + get_current_frame()._renderFence)); // prepare present // this will put the image we just rendered to into the visible window. @@ -966,7 +1151,7 @@ void VulkanEngine::draw() { presentInfo.pSwapchains = &_swapchain; presentInfo.swapchainCount = 1; - presentInfo.pWaitSemaphores = get_current_frame()._renderSemaphore->getPtr(); + presentInfo.pWaitSemaphores = &get_current_frame()._renderSemaphore; presentInfo.waitSemaphoreCount = 1; presentInfo.pImageIndices = &swapchainImageIndex; @@ -995,12 +1180,41 @@ void VulkanEngine::resize_swapchain() { resize_requested = false; } -void VulkanEngine::update() { +void VulkanEngine::immediate_submit( + std::function&& function) const { + VK_CHECK(vkResetFences(_device, 1, &_immFence)); + VK_CHECK(vkResetCommandBuffer(_immCommandBuffer, 0)); + + const VkCommandBuffer cmd = _immCommandBuffer; + + const VkCommandBufferBeginInfo cmdBeginInfo = + vkinit::command_buffer_begin_info( + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + + VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo)); + + function(cmd); + + VK_CHECK(vkEndCommandBuffer(cmd)); + + const VkCommandBufferSubmitInfo cmdinfo = + vkinit::command_buffer_submit_info(cmd); + const VkSubmitInfo2 submit = + vkinit::submit_info(&cmdinfo, nullptr, nullptr); + + // submit command buffer to the queue and execute it. + // _renderFence will now block until the graphic commands finish execution + VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, _immFence)); + + VK_CHECK(vkWaitForFences(_device, 1, &_immFence, true, 9999999999)); +} + +void VulkanEngine::update(const IModel::Ptr model) { if (resize_requested) { resize_swapchain(); } - draw(); + draw(model); } AllocatedImage VulkanEngine::create_image(VkExtent3D size, VkFormat format, @@ -1062,7 +1276,7 @@ AllocatedImage VulkanEngine::create_image(const void* data, VkExtent3D size, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, mipmapped); - command_buffers.immediate_submit([&](VkCommandBuffer cmd) { + immediate_submit([&](VkCommandBuffer cmd) { vkutil::transition_image(cmd, new_image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); @@ -1086,8 +1300,7 @@ AllocatedImage VulkanEngine::create_image(const void* data, VkExtent3D size, vkutil::transition_image(cmd, new_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - }, - (VulkanEngine*)this); + }); destroy_buffer(uploadbuffer); @@ -1099,29 +1312,119 @@ void VulkanEngine::destroy_image(const AllocatedImage& img) const { vmaDestroyImage(_allocator, img.image, img.allocation); } -void MeshNode::Draw(const glm::mat4& topMatrix, DrawContext& ctx) { - const glm::mat4 nodeMatrix = topMatrix * worldTransform; +void GLTFMetallic_Roughness::build_pipelines(VulkanEngine* engine) { + VkShaderModule meshFragShader; + if (!vkutil::load_shader_module("./shaders/mesh.frag.spv", engine->_device, + &meshFragShader)) { + fmt::println("Error when building the triangle fragment shader module"); + } + + VkShaderModule meshVertexShader; + if (!vkutil::load_shader_module("./shaders/mesh.vert.spv", engine->_device, + &meshVertexShader)) { + fmt::println("Error when building the triangle vertex shader module"); + } + + VkPushConstantRange matrixRange{}; + matrixRange.offset = 0; + matrixRange.size = sizeof(GPUDrawPushConstants); + matrixRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - for (auto& [startIndex, count, material] : mesh->surfaces) { - RenderObject def{}; - def.indexCount = count; - def.firstIndex = startIndex; - def.indexBuffer = mesh->meshBuffers.indexBuffer.buffer; - def.material = &material->data; + DescriptorLayoutBuilder layoutBuilder; + layoutBuilder.add_binding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + layoutBuilder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + layoutBuilder.add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - def.transform = nodeMatrix; - def.vertexBufferAddress = mesh->meshBuffers.vertexBufferAddress; + materialLayout = layoutBuilder.build( + engine->_device, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); - ctx.OpaqueSurfaces.push_back(def); + VkDescriptorSetLayout layouts[] = {engine->_gpuSceneDataDescriptorLayout, + materialLayout}; + + VkPipelineLayoutCreateInfo mesh_layout_info = + vkinit::pipeline_layout_create_info(); + mesh_layout_info.setLayoutCount = 2; + mesh_layout_info.pSetLayouts = layouts; + mesh_layout_info.pPushConstantRanges = &matrixRange; + mesh_layout_info.pushConstantRangeCount = 1; + + VkPipelineLayout newLayout; + VK_CHECK(vkCreatePipelineLayout(engine->_device, &mesh_layout_info, nullptr, + &newLayout)); + + opaquePipeline.layout = newLayout; + transparentPipeline.layout = newLayout; + + // build the stage-create-info for both vertex and fragment stages. This + // lets the pipeline know the shader modules per stage + PipelineBuilder pipelineBuilder; + pipelineBuilder.set_shaders(meshVertexShader, meshFragShader); + pipelineBuilder.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + pipelineBuilder.set_polygon_mode(VK_POLYGON_MODE_FILL); + pipelineBuilder.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); + pipelineBuilder.set_multisampling_none(); + pipelineBuilder.disable_blending(); + pipelineBuilder.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL); + + // render format + pipelineBuilder.set_color_attachment_format(engine->_drawImage.imageFormat); + pipelineBuilder.set_depth_format(engine->_depthImage.imageFormat); + + // use the triangle layout we created + pipelineBuilder._pipelineLayout = newLayout; + + // finally build the pipeline + opaquePipeline.pipeline = pipelineBuilder.build_pipeline(engine->_device); + + // create the transparent variant + pipelineBuilder.enable_blending_additive(); + + pipelineBuilder.enable_depthtest(false, VK_COMPARE_OP_GREATER_OR_EQUAL); + + transparentPipeline.pipeline = + pipelineBuilder.build_pipeline(engine->_device); + + vkDestroyShaderModule(engine->_device, meshFragShader, nullptr); + vkDestroyShaderModule(engine->_device, meshVertexShader, nullptr); +} + +MaterialInstance GLTFMetallic_Roughness::write_material( + VkDevice device, MaterialPass pass, const MaterialResources& resources, + DescriptorAllocatorGrowable& descriptorAllocator) { + MaterialInstance matData{}; + matData.passType = pass; + if (pass == MaterialPass::Transparent) { + matData.pipeline = &transparentPipeline; + } else { + matData.pipeline = &opaquePipeline; } - ENode::Draw(topMatrix, ctx); + matData.materialSet = descriptorAllocator.allocate(device, materialLayout); + + writer.clear(); + writer.write_buffer(0, resources.dataBuffer, sizeof(MaterialConstants), + resources.dataBufferOffset, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + writer.write_image(1, resources.colorImage.imageView, + resources.colorSampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + writer.write_image(2, resources.metalRoughImage.imageView, + resources.metalRoughSampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + + writer.update_set(device, matData.materialSet); + writer.update_set(device, matData.materialSet); + + return matData; } -void VulkanEngine::update_scene() { +void VulkanEngine::update_scene(const IModel::Ptr& model) { mainCamera->update(); - const glm::mat4 view = mainCamera->getViewMatrix(); + glm::mat4 view = mainCamera->getViewMatrix(); glm::mat4 projection = glm::perspective( glm::radians(70.f), @@ -1141,42 +1444,9 @@ void VulkanEngine::update_scene() { sceneData.sunlightColor = glm::vec4(1.f); sceneData.sunlightDirection = glm::vec4(0, 1, 0.5, 1.f); - for (const auto& [key, mesh] : meshes) { - const std::shared_ptr loadedMesh = mesh; - loadedMesh->Draw(transforms[key], mainDrawContext); + auto meshes = model->get_meshes(); + for (const auto& mesh_info : std::views::values(meshes)) { + auto renderable_gltf{RenderableGLTF(mesh_info.ptr)}; + renderable_gltf.Draw(mesh_info.transform, mainDrawContext); } } - -int64_t VulkanEngine::registerMesh(const std::string& filePath) { - std::random_device rd; - - // Use the Mersenne Twister engine for high-quality random numbers - std::mt19937_64 generator(rd()); - - // Create a uniform distribution for int64_t - std::uniform_int_distribution distribution; - - // Generate and print a random int64_t value - const int64_t random_int64 = distribution(generator); - - const std::string structurePath = {std::string(ASSETS_DIR) + filePath}; - const auto structureFile = loadGltf(this, structurePath); - - assert(structureFile.has_value()); - - meshes[random_int64] = *structureFile; - transforms[random_int64] = glm::mat4(1.0f); - - return random_int64; -} - -void VulkanEngine::unregisterMesh(int64_t id) { - if (meshes.find(id) != meshes.end()) { - meshes.erase(id); - transforms.erase(id); - } -} - -void VulkanEngine::setMeshTransform(int64_t id, glm::mat4 mat) { - transforms[id] = mat; -} diff --git a/src/graphics/vulkan/vk_loader.cpp b/src/graphics/vulkan/vk_loader.cpp deleted file mode 100644 index 5c00e7be..00000000 --- a/src/graphics/vulkan/vk_loader.cpp +++ /dev/null @@ -1,526 +0,0 @@ -#include "graphics/vulkan/vk_loader.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define GLM_ENABLE_EXPERIMENTAL -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/Logging.h" -#include "graphics/vulkan/vk_descriptors.h" -#include "graphics/vulkan/vk_engine.h" -#include "graphics/vulkan/vk_types.h" - -std::optional>> loadGltfMeshes( - VulkanEngine* engine, const std::filesystem::path& filePath) { - if (!std::filesystem::exists(filePath)) { - LOGW("Failed to find file: {}", filePath.string()); - return {}; - } - LOGI("Loading: {}", filePath.string()); - - fastgltf::Asset gltf; - - // Parse the glTF file and get the constructed asset - - static constexpr auto supportedExtensions = - fastgltf::Extensions::KHR_mesh_quantization | - fastgltf::Extensions::KHR_texture_transform | - fastgltf::Extensions::KHR_materials_variants; - - fastgltf::Parser parser(supportedExtensions); - - auto path = std::filesystem::path{filePath}; - - constexpr auto gltfOptions = - fastgltf::Options::DontRequireValidAssetMember | - fastgltf::Options::AllowDouble | fastgltf::Options::LoadGLBBuffers | - fastgltf::Options::LoadExternalBuffers | - fastgltf::Options::LoadExternalImages | - fastgltf::Options::GenerateMeshIndices; - - if (!std::filesystem::exists(path)) { - LOGW("Failed to find file: {}", path.string()); - return {}; - } - - fastgltf::GltfDataBuffer data; - data.loadFromFile(path); - - auto asset = parser.loadGltf(&data, path.parent_path(), gltfOptions); - - if (asset) { - gltf = std::move(asset.get()); - } else { - LOGE("Failed to load glTF: {}", - fastgltf::to_underlying(asset.error())); - return {}; - } - - std::vector> meshes; - - // use the same vectors for all meshes so that the memory doesnt reallocate - // as often - std::vector indices; - std::vector vertices; - for (auto& [primitives, _, name] : gltf.meshes) { - MeshAsset newmesh; - - newmesh.name = name; - - // clear the mesh arrays each mesh, we dont want to merge them by error - indices.clear(); - vertices.clear(); - - for (auto&& p : primitives) { - GeoSurface newSurface; - newSurface.startIndex = (uint32_t)indices.size(); - newSurface.count = - (uint32_t)gltf.accessors[p.indicesAccessor.value()].count; - - auto initial_vtx = static_cast(vertices.size()); - - // load indexes - { - fastgltf::Accessor& indexaccessor = - gltf.accessors[p.indicesAccessor.value()]; - indices.reserve(indices.size() + indexaccessor.count); - - fastgltf::iterateAccessor( - gltf, indexaccessor, [&](std::uint32_t idx) { - indices.push_back(idx + initial_vtx); - }); - } - - // load vertex positions - { - fastgltf::Accessor& posAccessor = - gltf.accessors[p.findAttribute("POSITION")->second]; - vertices.resize(vertices.size() + posAccessor.count); - - fastgltf::iterateAccessorWithIndex( - gltf, posAccessor, [&](glm::vec3 v, size_t index) { - Vertex newvtx; - newvtx.position = v; - newvtx.normal = {1, 0, 0}; - newvtx.color = glm::vec4{1.f}; - newvtx.uv_x = 0; - newvtx.uv_y = 0; - vertices[initial_vtx + index] = newvtx; - }); - } - - // load vertex normals - auto normals = p.findAttribute("NORMAL"); - if (normals != p.attributes.end()) { - fastgltf::iterateAccessorWithIndex( - gltf, gltf.accessors[normals->second], - [&](glm::vec3 v, size_t index) { - vertices[initial_vtx + index].normal = v; - }); - } - - // load UVs - auto uv = p.findAttribute("TEXCOORD_0"); - if (uv != p.attributes.end()) { - fastgltf::iterateAccessorWithIndex( - gltf, gltf.accessors[uv->second], - [&](glm::vec2 v, size_t index) { - vertices[initial_vtx + index].uv_x = v.x; - vertices[initial_vtx + index].uv_y = v.y; - }); - } - - // load vertex colors - auto colors = p.findAttribute("COLOR_0"); - if (colors != p.attributes.end()) { - fastgltf::iterateAccessorWithIndex( - gltf, gltf.accessors[colors->second], - [&](glm::vec4 v, size_t index) { - vertices[initial_vtx + index].color = v; - }); - } - newmesh.surfaces.push_back(newSurface); - } - - // display the vertex normals - constexpr bool OverrideColors = true; - if (OverrideColors) { - for (Vertex& vtx : vertices) { - vtx.color = glm::vec4(vtx.normal, 1.f); - } - } - newmesh.meshBuffers = engine->uploadMesh(indices, vertices); - - meshes.emplace_back(std::make_shared(std::move(newmesh))); - } - - return meshes; -} - -VkFilter extract_filter(fastgltf::Filter filter) { - switch (filter) { - // nearest samplers - case fastgltf::Filter::Nearest: - case fastgltf::Filter::NearestMipMapNearest: - case fastgltf::Filter::NearestMipMapLinear: - return VK_FILTER_NEAREST; - - // linear samplers - case fastgltf::Filter::Linear: - case fastgltf::Filter::LinearMipMapNearest: - case fastgltf::Filter::LinearMipMapLinear: - default: - return VK_FILTER_LINEAR; - } -} - -VkSamplerMipmapMode extract_mipmap_mode(fastgltf::Filter filter) { - switch (filter) { - case fastgltf::Filter::NearestMipMapNearest: - case fastgltf::Filter::LinearMipMapNearest: - return VK_SAMPLER_MIPMAP_MODE_NEAREST; - - case fastgltf::Filter::NearestMipMapLinear: - case fastgltf::Filter::LinearMipMapLinear: - default: - return VK_SAMPLER_MIPMAP_MODE_LINEAR; - } -} - -std::optional> loadGltf(VulkanEngine* engine, - std::string_view filePath) { - LOGI("Loading GLTF: {}", filePath);if (!std::filesystem::exists(filePath)) { - LOGW("File does not exist: {}", filePath); - } - auto scene = std::make_shared(); - scene->creator = engine; - LoadedGLTF& file = *scene; - fastgltf::Parser parser{}; - constexpr auto gltfOptions = - fastgltf::Options::DontRequireValidAssetMember | - fastgltf::Options::AllowDouble | fastgltf::Options::LoadGLBBuffers | - fastgltf::Options::LoadExternalBuffers; - fastgltf::GltfDataBuffer data; - data.loadFromFile(filePath); - fastgltf::Asset gltf; - std::filesystem::path path = filePath; - auto type = fastgltf::determineGltfFileType(&data); - if (type == fastgltf::GltfType::glTF) { - auto load = parser.loadGltf(&data, path.parent_path(), gltfOptions); - if (load) { - gltf = std::move(load.get()); - } else { - LOGE("Failed to load glTF: {} ", fastgltf::to_underlying(load.error())); - return {}; - } - } else if (type == fastgltf::GltfType::GLB) { - auto load = - parser.loadGltfBinary(&data, path.parent_path(), gltfOptions); - if (load) { - gltf = std::move(load.get()); - } else { - LOGE("Failed to load glTF: {} ", fastgltf::to_underlying(load.error())); - return {}; - } - } else { - LOGE("Failed to determine glTF container"); - return {}; - } - - // Estimate descriptor pool size based on materials - std::vector sizes = { - {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}}; - file.descriptorPool.init( - engine->_device, - static_cast(std::max(gltf.materials.size(), size_t(1))), - sizes); - - // Load samplers - for (fastgltf::Sampler& sampler : gltf.samplers) { - VkSamplerCreateInfo sampl = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = nullptr}; - sampl.maxLod = VK_LOD_CLAMP_NONE; - sampl.minLod = 0; - sampl.magFilter = extract_filter( - sampler.magFilter.value_or(fastgltf::Filter::Nearest)); - sampl.minFilter = extract_filter( - sampler.minFilter.value_or(fastgltf::Filter::Nearest)); - sampl.mipmapMode = extract_mipmap_mode( - sampler.minFilter.value_or(fastgltf::Filter::Nearest)); - VkSampler newSampler; - vkCreateSampler(engine->_device, &sampl, nullptr, &newSampler); - file.samplers.push_back(newSampler); - } - - std::vector> meshes; - std::vector> nodes; - std::vector images; - std::vector> materials; - - // Load all textures - images.reserve(gltf.images.size()); - for (size_t i = 0; i < gltf.images.size(); i++) { - images.push_back(engine->_errorCheckerboardImage->get()); - } - - // Create buffer to hold the material data - size_t materialCount = gltf.materials.size() ? gltf.materials.size() : 1; - file.materialDataBuffer = engine->create_buffer( - sizeof(GLTFMetallic_Roughness::MaterialConstants) * materialCount, - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); - - uint32_t data_index = 0; - auto* sceneMaterialConstants = - (GLTFMetallic_Roughness::MaterialConstants*) - file.materialDataBuffer.info.pMappedData; - - // Process all materials from the GLTF - for (fastgltf::Material& mat : gltf.materials) { - auto newMat = std::make_shared(); - materials.push_back(newMat); - file.materials[mat.name.c_str()] = newMat; - - GLTFMetallic_Roughness::MaterialConstants constants; - constants.colorFactors.x = mat.pbrData.baseColorFactor[0]; - constants.colorFactors.y = mat.pbrData.baseColorFactor[1]; - constants.colorFactors.z = mat.pbrData.baseColorFactor[2]; - constants.colorFactors.w = mat.pbrData.baseColorFactor[3]; - constants.metal_rough_factors.x = mat.pbrData.metallicFactor; - constants.metal_rough_factors.y = mat.pbrData.roughnessFactor; - - sceneMaterialConstants[data_index] = constants; - - auto passType = MaterialPass::MainColor; - if (mat.alphaMode == fastgltf::AlphaMode::Blend) { - passType = MaterialPass::Transparent; - } - - GLTFMetallic_Roughness::MaterialResources materialResources; - - materialResources.colorImage = engine->_whiteImage->get(); - materialResources.colorSampler = engine->_defaultSamplerLinear; - materialResources.metalRoughImage = engine->_whiteImage->get(); - materialResources.metalRoughSampler = engine->_defaultSamplerLinear; - materialResources.dataBuffer = file.materialDataBuffer.buffer; - materialResources.dataBufferOffset = - data_index * sizeof(GLTFMetallic_Roughness::MaterialConstants); - - if (mat.pbrData.baseColorTexture.has_value()) { - size_t img = gltf.textures[mat.pbrData.baseColorTexture.value() - .textureIndex] - .imageIndex.value(); - size_t sampler = gltf.textures[mat.pbrData.baseColorTexture.value() - .textureIndex] - .samplerIndex.value(); - materialResources.colorImage = images[img]; - materialResources.colorSampler = file.samplers[sampler]; - } - - newMat->data = engine->metalRoughMaterial.write_material( - engine->_device, passType, materialResources, - file.descriptorPool); - data_index++; - } - - // Add a fallback material if no materials were defined in the GLTF - if (materials.empty()) { - auto defaultMat = std::make_shared(); - materials.push_back(defaultMat); - - GLTFMetallic_Roughness::MaterialConstants constants = {}; - constants.colorFactors = glm::vec4(1.0f); // White base color - constants.metal_rough_factors = glm::vec4(0.0f); // Non-metallic, smooth - - sceneMaterialConstants[0] = constants; - - GLTFMetallic_Roughness::MaterialResources resources; - resources.colorImage = engine->_whiteImage->get(); - resources.colorSampler = engine->_defaultSamplerLinear; - resources.metalRoughImage = engine->_whiteImage->get(); - resources.metalRoughSampler = engine->_defaultSamplerLinear; - resources.dataBuffer = file.materialDataBuffer.buffer; - resources.dataBufferOffset = 0; - - defaultMat->data = engine->metalRoughMaterial.write_material( - engine->_device, MaterialPass::MainColor, resources, - file.descriptorPool); - } - - std::vector indices; - std::vector vertices; - - for (auto& [primitives, _, name] : gltf.meshes) { - auto newmesh = std::make_shared(); - meshes.push_back(newmesh); - file.meshes[name.c_str()] = newmesh; - newmesh->name = name; - indices.clear(); - vertices.clear(); - - for (auto&& p : primitives) { - GeoSurface newSurface; - newSurface.startIndex = (uint32_t)indices.size(); - newSurface.count = - (uint32_t)gltf.accessors[p.indicesAccessor.value()].count; - auto initial_vtx = static_cast(vertices.size()); - - // Load indices - { - fastgltf::Accessor& indexaccessor = - gltf.accessors[p.indicesAccessor.value()]; - indices.reserve(indices.size() + indexaccessor.count); - fastgltf::iterateAccessor( - gltf, indexaccessor, [&](std::uint32_t idx) { - indices.push_back(idx + initial_vtx); - }); - } - - // Load vertex positions - { - fastgltf::Accessor& posAccessor = - gltf.accessors[p.findAttribute("POSITION")->second]; - vertices.resize(vertices.size() + posAccessor.count); - fastgltf::iterateAccessorWithIndex( - gltf, posAccessor, [&](glm::vec3 v, size_t index) { - Vertex newvtx; - newvtx.position = v; - newvtx.normal = {1, 0, 0}; - newvtx.color = glm::vec4{1.f}; - newvtx.uv_x = 0; - newvtx.uv_y = 0; - vertices[initial_vtx + index] = newvtx; - }); - } - - // Load vertex normals - auto normals = p.findAttribute("NORMAL"); - if (normals != p.attributes.end()) { - fastgltf::iterateAccessorWithIndex( - gltf, gltf.accessors[normals->second], - [&](glm::vec3 v, size_t index) { - vertices[initial_vtx + index].normal = v; - }); - } - - // Load UVs - auto uv = p.findAttribute("TEXCOORD_0"); - if (uv != p.attributes.end()) { - fastgltf::iterateAccessorWithIndex( - gltf, gltf.accessors[uv->second], - [&](glm::vec2 v, size_t index) { - vertices[initial_vtx + index].uv_x = v.x; - vertices[initial_vtx + index].uv_y = v.y; - }); - } - - // Load vertex colors - auto colors = p.findAttribute("COLOR_0"); - if (colors != p.attributes.end()) { - fastgltf::iterateAccessorWithIndex( - gltf, gltf.accessors[colors->second], - [&](glm::vec4 v, size_t index) { - vertices[initial_vtx + index].color = v; - }); - } - - // Assign material safely - if (p.materialIndex.has_value()) { - newSurface.material = materials[p.materialIndex.value()]; - } else { - newSurface.material = materials[0]; // Always valid now - } - - newmesh->surfaces.push_back(newSurface); - } - newmesh->meshBuffers = engine->uploadMesh(indices, vertices); - } - - // Load all nodes and their meshes - for (fastgltf::Node& node : gltf.nodes) { - std::shared_ptr newNode; - if (node.meshIndex.has_value()) { - newNode = std::make_shared(); - dynamic_cast(newNode.get())->mesh = - meshes[*node.meshIndex]; - } else { - newNode = std::make_shared(); - } - nodes.push_back(newNode); - file.nodes[node.name.c_str()]; - - std::visit(fastgltf::visitor{ - [&](const fastgltf::Node::TransformMatrix& matrix) { - memcpy(&newNode->localTransform, matrix.data(), - sizeof(matrix)); - }, - [&](const fastgltf::TRS& transform) { - const glm::vec3 tl(transform.translation[0], - transform.translation[1], - transform.translation[2]); - const glm::quat rot(transform.rotation[3], - transform.rotation[0], - transform.rotation[1], - transform.rotation[2]); - const glm::vec3 sc(transform.scale[0], - transform.scale[1], - transform.scale[2]); - const glm::mat4 tm = - glm::translate(glm::mat4(1.f), tl); - const glm::mat4 rm = glm::toMat4(rot); - const glm::mat4 sm = - glm::scale(glm::mat4(1.f), sc); - newNode->localTransform = tm * rm * sm; - }}, - node.transform); - } - - // Setup transform hierarchy - for (size_t i = 0; i < gltf.nodes.size(); i++) { - fastgltf::Node& node = gltf.nodes[i]; - std::shared_ptr& sceneNode = nodes[i]; - for (auto& c : node.children) { - sceneNode->children.push_back(nodes[c]); - nodes[c]->parent = sceneNode; - } - } - - // Find top-level nodes - for (auto& n : nodes) { - if (n->parent.lock() == nullptr) { - file.topNodes.push_back(n); - n->refreshTransform(glm::mat4{1.f}); - } - } - - return scene; -} - -void LoadedGLTF::Draw(const glm::mat4& topMatrix, DrawContext& ctx) { - // create renderables from the scenenodes - for (const auto& n : topNodes) { - n->Draw(topMatrix, ctx); - } -} - -void LoadedGLTF::clearAll() {} diff --git a/src/include/core/Controller.h b/src/include/core/Controller.h index ed2e98c9..4c439d2b 100644 --- a/src/include/core/Controller.h +++ b/src/include/core/Controller.h @@ -2,5 +2,6 @@ #include "IController.h" #include "interfaces/IModel.h" +#include "interfaces/IView.h" -IController::Ptr createController(IModel::Ptr ptr); +IController::Ptr createController(IModel::Ptr model, IView::Ptr view); diff --git a/src/include/core/ControllerImpl.h b/src/include/core/ControllerImpl.h index f7025dd8..97185297 100644 --- a/src/include/core/ControllerImpl.h +++ b/src/include/core/ControllerImpl.h @@ -5,16 +5,21 @@ #include "IController.h" #include "interfaces/IModel.h" +#include "interfaces/IView.h" -class ControllerImpl : public IController, - public std::enable_shared_from_this { +class MeshController; + +class ControllerImpl : public IController { public: - explicit ControllerImpl(IModel::Ptr model); + explicit ControllerImpl(IModel::Ptr model, IView::Ptr view); - void init() const override; - void update() const override; - void processEvent(SDL_Event& e) const override; + void run() final; + void update() final; + std::weak_ptr getMeshController() final; + void process_event(const SDL_Event& e) final; private: - std::shared_ptr _model; + IModel::Ptr _model; + IView::Ptr _view; + std::shared_ptr _mesh_controller; }; diff --git a/src/include/core/Mesh.h b/src/include/core/Mesh.h deleted file mode 100644 index a8e22857..00000000 --- a/src/include/core/Mesh.h +++ /dev/null @@ -1,25 +0,0 @@ -// Mesh.h -#pragma once - -#include - -#include "glm/detail/type_mat4x4.hpp" -#include "graphics/vulkan/vk_engine.h" - -class Mesh { -public: - Mesh(const std::string& filePath); - ~Mesh(); - - // Add model change functionality - void set_model(const std::string& filePath); - void remove_model(); - - void set_transform(glm::mat4 t); - glm::mat4 get_transform(); - -private: - glm::mat4 _transform; - std::string _currentModelPath; // Track current model path - int64_t _rid; -}; \ No newline at end of file diff --git a/src/include/core/MeshController.h b/src/include/core/MeshController.h new file mode 100644 index 00000000..b577ad06 --- /dev/null +++ b/src/include/core/MeshController.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include "interfaces/IModel.h" +#include "scene/Mesh.h" + +/*! \brief + * A class that allows you to add, delete, modify meshes + */ +class MeshController { +public: + MeshController() = delete; + explicit MeshController(IModel::Ptr model); + ~MeshController() = default; + /*! \brief Adds a mesh to the ones to be rendered + * \param file_path The path to the mesh + * \return Returns render-id of the added mesh + */ + Mesh::rid_t create_mesh(const std::filesystem::path& file_path) const; + /*! \brief Removes meshes from those to be rendered + * \param id Render-id of the mesh + */ + void delete_mesh(Mesh::rid_t id) const; + + /*! \brief Sets a transform matrix of a mesh + * \param id Render-id of the mesh + * \param t Transform matrix + */ + void set_transform(Mesh::rid_t id, glm::mat4 t) const; + /*! \brief Gets a transform matrix of a mesh + * \param id Render-id of the mesh + * \return Returns a transform matrix of the mesh + */ + [[nodiscard]] glm::mat4 get_transform(Mesh::rid_t id) const; + /*! \brief Gets all meshes render-ids + * \return Returns a vector of all render-ids + */ + [[nodiscard]] std::vector get_meshes() const; + +private: + IModel::Ptr _model; +}; \ No newline at end of file diff --git a/src/include/core/Model.h b/src/include/core/Model.h index 1a3dbef1..e5ef3990 100644 --- a/src/include/core/Model.h +++ b/src/include/core/Model.h @@ -1,6 +1,5 @@ #pragma once #include "interfaces/IModel.h" -#include "interfaces/IView.h" IModel::Ptr createModel(); \ No newline at end of file diff --git a/src/include/core/ModelImpl.h b/src/include/core/ModelImpl.h index 2f2b6dc9..6bc44a5f 100644 --- a/src/include/core/ModelImpl.h +++ b/src/include/core/ModelImpl.h @@ -1,15 +1,10 @@ #pragma once #include -#include -#include -#include - #include "graphics/vulkan/vk_engine.h" #include "interfaces/IModel.h" #include "scene/Camera.h" - -class Mesh; +#include "scene/Mesh.h" class ModelImpl : public IModel { public: @@ -23,15 +18,21 @@ class ModelImpl : public IModel { ModelImpl &operator=(const ModelImpl &) = delete; void registerWindow(struct SDL_Window *window) override; - void updateVulkan() override; - void createMesh(std::string name) override; - void setMeshTransform(std::string name, glm::mat4x4 transform) override; + Mesh::rid_t createMesh(const std::filesystem::path &file_path) final; + void delete_mesh(Mesh::rid_t rid) final; + + void setMeshTransform(Mesh::rid_t rid, glm::mat4x4 transform) final; + glm::mat4 get_mesh_transform(Mesh::rid_t) final; + + const MeshMap &get_meshes() final; Camera *getCamera() override; + VulkanEngine &get_engine() final; + private: - std::unordered_map> _meshes; + MeshMap _meshes; VulkanEngine _engine; diff --git a/src/include/core/View.h b/src/include/core/View.h index 73e9012c..9d01bd9e 100644 --- a/src/include/core/View.h +++ b/src/include/core/View.h @@ -1,7 +1,6 @@ #pragma once -#include "IController.h" #include "interfaces/IModel.h" #include "interfaces/IView.h" -IView::Ptr createView(IController::Ptr controller, IModel::Ptr model); \ No newline at end of file +IView::Ptr createView(IModel::Ptr model); \ No newline at end of file diff --git a/src/include/core/ViewImpl.h b/src/include/core/ViewImpl.h index 1f599ab6..77747390 100644 --- a/src/include/core/ViewImpl.h +++ b/src/include/core/ViewImpl.h @@ -1,18 +1,20 @@ #pragma once -#include "IController.h" +#include +#include + #include "interfaces/IModel.h" #include "interfaces/IView.h" class ViewImpl : public IView { public: - ViewImpl(IController::Ptr controller, IModel::Ptr model); + explicit ViewImpl(IModel::Ptr model); ~ViewImpl() override; void run() const override; + void process_event(const SDL_Event& e) final; private: - IController::Ptr _controller; IModel::Ptr _model; struct SDL_Window* window{nullptr}; diff --git a/src/include/graphics/vulkan/MeshNode.h b/src/include/graphics/vulkan/MeshNode.h new file mode 100644 index 00000000..b27286a7 --- /dev/null +++ b/src/include/graphics/vulkan/MeshNode.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include "graphics/vulkan/vk_types.h" + +namespace Mesh::GLTF { +struct MeshAsset; +} + +struct MeshNode final : ENode { + std::shared_ptr mesh; + + void Draw(const glm::mat4& topMatrix, DrawContext& ctx) override; +}; diff --git a/src/include/graphics/vulkan/RenderableGLTF.h b/src/include/graphics/vulkan/RenderableGLTF.h new file mode 100644 index 00000000..f0e5f584 --- /dev/null +++ b/src/include/graphics/vulkan/RenderableGLTF.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include "scene/Mesh.h" +#include "vk_types.h" + +class RenderableGLTF : public IRenderable { +public: + using LoadedGltfPtr = std::shared_ptr; + explicit RenderableGLTF(LoadedGltfPtr gltf); + + void Draw(const glm::mat4& topMatrix, DrawContext& ctx) final; + +private: + std::shared_ptr _gltf; +}; diff --git a/src/include/graphics/vulkan/vk_engine.h b/src/include/graphics/vulkan/vk_engine.h index 40db0c65..6128d4ba 100644 --- a/src/include/graphics/vulkan/vk_engine.h +++ b/src/include/graphics/vulkan/vk_engine.h @@ -1,34 +1,80 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include "RenderableGLTF.h" +#include "interfaces/IModel.h" +#include "scene/Camera.h" +#include "scene/Mesh.h" #include "vk_descriptors.h" +#include "vk_pipelines.h" #include "vk_types.h" -#include "vk_smart_wrappers.h" -#include "pipelines.h" -#include "ComputePipeline.h" +struct MeshAsset; -#include "vk_command_buffers.h" -#include "vk_command_buffers_container.h" +constexpr unsigned int FRAME_OVERLAP = 2; -class Camera; -class VulkanEngine; -struct DrawContext; -struct LoadedGLTF; -struct MeshAsset; +struct GLTFMetallic_Roughness { + MaterialPipeline opaquePipeline; + MaterialPipeline transparentPipeline; + + VkDescriptorSetLayout materialLayout; + + struct MaterialConstants { + glm::vec4 colorFactors; + glm::vec4 metal_rough_factors; + // padding, we need it anyway for uniform buffers + glm::vec4 extra[14]; + }; + + struct MaterialResources { + AllocatedImage colorImage; + VkSampler colorSampler; + AllocatedImage metalRoughImage; + VkSampler metalRoughSampler; + VkBuffer dataBuffer; + uint32_t dataBufferOffset; + }; + + DescriptorWriter writer; + + void build_pipelines(VulkanEngine* engine); + void clear_resources(VkDevice device); + + MaterialInstance write_material( + VkDevice device, MaterialPass pass, + const MaterialResources& resources, + DescriptorAllocatorGrowable& descriptorAllocator); +}; + +struct DeletionQueue { + std::deque> deletors; + + void push_function(std::function&& function) { + deletors.push_back(function); + } + + void flush() { + // reverse iterate the deletion queue to execute all the functions + for (auto& deletor : std::ranges::reverse_view(deletors)) { + deletor(); // call functors + } + + deletors.clear(); + } +}; + +struct FrameData { + VkCommandPool _commandPool; + VkCommandBuffer _mainCommandBuffer; + VkSemaphore _swapchainSemaphore, _renderSemaphore; + VkFence _renderFence; + + DeletionQueue _deletionQueue; + DescriptorAllocatorGrowable _frameDescriptors; +}; struct GPUSceneData { glm::mat4 view; @@ -39,14 +85,6 @@ struct GPUSceneData { glm::vec4 sunlightColor; }; -struct MeshNode : public ENode { - MeshNode() = default; - - std::shared_ptr mesh; - - void Draw(const glm::mat4& topMatrix, DrawContext& ctx) override; -}; - struct RenderObject { uint32_t indexCount; uint32_t firstIndex; @@ -64,33 +102,21 @@ struct DrawContext { class VulkanEngine { public: - - Pipelines pipelines; - - CommandBuffers command_buffers; - CommandBuffersContainer command_buffers_container; - - int64_t registerMesh(const std::string& filePath); - - void unregisterMesh(int64_t id); - - void setMeshTransform(int64_t id, glm::mat4 mat); - - std::unordered_map> meshes; - - std::unordered_map transforms; - - std::unordered_map> loadedScenes; + std::unordered_map> + loadedScenes; Camera* mainCamera; DrawContext mainDrawContext; std::unordered_map> loadedNodes; - void update_scene(); + void update_scene(const IModel::Ptr& model); + + FrameData _frames[FRAME_OVERLAP]; FrameData& get_current_frame() { - return command_buffers_container.get_current_frame(_frameNumber); + return _frames[_frameNumber % FRAME_OVERLAP]; }; VkQueue _graphicsQueue; @@ -112,10 +138,10 @@ class VulkanEngine { void cleanup(); // draw loop - void draw(); + void draw(IModel::Ptr model); // run main loop - void update(); + void update(IModel::Ptr model); VkInstance _instance; // Vulkan library handle VkDebugUtilsMessengerEXT _debug_messenger; // Vulkan debug output handle @@ -130,10 +156,12 @@ class VulkanEngine { std::vector _swapchainImageViews; VkExtent2D _swapchainExtent; + DeletionQueue _mainDeletionQueue; + VmaAllocator _allocator; - std::unique_ptr _drawImage; - std::unique_ptr _depthImage; + AllocatedImage _drawImage; + AllocatedImage _depthImage; VkExtent2D _drawExtent; float renderScale = 1.f; @@ -142,9 +170,24 @@ class VulkanEngine { VkDescriptorSet _drawImageDescriptors; VkDescriptorSetLayout _drawImageDescriptorLayout; + VkPipeline _gradientPipeline; + VkPipelineLayout _gradientPipelineLayout; + + // immediate submit structures + VkFence _immFence; + VkCommandBuffer _immCommandBuffer; + VkCommandPool _immCommandPool; + + VkPipelineLayout _trianglePipelineLayout; + VkPipeline _trianglePipeline; + + VkPipelineLayout _meshPipelineLayout; + VkPipeline _meshPipeline; GPUMeshBuffers rectangle; + void immediate_submit( + std::function&& function) const; GPUMeshBuffers uploadMesh(std::span indices, std::span vertices); @@ -164,10 +207,10 @@ class VulkanEngine { bool mipmapped = false) const; void destroy_image(const AllocatedImage& img) const; - std::unique_ptr _whiteImage; - std::unique_ptr _blackImage; - std::unique_ptr _greyImage; - std::unique_ptr _errorCheckerboardImage; + AllocatedImage _whiteImage; + AllocatedImage _blackImage; + AllocatedImage _greyImage; + AllocatedImage _errorCheckerboardImage; VkSampler _defaultSamplerLinear; VkSampler _defaultSamplerNearest; @@ -181,10 +224,6 @@ class VulkanEngine { VmaMemoryUsage memoryUsage) const; private: - // Smart pointer collections for automatic cleanup - std::vector> _managedBuffers; - std::vector> _managedImages; - static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, @@ -193,6 +232,8 @@ class VulkanEngine { void init_vulkan(); void init_swapchain(); + void init_commands(); + void init_sync_structures(); void create_swapchain(uint32_t width, uint32_t height); void destroy_swapchain(); @@ -202,8 +243,11 @@ class VulkanEngine { void init_descriptors(); void init_pipelines(); + void init_background_pipelines(); void init_imgui(); + void init_triangle_pipeline(); + void draw_imgui(VkCommandBuffer cmd, VkImageView targetImageView) const; void draw_geometry(VkCommandBuffer cmd); @@ -215,4 +259,4 @@ class VulkanEngine { void init_mesh_pipeline(); void init_default_data(); -}; +}; \ No newline at end of file diff --git a/src/include/graphics/vulkan/vk_types.h b/src/include/graphics/vulkan/vk_types.h index 172f637e..e0c8e55b 100644 --- a/src/include/graphics/vulkan/vk_types.h +++ b/src/include/graphics/vulkan/vk_types.h @@ -1,18 +1,14 @@ #pragma once -#include -#include -#include #include -#include #include #include -#include #include #include "core/Logging.h" #include "glm/mat4x4.hpp" #include "glm/vec4.hpp" + #include "vk_mem_alloc.h" #include "vulkan/vulkan.h" @@ -41,10 +37,10 @@ struct AllocatedBuffer { struct Vertex { glm::vec3 position; - float uv_x; glm::vec3 normal; - float uv_y; glm::vec4 color; + float uv_x; + float uv_y; }; // holds the resources needed for a mesh @@ -76,8 +72,11 @@ struct MaterialInstance { struct DrawContext; // base class for a renderable dynamic object -class IRenderable { +struct IRenderable { + virtual ~IRenderable() = default; virtual void Draw(const glm::mat4& topMatrix, DrawContext& ctx) = 0; + + using Ptr = std::shared_ptr; }; // implementation of a drawable scene node. diff --git a/src/include/interfaces/IModel.h b/src/include/interfaces/IModel.h index bdfc421c..8f5dff95 100644 --- a/src/include/interfaces/IModel.h +++ b/src/include/interfaces/IModel.h @@ -5,10 +5,12 @@ #include #include #include +#include #include "SDL2/SDL.h" #include "SDL2/SDL_vulkan.h" #include "scene/Camera.h" +#include "scene/Mesh.h" /*! * \brief Interface for managing models and their integration with Vulkan. @@ -40,33 +42,52 @@ class IModel { virtual void registerWindow(struct SDL_Window* window) = 0; /*! - * \brief Updates Vulkan-related states. - * - * This method updates internal Vulkan-related states or data structures. - * Should be called regularly to keep Vulkan rendering in sync with the - * application state. + * \brief Loads (parses) a new mesh to the model + * \param file_path The path to the mesh + * \return Returns render-id of the mesh that helps identify the provided + * mesh. */ - virtual void updateVulkan() = 0; - + virtual Mesh::rid_t createMesh(const std::filesystem::path& file_path) = 0; /*! - * \brief Creates a new mesh with the given name. - * - * \param name Name of the mesh to be created. - * - * This method creates a new mesh identified by the provided name. + * \brief Deletes the provided mesh from a storage + * @param rid Render-id of the mesh to be deleted */ - virtual void createMesh(std::string name) = 0; + virtual void delete_mesh(Mesh::rid_t rid) = 0; /*! - * \brief Sets the transformation matrix for a mesh. - * - * \param name Name of the mesh. + * \brief Sets the transformation matrix for a mesh identified by render-id + * \param rid Render-id of the mesh to be transformed * \param transform Transformation matrix to be applied to the mesh. - * - * This method sets the transformation matrix for the mesh identified by the - * provided name. */ - virtual void setMeshTransform(std::string name, glm::mat4x4 transform) = 0; + virtual void setMeshTransform(Mesh::rid_t rid, glm::mat4x4 transform) = 0; + /*! + * \brief Gets the transformation matrix of a mesh. + * \param rid Render-id of the mesh + * \return Returns a transform matrix of the mesh + */ + virtual glm::mat4 get_mesh_transform(Mesh::rid_t rid) = 0; + + /*! + * \brief Stores mesh data + */ + struct MeshPair { + /*! + * \brief Pointer to the loaded mesh (GLTF) + */ + std::shared_ptr ptr; + /*! + * \brief Transform matrix of the mesh + */ + glm::mat4 transform; + }; + + using MeshMap = std::unordered_map; + /*! + * \brief Gets all the stored meshes + * \return Returns a MeshMap that stores pairs of the render-id of the mesh + * and the mesh data + */ + virtual const MeshMap& get_meshes() = 0; /*! * \brief Retrieves the camera instance. @@ -78,27 +99,11 @@ class IModel { */ [[nodiscard]] virtual Camera* getCamera() = 0; - /*! \brief - * Gets the chip handler from HIDAPI required to change the settings by the - * Controller - * @return - * Returns the device handler in case, can't be nullptr - */ - //[[nodiscard]] virtual hid_device* getChipHandler() const = 0; - - /*! \brief - * Requests from chip the level of brightness - * @return - * Returns the level of brightness [0; 100] - */ - // virtual uint8_t getBrightness() = 0; - - /*! \brief - * Requests RGB values from the chip - * @return - * Returns the struct with .R, .G, .B fields + /*! + * \brief Gets the stored engine + * \return Returns a ref to the engine */ - // virtual Color getRGB() = 0; + virtual VulkanEngine& get_engine() = 0; using Ptr = std::shared_ptr; }; diff --git a/src/include/interfaces/IView.h b/src/include/interfaces/IView.h index acde9d61..0b7fb751 100644 --- a/src/include/interfaces/IView.h +++ b/src/include/interfaces/IView.h @@ -10,17 +10,7 @@ class IView { virtual ~IView() = default; virtual void run() const = 0; + virtual void process_event(const SDL_Event& e) = 0; - /*! \brief - * Prints in console current settings - * (level of brightness and RGB) - */ - // virtual void showCurrentSettings() const = 0; - - /*! \brief - * Runs user interface that helps user interact with backlight - */ - // virtual void runMenu() const = 0; - - using Ptr = std::unique_ptr; + using Ptr = std::shared_ptr; }; diff --git a/src/include/graphics/vulkan/vk_loader.h b/src/include/scene/Mesh.h similarity index 54% rename from src/include/graphics/vulkan/vk_loader.h rename to src/include/scene/Mesh.h index 6df2ba91..c8c31559 100644 --- a/src/include/graphics/vulkan/vk_loader.h +++ b/src/include/scene/Mesh.h @@ -1,22 +1,16 @@ -#pragma once +#pragma once -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include "vk_descriptors.h" -#include "vk_types.h" +#include "graphics/vulkan/vk_descriptors.h" +#include "graphics/vulkan/vk_types.h" class VulkanEngine; -struct DrawContext; +namespace Mesh { +using rid_t = int64_t; + +namespace GLTF { struct GLTFMaterial { MaterialInstance data; }; @@ -34,12 +28,7 @@ struct MeshAsset { GPUMeshBuffers meshBuffers; }; -std::optional>> loadGltfMeshes( - VulkanEngine* engine, const std::filesystem::path& filePath); - -struct LoadedGLTF final : public IRenderable { - LoadedGLTF() = default; - +struct LoadedGLTF { // storage for all the data on a given glTF file std::unordered_map> meshes; std::unordered_map> nodes; @@ -57,16 +46,6 @@ struct LoadedGLTF final : public IRenderable { AllocatedBuffer materialDataBuffer; VulkanEngine* creator; - - ~LoadedGLTF() { - clearAll(); - }; - - void Draw(const glm::mat4& topMatrix, DrawContext& ctx); - -private: - void clearAll(); }; - -std::optional> loadGltf(VulkanEngine* engine, - std::string_view filePath); +} // namespace GLTF +}; // namespace Mesh \ No newline at end of file diff --git a/vcpkg b/vcpkg index 9b75e789..b1b19307 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 9b75e789ece3f942159b8500584e35aafe3979ff +Subproject commit b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01 diff --git a/vcpkg.json b/vcpkg.json index e932e3d7..49225b84 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -29,6 +29,10 @@ { "name": "fastgltf", "version": "0.7.1" + }, + { + "name": "fmt", + "version": "11.2.0" } ] } \ No newline at end of file