fix: alot of stuff changed. Mostly bugfixxes / architechture changes
This commit is contained in:
+34
-2
@@ -2,6 +2,24 @@ cmake_minimum_required(VERSION 3.31)
|
||||
project(Destrum)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
include(CTest)
|
||||
|
||||
option(DESTRUM_ENABLE_WARNINGS "Enable warnings for first-party targets" ON)
|
||||
|
||||
function(destrum_enable_warnings target)
|
||||
if (NOT DESTRUM_ENABLE_WARNINGS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
target_compile_options(${target} PRIVATE /W4 /permissive-)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if (MSVC)
|
||||
# Disable Just My Code debugging
|
||||
@@ -12,8 +30,18 @@ add_subdirectory(destrum)
|
||||
add_subdirectory(lightkeeper)
|
||||
add_subdirectory(TheChef)
|
||||
|
||||
add_custom_target(CleanupAssets)
|
||||
add_dependencies(CleanupAssets
|
||||
if (TARGET _internal_validate_engine_shaders)
|
||||
add_custom_target(ValidateShaders)
|
||||
add_dependencies(ValidateShaders _internal_validate_engine_shaders)
|
||||
endif()
|
||||
|
||||
destrum_enable_warnings(destrum)
|
||||
destrum_enable_warnings(lightkeeper)
|
||||
|
||||
add_custom_target(CleanupAssets
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "$<TARGET_FILE_DIR:lightkeeper>/assets/game"
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "$<TARGET_FILE_DIR:lightkeeper>/assets/engine"
|
||||
DEPENDS
|
||||
_internal_clean_game_assets
|
||||
_internal_clean_engine_assets
|
||||
)
|
||||
@@ -40,3 +68,7 @@ if (ENABLE_SANITIZERS AND CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -77,15 +77,25 @@ cmake -S . -B build
|
||||
Build everything:
|
||||
|
||||
```bash
|
||||
cmake --build build
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
Run the first-party regression tests:
|
||||
|
||||
```bash
|
||||
ctest --test-dir build -C Release --output-on-failure
|
||||
```
|
||||
|
||||
Run the demo executable:
|
||||
|
||||
```bash
|
||||
./build/lightkeeper/lightkeeper
|
||||
./build/lightkeeper/Release/lightkeeper.exe
|
||||
```
|
||||
|
||||
For single-configuration generators, use the executable path produced by that
|
||||
generator instead. Runtime assets are placed beside the executable in both
|
||||
layouts.
|
||||
|
||||
## Asset cooking
|
||||
|
||||
Assets are cooked automatically as part of the build through the `TheChef` tool.
|
||||
@@ -95,9 +105,17 @@ You can also run the top-level custom targets manually:
|
||||
```bash
|
||||
cmake --build build --target CookAssets
|
||||
cmake --build build --target CleanupAssets
|
||||
cmake --build build --target ValidateShaders
|
||||
```
|
||||
|
||||
`CookAssets` processes both engine and game assets. `CleanupAssets` removes generated runtime asset output.
|
||||
`ValidateShaders` runs `spirv-val` over the cooked engine shaders when the Vulkan SDK provides it.
|
||||
|
||||
Install the application, engine library, public headers, and runtime assets:
|
||||
|
||||
```bash
|
||||
cmake --install build --config Release
|
||||
```
|
||||
|
||||
## Demo app
|
||||
|
||||
|
||||
+62
-7
@@ -31,6 +31,11 @@ set(SRC_FILES
|
||||
"src/Graphics/Util.cpp"
|
||||
"src/Graphics/RenderResources.cpp"
|
||||
|
||||
"src/Graphics/Managers/VulkanInstanceManager.cpp"
|
||||
"src/Graphics/Managers/MemoryManager.cpp"
|
||||
"src/Graphics/Managers/FrameManager.cpp"
|
||||
"src/Graphics/Managers/ImageManager.cpp"
|
||||
|
||||
"src/Graphics/Resources/GPUImage.cpp"
|
||||
"src/Graphics/Resources/NBuffer.cpp"
|
||||
"src/Graphics/Resources/Cubemap.cpp"
|
||||
@@ -139,30 +144,80 @@ set(ASSETS_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_runtime")
|
||||
# VERBATIM
|
||||
#)
|
||||
|
||||
file(GLOB_RECURSE ENGINE_ASSET_SOURCES CONFIGURE_DEPENDS
|
||||
"${ASSETS_SRC_DIR}/*")
|
||||
|
||||
add_custom_target(_internal_clean_engine_assets
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
--clean
|
||||
DEPENDS TheChef
|
||||
)
|
||||
|
||||
add_custom_target(_internal_cook_engine_assets
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
--clean
|
||||
)
|
||||
|
||||
add_custom_target(_internal_cook_engine_assets ALL
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
DEPENDS TheChef
|
||||
DEPENDS TheChef ${ENGINE_ASSET_SOURCES}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
find_program(SPIRV_VAL_EXECUTABLE NAMES spirv-val spirv-val.exe)
|
||||
if (SPIRV_VAL_EXECUTABLE)
|
||||
file(GLOB_RECURSE ENGINE_SHADER_SOURCES CONFIGURE_DEPENDS
|
||||
"${ASSETS_SRC_DIR}/*.vert"
|
||||
"${ASSETS_SRC_DIR}/*.frag"
|
||||
"${ASSETS_SRC_DIR}/*.comp")
|
||||
|
||||
set(VALIDATE_SHADER_COMMANDS)
|
||||
foreach(shader_source IN LISTS ENGINE_SHADER_SOURCES)
|
||||
file(RELATIVE_PATH shader_relative "${ASSETS_SRC_DIR}" "${shader_source}")
|
||||
list(APPEND VALIDATE_SHADER_COMMANDS
|
||||
COMMAND "${SPIRV_VAL_EXECUTABLE}"
|
||||
"${ASSETS_RUNTIME_DIR}/${shader_relative}.spv")
|
||||
endforeach()
|
||||
|
||||
add_custom_target(_internal_validate_engine_shaders
|
||||
DEPENDS _internal_cook_engine_assets
|
||||
${VALIDATE_SHADER_COMMANDS}
|
||||
)
|
||||
endif()
|
||||
|
||||
function(destrum_cook_engine_assets GAME_TARGET GAME_OUTPUT_DIR)
|
||||
# This resolves to destrum's own directory at function DEFINITION time
|
||||
set(_engine_src "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/assets_src")
|
||||
set(_engine_runtime "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/assets_runtime")
|
||||
set(_output_engine "${GAME_OUTPUT_DIR}/assets/engine")
|
||||
set(_output_engine "$<TARGET_FILE_DIR:${GAME_TARGET}>/assets/engine")
|
||||
|
||||
if (WIN32)
|
||||
set(_engine_asset_install_commands
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${_engine_runtime}" "${_output_engine}")
|
||||
else()
|
||||
set(_engine_asset_install_commands
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
"${_engine_runtime}" "${_output_engine}")
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET ${GAME_TARGET} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${GAME_OUTPUT_DIR}/assets"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${_output_engine}/.."
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${_output_engine}"
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink "${_engine_runtime}" "${_output_engine}"
|
||||
${_engine_asset_install_commands}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_dependencies(${GAME_TARGET} _internal_cook_engine_assets)
|
||||
|
||||
install(DIRECTORY "${_engine_runtime}/" DESTINATION "bin/assets/engine")
|
||||
endfunction()
|
||||
|
||||
install(TARGETS destrum
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/include/" DESTINATION include)
|
||||
|
||||
@@ -8,7 +8,8 @@ layout (set = 0, binding = 1) uniform sampler samplers[];
|
||||
|
||||
#define NEAREST_SAMPLER_ID 0
|
||||
#define LINEAR_SAMPLER_ID 1
|
||||
#define SHADOW_SAMPLER_ID 2
|
||||
#define ANISOTROPIC_SAMPLER_ID 2
|
||||
#define SHADOW_SAMPLER_ID 3
|
||||
|
||||
vec4 sampleTexture2DNearest(uint texID, vec2 uv) {
|
||||
return texture(nonuniformEXT(sampler2D(textures[texID], samplers[NEAREST_SAMPLER_ID])), uv);
|
||||
|
||||
@@ -27,7 +27,7 @@ layout (push_constant) uniform constants
|
||||
uint numVertices;
|
||||
VertexBuffer inputBuffer;
|
||||
SkinningData skinningData;
|
||||
VertexBuffer outputBuffer;
|
||||
WritableVertexBuffer outputBuffer;
|
||||
} pcs;
|
||||
|
||||
layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
@@ -16,4 +16,8 @@ layout (buffer_reference, std430) readonly buffer VertexBuffer {
|
||||
Vertex vertices[];
|
||||
};
|
||||
|
||||
layout (buffer_reference, std430) buffer WritableVertexBuffer {
|
||||
Vertex vertices[];
|
||||
};
|
||||
|
||||
#endif // VERTEX_GLSL
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef APP_H
|
||||
#define APP_H
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "glm/vec2.hpp"
|
||||
@@ -25,6 +26,7 @@ public:
|
||||
};
|
||||
|
||||
App();
|
||||
virtual ~App();
|
||||
|
||||
void init(const AppParams& params);
|
||||
void run();
|
||||
@@ -33,10 +35,10 @@ public:
|
||||
virtual void customInit() = 0;
|
||||
virtual void customUpdate(float dt) = 0;
|
||||
virtual void customDraw() = 0;
|
||||
virtual void customCleanup() = 0;
|
||||
virtual void customCleanup() {}
|
||||
virtual void customFixedUpdate(float dt) = 0;
|
||||
|
||||
virtual void onWindowResize(int newWidth, int newHeight) {};
|
||||
virtual void onWindowResize(int, int) {};
|
||||
|
||||
protected:
|
||||
SDL_Window* window{nullptr};
|
||||
@@ -51,6 +53,9 @@ protected:
|
||||
Camera camera{glm::vec3(0.f, 0.f, -5.f), glm::vec3(0, 1, 0)};
|
||||
|
||||
bool isRunning{false};
|
||||
bool cleanedUp{false};
|
||||
bool cleaningUp{false};
|
||||
bool customInitStarted{false};
|
||||
bool gamePaused{false};
|
||||
|
||||
bool frameLimit{false};
|
||||
|
||||
@@ -18,11 +18,14 @@ class Animator final: public Component {
|
||||
public:
|
||||
explicit Animator(GameObject& parent);
|
||||
|
||||
void Update() override;
|
||||
using Component::Update;
|
||||
void Update(float dt) override;
|
||||
std::string GetTypeName() const override
|
||||
{
|
||||
return "Animator";
|
||||
}
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
void ImGuiInspector() override;
|
||||
|
||||
void addClip(std::shared_ptr<SkeletalAnimation> clip);
|
||||
|
||||
@@ -10,13 +10,18 @@ public:
|
||||
explicit MeshRendererComponent(GameObject &parent);
|
||||
|
||||
void Start() override;
|
||||
void Update() override;
|
||||
void Destroy() override;
|
||||
void Update(float dt) override;
|
||||
void Render(const RenderContext& ctx) override;
|
||||
|
||||
void SetMeshID(MeshID id) { meshID = id; }
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
void ResolveReferences(const ObjectMap& objects) override;
|
||||
|
||||
void SetMeshID(MeshID id);
|
||||
MeshID GetMeshID() const { return meshID; }
|
||||
|
||||
void SetMaterialID(MaterialID id) { materialID = id; }
|
||||
void SetMaterialID(MaterialID id);
|
||||
MaterialID GetMaterialID() const { return materialID; }
|
||||
std::string GetTypeName() const override
|
||||
{
|
||||
@@ -26,6 +31,8 @@ public:
|
||||
private:
|
||||
MeshID meshID{NULL_MESH_ID};
|
||||
MaterialID materialID{NULL_MATERIAL_ID};
|
||||
std::string meshKey;
|
||||
std::string materialKey;
|
||||
|
||||
std::unique_ptr<SkinnedMesh> m_skinnedMesh;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ class OrbitAndSpin final : public Component {
|
||||
public:
|
||||
OrbitAndSpin(
|
||||
GameObject& parent,
|
||||
float radius,
|
||||
float radius = 5.0f,
|
||||
glm::vec3 center = glm::vec3(0.0f)
|
||||
)
|
||||
: Component(parent, "OrbitAndSpin")
|
||||
@@ -20,9 +20,15 @@ public:
|
||||
// Call after constructing if you want deterministic randomness per object
|
||||
void Randomize(uint32_t seed);
|
||||
|
||||
void Update() override;
|
||||
using Component::Update;
|
||||
void Update(float dt) override;
|
||||
void Start() override;
|
||||
|
||||
std::string GetTypeName() const override { return "OrbitAndSpin"; }
|
||||
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
|
||||
// optional setters
|
||||
void SetRadius(float r) { m_Radius = r; }
|
||||
void SetCenter(glm::vec3 c) { m_Center = c; }
|
||||
@@ -53,6 +59,7 @@ private:
|
||||
float m_SpinSpeed = 2.0f; // rad/sec
|
||||
|
||||
MaterialID m_MaterialID{0};
|
||||
bool m_BaseScaleLoaded{false};
|
||||
};
|
||||
|
||||
#endif //ORBITANDSPIN_H
|
||||
|
||||
@@ -7,9 +7,12 @@ class BoxCollider final : public Collider {
|
||||
public:
|
||||
explicit BoxCollider(GameObject& owner, const glm::vec3& halfExtents = glm::vec3{0.5f})
|
||||
: Collider(owner)
|
||||
, m_HalfExtents(halfExtents) {}
|
||||
, m_HalfExtents(glm::max(halfExtents, glm::vec3{0.0001f})) {}
|
||||
|
||||
void Update() override {}
|
||||
void Update(float dt) override {}
|
||||
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
|
||||
std::string GetTypeName() const override
|
||||
{
|
||||
@@ -26,7 +29,8 @@ public:
|
||||
}
|
||||
|
||||
void SetHalfExtents(const glm::vec3& halfExtents) {
|
||||
m_HalfExtents = halfExtents;
|
||||
m_HalfExtents = glm::max(halfExtents, glm::vec3{0.0001f});
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -2,14 +2,37 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <destrum/Components/Collider.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
|
||||
class CapsuleCollider final : public Collider {
|
||||
public:
|
||||
explicit CapsuleCollider(GameObject& owner, float radius = 0.5f, float height = 2.0f)
|
||||
: Collider(owner)
|
||||
, m_Radius(radius)
|
||||
, m_Height(height) {}
|
||||
, m_Radius(std::max(radius, 0.0f))
|
||||
, m_Height(std::max(height, std::max(radius, 0.0f) * 2.0f)) {}
|
||||
|
||||
void Update(float dt) override {}
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "CapsuleCollider";
|
||||
}
|
||||
|
||||
nlohmann::json Serialize() const override {
|
||||
auto data = SerializeCollider();
|
||||
data["radius"] = m_Radius;
|
||||
data["height"] = m_Height;
|
||||
return data;
|
||||
}
|
||||
|
||||
void Deserialize(const nlohmann::json& data) override {
|
||||
DeserializeCollider(data);
|
||||
if (data.contains("radius")) {
|
||||
SetRadius(data.at("radius").get<float>());
|
||||
}
|
||||
if (data.contains("height")) {
|
||||
SetHeight(data.at("height").get<float>());
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
|
||||
return PhysicsShapeDesc::Capsule(m_Radius, m_Height, m_CenterOffset, m_IsTrigger);
|
||||
@@ -20,7 +43,9 @@ public:
|
||||
}
|
||||
|
||||
void SetRadius(float radius) {
|
||||
m_Radius = std::max(radius, 0.0f);
|
||||
m_Radius = std::max(radius, 0.0001f);
|
||||
m_Height = std::max(m_Height, m_Radius * 2.0f);
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] float GetHeight() const {
|
||||
@@ -29,6 +54,7 @@ public:
|
||||
|
||||
void SetHeight(float height) {
|
||||
m_Height = std::max(height, m_Radius * 2.0f);
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -15,12 +15,33 @@ public:
|
||||
[[nodiscard]] virtual PhysicsShapeDesc BuildPhysicsShape() const = 0;
|
||||
|
||||
[[nodiscard]] bool IsTrigger() const { return m_IsTrigger; }
|
||||
void SetTrigger(bool trigger) { m_IsTrigger = trigger; }
|
||||
void SetTrigger(bool trigger) { m_IsTrigger = trigger; NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] const glm::vec3& GetCenterOffset() const { return m_CenterOffset; }
|
||||
void SetCenterOffset(const glm::vec3& offset) { m_CenterOffset = offset; }
|
||||
void SetCenterOffset(const glm::vec3& offset) { m_CenterOffset = offset; NotifyPhysicsChanged(); }
|
||||
|
||||
protected:
|
||||
[[nodiscard]] nlohmann::json SerializeCollider() const {
|
||||
return {
|
||||
{"centerOffset", {m_CenterOffset.x, m_CenterOffset.y, m_CenterOffset.z}},
|
||||
{"isTrigger", m_IsTrigger}
|
||||
};
|
||||
}
|
||||
|
||||
void DeserializeCollider(const nlohmann::json& data) {
|
||||
if (data.contains("centerOffset")) {
|
||||
const auto& offset = data.at("centerOffset");
|
||||
m_CenterOffset = {
|
||||
offset.at(0).get<float>(),
|
||||
offset.at(1).get<float>(),
|
||||
offset.at(2).get<float>()
|
||||
};
|
||||
}
|
||||
if (data.contains("isTrigger")) {
|
||||
m_IsTrigger = data.at("isTrigger").get<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
bool m_IsTrigger{false};
|
||||
glm::vec3 m_CenterOffset{0.0f};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <destrum/ObjectModel/Component.h>
|
||||
@@ -12,13 +13,18 @@ public:
|
||||
explicit Rigidbody(GameObject& owner)
|
||||
: Component(owner) {}
|
||||
|
||||
~Rigidbody() override = default;
|
||||
~Rigidbody() override;
|
||||
|
||||
void Update() override {}
|
||||
void Update(float dt) override {}
|
||||
void Start() override;
|
||||
void Destroy() override;
|
||||
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
|
||||
std::string GetTypeName() const override
|
||||
{
|
||||
return "RigidBody";
|
||||
return "Rigidbody";
|
||||
}
|
||||
|
||||
void AttachPhysicsBody(PhysicsWorld* world, PhysicsBodyHandle body);
|
||||
@@ -34,22 +40,22 @@ public:
|
||||
[[nodiscard]] glm::vec3 GetLinearVelocity() const;
|
||||
|
||||
[[nodiscard]] RigidbodyType GetType() const { return m_Type; }
|
||||
void SetType(RigidbodyType type) { m_Type = type; }
|
||||
void SetType(RigidbodyType type) { m_Type = type; NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] float GetMass() const { return m_Mass; }
|
||||
void SetMass(float mass) { m_Mass = mass > 0.0f ? mass : 0.0001f; }
|
||||
void SetMass(float mass) { m_Mass = mass > 0.0f ? mass : 0.0001f; NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] float GetFriction() const { return m_Friction; }
|
||||
void SetFriction(float friction) { m_Friction = friction; }
|
||||
void SetFriction(float friction) { m_Friction = std::max(friction, 0.0f); NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] float GetRestitution() const { return m_Restitution; }
|
||||
void SetRestitution(float restitution) { m_Restitution = restitution; }
|
||||
void SetRestitution(float restitution) { m_Restitution = std::clamp(restitution, 0.0f, 1.0f); NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] bool UsesGravity() const { return m_UseGravity; }
|
||||
void SetUseGravity(bool useGravity) { m_UseGravity = useGravity; }
|
||||
void SetUseGravity(bool useGravity) { m_UseGravity = useGravity; NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] bool AllowsSleep() const { return m_AllowSleep; }
|
||||
void SetAllowSleep(bool allowSleep) { m_AllowSleep = allowSleep; }
|
||||
void SetAllowSleep(bool allowSleep) { m_AllowSleep = allowSleep; NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] PhysicsWorld* GetPhysicsWorld() const { return m_World; }
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ class SphereCollider final : public Collider {
|
||||
public:
|
||||
explicit SphereCollider(GameObject& owner, float radius = 0.5f)
|
||||
: Collider(owner)
|
||||
, m_Radius(radius) {}
|
||||
, m_Radius(std::max(radius, 0.0001f)) {}
|
||||
|
||||
void Update() override {}
|
||||
void Update(float dt) override {}
|
||||
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
std::string GetTypeName() const override
|
||||
{
|
||||
return "SphereCollider";
|
||||
@@ -25,7 +28,8 @@ public:
|
||||
}
|
||||
|
||||
void SetRadius(float radius) {
|
||||
m_Radius = std::max(radius, 0.0f);
|
||||
m_Radius = std::max(radius, 0.0001f);
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -8,14 +8,20 @@
|
||||
|
||||
class Rotator final : public Component {
|
||||
public:
|
||||
explicit Rotator(GameObject& parent, float distance, float speed);
|
||||
explicit Rotator(GameObject& parent, float distance = 0.0f, float speed = 0.0f);
|
||||
|
||||
Rotator(const Rotator& other) = delete;
|
||||
Rotator(Rotator&& other) noexcept = delete;
|
||||
Rotator& operator=(const Rotator& other) = delete;
|
||||
Rotator& operator=(Rotator&& other) noexcept = delete;
|
||||
|
||||
void Update() override;
|
||||
using Component::Update;
|
||||
void Update(float dt) override;
|
||||
|
||||
std::string GetTypeName() const override { return "Rotator"; }
|
||||
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
|
||||
void SetDistance(float distance);
|
||||
void SetSpeed(float speed) { m_Speed = speed; }
|
||||
|
||||
@@ -6,14 +6,26 @@
|
||||
|
||||
class Spinner final : public Component {
|
||||
public:
|
||||
explicit Spinner(GameObject& parent, glm::vec3 axis, float speedRadPerSec)
|
||||
explicit Spinner(GameObject& parent,
|
||||
glm::vec3 axis = glm::vec3{0.0f, 1.0f, 0.0f},
|
||||
float speedRadPerSec = 1.0f)
|
||||
: Component(parent, "Spinner")
|
||||
, m_Axis(glm::normalize(axis))
|
||||
, m_Axis(glm::dot(axis, axis) > 0.000001f ? glm::normalize(axis) : glm::vec3{0.0f, 1.0f, 0.0f})
|
||||
, m_Speed(speedRadPerSec) {}
|
||||
|
||||
void Update() override;
|
||||
using Component::Update;
|
||||
void Update(float dt) override;
|
||||
|
||||
void SetAxis(const glm::vec3& axis) { m_Axis = glm::normalize(axis); }
|
||||
std::string GetTypeName() const override { return "Spinner"; }
|
||||
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
|
||||
void SetAxis(const glm::vec3& axis) {
|
||||
m_Axis = glm::dot(axis, axis) > 0.000001f
|
||||
? glm::normalize(axis)
|
||||
: glm::vec3{0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
void SetSpeed(float speedRadPerSec) { m_Speed = speedRadPerSec; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#ifndef EVENT_H
|
||||
#define EVENT_H
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
class EventListener;
|
||||
|
||||
@@ -41,7 +43,11 @@ private:
|
||||
|
||||
template <typename... EventArgs>
|
||||
class Event final: public BaseEvent {
|
||||
using EventFunction = std::pair<void*, std::function<void(EventArgs...)>>;
|
||||
struct EventFunction {
|
||||
void* listener{nullptr};
|
||||
std::uint64_t id{0};
|
||||
std::function<void(EventArgs...)> callback;
|
||||
};
|
||||
|
||||
public:
|
||||
Event() = default;
|
||||
@@ -65,28 +71,45 @@ public:
|
||||
listener->AddEvent(this);
|
||||
m_EventListeners.insert(listener);
|
||||
|
||||
m_FunctionBinds.emplace_back(
|
||||
listener, [object, memberFunction] (EventArgs... args) { (object->*memberFunction)(args...); });
|
||||
const auto id = m_NextBindingId++;
|
||||
m_ActiveBindings.insert(id);
|
||||
m_FunctionBinds.push_back({
|
||||
listener,
|
||||
id,
|
||||
[object, memberFunction] (EventArgs... args) { (object->*memberFunction)(args...); }
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void AddListener(Function function) {
|
||||
m_FunctionBinds.emplace_back(nullptr, [function] (EventArgs... args) { function(args...); });
|
||||
const auto id = m_NextBindingId++;
|
||||
m_ActiveBindings.insert(id);
|
||||
m_FunctionBinds.push_back({
|
||||
nullptr,
|
||||
id,
|
||||
[function] (EventArgs... args) { function(args...); }
|
||||
});
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void Invoke(Args&&... args) {
|
||||
m_Invoking = true;
|
||||
for (auto&& listenerFunction: m_FunctionBinds)
|
||||
listenerFunction.second(args...);
|
||||
m_Invoking = false;
|
||||
// Callbacks are allowed to unregister themselves or register another
|
||||
// callback. Iterate over a snapshot so those mutations cannot
|
||||
// invalidate this invocation.
|
||||
const auto listeners = m_FunctionBinds;
|
||||
for (const auto& listenerFunction: listeners) {
|
||||
if (m_ActiveBindings.contains(listenerFunction.id)) {
|
||||
listenerFunction.callback(args...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveListener(EventListener* listener) override {
|
||||
m_EventListeners.erase(listener);
|
||||
|
||||
for (auto it = m_FunctionBinds.begin(); it != m_FunctionBinds.end();) {
|
||||
if (it->first == static_cast<void*>(listener)) {
|
||||
if (it->listener == static_cast<void*>(listener)) {
|
||||
m_ActiveBindings.erase(it->id);
|
||||
it = m_FunctionBinds.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
@@ -95,9 +118,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_Invoking{false};
|
||||
std::vector<EventFunction> m_FunctionBinds{};
|
||||
std::unordered_set<EventListener*> m_EventListeners{};
|
||||
std::unordered_set<std::uint64_t> m_ActiveBindings{};
|
||||
std::uint64_t m_NextBindingId{1};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
#define ASSETFS_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <destrum/Singleton.h>
|
||||
@@ -20,6 +23,7 @@ struct FSMount {
|
||||
class AssetFS final: public Singleton<AssetFS> {
|
||||
public:
|
||||
void Init(std::filesystem::path exeDir);
|
||||
void Reset();
|
||||
void Mount(std::string scheme, std::filesystem::path root);
|
||||
|
||||
std::vector<uint8_t> ReadBytes(std::string_view vpath);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#ifndef MANIFEST_H
|
||||
#define MANIFEST_H
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
struct ManifestAsset {
|
||||
|
||||
@@ -9,7 +9,7 @@ struct GPUImage;
|
||||
|
||||
class BindlessSetManager {
|
||||
public:
|
||||
void init(VkDevice device, float maxAnisotropy);
|
||||
void init(VkDevice device, VkPhysicalDevice physicalDevice, float maxAnisotropy);
|
||||
void cleanup(VkDevice device);
|
||||
|
||||
VkDescriptorSetLayout getDescSetLayout() const { return descSetLayout; }
|
||||
@@ -18,16 +18,21 @@ public:
|
||||
void addImage(VkDevice device, std::uint32_t id, const VkImageView imageView);
|
||||
void addSampler(VkDevice device, std::uint32_t id, VkSampler sampler);
|
||||
|
||||
[[nodiscard]] std::uint32_t getMaxImageCount() const { return maxBindlessResources; }
|
||||
|
||||
private:
|
||||
void initDefaultSamplers(VkDevice device, float maxAnisotropy);
|
||||
|
||||
VkDescriptorPool descPool;
|
||||
VkDescriptorSetLayout descSetLayout;
|
||||
VkDescriptorSet descSet;
|
||||
VkDescriptorPool descPool{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout descSetLayout{VK_NULL_HANDLE};
|
||||
VkDescriptorSet descSet{VK_NULL_HANDLE};
|
||||
|
||||
VkSampler nearestSampler;
|
||||
VkSampler linearSampler;
|
||||
VkSampler shadowMapSampler;
|
||||
std::uint32_t maxBindlessResources{0};
|
||||
|
||||
VkSampler nearestSampler{VK_NULL_HANDLE};
|
||||
VkSampler linearSampler{VK_NULL_HANDLE};
|
||||
VkSampler anisotropicSampler{VK_NULL_HANDLE};
|
||||
VkSampler shadowMapSampler{VK_NULL_HANDLE};
|
||||
};
|
||||
|
||||
#endif //BINDLESSSETMANAGER_H
|
||||
|
||||
@@ -30,6 +30,9 @@ public:
|
||||
[[nodiscard]] const GPUImage& getImage(ImageID id) const;
|
||||
|
||||
[[nodiscard]] ImageID getFreeImageId() const;
|
||||
[[nodiscard]] std::uint32_t getMaxImageCount() const {
|
||||
return bindlessSetManager.getMaxImageCount();
|
||||
}
|
||||
|
||||
void destroyImages();
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define MATERIALCACHE_H
|
||||
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include <destrum/Graphics/ids.h>
|
||||
#include <destrum/Graphics/Material.h>
|
||||
@@ -28,6 +30,8 @@ public:
|
||||
MaterialID addMaterial(Material material);
|
||||
MaterialID addSimpleTextureMaterial(ImageID textureID);
|
||||
[[nodiscard]] const Material& getMaterial(MaterialID id) const;
|
||||
[[nodiscard]] const std::string& getMaterialKey(MaterialID id) const;
|
||||
[[nodiscard]] std::optional<MaterialID> findMaterialByKey(std::string_view key) const;
|
||||
|
||||
[[nodiscard]] MaterialID getFreeMaterialId() const;
|
||||
[[nodiscard]] MaterialID getPlaceholderMaterialId() const;
|
||||
@@ -41,11 +45,13 @@ public:
|
||||
|
||||
private:
|
||||
std::vector<Material> materials;
|
||||
std::vector<std::string> materialKeys;
|
||||
|
||||
static constexpr auto MAX_MATERIALS = 1000;
|
||||
GPUBuffer materialDataBuffer;
|
||||
|
||||
MaterialDefaultTextures defaultTextures;
|
||||
GfxDevice* device{nullptr};
|
||||
|
||||
// material which is used for meshes without materials
|
||||
MaterialID placeholderMaterialId{NULL_MATERIAL_ID};
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#include <destrum/Graphics/Resources/Mesh.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "../ids.h"
|
||||
|
||||
class GfxDevice;
|
||||
@@ -14,12 +17,15 @@ public:
|
||||
MeshID addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh);
|
||||
const GPUMesh& getMesh(MeshID id) const;
|
||||
const CPUMesh& getCPUMesh(MeshID id) const;
|
||||
[[nodiscard]] const std::string& getMeshKey(MeshID id) const;
|
||||
[[nodiscard]] std::optional<MeshID> findMeshByKey(std::string_view key) const;
|
||||
|
||||
private:
|
||||
void uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) const;
|
||||
|
||||
std::vector<GPUMesh> meshes;
|
||||
std::vector<CPUMesh> cpuMeshes;
|
||||
std::vector<std::string> meshKeys;
|
||||
};
|
||||
|
||||
#endif //MESHCACHE_H
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
#define GPUIMAGE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
#include <glm/glm.hpp>
|
||||
@@ -10,20 +13,70 @@
|
||||
#include <destrum/Graphics/ids.h>
|
||||
|
||||
struct GPUImage {
|
||||
VkImage image;
|
||||
VkImageView imageView;
|
||||
VmaAllocation allocation;
|
||||
VkFormat format;
|
||||
VkImageUsageFlags usage;
|
||||
VkExtent3D extent;
|
||||
GPUImage() = default;
|
||||
~GPUImage() = default;
|
||||
GPUImage(const GPUImage&) = delete;
|
||||
GPUImage& operator=(const GPUImage&) = delete;
|
||||
|
||||
GPUImage(GPUImage&& other) noexcept {
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
GPUImage& operator=(GPUImage&& other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
image = other.image;
|
||||
imageView = other.imageView;
|
||||
allocation = other.allocation;
|
||||
format = other.format;
|
||||
usage = other.usage;
|
||||
extent = other.extent;
|
||||
layout = other.layout;
|
||||
mipLevels = other.mipLevels;
|
||||
numLayers = other.numLayers;
|
||||
layerLayouts = std::move(other.layerLayouts);
|
||||
isCubemap = other.isCubemap;
|
||||
debugName = std::move(other.debugName);
|
||||
id = other.id;
|
||||
|
||||
other.image = VK_NULL_HANDLE;
|
||||
other.imageView = VK_NULL_HANDLE;
|
||||
other.allocation = VK_NULL_HANDLE;
|
||||
other.id = NULL_BINDLESS_ID;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VkImage image{VK_NULL_HANDLE};
|
||||
VkImageView imageView{VK_NULL_HANDLE};
|
||||
VmaAllocation allocation{VK_NULL_HANDLE};
|
||||
VkFormat format{VK_FORMAT_UNDEFINED};
|
||||
VkImageUsageFlags usage{0};
|
||||
VkExtent3D extent{};
|
||||
mutable VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
std::uint32_t mipLevels{1};
|
||||
std::uint32_t numLayers{1};
|
||||
mutable std::vector<VkImageLayout> layerLayouts;
|
||||
bool isCubemap{false};
|
||||
std::string debugName{};
|
||||
|
||||
[[nodiscard]] glm::ivec2 getSize2D() const { return glm::ivec2{extent.width, extent.height}; }
|
||||
[[nodiscard]] VkExtent2D getExtent2D() const { return VkExtent2D{extent.width, extent.height}; }
|
||||
|
||||
[[nodiscard]] VkImageLayout getLayout(std::uint32_t layer = 0) const
|
||||
{
|
||||
return layerLayouts.empty() ? layout : layerLayouts.at(layer);
|
||||
}
|
||||
|
||||
void setLayout(VkImageLayout newLayout, std::uint32_t layer = 0) const
|
||||
{
|
||||
layout = newLayout;
|
||||
if (!layerLayouts.empty()) {
|
||||
layerLayouts.at(layer) = newLayout;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] BindlessID getBindlessId() const
|
||||
{
|
||||
assert(id != NULL_BINDLESS_ID && "Image wasn't added to bindless set");
|
||||
@@ -31,10 +84,10 @@ struct GPUImage {
|
||||
}
|
||||
|
||||
// should be called by ImageCache only
|
||||
void setBindlessId(const std::uint32_t id)
|
||||
void setBindlessId(const std::uint32_t bindlessId)
|
||||
{
|
||||
assert(id != NULL_BINDLESS_ID);
|
||||
this->id = id;
|
||||
assert(bindlessId != NULL_BINDLESS_ID);
|
||||
id = bindlessId;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isInitialized() const { return id != NULL_BINDLESS_ID; }
|
||||
|
||||
@@ -10,11 +10,8 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <VkBootstrap.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <volk.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
@@ -27,6 +24,10 @@
|
||||
|
||||
#include "ImmediateExecuter.h"
|
||||
#include "Util.h"
|
||||
#include "Managers/FrameManager.h"
|
||||
#include "Managers/VulkanInstanceManager.h"
|
||||
#include "Managers/MemoryManager.h"
|
||||
#include "Managers/ImageManager.h"
|
||||
|
||||
class ImguiPass;
|
||||
|
||||
@@ -35,7 +36,6 @@ namespace tracy
|
||||
{
|
||||
class VkCtx;
|
||||
}
|
||||
|
||||
using DestrumTracyVkCtx = tracy::VkCtx*;
|
||||
#else
|
||||
using DestrumTracyVkCtx = void*;
|
||||
@@ -46,22 +46,16 @@ using ImmediateExecuteFunction = std::function<void(VkCommandBuffer)>;
|
||||
class GfxDevice
|
||||
{
|
||||
public:
|
||||
struct FrameData
|
||||
{
|
||||
VkCommandPool commandPool{VK_NULL_HANDLE};
|
||||
VkCommandBuffer commandBuffer{VK_NULL_HANDLE};
|
||||
};
|
||||
|
||||
struct EndFrameProps
|
||||
{
|
||||
VkClearColorValue clearColor{{0.f, 0.f, 0.f, 1.f}};
|
||||
glm::ivec4 drawImageBlitRect{}; // where to blit draw image to
|
||||
glm::ivec4 drawImageBlitRect{};
|
||||
ImguiPass* imguiPass{nullptr};
|
||||
};
|
||||
|
||||
public:
|
||||
GfxDevice();
|
||||
~GfxDevice() = default;
|
||||
~GfxDevice();
|
||||
|
||||
GfxDevice(const GfxDevice&) = delete;
|
||||
GfxDevice& operator=(const GfxDevice&) = delete;
|
||||
@@ -85,23 +79,25 @@ public:
|
||||
return swapchain.isDirty();
|
||||
}
|
||||
|
||||
VulkanImmediateExecutor& GetImmediateExecuter();
|
||||
VulkanImmediateExecutor& getImmediateExecuter();
|
||||
[[deprecated("Use getImmediateExecuter()")]]
|
||||
VulkanImmediateExecutor& GetImmediateExecuter() { return getImmediateExecuter(); }
|
||||
|
||||
void immediateSubmit(ImmediateExecuteFunction&& f) const;
|
||||
|
||||
[[nodiscard]] std::uint32_t getCurrentFrameIndex() const
|
||||
{
|
||||
return frameNumber % FRAMES_IN_FLIGHT;
|
||||
return frameManager.getCurrentFrameIndex();
|
||||
}
|
||||
|
||||
[[nodiscard]] FrameData& getCurrentFrame()
|
||||
[[nodiscard]] FrameManager::FrameData& getCurrentFrame()
|
||||
{
|
||||
return frames[getCurrentFrameIndex()];
|
||||
return frameManager.getCurrentFrame();
|
||||
}
|
||||
|
||||
[[nodiscard]] const FrameData& getCurrentFrame() const
|
||||
[[nodiscard]] const FrameManager::FrameData& getCurrentFrame() const
|
||||
{
|
||||
return frames[getCurrentFrameIndex()];
|
||||
return frameManager.getCurrentFrame();
|
||||
}
|
||||
|
||||
[[nodiscard]] VkExtent2D getSwapchainExtent() const
|
||||
@@ -112,68 +108,94 @@ public:
|
||||
[[nodiscard]] GPUBuffer createBuffer(
|
||||
std::size_t allocSize,
|
||||
VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO) const;
|
||||
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE) const
|
||||
{
|
||||
return memoryManager.createBuffer(allocSize, usage, memoryUsage);
|
||||
}
|
||||
|
||||
void destroyBuffer(const GPUBuffer& buffer) const;
|
||||
void destroyBuffer(GPUBuffer& buffer) const
|
||||
{
|
||||
memoryManager.destroyBuffer(buffer);
|
||||
}
|
||||
|
||||
[[nodiscard]] VkDeviceAddress getBufferAddress(const GPUBuffer& buffer) const;
|
||||
[[nodiscard]] VkDeviceAddress getBufferAddress(const GPUBuffer& buffer) const
|
||||
{
|
||||
return memoryManager.getBufferAddress(buffer);
|
||||
}
|
||||
|
||||
[[nodiscard]] GPUImage createImageRaw(
|
||||
const vkutil::CreateImageInfo& createInfo,
|
||||
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo = std::nullopt) const;
|
||||
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo = std::nullopt) const
|
||||
{
|
||||
return imageManager.createImage(createInfo, customAllocationCreateInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<GPUImage> loadImageFromFileRaw(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent) const;
|
||||
TextureIntent intent) const
|
||||
{
|
||||
return imageManager.loadImageFromFile(path, usage, mipMap, intent);
|
||||
}
|
||||
|
||||
void uploadImageDataSized(
|
||||
const GPUImage& image,
|
||||
const void* pixelData,
|
||||
std::size_t byteSize,
|
||||
std::uint32_t layer) const;
|
||||
|
||||
void destroyImage(const GPUImage& image) const;
|
||||
|
||||
[[nodiscard]] vkb::Device getDevice() const
|
||||
std::uint32_t layer) const
|
||||
{
|
||||
return device;
|
||||
imageManager.uploadImageData(image, pixelData, byteSize, layer);
|
||||
}
|
||||
|
||||
[[nodiscard]] vkb::Device getVkbDevice() const
|
||||
void destroyImage(GPUImage& image) const
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkInstance getVkInstance() const
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkPhysicalDevice getVkPhysicalDevice() const
|
||||
{
|
||||
return physicalDevice;
|
||||
imageManager.destroyImage(image);
|
||||
}
|
||||
|
||||
[[nodiscard]] VkDevice getVkDevice() const
|
||||
{
|
||||
return device;
|
||||
return instanceManager.getDevice();
|
||||
}
|
||||
|
||||
[[nodiscard]] VkDevice getDevice() const
|
||||
{
|
||||
return getVkDevice();
|
||||
}
|
||||
|
||||
[[nodiscard]] vkb::Device getVkbDevice() const
|
||||
{
|
||||
return instanceManager.getVkbDevice();
|
||||
}
|
||||
|
||||
[[nodiscard]] VkInstance getVkInstance() const
|
||||
{
|
||||
return instanceManager.getInstance();
|
||||
}
|
||||
|
||||
[[nodiscard]] VkPhysicalDevice getVkPhysicalDevice() const
|
||||
{
|
||||
return instanceManager.getPhysicalDevice();
|
||||
}
|
||||
|
||||
[[nodiscard]] VmaAllocator getAllocator() const
|
||||
{
|
||||
return allocator;
|
||||
return memoryManager.getAllocator();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint32_t getGraphicsQueueFamily() const
|
||||
{
|
||||
return graphicsQueueFamily;
|
||||
return instanceManager.getGraphicsQueueFamily();
|
||||
}
|
||||
|
||||
[[nodiscard]] VkQueue getGraphicsQueue() const
|
||||
{
|
||||
return graphicsQueue;
|
||||
return instanceManager.getGraphicsQueue();
|
||||
}
|
||||
|
||||
[[nodiscard]] VkQueue getPresentQueue() const
|
||||
{
|
||||
return instanceManager.getPresentQueue();
|
||||
}
|
||||
|
||||
[[nodiscard]] VkFormat getSwapchainFormat() const
|
||||
@@ -193,30 +215,29 @@ public:
|
||||
|
||||
[[nodiscard]] DestrumTracyVkCtx getTracyVkCtx() const
|
||||
{
|
||||
return tracyVkCtx;
|
||||
return instanceManager.getTracyVkCtx();
|
||||
}
|
||||
|
||||
VulkanInstanceManager& getInstanceManager() { return instanceManager; }
|
||||
MemoryManager& getMemoryManager() { return memoryManager; }
|
||||
|
||||
private:
|
||||
vkb::Instance instance;
|
||||
vkb::PhysicalDevice physicalDevice;
|
||||
vkb::Device device;
|
||||
VmaAllocator allocator{VK_NULL_HANDLE};
|
||||
|
||||
DestrumTracyVkCtx tracyVkCtx{nullptr};
|
||||
|
||||
std::uint32_t graphicsQueueFamily{0};
|
||||
VkQueue graphicsQueue{VK_NULL_HANDLE};
|
||||
|
||||
VkSurfaceKHR surface{VK_NULL_HANDLE};
|
||||
VkFormat swapchainFormat{VK_FORMAT_UNDEFINED};
|
||||
Swapchain swapchain;
|
||||
|
||||
std::array<FrameData, FRAMES_IN_FLIGHT> frames{};
|
||||
std::uint32_t frameNumber{0};
|
||||
VulkanInstanceManager instanceManager;
|
||||
MemoryManager memoryManager;
|
||||
FrameManager frameManager;
|
||||
ImageManager imageManager;
|
||||
|
||||
VulkanImmediateExecutor executor;
|
||||
|
||||
Swapchain swapchain;
|
||||
VkFormat swapchainFormat{VK_FORMAT_UNDEFINED};
|
||||
|
||||
std::uint32_t activeFrameIndex{0};
|
||||
std::uint32_t activeSwapchainImageIndex{0};
|
||||
bool frameActive{false};
|
||||
|
||||
bool m_vSync{false};
|
||||
bool initialized{false};
|
||||
};
|
||||
|
||||
#endif // GFXDEVICE_H
|
||||
#endif
|
||||
|
||||
@@ -13,15 +13,17 @@ public:
|
||||
|
||||
void immediateSubmit(std::function<void(VkCommandBuffer cmd)>&& function) const;
|
||||
|
||||
[[nodiscard]] VkCommandBuffer getCommandBuffer() const { return immCommandBuffer; }
|
||||
|
||||
private:
|
||||
bool initialized{false};
|
||||
|
||||
VkDevice device;
|
||||
VkQueue graphicsQueue;
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkQueue graphicsQueue{VK_NULL_HANDLE};
|
||||
|
||||
VkCommandBuffer immCommandBuffer;
|
||||
VkCommandPool immCommandPool;
|
||||
VkFence immFence;
|
||||
VkCommandBuffer immCommandBuffer{VK_NULL_HANDLE};
|
||||
VkCommandPool immCommandPool{VK_NULL_HANDLE};
|
||||
VkFence immFence{VK_NULL_HANDLE};
|
||||
};
|
||||
|
||||
#endif //IMMEDIATEEXECUTER_H
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef FRAMEMANAGER_H
|
||||
#define FRAMEMANAGER_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
class FrameManager {
|
||||
public:
|
||||
struct FrameData {
|
||||
VkCommandPool commandPool{VK_NULL_HANDLE};
|
||||
VkCommandBuffer commandBuffer{VK_NULL_HANDLE};
|
||||
};
|
||||
|
||||
FrameManager() = default;
|
||||
~FrameManager();
|
||||
|
||||
FrameManager(const FrameManager&) = delete;
|
||||
FrameManager& operator=(const FrameManager&) = delete;
|
||||
FrameManager(FrameManager&&) = delete;
|
||||
FrameManager& operator=(FrameManager&&) = delete;
|
||||
|
||||
void init(VkDevice device, std::uint32_t queueFamily);
|
||||
void cleanup(VkDevice device);
|
||||
|
||||
[[nodiscard]] VkCommandBuffer beginFrame();
|
||||
void endFrame(VkCommandBuffer cmd);
|
||||
|
||||
void waitIdle() const;
|
||||
|
||||
[[nodiscard]] std::uint32_t getCurrentFrameIndex() const {
|
||||
return frameNumber % 2;
|
||||
}
|
||||
|
||||
[[nodiscard]] FrameData& getCurrentFrame() {
|
||||
return frames[getCurrentFrameIndex()];
|
||||
}
|
||||
|
||||
[[nodiscard]] const FrameData& getCurrentFrame() const {
|
||||
return frames[getCurrentFrameIndex()];
|
||||
}
|
||||
|
||||
[[nodiscard]] VkCommandBuffer getCurrentCommandBuffer() {
|
||||
return frames[getCurrentFrameIndex()].commandBuffer;
|
||||
}
|
||||
|
||||
void nextFrame();
|
||||
|
||||
private:
|
||||
static constexpr std::size_t FRAMES_IN_FLIGHT = 2;
|
||||
std::array<FrameData, FRAMES_IN_FLIGHT> frames{};
|
||||
std::uint32_t frameNumber{0};
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef IMAGEMANAGER_H
|
||||
#define IMAGEMANAGER_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <destrum/Graphics/GPUImage.h>
|
||||
#include <destrum/Graphics/TextureIntent.h>
|
||||
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
class VulkanImmediateExecutor;
|
||||
class MemoryManager;
|
||||
|
||||
class ImageManager {
|
||||
public:
|
||||
ImageManager() = default;
|
||||
~ImageManager();
|
||||
|
||||
ImageManager(const ImageManager&) = delete;
|
||||
ImageManager& operator=(const ImageManager&) = delete;
|
||||
ImageManager(ImageManager&&) = delete;
|
||||
ImageManager& operator=(ImageManager&&) = delete;
|
||||
|
||||
void init(
|
||||
VkDevice device,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
const MemoryManager& memoryManager,
|
||||
VulkanImmediateExecutor& executor);
|
||||
|
||||
[[nodiscard]] GPUImage createImage(
|
||||
const vkutil::CreateImageInfo& createInfo,
|
||||
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo = std::nullopt) const;
|
||||
|
||||
[[nodiscard]] std::optional<GPUImage> loadImageFromFile(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent) const;
|
||||
|
||||
void uploadImageData(
|
||||
const GPUImage& image,
|
||||
const void* pixelData,
|
||||
std::size_t byteSize,
|
||||
std::uint32_t layer = 0) const;
|
||||
|
||||
void destroyImage(GPUImage& image) const;
|
||||
|
||||
private:
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkPhysicalDevice physicalDevice{VK_NULL_HANDLE};
|
||||
const MemoryManager* memoryManager{nullptr};
|
||||
VulkanImmediateExecutor* executor{nullptr};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef MEMORYMANAGER_H
|
||||
#define MEMORYMANAGER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <destrum/Graphics/Resources/Buffer.h>
|
||||
|
||||
class VulkanInstanceManager;
|
||||
|
||||
class MemoryManager {
|
||||
public:
|
||||
MemoryManager() = default;
|
||||
~MemoryManager();
|
||||
|
||||
MemoryManager(const MemoryManager&) = delete;
|
||||
MemoryManager& operator=(const MemoryManager&) = delete;
|
||||
MemoryManager(MemoryManager&&) = delete;
|
||||
MemoryManager& operator=(MemoryManager&&) = delete;
|
||||
|
||||
void init(const VulkanInstanceManager& instanceManager);
|
||||
void cleanup(VkDevice device);
|
||||
|
||||
[[nodiscard]] VmaAllocator getAllocator() const { return allocator; }
|
||||
[[nodiscard]] VkDevice getDevice() const { return device; }
|
||||
|
||||
[[nodiscard]] GPUBuffer createBuffer(
|
||||
std::size_t allocSize,
|
||||
VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO) const;
|
||||
|
||||
void destroyBuffer(GPUBuffer& buffer) const;
|
||||
|
||||
void flushAllocation(
|
||||
const GPUBuffer& buffer,
|
||||
VkDeviceSize offset = 0,
|
||||
VkDeviceSize size = VK_WHOLE_SIZE) const;
|
||||
|
||||
[[nodiscard]] VkDeviceAddress getBufferAddress(const GPUBuffer& buffer) const;
|
||||
|
||||
private:
|
||||
VmaAllocator allocator{VK_NULL_HANDLE};
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,117 @@
|
||||
#ifndef VULKANINSTANCEMANAGER_H
|
||||
#define VULKANINSTANCEMANAGER_H
|
||||
|
||||
#include <SDL_video.h>
|
||||
|
||||
#include <VkBootstrap.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#if defined(TRACY_ENABLE)
|
||||
namespace tracy
|
||||
{
|
||||
class VkCtx;
|
||||
}
|
||||
using DestrumTracyVkCtx = tracy::VkCtx*;
|
||||
#else
|
||||
using DestrumTracyVkCtx = void*;
|
||||
#endif
|
||||
|
||||
class VulkanInstanceManager
|
||||
{
|
||||
public:
|
||||
struct DeviceFeatures
|
||||
{
|
||||
VkPhysicalDeviceFeatures device{
|
||||
.imageCubeArray = VK_TRUE,
|
||||
.geometryShader = VK_TRUE,
|
||||
.depthClamp = VK_TRUE,
|
||||
.fillModeNonSolid = VK_TRUE,
|
||||
.samplerAnisotropy = VK_TRUE,
|
||||
};
|
||||
|
||||
VkPhysicalDeviceVulkan12Features features12{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
|
||||
.descriptorIndexing = VK_TRUE,
|
||||
.shaderSampledImageArrayNonUniformIndexing = VK_TRUE,
|
||||
.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE,
|
||||
.descriptorBindingPartiallyBound = VK_TRUE,
|
||||
.runtimeDescriptorArray = VK_TRUE,
|
||||
.scalarBlockLayout = VK_TRUE,
|
||||
.bufferDeviceAddress = VK_TRUE,
|
||||
};
|
||||
|
||||
VkPhysicalDeviceVulkan13Features features13{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
|
||||
.synchronization2 = VK_TRUE,
|
||||
.dynamicRendering = VK_TRUE,
|
||||
};
|
||||
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT extendedDynamicState3{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
.extendedDynamicState3PolygonMode = VK_TRUE,
|
||||
};
|
||||
};
|
||||
|
||||
VulkanInstanceManager() = default;
|
||||
~VulkanInstanceManager();
|
||||
|
||||
VulkanInstanceManager(const VulkanInstanceManager&) = delete;
|
||||
VulkanInstanceManager& operator=(const VulkanInstanceManager&) = delete;
|
||||
VulkanInstanceManager(VulkanInstanceManager&&) = delete;
|
||||
VulkanInstanceManager& operator=(VulkanInstanceManager&&) = delete;
|
||||
|
||||
void init(
|
||||
SDL_Window* window,
|
||||
const std::string& appName,
|
||||
const DeviceFeatures& features = DeviceFeatures{});
|
||||
|
||||
void cleanup();
|
||||
|
||||
void initTracy(VkCommandBuffer tracyInitCmd);
|
||||
|
||||
void waitIdle() const;
|
||||
|
||||
[[nodiscard]] bool isValid() const { return initialized; }
|
||||
|
||||
[[nodiscard]] VkInstance getInstance() const { return instance; }
|
||||
[[nodiscard]] VkPhysicalDevice getPhysicalDevice() const { return physicalDevice; }
|
||||
[[nodiscard]] VkDevice getDevice() const { return device; }
|
||||
|
||||
[[nodiscard]] const vkb::Instance& getVkbInstance() const { return vkbInstance; }
|
||||
[[nodiscard]] const vkb::PhysicalDevice& getVkbPhysicalDevice() const { return vkbPhysicalDevice; }
|
||||
[[nodiscard]] const vkb::Device& getVkbDevice() const { return vkbDevice; }
|
||||
|
||||
[[nodiscard]] VkSurfaceKHR getSurface() const { return surface; }
|
||||
|
||||
[[nodiscard]] uint32_t getGraphicsQueueFamily() const { return graphicsQueueFamily; }
|
||||
[[nodiscard]] VkQueue getGraphicsQueue() const { return graphicsQueue; }
|
||||
[[nodiscard]] uint32_t getPresentQueueFamily() const { return presentQueueFamily; }
|
||||
[[nodiscard]] VkQueue getPresentQueue() const { return presentQueue; }
|
||||
|
||||
[[nodiscard]] DestrumTracyVkCtx getTracyVkCtx() const { return tracyVkCtx; }
|
||||
|
||||
private:
|
||||
vkb::Instance vkbInstance{};
|
||||
vkb::PhysicalDevice vkbPhysicalDevice{};
|
||||
vkb::Device vkbDevice{};
|
||||
|
||||
VkInstance instance{VK_NULL_HANDLE};
|
||||
VkPhysicalDevice physicalDevice{VK_NULL_HANDLE};
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
|
||||
VkSurfaceKHR surface{VK_NULL_HANDLE};
|
||||
|
||||
uint32_t graphicsQueueFamily{VK_QUEUE_FAMILY_IGNORED};
|
||||
VkQueue graphicsQueue{VK_NULL_HANDLE};
|
||||
uint32_t presentQueueFamily{VK_QUEUE_FAMILY_IGNORED};
|
||||
VkQueue presentQueue{VK_NULL_HANDLE};
|
||||
|
||||
DestrumTracyVkCtx tracyVkCtx{nullptr};
|
||||
|
||||
bool initialized{false};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -11,11 +11,11 @@
|
||||
|
||||
|
||||
struct MeshDrawCommand {
|
||||
MeshID meshId;
|
||||
glm::mat4 transformMatrix;
|
||||
MeshID meshId{NULL_MESH_ID};
|
||||
glm::mat4 transformMatrix{1.0f};
|
||||
|
||||
// for frustum culling
|
||||
Sphere worldBoundingSphere;
|
||||
Sphere worldBoundingSphere{};
|
||||
|
||||
// If set - mesh will be drawn with overrideMaterialId
|
||||
// instead of whatever material the mesh has
|
||||
@@ -25,7 +25,7 @@ struct MeshDrawCommand {
|
||||
|
||||
// skinned meshes only
|
||||
const SkinnedMesh* skinnedMesh{nullptr};
|
||||
std::uint32_t jointMatricesStartIndex;
|
||||
std::uint32_t jointMatricesStartIndex{0};
|
||||
};
|
||||
|
||||
#endif //MESHDRAWCOMMAND_H
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
[[nodiscard]] bool wantsKeyboard() const;
|
||||
|
||||
private:
|
||||
void initVulkanBackend();
|
||||
|
||||
void transitionToColorAttachment(
|
||||
VkCommandBuffer cmd,
|
||||
VkImage image,
|
||||
@@ -50,6 +52,9 @@ private:
|
||||
|
||||
bool initialized = false;
|
||||
bool frameBegun = false;
|
||||
bool contextCreated = false;
|
||||
bool sdlInitialized = false;
|
||||
bool vulkanInitialized = false;
|
||||
};
|
||||
|
||||
#endif // DESTRUM_IMGUI_PASS_H
|
||||
|
||||
@@ -27,12 +27,16 @@ public:
|
||||
const MeshDrawCommand& dc);
|
||||
|
||||
void beginDrawing(std::size_t frameIndex);
|
||||
void flushCurrentFrame(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
std::size_t frameIndex);
|
||||
std::size_t appendJointMatrices(
|
||||
std::span<const glm::mat4> jointMatrices,
|
||||
std::size_t frameIndex);
|
||||
|
||||
private:
|
||||
VkPipelineLayout m_pipelineLayout;
|
||||
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
||||
std::unique_ptr<ComputePipeline> skinningPipeline;
|
||||
struct PushConstants {
|
||||
VkDeviceAddress jointMatricesBuffer;
|
||||
|
||||
@@ -113,6 +113,7 @@ private:
|
||||
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
|
||||
|
||||
std::unique_ptr<SkinningPipeline> skinningPipeline;
|
||||
bool initialized{false};
|
||||
};
|
||||
|
||||
#endif //RENDERER_H
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
|
||||
struct GPUBuffer {
|
||||
VkBuffer buffer{VK_NULL_HANDLE};
|
||||
VmaAllocation allocation;
|
||||
VmaAllocationInfo info;
|
||||
VmaAllocation allocation{VK_NULL_HANDLE};
|
||||
VmaAllocationInfo info{};
|
||||
|
||||
VkDeviceAddress address{0};
|
||||
bool hostVisible{false};
|
||||
};
|
||||
|
||||
#endif //BUFFER_H
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <destrum/Graphics/Resources/Buffer.h>
|
||||
|
||||
class GfxDevice;
|
||||
class MemoryManager;
|
||||
|
||||
class NBuffer {
|
||||
public:
|
||||
@@ -33,6 +34,7 @@ private:
|
||||
std::size_t gpuBufferSize{0};
|
||||
std::vector<GPUBuffer> stagingBuffers;
|
||||
GPUBuffer gpuBuffer;
|
||||
const MemoryManager* memoryManager{nullptr};
|
||||
bool initialized{false};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,57 +3,93 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include "VkBootstrap.h"
|
||||
#include <VkBootstrap.h>
|
||||
|
||||
class GfxDevice;
|
||||
static constexpr int FRAMES_IN_FLIGHT = 2;
|
||||
|
||||
class Swapchain {
|
||||
public:
|
||||
struct AcquireResult {
|
||||
VkResult result{VK_SUCCESS};
|
||||
VkImage image{VK_NULL_HANDLE};
|
||||
std::uint32_t imageIndex{0};
|
||||
|
||||
[[nodiscard]] bool hasImage() const {
|
||||
return image != VK_NULL_HANDLE &&
|
||||
(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR);
|
||||
}
|
||||
};
|
||||
|
||||
void initSync(VkDevice device);
|
||||
//Ye ye, passing a pointer is bad bla bla @Kobe
|
||||
void createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync);
|
||||
void recreateSwapchain(const GfxDevice& gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync);
|
||||
void cleanup();
|
||||
void createSwapchain(
|
||||
VkDevice device,
|
||||
vkb::Device vkbDevice,
|
||||
VkSurfaceKHR surface,
|
||||
VkFormat format,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
bool vSync);
|
||||
|
||||
void recreateSwapchain(
|
||||
VkDevice device,
|
||||
vkb::Device vkbDevice,
|
||||
VkSurfaceKHR surface,
|
||||
VkFormat format,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
bool vSync);
|
||||
|
||||
void cleanup(VkDevice device);
|
||||
|
||||
[[nodiscard]] VkExtent2D getExtent() const { return extent; }
|
||||
|
||||
[[nodiscard]] const std::vector<VkImage>& getImages() const { return images; }
|
||||
[[nodiscard]] std::uint32_t getImageCount() const { return static_cast<std::uint32_t>(images.size()); }
|
||||
|
||||
void beginFrame(int index) const;
|
||||
void beginFrame(VkDevice device, int index) const;
|
||||
|
||||
void resetFences(int index) const;
|
||||
void resetFences(VkDevice device, int index) const;
|
||||
|
||||
std::pair<VkImage, int> acquireNextImage(int index);
|
||||
AcquireResult acquireNextImage(VkDevice device, std::uint32_t index);
|
||||
|
||||
void submitAndPresent(VkCommandBuffer cmd, VkQueue graphicsQueue, std::uint32_t imageIndex, std::uint32_t frameIndex);
|
||||
void submitAndPresent(
|
||||
VkDevice device,
|
||||
VkCommandBuffer cmd,
|
||||
VkQueue graphicsQueue,
|
||||
VkQueue presentQueue,
|
||||
std::uint32_t imageIndex,
|
||||
std::uint32_t frameIndex);
|
||||
|
||||
[[nodiscard]] bool isDirty() const { return dirty; }
|
||||
|
||||
[[nodiscard]] VkImageView getImageView(int index) const { return imageViews[index]; }
|
||||
|
||||
|
||||
VkImageView getImageView(std::uint32_t imageIndex) const {
|
||||
[[nodiscard]] VkImageView getImageView(std::uint32_t imageIndex) const {
|
||||
return imageViews.at(imageIndex);
|
||||
}
|
||||
|
||||
[[nodiscard]] VkSurfaceKHR getSurface() const { return surface; }
|
||||
[[nodiscard]] vkb::Swapchain getVkbSwapchain() const { return m_swapchain; }
|
||||
[[nodiscard]] VkFormat getFormat() const { return m_swapchain.image_format; }
|
||||
|
||||
private:
|
||||
struct FrameData {
|
||||
VkSemaphore swapchainSemaphore;
|
||||
VkFence renderFence;
|
||||
VkSemaphore swapchainSemaphore{VK_NULL_HANDLE};
|
||||
VkFence renderFence{VK_NULL_HANDLE};
|
||||
};
|
||||
std::vector<VkSemaphore> imageRenderSemaphores;
|
||||
|
||||
std::vector<VkSemaphore> imageRenderSemaphores;
|
||||
std::array<FrameData, FRAMES_IN_FLIGHT> frames;
|
||||
vkb::Swapchain m_swapchain; //Euuuh, m_ cuz like cpluhpluh
|
||||
vkb::Swapchain m_swapchain;
|
||||
std::vector<VkImage> images;
|
||||
std::vector<VkImageView> imageViews;
|
||||
bool dirty{false};
|
||||
GfxDevice* m_gfxDevice{nullptr};
|
||||
VkSurfaceKHR surface{VK_NULL_HANDLE};
|
||||
VkExtent2D extent{};
|
||||
};
|
||||
|
||||
#endif //SWAPCHAIN_H
|
||||
#endif
|
||||
|
||||
@@ -3,16 +3,31 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include "glm/vec4.hpp"
|
||||
|
||||
|
||||
#define VK_CHECK(call) \
|
||||
do { \
|
||||
VkResult result_ = call; \
|
||||
assert(result_ == VK_SUCCESS); \
|
||||
inline void checkVkResult(VkResult result, const char* expression, const char* file, int line)
|
||||
{
|
||||
if (result == VK_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
std::string{"Vulkan call failed: "} + expression +
|
||||
" returned " + std::to_string(static_cast<int>(result)) +
|
||||
" at " + file + ":" + std::to_string(line));
|
||||
}
|
||||
|
||||
#define VK_CHECK(call) \
|
||||
do { \
|
||||
const VkResult result_ = (call); \
|
||||
checkVkResult(result_, #call, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
|
||||
namespace vkutil {
|
||||
@@ -34,6 +49,13 @@ namespace vkutil {
|
||||
VkImageLayout currentLayout,
|
||||
VkImageLayout newLayout);
|
||||
|
||||
void transitionImage(
|
||||
VkCommandBuffer cmd,
|
||||
VkImage image,
|
||||
VkImageLayout currentLayout,
|
||||
VkImageLayout newLayout,
|
||||
const VkImageSubresourceRange& subresourceRange);
|
||||
|
||||
void copyImageToImage(
|
||||
VkCommandBuffer cmd,
|
||||
VkImage source,
|
||||
@@ -59,6 +81,12 @@ namespace vkutil {
|
||||
VkDeviceSize offset = 0,
|
||||
VkDeviceSize size = VK_WHOLE_SIZE);
|
||||
|
||||
void bufferHostWriteToTransferReadBarrier(
|
||||
VkCommandBuffer cmd,
|
||||
VkBuffer buffer,
|
||||
VkDeviceSize offset = 0,
|
||||
VkDeviceSize size = VK_WHOLE_SIZE);
|
||||
|
||||
void addDebugLabel(VkDevice device, VkImage image, const char* label);
|
||||
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label);
|
||||
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label);
|
||||
@@ -76,9 +104,59 @@ namespace vkutil {
|
||||
};
|
||||
|
||||
struct RenderInfo {
|
||||
VkRenderingAttachmentInfo colorAttachment;
|
||||
VkRenderingAttachmentInfo depthAttachment;
|
||||
VkRenderingInfo renderingInfo;
|
||||
VkRenderingAttachmentInfo colorAttachment{};
|
||||
VkRenderingAttachmentInfo depthAttachment{};
|
||||
VkRenderingInfo renderingInfo{};
|
||||
|
||||
RenderInfo() = default;
|
||||
|
||||
RenderInfo(const RenderInfo& other)
|
||||
: colorAttachment(other.colorAttachment),
|
||||
depthAttachment(other.depthAttachment),
|
||||
renderingInfo(other.renderingInfo)
|
||||
{
|
||||
rebind();
|
||||
}
|
||||
|
||||
RenderInfo& operator=(const RenderInfo& other)
|
||||
{
|
||||
if (this != &other) {
|
||||
colorAttachment = other.colorAttachment;
|
||||
depthAttachment = other.depthAttachment;
|
||||
renderingInfo = other.renderingInfo;
|
||||
rebind();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderInfo(RenderInfo&& other) noexcept
|
||||
: colorAttachment(other.colorAttachment),
|
||||
depthAttachment(other.depthAttachment),
|
||||
renderingInfo(other.renderingInfo)
|
||||
{
|
||||
rebind();
|
||||
}
|
||||
|
||||
RenderInfo& operator=(RenderInfo&& other) noexcept
|
||||
{
|
||||
if (this != &other) {
|
||||
colorAttachment = other.colorAttachment;
|
||||
depthAttachment = other.depthAttachment;
|
||||
renderingInfo = other.renderingInfo;
|
||||
rebind();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void rebind()
|
||||
{
|
||||
renderingInfo.pColorAttachments = renderingInfo.colorAttachmentCount != 0
|
||||
? &colorAttachment
|
||||
: nullptr;
|
||||
renderingInfo.pDepthAttachment = renderingInfo.pDepthAttachment != nullptr
|
||||
? &depthAttachment
|
||||
: nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
RenderInfo createRenderingInfo(const RenderingInfoParams& params);
|
||||
|
||||
@@ -33,9 +33,11 @@ public:
|
||||
virtual void Start();
|
||||
virtual void SetEnabled(bool enabled);
|
||||
|
||||
virtual void Update() = 0;
|
||||
virtual void LateUpdate();
|
||||
virtual void FixedUpdate();
|
||||
// Reason for keeping No DT function -> i am tired and lazy
|
||||
// If im feeling frisky im gonna claim backwards compatibility
|
||||
virtual void Update(float dt);
|
||||
virtual void LateUpdate(float dt);
|
||||
virtual void FixedUpdate(float fixedDt);
|
||||
|
||||
virtual void ImGuiInspector();
|
||||
virtual void ImGuiRender();
|
||||
@@ -49,10 +51,10 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual void Deserialize(const nlohmann::json& data) {
|
||||
virtual void Deserialize(const nlohmann::json&) {
|
||||
}
|
||||
|
||||
virtual void ResolveReferences(const ObjectMap& objects) {
|
||||
virtual void ResolveReferences(const ObjectMap&) {
|
||||
}
|
||||
|
||||
bool HasStarted{false};
|
||||
@@ -60,9 +62,15 @@ public:
|
||||
protected:
|
||||
explicit Component(GameObject& pParent, const std::string& name = "Component");
|
||||
|
||||
[[nodiscard]] float GetLastUpdateDeltaTime() const { return m_LastUpdateDeltaTime; }
|
||||
[[nodiscard]] float GetLastFixedDeltaTime() const { return m_LastFixedDeltaTime; }
|
||||
void NotifyPhysicsChanged();
|
||||
|
||||
private:
|
||||
GameObject* m_ParentGameObjectPtr{};
|
||||
bool m_IsEnabled{true};
|
||||
float m_LastUpdateDeltaTime{0.0f};
|
||||
float m_LastFixedDeltaTime{0.0f};
|
||||
};
|
||||
|
||||
#endif // COMPONENT_H
|
||||
@@ -10,20 +10,24 @@
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
|
||||
class Scene;
|
||||
class SceneSerializer;
|
||||
|
||||
class GameObject final : public Object {
|
||||
public:
|
||||
friend class Scene;
|
||||
friend class SceneSerializer;
|
||||
friend class Transform;
|
||||
|
||||
void Update();
|
||||
void LateUpdate();
|
||||
void FixedUpdate();
|
||||
void Update(float dt);
|
||||
void LateUpdate(float dt);
|
||||
void FixedUpdate(float fixedDt);
|
||||
|
||||
void Render(const RenderContext& ctx) const;
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
void CleanupComponents();
|
||||
void RefreshPhysics();
|
||||
|
||||
void SetActive(bool active) {
|
||||
if (active == m_Active) {
|
||||
@@ -32,6 +36,7 @@ public:
|
||||
|
||||
m_Active = active;
|
||||
SetActiveDirty();
|
||||
RefreshPhysics();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsActive() const { return m_Active; }
|
||||
@@ -71,14 +76,17 @@ public:
|
||||
auto& addedComponent = m_Components.emplace_back(
|
||||
std::make_unique<TComponent>(*this, std::forward<Args>(args)...));
|
||||
|
||||
RefreshPhysics();
|
||||
return static_cast<TComponent*>(addedComponent.get());
|
||||
}
|
||||
|
||||
template <typename TComponent>
|
||||
[[nodiscard]] TComponent* GetComponent() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (auto casted = dynamic_cast<TComponent*>(component.get())) {
|
||||
return casted;
|
||||
if (component != nullptr && !component->IsBeingDestroyed()) {
|
||||
if (auto casted = dynamic_cast<TComponent*>(component.get())) {
|
||||
return casted;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
@@ -87,8 +95,10 @@ public:
|
||||
template <typename TComponent>
|
||||
[[nodiscard]] const TComponent* GetComponent() const {
|
||||
for (const auto& component : m_Components) {
|
||||
if (auto casted = dynamic_cast<const TComponent*>(component.get())) {
|
||||
return casted;
|
||||
if (component != nullptr && !component->IsBeingDestroyed()) {
|
||||
if (auto casted = dynamic_cast<const TComponent*>(component.get())) {
|
||||
return casted;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
@@ -97,9 +107,11 @@ public:
|
||||
template <typename TComponent>
|
||||
TComponent* DestroyComponent() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (auto casted = dynamic_cast<TComponent*>(component.get())) {
|
||||
casted->Destroy();
|
||||
return casted;
|
||||
if (component != nullptr && !component->IsBeingDestroyed()) {
|
||||
if (auto casted = dynamic_cast<TComponent*>(component.get())) {
|
||||
casted->Destroy();
|
||||
return casted;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
@@ -108,7 +120,8 @@ public:
|
||||
template <typename TComponent>
|
||||
[[nodiscard]] bool HasComponent() const {
|
||||
for (const auto& component : m_Components) {
|
||||
if (dynamic_cast<TComponent*>(component.get())) {
|
||||
if (component != nullptr && !component->IsBeingDestroyed() &&
|
||||
dynamic_cast<TComponent*>(component.get())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -145,6 +158,7 @@ private:
|
||||
|
||||
void SetActiveDirty();
|
||||
void UpdateActiveState();
|
||||
static ObjectId AllocateId();
|
||||
|
||||
inline static ObjectId s_NextId{1};
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ private:
|
||||
void SetRotationDirty();
|
||||
void SetScaleDirty();
|
||||
|
||||
void SetLocalFromWorldMatrix(const glm::mat4& worldMatrix);
|
||||
void UpdateWorldCache();
|
||||
|
||||
glm::vec3 m_LocalPosition{};
|
||||
glm::quat m_LocalRotation{ glm::mat4{ 1.0f }};
|
||||
glm::vec3 m_LocalScale{ 1, 1, 1 };
|
||||
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
[[nodiscard]] const PhysicsWorld& GetWorld() const { return *m_World; }
|
||||
|
||||
void RegisterGameObject(GameObject& object);
|
||||
void RefreshGameObject(GameObject& object);
|
||||
void UnregisterGameObject(GameObject& object);
|
||||
|
||||
void FixedUpdate(float fixedDt);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include <destrum/Physics/PhysicsTypes.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
|
||||
@@ -16,9 +19,10 @@ struct PhysicsRaycastHit {
|
||||
|
||||
class PhysicsWorld {
|
||||
public:
|
||||
virtual ~PhysicsWorld() = default;
|
||||
virtual ~PhysicsWorld();
|
||||
|
||||
void RegisterRigidbody(Rigidbody& rigidbody);
|
||||
void RefreshRigidbody(Rigidbody& rigidbody);
|
||||
void UnregisterRigidbody(Rigidbody& rigidbody);
|
||||
|
||||
virtual void Step(float fixedDt) = 0;
|
||||
@@ -44,4 +48,7 @@ public:
|
||||
const glm::vec3& direction,
|
||||
float maxDistance,
|
||||
PhysicsRaycastHit& hit) const = 0;
|
||||
|
||||
private:
|
||||
std::unordered_set<Rigidbody*> m_RegisteredRigidbodies;
|
||||
};
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
#include "destrum/Physics/PhysicsSceneBridge.h"
|
||||
|
||||
class GameObject;
|
||||
class SceneSerializer;
|
||||
|
||||
class Scene final {
|
||||
friend Scene& SceneManager::CreateScene(const std::string& name);
|
||||
friend class SceneSerializer;
|
||||
|
||||
public:
|
||||
// void Add(std::shared_ptr<GameObject> object);
|
||||
@@ -23,10 +25,10 @@ public:
|
||||
|
||||
void Load();
|
||||
|
||||
void Update();
|
||||
void Update(float dt);
|
||||
void FixedUpdate(float dt);
|
||||
void LateUpdate();
|
||||
void Render(const RenderContext& ctx) const;
|
||||
void LateUpdate(float dt);
|
||||
void Render(const RenderContext& ctx);
|
||||
void RenderImgui();
|
||||
|
||||
void CleanupDestroyedGameObjects();
|
||||
@@ -36,6 +38,11 @@ public:
|
||||
[[nodiscard]] bool IsBeingUnloaded() const { return m_BeingUnloaded; }
|
||||
|
||||
void CommitPendingAdditions();
|
||||
void RefreshPhysics();
|
||||
|
||||
[[nodiscard]] bool IsIterating() const { return m_IterationDepth != 0; }
|
||||
void BeginIteration();
|
||||
void EndIteration();
|
||||
|
||||
[[nodiscard]] const std::vector<std::shared_ptr<GameObject>>& GetObjects() const {
|
||||
return m_objects;
|
||||
@@ -50,20 +57,40 @@ public:
|
||||
}
|
||||
|
||||
void UnloadBindings() {
|
||||
if (!m_bindingsLoaded) {
|
||||
m_bindingsUnloaded = true;
|
||||
return;
|
||||
}
|
||||
if (m_unregisterBindings) {
|
||||
m_unregisterBindings();
|
||||
}
|
||||
m_bindingsLoaded = false;
|
||||
m_bindingsUnloaded = true;
|
||||
}
|
||||
|
||||
void LoadBindings() {
|
||||
OnSceneLoaded.Invoke();
|
||||
if (m_registerBindings) {
|
||||
m_registerBindings();
|
||||
if (m_BeingUnloaded || m_bindingsLoaded) {
|
||||
return;
|
||||
}
|
||||
m_bindingsLoaded = true;
|
||||
m_bindingsUnloaded = false;
|
||||
try {
|
||||
OnSceneLoaded.Invoke();
|
||||
if (m_registerBindings) {
|
||||
m_registerBindings();
|
||||
}
|
||||
} catch (...) {
|
||||
if (m_unregisterBindings) {
|
||||
m_unregisterBindings();
|
||||
}
|
||||
m_bindingsLoaded = false;
|
||||
m_bindingsUnloaded = true;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& GetName() const { return m_name; }
|
||||
[[nodiscard]] unsigned int GetId() const { return m_idCounter++; }
|
||||
[[nodiscard]] unsigned int GetId() const { return m_id; }
|
||||
|
||||
~Scene();
|
||||
Scene(const Scene& other) = delete;
|
||||
@@ -84,8 +111,10 @@ private:
|
||||
std::vector<std::shared_ptr<GameObject>> m_objects{};
|
||||
std::vector<std::shared_ptr<GameObject>> m_pendingAdditions{};
|
||||
bool m_BeingUnloaded{false};
|
||||
std::size_t m_IterationDepth{0};
|
||||
|
||||
static unsigned int m_idCounter;
|
||||
unsigned int m_id{0};
|
||||
|
||||
bool m_renderImgui{false};
|
||||
|
||||
@@ -94,6 +123,8 @@ private:
|
||||
|
||||
std::function<void()> m_registerBindings;
|
||||
std::function<void()> m_unregisterBindings;
|
||||
bool m_bindingsUnloaded{false};
|
||||
bool m_bindingsLoaded{false};
|
||||
};
|
||||
|
||||
#endif // SCENE_H
|
||||
|
||||
@@ -15,12 +15,11 @@ class SceneManager final: public Singleton<SceneManager> {
|
||||
public:
|
||||
Scene& CreateScene(const std::string& name);
|
||||
|
||||
//TODO: Verry bad fix
|
||||
Scene& GetCurrentScene() const { return *m_scenes[m_ActiveSceneIndex]; }
|
||||
Scene& GetCurrentScene() const;
|
||||
|
||||
void Update();
|
||||
void Update(float dt);
|
||||
void FixedUpdate(float dt);
|
||||
void LateUpdate();
|
||||
void LateUpdate(float dt);
|
||||
|
||||
void Render(const RenderContext& ctx);
|
||||
void RenderImgui();
|
||||
@@ -35,7 +34,7 @@ public:
|
||||
void Destroy();
|
||||
|
||||
void SwitchScene(int index);
|
||||
int GetActiveSceneId() const { return m_ActiveSceneIndex; }
|
||||
int GetActiveSceneId() const { return m_scenes.empty() ? -1 : m_ActiveSceneIndex; }
|
||||
|
||||
[[nodiscard]] const std::vector<std::shared_ptr<Scene>>& GetScenes() const { return m_scenes; }
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ public:
|
||||
return it->second(owner);
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool IsRegistered(const std::string& typeName) {
|
||||
return Registry().contains(typeName);
|
||||
}
|
||||
|
||||
private:
|
||||
static std::unordered_map<std::string, CreateFn>& Registry() {
|
||||
static std::unordered_map<std::string, CreateFn> registry;
|
||||
|
||||
@@ -13,6 +13,7 @@ public:
|
||||
friend class Singleton<GameState>;
|
||||
|
||||
void SetGfxDevice(GfxDevice* device) { m_gfxDevice = device; }
|
||||
[[nodiscard]] bool HasGfxDevice() const { return m_gfxDevice != nullptr; }
|
||||
GfxDevice& Gfx() {
|
||||
assert(m_gfxDevice && "GfxDevice not registered yet!");
|
||||
return *m_gfxDevice;
|
||||
@@ -23,6 +24,7 @@ public:
|
||||
}
|
||||
|
||||
void SetRenderer(GameRenderer* renderer) { m_renderer = renderer; }
|
||||
[[nodiscard]] bool HasRenderer() const { return m_renderer != nullptr; }
|
||||
GameRenderer& Renderer() {
|
||||
assert(m_renderer && "Renderer not registered yet!");
|
||||
return *m_renderer;
|
||||
|
||||
+97
-33
@@ -1,9 +1,12 @@
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <SDL_vulkan.h>
|
||||
#include <thread>
|
||||
#include <stdexcept>
|
||||
#include <destrum/App.h>
|
||||
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "imgui.h"
|
||||
@@ -25,41 +28,60 @@ App::App()
|
||||
{
|
||||
}
|
||||
|
||||
App::~App()
|
||||
{
|
||||
if (!cleanedUp) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (...) {
|
||||
// Destructors must not throw. Initialization failures are already
|
||||
// reported by the caller, while cleanup is best effort here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::init(const AppParams& params)
|
||||
{
|
||||
m_params = params;
|
||||
ZoneScopedN("App::init");
|
||||
tracy::SetThreadName("Main Thread");
|
||||
TracySetProgramName(params.appName.c_str());
|
||||
AssetFS::GetInstance().Init(params.exeDir);
|
||||
// AssetFS::GetInstance().Mount("engine", params.exeDir / "assets" / "engine");
|
||||
// AssetFS::GetInstance().Mount("game", params.exeDir / "assets" / "game");
|
||||
cleanedUp = false;
|
||||
customInitStarted = false;
|
||||
try {
|
||||
m_params = params;
|
||||
ZoneScopedN("App::init");
|
||||
tracy::SetThreadName("Main Thread");
|
||||
TracySetProgramName(params.appName.c_str());
|
||||
AssetFS::GetInstance().Init(params.exeDir);
|
||||
|
||||
window = SDL_CreateWindow(
|
||||
params.windowTitle.c_str(),
|
||||
// pos
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
// size
|
||||
params.windowSize.x,
|
||||
params.windowSize.y,
|
||||
SDL_WINDOW_VULKAN);
|
||||
window = SDL_CreateWindow(
|
||||
params.windowTitle.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
params.windowSize.x,
|
||||
params.windowSize.y,
|
||||
SDL_WINDOW_VULKAN);
|
||||
|
||||
SDL_SetWindowResizable(window, SDL_TRUE);
|
||||
if (!window)
|
||||
{
|
||||
spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError());
|
||||
std::exit(1);
|
||||
if (!window) {
|
||||
spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError());
|
||||
throw std::runtime_error(
|
||||
"Failed to create window: " + std::string{SDL_GetError()});
|
||||
}
|
||||
SDL_SetWindowResizable(window, SDL_TRUE);
|
||||
|
||||
gfxDevice.init(window, params.appName, false);
|
||||
imguiPass.init(window, gfxDevice);
|
||||
|
||||
InputManager::GetInstance().Init();
|
||||
Time::GetInstance().Update();
|
||||
|
||||
customInitStarted = true;
|
||||
customInit();
|
||||
cleanedUp = false;
|
||||
} catch (...) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (...) {
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
gfxDevice.init(window, params.appName, false);
|
||||
imguiPass.init(window, gfxDevice);
|
||||
|
||||
|
||||
InputManager::GetInstance().Init();
|
||||
Time::GetInstance().Update();
|
||||
|
||||
customInit();
|
||||
}
|
||||
|
||||
void App::run()
|
||||
@@ -95,9 +117,8 @@ void App::run()
|
||||
accumulator += dt;
|
||||
|
||||
{
|
||||
ZoneScopedN("Input BeginFrame + Camera");
|
||||
ZoneScopedN("Input BeginFrame");
|
||||
InputManager::GetInstance().BeginFrame();
|
||||
camera.Update(dt);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -157,6 +178,10 @@ void App::run()
|
||||
|
||||
if (!isRunning) break;
|
||||
|
||||
// Consume SDL events before updating the base camera so held and
|
||||
// newly pressed inputs are applied in the same frame.
|
||||
camera.Update(dt);
|
||||
|
||||
{
|
||||
ZoneScopedN("ImGui BeginFrame");
|
||||
imguiPass.beginFrame();
|
||||
@@ -258,6 +283,7 @@ void App::run()
|
||||
{
|
||||
ZoneScopedN("Recreate Swapchain");
|
||||
gfxDevice.recreateSwapchain(w, h);
|
||||
imguiPass.onSwapchainRecreated();
|
||||
onWindowResize(w, h);
|
||||
}
|
||||
|
||||
@@ -288,8 +314,46 @@ void App::run()
|
||||
|
||||
void App::cleanup()
|
||||
{
|
||||
if (cleanedUp || cleaningUp) {
|
||||
return;
|
||||
}
|
||||
|
||||
cleaningUp = true;
|
||||
spdlog::info("Cleaning up");
|
||||
customCleanup();
|
||||
|
||||
std::exception_ptr firstException;
|
||||
const auto attempt = [&firstException](auto&& operation) {
|
||||
try {
|
||||
operation();
|
||||
} catch (...) {
|
||||
if (!firstException) {
|
||||
firstException = std::current_exception();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (customInitStarted) {
|
||||
attempt([this] { customCleanup(); });
|
||||
}
|
||||
attempt([] { SceneManager::GetInstance().Destroy(); });
|
||||
attempt([this] { renderer.cleanup(gfxDevice); });
|
||||
attempt([this] { imguiPass.cleanup(); });
|
||||
attempt([this] { resources.cleanup(gfxDevice); });
|
||||
attempt([this] { gfxDevice.cleanup(); });
|
||||
|
||||
if (window) {
|
||||
SDL_DestroyWindow(window);
|
||||
window = nullptr;
|
||||
}
|
||||
|
||||
AssetFS::GetInstance().Reset();
|
||||
customInitStarted = false;
|
||||
cleanedUp = true;
|
||||
cleaningUp = false;
|
||||
|
||||
if (firstException) {
|
||||
std::rethrow_exception(firstException);
|
||||
}
|
||||
}
|
||||
|
||||
void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
|
||||
|
||||
@@ -1,33 +1,247 @@
|
||||
#include <destrum/Components/Animator.h>
|
||||
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
namespace {
|
||||
nlohmann::json Vec3Json(const glm::vec3& value) {
|
||||
return {value.x, value.y, value.z};
|
||||
}
|
||||
|
||||
glm::vec3 ReadVec3(const nlohmann::json& value) {
|
||||
return {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
||||
}
|
||||
|
||||
nlohmann::json QuatJson(const glm::quat& value) {
|
||||
return {value.x, value.y, value.z, value.w};
|
||||
}
|
||||
|
||||
glm::quat ReadQuat(const nlohmann::json& value) {
|
||||
return {
|
||||
value.at(3).get<float>(),
|
||||
value.at(0).get<float>(),
|
||||
value.at(1).get<float>(),
|
||||
value.at(2).get<float>()
|
||||
};
|
||||
}
|
||||
|
||||
nlohmann::json Mat4Json(const glm::mat4& value) {
|
||||
nlohmann::json result = nlohmann::json::array();
|
||||
for (std::size_t column = 0; column < 4; ++column) {
|
||||
for (std::size_t row = 0; row < 4; ++row) {
|
||||
result.push_back(value[static_cast<glm::mat4::length_type>(column)]
|
||||
[static_cast<glm::mat4::length_type>(row)]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::mat4 ReadMat4(const nlohmann::json& value) {
|
||||
glm::mat4 result{1.0f};
|
||||
for (std::size_t column = 0; column < 4; ++column) {
|
||||
for (std::size_t row = 0; row < 4; ++row) {
|
||||
result[static_cast<glm::mat4::length_type>(column)]
|
||||
[static_cast<glm::mat4::length_type>(row)] =
|
||||
value.at(column * 4 + row).get<float>();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Animator::Animator(GameObject& parent)
|
||||
: Component(parent, "Animator") {}
|
||||
|
||||
void Animator::Update() {
|
||||
nlohmann::json Animator::Serialize() const {
|
||||
nlohmann::json skeleton;
|
||||
skeleton["hierarchy"] = nlohmann::json::array();
|
||||
for (const auto& node : m_skeleton.hierarchy) {
|
||||
skeleton["hierarchy"].push_back({
|
||||
{"id", node.id},
|
||||
{"children", node.children}
|
||||
});
|
||||
}
|
||||
skeleton["inverseBindMatrices"] = nlohmann::json::array();
|
||||
for (const auto& matrix : m_skeleton.inverseBindMatrices) {
|
||||
skeleton["inverseBindMatrices"].push_back(Mat4Json(matrix));
|
||||
}
|
||||
skeleton["joints"] = nlohmann::json::array();
|
||||
for (const auto& joint : m_skeleton.joints) {
|
||||
skeleton["joints"].push_back({
|
||||
{"id", joint.id},
|
||||
{"translation", Vec3Json(joint.localTranslation)},
|
||||
{"rotation", QuatJson(joint.localRotation)},
|
||||
{"scale", Vec3Json(joint.localScale)}
|
||||
});
|
||||
}
|
||||
skeleton["jointNames"] = m_skeleton.jointNames;
|
||||
skeleton["parentIndex"] = m_skeleton.parentIndex;
|
||||
skeleton["rootPreTransform"] = Mat4Json(m_skeleton.rootPreTransform);
|
||||
|
||||
nlohmann::json clips = nlohmann::json::array();
|
||||
for (const auto& [name, clip] : m_clips) {
|
||||
nlohmann::json clipJson{
|
||||
{"name", name},
|
||||
{"duration", clip->duration},
|
||||
{"looped", clip->looped},
|
||||
{"startFrame", clip->startFrame},
|
||||
{"tracks", nlohmann::json::array()},
|
||||
{"events", nlohmann::json::object()}
|
||||
};
|
||||
for (const auto& track : clip->tracks) {
|
||||
nlohmann::json trackJson{
|
||||
{"jointIndex", track.jointIndex},
|
||||
{"keyframes", nlohmann::json::array()}
|
||||
};
|
||||
for (const auto& keyframe : track.keyframes) {
|
||||
trackJson["keyframes"].push_back({
|
||||
{"time", keyframe.time},
|
||||
{"translation", Vec3Json(keyframe.translation)},
|
||||
{"rotation", QuatJson(keyframe.rotation)},
|
||||
{"scale", Vec3Json(keyframe.scale)}
|
||||
});
|
||||
}
|
||||
clipJson["tracks"].push_back(std::move(trackJson));
|
||||
}
|
||||
for (const auto& [frame, events] : clip->events) {
|
||||
clipJson["events"][std::to_string(frame)] = events;
|
||||
}
|
||||
clips.push_back(std::move(clipJson));
|
||||
}
|
||||
|
||||
const auto playback = [](const PlaybackState& state, const std::string& name) {
|
||||
return nlohmann::json{
|
||||
{"clip", name},
|
||||
{"time", state.time},
|
||||
{"speed", state.speed}
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
{"skeleton", std::move(skeleton)},
|
||||
{"clips", std::move(clips)},
|
||||
{"current", playback(m_current, m_currentClipName)},
|
||||
{"previous", playback(m_previous, m_previous.clip ? m_previous.clip->name : std::string{})},
|
||||
{"blendT", m_blendT},
|
||||
{"blendDuration", m_blendDuration}
|
||||
};
|
||||
}
|
||||
|
||||
void Animator::Deserialize(const nlohmann::json& data) {
|
||||
m_skeleton = {};
|
||||
m_clips.clear();
|
||||
m_current = {};
|
||||
m_previous = {};
|
||||
m_currentClipName.clear();
|
||||
|
||||
if (data.contains("skeleton")) {
|
||||
const auto& skeleton = data.at("skeleton");
|
||||
for (const auto& node : skeleton.value("hierarchy", nlohmann::json::array())) {
|
||||
m_skeleton.hierarchy.push_back({
|
||||
node.at("id").get<JointId>(),
|
||||
node.value("children", std::vector<JointId>{})
|
||||
});
|
||||
}
|
||||
for (const auto& matrix : skeleton.value("inverseBindMatrices", nlohmann::json::array())) {
|
||||
m_skeleton.inverseBindMatrices.push_back(ReadMat4(matrix));
|
||||
}
|
||||
for (const auto& joint : skeleton.value("joints", nlohmann::json::array())) {
|
||||
m_skeleton.joints.push_back({
|
||||
joint.at("id").get<JointId>(),
|
||||
ReadVec3(joint.at("translation")),
|
||||
ReadQuat(joint.at("rotation")),
|
||||
ReadVec3(joint.at("scale"))
|
||||
});
|
||||
}
|
||||
m_skeleton.jointNames = skeleton.value("jointNames", std::vector<std::string>{});
|
||||
m_skeleton.parentIndex = skeleton.value("parentIndex", std::vector<int>{});
|
||||
if (skeleton.contains("rootPreTransform")) {
|
||||
m_skeleton.rootPreTransform = ReadMat4(skeleton.at("rootPreTransform"));
|
||||
}
|
||||
if (m_skeleton.parentIndex.size() != m_skeleton.joints.size()) {
|
||||
buildParentIndex(m_skeleton);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& clipJson : data.value("clips", nlohmann::json::array())) {
|
||||
auto clip = std::make_shared<SkeletalAnimation>();
|
||||
clip->name = clipJson.at("name").get<std::string>();
|
||||
clip->duration = clipJson.value("duration", 0.0f);
|
||||
clip->looped = clipJson.value("looped", true);
|
||||
clip->startFrame = clipJson.value("startFrame", 0);
|
||||
for (const auto& trackJson : clipJson.value("tracks", nlohmann::json::array())) {
|
||||
SkeletalAnimation::Track track;
|
||||
track.jointIndex = trackJson.at("jointIndex").get<std::uint32_t>();
|
||||
for (const auto& keyframeJson : trackJson.value("keyframes", nlohmann::json::array())) {
|
||||
track.keyframes.push_back({
|
||||
keyframeJson.at("time").get<float>(),
|
||||
ReadVec3(keyframeJson.at("translation")),
|
||||
ReadQuat(keyframeJson.at("rotation")),
|
||||
ReadVec3(keyframeJson.at("scale"))
|
||||
});
|
||||
}
|
||||
clip->tracks.push_back(std::move(track));
|
||||
}
|
||||
if (clipJson.contains("events")) {
|
||||
for (auto it = clipJson.at("events").begin(); it != clipJson.at("events").end(); ++it) {
|
||||
clip->events[std::stoi(it.key())] = it.value().get<std::vector<std::string>>();
|
||||
}
|
||||
}
|
||||
m_clips[clip->name] = std::move(clip);
|
||||
}
|
||||
|
||||
const auto restorePlayback = [this](const nlohmann::json& playback, PlaybackState& state,
|
||||
std::string* clipName) {
|
||||
const std::string name = playback.value("clip", std::string{});
|
||||
state.time = playback.value("time", 0.0f);
|
||||
state.speed = playback.value("speed", 1.0f);
|
||||
state.clip = nullptr;
|
||||
if (!name.empty()) {
|
||||
const auto it = m_clips.find(name);
|
||||
if (it == m_clips.end()) {
|
||||
throw std::runtime_error("Animator clip not found: " + name);
|
||||
}
|
||||
state.clip = it->second.get();
|
||||
}
|
||||
if (clipName != nullptr) {
|
||||
*clipName = name;
|
||||
}
|
||||
};
|
||||
|
||||
if (data.contains("current")) {
|
||||
restorePlayback(data.at("current"), m_current, &m_currentClipName);
|
||||
}
|
||||
if (data.contains("previous")) {
|
||||
restorePlayback(data.at("previous"), m_previous, nullptr);
|
||||
}
|
||||
m_blendT = data.value("blendT", 0.0f);
|
||||
m_blendDuration = data.value("blendDuration", 0.0f);
|
||||
}
|
||||
|
||||
void Animator::Update(float dt) {
|
||||
if (!m_current.clip) return;
|
||||
|
||||
const float dt = Time::GetInstance().DeltaTime();
|
||||
|
||||
m_current.time += dt * m_current.speed;
|
||||
if (m_current.clip->looped)
|
||||
if (m_current.clip->duration > 0.0f && m_current.clip->looped)
|
||||
m_current.time = std::fmod(m_current.time, m_current.clip->duration);
|
||||
else
|
||||
else if (m_current.clip->duration > 0.0f)
|
||||
m_current.time = std::min(m_current.time, m_current.clip->duration);
|
||||
|
||||
if (m_previous.clip) {
|
||||
m_previous.time += dt * m_previous.speed;
|
||||
if (m_previous.clip->looped)
|
||||
if (m_previous.clip->duration > 0.0f && m_previous.clip->looped)
|
||||
m_previous.time = std::fmod(m_previous.time, m_previous.clip->duration);
|
||||
|
||||
m_blendT += dt / m_blendDuration;
|
||||
if (m_blendDuration > 0.0f) {
|
||||
m_blendT += dt / m_blendDuration;
|
||||
} else {
|
||||
m_blendT = 1.0f;
|
||||
}
|
||||
if (m_blendT >= 1.f) {
|
||||
m_blendT = 1.f;
|
||||
m_previous = {};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "destrum/ObjectModel/GameObject.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
MeshRendererComponent::MeshRendererComponent(GameObject& parent): Component(parent, "MeshRendererComponent") {
|
||||
|
||||
@@ -12,7 +13,11 @@ MeshRendererComponent::MeshRendererComponent(GameObject& parent): Component(pare
|
||||
|
||||
void MeshRendererComponent::Start() {
|
||||
Component::Start();
|
||||
if (auto* animator = GetGameObject()->GetComponent<Animator>()) {
|
||||
ResolveReferences(ObjectMap{});
|
||||
if (GetGameObject()->GetComponent<Animator>() &&
|
||||
meshID != NULL_MESH_ID &&
|
||||
GameState::GetInstance().HasGfxDevice() &&
|
||||
GameState::GetInstance().HasRenderer()) {
|
||||
const auto& gfxDevice = GameState::GetInstance().Gfx();
|
||||
const auto& mesh = GameState::GetInstance().Renderer().getResources()->meshes().getMesh(meshID);
|
||||
|
||||
@@ -26,14 +31,101 @@ void MeshRendererComponent::Start() {
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Update() {
|
||||
void MeshRendererComponent::Destroy()
|
||||
{
|
||||
if (m_skinnedMesh && GameState::GetInstance().HasGfxDevice()) {
|
||||
auto& gfxDevice = GameState::GetInstance().Gfx();
|
||||
gfxDevice.waitIdle();
|
||||
gfxDevice.destroyBuffer(m_skinnedMesh->skinnedVertexBuffer);
|
||||
}
|
||||
m_skinnedMesh.reset();
|
||||
Component::Destroy();
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Update(float) {
|
||||
}
|
||||
|
||||
nlohmann::json MeshRendererComponent::Serialize() const {
|
||||
return {
|
||||
{"meshId", meshID},
|
||||
{"materialId", materialID},
|
||||
{"meshKey", meshKey},
|
||||
{"materialKey", materialKey}
|
||||
};
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Deserialize(const nlohmann::json& data) {
|
||||
if (data.contains("meshId")) {
|
||||
meshID = data.at("meshId").get<MeshID>();
|
||||
}
|
||||
if (data.contains("materialId")) {
|
||||
materialID = data.at("materialId").get<MaterialID>();
|
||||
}
|
||||
if (data.contains("meshKey")) {
|
||||
meshKey = data.at("meshKey").get<std::string>();
|
||||
}
|
||||
if (data.contains("materialKey")) {
|
||||
materialKey = data.at("materialKey").get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::ResolveReferences(const ObjectMap&) {
|
||||
if (!GameState::GetInstance().HasRenderer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderResources* resources = GameState::GetInstance().Renderer().getResources();
|
||||
if (resources == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshKey.empty()) {
|
||||
const auto resolved = resources->meshes().findMeshByKey(meshKey);
|
||||
if (!resolved) {
|
||||
throw std::runtime_error("Mesh resource not found: " + meshKey);
|
||||
}
|
||||
meshID = *resolved;
|
||||
}
|
||||
if (!materialKey.empty()) {
|
||||
const auto resolved = resources->materials().findMaterialByKey(materialKey);
|
||||
if (!resolved) {
|
||||
throw std::runtime_error("Material resource not found: " + materialKey);
|
||||
}
|
||||
materialID = *resolved;
|
||||
}
|
||||
|
||||
if (meshID != NULL_MESH_ID && meshKey.empty()) {
|
||||
(void)resources->meshes().getMesh(meshID);
|
||||
}
|
||||
if (materialID != NULL_MATERIAL_ID && materialKey.empty()) {
|
||||
(void)resources->materials().getMaterial(materialID);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::SetMeshID(MeshID id) {
|
||||
meshID = id;
|
||||
meshKey.clear();
|
||||
if (id != NULL_MESH_ID && GameState::GetInstance().HasRenderer()) {
|
||||
if (auto* resources = GameState::GetInstance().Renderer().getResources()) {
|
||||
meshKey = resources->meshes().getMeshKey(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::SetMaterialID(MaterialID id) {
|
||||
materialID = id;
|
||||
materialKey.clear();
|
||||
if (id != NULL_MATERIAL_ID && GameState::GetInstance().HasRenderer()) {
|
||||
if (auto* resources = GameState::GetInstance().Renderer().getResources()) {
|
||||
materialKey = resources->materials().getMaterialKey(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Render(const RenderContext& ctx) {
|
||||
if (meshID == NULL_MESH_ID || materialID == NULL_MATERIAL_ID) return;
|
||||
|
||||
if (auto* animator = GetGameObject()->GetComponent<Animator>(); animator && m_skinnedMesh) {
|
||||
const auto& mesh = ctx.renderer.getResources()->meshes().getCPUMesh(meshID);
|
||||
const auto skeleton = GetGameObject()->GetComponent<Animator>()->getSkeleton();
|
||||
std::uint32_t frameIdx = GameState::GetInstance().Gfx().getCurrentFrameIndex();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "destrum/ObjectModel/Transform.h"
|
||||
#include "destrum/Components/MeshRendererComponent.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "destrum/Util/DeltaTime.h"
|
||||
|
||||
static glm::vec3 RandomUnitVector(std::mt19937& rng)
|
||||
{
|
||||
@@ -54,6 +55,9 @@ void OrbitAndSpin::Randomize(uint32_t seed)
|
||||
|
||||
void OrbitAndSpin::BuildOrbitBasis()
|
||||
{
|
||||
if (glm::dot(m_OrbitAxis, m_OrbitAxis) < 1e-8f)
|
||||
m_OrbitAxis = glm::vec3(0, 1, 0);
|
||||
|
||||
m_OrbitAxis = glm::normalize(m_OrbitAxis);
|
||||
|
||||
// pick any vector not parallel to axis
|
||||
@@ -63,9 +67,8 @@ void OrbitAndSpin::BuildOrbitBasis()
|
||||
m_V = glm::normalize(glm::cross(m_OrbitAxis, m_U));
|
||||
}
|
||||
|
||||
void OrbitAndSpin::Update()
|
||||
void OrbitAndSpin::Update(float dt)
|
||||
{
|
||||
float dt = 1.0f / 60.0f;
|
||||
|
||||
// orbit
|
||||
m_OrbitAngle += m_OrbitSpeed * dt;
|
||||
@@ -84,8 +87,6 @@ void OrbitAndSpin::Update()
|
||||
// grow (always positive)
|
||||
m_GrowPhase += m_GrowSpeed * dt;
|
||||
|
||||
m_GrowPhase += m_GrowSpeed * dt;
|
||||
|
||||
// 0..1
|
||||
float t = 0.5f * (std::sin(m_GrowPhase) + 1.0f);
|
||||
|
||||
@@ -93,7 +94,7 @@ void OrbitAndSpin::Update()
|
||||
float s = glm::mix(m_GrowMin, m_GrowMax, t);
|
||||
|
||||
// respect original scale
|
||||
GetTransform().SetLocalScale(glm::vec3(s));
|
||||
GetTransform().SetLocalScale(m_BaseScale * s);
|
||||
|
||||
// GetTransform().SetLocalScale(glm::vec3(std::sin(m_GrowPhase)));
|
||||
|
||||
@@ -109,8 +110,68 @@ void OrbitAndSpin::Update()
|
||||
|
||||
void OrbitAndSpin::Start() {
|
||||
auto meshComp = this->GetGameObject()->GetComponent<MeshRendererComponent>();
|
||||
m_MaterialID = meshComp->GetMaterialID();
|
||||
if (meshComp != nullptr) {
|
||||
m_MaterialID = meshComp->GetMaterialID();
|
||||
}
|
||||
|
||||
m_BaseScale = GetTransform().GetLocalScale(); // <-- important
|
||||
if (!m_BaseScaleLoaded) {
|
||||
m_BaseScale = GetTransform().GetLocalScale();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nlohmann::json OrbitAndSpin::Serialize() const {
|
||||
return {
|
||||
{"radius", m_Radius},
|
||||
{"center", {m_Center.x, m_Center.y, m_Center.z}},
|
||||
{"orbitAxis", {m_OrbitAxis.x, m_OrbitAxis.y, m_OrbitAxis.z}},
|
||||
{"orbitSpeed", m_OrbitSpeed},
|
||||
{"orbitAngle", m_OrbitAngle},
|
||||
{"orbitPhase", m_OrbitPhase},
|
||||
{"growPhase", m_GrowPhase},
|
||||
{"growSpeed", m_GrowSpeed},
|
||||
{"growMin", m_GrowMin},
|
||||
{"growMax", m_GrowMax},
|
||||
{"spinAxis", {m_SpinAxis.x, m_SpinAxis.y, m_SpinAxis.z}},
|
||||
{"spinSpeed", m_SpinSpeed},
|
||||
{"baseScale", {m_BaseScale.x, m_BaseScale.y, m_BaseScale.z}},
|
||||
{"materialId", m_MaterialID}
|
||||
};
|
||||
}
|
||||
|
||||
void OrbitAndSpin::Deserialize(const nlohmann::json& data) {
|
||||
const auto readVec3 = [&data](const char* key, glm::vec3& value) {
|
||||
if (!data.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& array = data.at(key);
|
||||
value = {array.at(0).get<float>(), array.at(1).get<float>(), array.at(2).get<float>()};
|
||||
};
|
||||
|
||||
if (data.contains("radius")) m_Radius = data.at("radius").get<float>();
|
||||
readVec3("center", m_Center);
|
||||
readVec3("orbitAxis", m_OrbitAxis);
|
||||
if (data.contains("orbitSpeed")) m_OrbitSpeed = data.at("orbitSpeed").get<float>();
|
||||
if (data.contains("orbitAngle")) m_OrbitAngle = data.at("orbitAngle").get<float>();
|
||||
if (data.contains("orbitPhase")) m_OrbitPhase = data.at("orbitPhase").get<float>();
|
||||
if (data.contains("growPhase")) m_GrowPhase = data.at("growPhase").get<float>();
|
||||
if (data.contains("growSpeed")) m_GrowSpeed = data.at("growSpeed").get<float>();
|
||||
if (data.contains("growMin")) m_GrowMin = data.at("growMin").get<float>();
|
||||
if (data.contains("growMax")) m_GrowMax = data.at("growMax").get<float>();
|
||||
readVec3("spinAxis", m_SpinAxis);
|
||||
readVec3("baseScale", m_BaseScale);
|
||||
m_BaseScaleLoaded = data.contains("baseScale");
|
||||
if (data.contains("spinSpeed")) m_SpinSpeed = data.at("spinSpeed").get<float>();
|
||||
if (data.contains("materialId")) m_MaterialID = data.at("materialId").get<MaterialID>();
|
||||
|
||||
if (m_GrowMin > m_GrowMax) {
|
||||
std::swap(m_GrowMin, m_GrowMax);
|
||||
}
|
||||
if (glm::dot(m_SpinAxis, m_SpinAxis) < 1e-8f) {
|
||||
m_SpinAxis = glm::vec3(0, 1, 0);
|
||||
} else {
|
||||
m_SpinAxis = glm::normalize(m_SpinAxis);
|
||||
}
|
||||
BuildOrbitBasis();
|
||||
}
|
||||
|
||||
@@ -1 +1,19 @@
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
|
||||
nlohmann::json BoxCollider::Serialize() const {
|
||||
auto data = SerializeCollider();
|
||||
data["halfExtents"] = {m_HalfExtents.x, m_HalfExtents.y, m_HalfExtents.z};
|
||||
return data;
|
||||
}
|
||||
|
||||
void BoxCollider::Deserialize(const nlohmann::json& data) {
|
||||
DeserializeCollider(data);
|
||||
if (data.contains("halfExtents")) {
|
||||
const auto& extents = data.at("halfExtents");
|
||||
SetHalfExtents({
|
||||
extents.at(0).get<float>(),
|
||||
extents.at(1).get<float>(),
|
||||
extents.at(2).get<float>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,59 @@
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
|
||||
#include <destrum/Physics/PhysicsWorld.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] const char* RigidbodyTypeName(RigidbodyType type) {
|
||||
switch (type) {
|
||||
case RigidbodyType::Static:
|
||||
return "Static";
|
||||
case RigidbodyType::Kinematic:
|
||||
return "Kinematic";
|
||||
case RigidbodyType::Dynamic:
|
||||
default:
|
||||
return "Dynamic";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] RigidbodyType ParseRigidbodyType(const nlohmann::json& value) {
|
||||
if (value.is_string()) {
|
||||
const std::string type = value.get<std::string>();
|
||||
if (type == "Static") return RigidbodyType::Static;
|
||||
if (type == "Kinematic") return RigidbodyType::Kinematic;
|
||||
return RigidbodyType::Dynamic;
|
||||
}
|
||||
|
||||
const int type = value.get<int>();
|
||||
switch (type) {
|
||||
case 0: return RigidbodyType::Static;
|
||||
case 2: return RigidbodyType::Kinematic;
|
||||
case 1:
|
||||
default: return RigidbodyType::Dynamic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rigidbody::~Rigidbody() {
|
||||
if (m_World != nullptr) {
|
||||
m_World->UnregisterRigidbody(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void Rigidbody::Destroy() {
|
||||
if (m_World != nullptr) {
|
||||
m_World->UnregisterRigidbody(*this);
|
||||
}
|
||||
Component::Destroy();
|
||||
}
|
||||
|
||||
void Rigidbody::Start()
|
||||
{
|
||||
if (GetGameObject() != nullptr && GetGameObject()->GetScene() != nullptr) {
|
||||
GetGameObject()->GetScene()->GetPhysics().RegisterGameObject(*GetGameObject());
|
||||
}
|
||||
}
|
||||
|
||||
void Rigidbody::AttachPhysicsBody(PhysicsWorld* world, PhysicsBodyHandle body) {
|
||||
m_World = world;
|
||||
@@ -43,3 +96,35 @@ glm::vec3 Rigidbody::GetLinearVelocity() const {
|
||||
|
||||
return m_World->GetLinearVelocity(m_Body);
|
||||
}
|
||||
|
||||
nlohmann::json Rigidbody::Serialize() const {
|
||||
return {
|
||||
{"type", RigidbodyTypeName(m_Type)},
|
||||
{"mass", m_Mass},
|
||||
{"friction", m_Friction},
|
||||
{"restitution", m_Restitution},
|
||||
{"useGravity", m_UseGravity},
|
||||
{"allowSleep", m_AllowSleep}
|
||||
};
|
||||
}
|
||||
|
||||
void Rigidbody::Deserialize(const nlohmann::json& data) {
|
||||
if (data.contains("type")) {
|
||||
m_Type = ParseRigidbodyType(data.at("type"));
|
||||
}
|
||||
if (data.contains("mass")) {
|
||||
SetMass(data.at("mass").get<float>());
|
||||
}
|
||||
if (data.contains("friction")) {
|
||||
SetFriction(data.at("friction").get<float>());
|
||||
}
|
||||
if (data.contains("restitution")) {
|
||||
SetRestitution(data.at("restitution").get<float>());
|
||||
}
|
||||
if (data.contains("useGravity")) {
|
||||
m_UseGravity = data.at("useGravity").get<bool>();
|
||||
}
|
||||
if (data.contains("allowSleep")) {
|
||||
m_AllowSleep = data.at("allowSleep").get<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,14 @@
|
||||
#include <destrum/Components/Physics/SphereCollider.h>
|
||||
|
||||
nlohmann::json SphereCollider::Serialize() const {
|
||||
auto data = SerializeCollider();
|
||||
data["radius"] = m_Radius;
|
||||
return data;
|
||||
}
|
||||
|
||||
void SphereCollider::Deserialize(const nlohmann::json& data) {
|
||||
DeserializeCollider(data);
|
||||
if (data.contains("radius")) {
|
||||
SetRadius(data.at("radius").get<float>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <glm/gtc/quaternion.hpp> // glm::quat, glm::angleAxis
|
||||
#include <glm/gtx/quaternion.hpp> // operator*(quat, vec3)
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
glm::vec3 Rotator::MakePerpendicularUnitVector(const glm::vec3& axis)
|
||||
{
|
||||
@@ -84,12 +85,46 @@ void Rotator::SetDistance(float distance)
|
||||
m_InitialOffset = MakePerpendicularUnitVector(m_Axis) * m_Distance;
|
||||
}
|
||||
|
||||
void Rotator::Update()
|
||||
void Rotator::Update(float dt)
|
||||
{
|
||||
// Replace 0.001f with your engine delta time if you have one.
|
||||
m_CurrentAngle += m_Speed * 0.001f;
|
||||
m_CurrentAngle += m_Speed * dt;
|
||||
|
||||
const glm::quat q = glm::angleAxis(m_CurrentAngle, glm::normalize(m_Axis));
|
||||
const glm::vec3 rotatedOffset = q * m_InitialOffset;
|
||||
GetTransform().SetLocalPosition(m_Pivot + rotatedOffset);
|
||||
}
|
||||
|
||||
nlohmann::json Rotator::Serialize() const {
|
||||
return {
|
||||
{"distance", m_Distance},
|
||||
{"speed", m_Speed},
|
||||
{"currentAngle", m_CurrentAngle},
|
||||
{"pivot", {m_Pivot.x, m_Pivot.y, m_Pivot.z}},
|
||||
{"axis", {m_Axis.x, m_Axis.y, m_Axis.z}},
|
||||
{"initialOffset", {m_InitialOffset.x, m_InitialOffset.y, m_InitialOffset.z}}
|
||||
};
|
||||
}
|
||||
|
||||
void Rotator::Deserialize(const nlohmann::json& data) {
|
||||
if (data.contains("distance")) {
|
||||
m_Distance = data.at("distance").get<float>();
|
||||
}
|
||||
if (data.contains("speed")) {
|
||||
m_Speed = data.at("speed").get<float>();
|
||||
}
|
||||
if (data.contains("currentAngle")) {
|
||||
m_CurrentAngle = data.at("currentAngle").get<float>();
|
||||
}
|
||||
if (data.contains("pivot")) {
|
||||
const auto& value = data.at("pivot");
|
||||
m_Pivot = {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
||||
}
|
||||
if (data.contains("axis")) {
|
||||
const auto& value = data.at("axis");
|
||||
SetAxis({value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()});
|
||||
}
|
||||
if (data.contains("initialOffset")) {
|
||||
const auto& value = data.at("initialOffset");
|
||||
m_InitialOffset = {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,33 @@
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include "destrum/ObjectModel/Transform.h"
|
||||
#include "destrum/Util/DeltaTime.h"
|
||||
|
||||
void Spinner::Update()
|
||||
void Spinner::Update(float dt)
|
||||
{
|
||||
// Replace with your engine dt if you have it available in Component.
|
||||
const float dt = 1.0f / 60.0f;
|
||||
|
||||
|
||||
m_Angle += m_Speed * dt;
|
||||
|
||||
// If you already have SetLocalRotation / SetWorldRotation, use that.
|
||||
// Here I'm assuming you can set rotation as a quaternion or Euler somewhere.
|
||||
// If not, tell me your Transform rotation API and I’ll adjust.
|
||||
|
||||
const glm::quat q = glm::angleAxis(m_Angle, m_Axis);
|
||||
|
||||
// Example APIs you might have:
|
||||
// GetTransform().SetLocalRotation(q);
|
||||
// or GetTransform().SetWorldRotation(q);
|
||||
|
||||
GetTransform().SetLocalRotation(q);
|
||||
}
|
||||
|
||||
nlohmann::json Spinner::Serialize() const {
|
||||
return {
|
||||
{"axis", {m_Axis.x, m_Axis.y, m_Axis.z}},
|
||||
{"speed", m_Speed},
|
||||
{"angle", m_Angle}
|
||||
};
|
||||
}
|
||||
|
||||
void Spinner::Deserialize(const nlohmann::json& data) {
|
||||
if (data.contains("axis")) {
|
||||
const auto& value = data.at("axis");
|
||||
SetAxis({value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()});
|
||||
}
|
||||
if (data.contains("speed")) {
|
||||
m_Speed = data.at("speed").get<float>();
|
||||
}
|
||||
if (data.contains("angle")) {
|
||||
m_Angle = data.at("angle").get<float>();
|
||||
}
|
||||
}
|
||||
|
||||
+194
-81
@@ -1,95 +1,204 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
void AssetFS::Init(std::filesystem::path exeDir) {
|
||||
Mount("engine", exeDir / "assets/engine");
|
||||
Mount("game", exeDir / "assets/game");
|
||||
initialized = true;
|
||||
}
|
||||
namespace {
|
||||
struct ParsedVirtualPath {
|
||||
std::string scheme;
|
||||
std::filesystem::path relativePath;
|
||||
};
|
||||
|
||||
void AssetFS::Mount(std::string scheme, std::filesystem::path root) {
|
||||
spdlog::debug("Mounting assetfs scheme '{}' to root '{}'", scheme, root.string());
|
||||
|
||||
FSMount m;
|
||||
m.scheme = std::move(scheme);
|
||||
m.root = std::move(root);
|
||||
|
||||
const auto manifestPath = m.root / "manifest.json";
|
||||
if (std::filesystem::exists(manifestPath)) {
|
||||
m.manifest = FS::LoadAssetManifest(manifestPath);
|
||||
}
|
||||
|
||||
mounts.push_back(std::move(m));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> AssetFS::ReadBytes(std::string_view vpath) {
|
||||
assert(initialized && "AssetFS not initialized");
|
||||
// parse "engine://path/inside"
|
||||
auto pos = vpath.find("://");
|
||||
if (pos == std::string_view::npos) throw std::runtime_error("bad vpath");
|
||||
|
||||
std::string scheme(vpath.substr(0, pos));
|
||||
std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||
|
||||
for (auto& m : mounts) {
|
||||
if (m.scheme == scheme) {
|
||||
auto full = m.root / rel;
|
||||
return ReadFile(full);
|
||||
[[nodiscard]] std::filesystem::path ValidateRelativePath(std::string_view value) {
|
||||
if (value.empty()) {
|
||||
throw std::runtime_error("asset path is empty");
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("mount not found");
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetFullPath(std::string_view vpath) const {
|
||||
assert(initialized && "AssetFS not initialized");
|
||||
// parse "engine://path/inside"
|
||||
auto pos = vpath.find("://");
|
||||
if (pos == std::string_view::npos) throw std::runtime_error("bad vpath");
|
||||
|
||||
std::string scheme(vpath.substr(0, pos));
|
||||
std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||
|
||||
for (auto& m : mounts) {
|
||||
if (m.scheme == scheme) {
|
||||
auto full = m.root / rel;
|
||||
return full;
|
||||
const std::filesystem::path path{std::string(value)};
|
||||
if (path.empty() || path.is_absolute() || path.has_root_path()) {
|
||||
throw std::runtime_error("asset path must be relative");
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("mount not found");
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetCookedPathForFile(std::string_view vpath) const {
|
||||
assert(initialized && "AssetFS not initialized");
|
||||
|
||||
const auto pos = vpath.find("://");
|
||||
if (pos == std::string_view::npos)
|
||||
throw std::runtime_error("bad vpath");
|
||||
|
||||
const std::string scheme(vpath.substr(0, pos));
|
||||
const std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||
const std::string relStr = rel.generic_string();
|
||||
|
||||
for (const auto& m : mounts) {
|
||||
if (m.scheme != scheme) continue;
|
||||
|
||||
// If we have a manifest, consult it
|
||||
if (m.manifest) {
|
||||
if (const ManifestAsset* asset = m.manifest->FindBySrc(relStr)) {
|
||||
if (asset->out) {
|
||||
return m.root / *asset->out;
|
||||
}
|
||||
for (const auto& part : path) {
|
||||
if (part == "..") {
|
||||
throw std::runtime_error("asset path traversal is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to raw file
|
||||
return m.root / rel;
|
||||
const std::filesystem::path normalized = path.lexically_normal();
|
||||
if (normalized.empty() || normalized == ".") {
|
||||
throw std::runtime_error("asset path is invalid");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
throw std::runtime_error("mount not found");
|
||||
return {};
|
||||
[[nodiscard]] ParsedVirtualPath ParseVirtualPath(std::string_view value) {
|
||||
const std::size_t separator = value.find("://");
|
||||
if (separator == std::string_view::npos || separator == 0) {
|
||||
throw std::runtime_error("asset path must use scheme://relative/path syntax");
|
||||
}
|
||||
|
||||
const std::string scheme(value.substr(0, separator));
|
||||
if (scheme.find_first_of("/\\:") != std::string::npos) {
|
||||
throw std::runtime_error("asset scheme is invalid");
|
||||
}
|
||||
|
||||
return {
|
||||
scheme,
|
||||
ValidateRelativePath(value.substr(separator + 3))
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::filesystem::path ResolvePath(const FSMount& mount,
|
||||
const std::filesystem::path& relativePath) {
|
||||
const std::filesystem::path root = mount.root.lexically_normal();
|
||||
const std::filesystem::path full = (root / relativePath).lexically_normal();
|
||||
const std::filesystem::path relative = full.lexically_relative(root);
|
||||
|
||||
if (relative.empty() || relative.is_absolute() || relative == ".." ||
|
||||
relative.generic_string().starts_with("../")) {
|
||||
throw std::runtime_error("asset path escapes its mount");
|
||||
}
|
||||
|
||||
std::error_code error;
|
||||
const std::filesystem::path canonicalRoot = std::filesystem::weakly_canonical(root, error);
|
||||
if (error) {
|
||||
throw std::runtime_error("failed to resolve asset mount: " + root.string());
|
||||
}
|
||||
|
||||
error.clear();
|
||||
const std::filesystem::path canonicalFull = std::filesystem::weakly_canonical(full, error);
|
||||
if (error) {
|
||||
throw std::runtime_error("failed to resolve asset path: " + full.string());
|
||||
}
|
||||
|
||||
const std::filesystem::path canonicalRelative =
|
||||
canonicalFull.lexically_relative(canonicalRoot);
|
||||
if (canonicalRelative.empty() || canonicalRelative.is_absolute() ||
|
||||
canonicalRelative == ".." || canonicalRelative.generic_string().starts_with("../")) {
|
||||
throw std::runtime_error("asset path escapes its mount");
|
||||
}
|
||||
|
||||
return full;
|
||||
}
|
||||
|
||||
[[nodiscard]] const FSMount* FindMount(const std::vector<FSMount>& mounts,
|
||||
const std::string& scheme) {
|
||||
for (const FSMount& mount : mounts) {
|
||||
if (mount.scheme == scheme) {
|
||||
return &mount;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AssetFS::Init(std::filesystem::path exeDir) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t originalMountCount = mounts.size();
|
||||
try {
|
||||
Mount("engine", exeDir / "assets/engine");
|
||||
Mount("game", exeDir / "assets/game");
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
mounts.resize(originalMountCount);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void AssetFS::Reset() {
|
||||
mounts.clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void AssetFS::Mount(std::string scheme, std::filesystem::path root) {
|
||||
if (scheme.empty() || scheme.find_first_of("/\\:") != std::string::npos) {
|
||||
throw std::runtime_error("asset mount scheme is invalid");
|
||||
}
|
||||
if (FindMount(mounts, scheme) != nullptr) {
|
||||
throw std::runtime_error("asset mount already exists: " + scheme);
|
||||
}
|
||||
|
||||
FSMount mount;
|
||||
mount.scheme = std::move(scheme);
|
||||
mount.root = std::filesystem::absolute(std::move(root)).lexically_normal();
|
||||
|
||||
const std::filesystem::path manifestPath = mount.root / "manifest.json";
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_regular_file(manifestPath, error)) {
|
||||
mount.manifest = FS::LoadAssetManifest(manifestPath);
|
||||
}
|
||||
|
||||
spdlog::debug("Mounting assetfs scheme '{}' to root '{}'", mount.scheme, mount.root.string());
|
||||
mounts.push_back(std::move(mount));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> AssetFS::ReadBytes(std::string_view vpath) {
|
||||
return ReadFile(GetFullPath(vpath));
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetFullPath(std::string_view vpath) const {
|
||||
if (!initialized) {
|
||||
throw std::runtime_error("AssetFS is not initialized");
|
||||
}
|
||||
|
||||
const ParsedVirtualPath parsed = ParseVirtualPath(vpath);
|
||||
const FSMount* mount = FindMount(mounts, parsed.scheme);
|
||||
if (mount == nullptr) {
|
||||
throw std::runtime_error("asset mount not found: " + parsed.scheme);
|
||||
}
|
||||
|
||||
return ResolvePath(*mount, parsed.relativePath);
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetCookedPathForFile(std::string_view vpath) const {
|
||||
if (!initialized) {
|
||||
throw std::runtime_error("AssetFS is not initialized");
|
||||
}
|
||||
|
||||
const ParsedVirtualPath parsed = ParseVirtualPath(vpath);
|
||||
const FSMount* mount = FindMount(mounts, parsed.scheme);
|
||||
if (mount == nullptr) {
|
||||
throw std::runtime_error("asset mount not found: " + parsed.scheme);
|
||||
}
|
||||
|
||||
const std::filesystem::path rawPath = ResolvePath(*mount, parsed.relativePath);
|
||||
if (mount->manifest) {
|
||||
if (const ManifestAsset* asset = mount->manifest->FindBySrc(parsed.relativePath.generic_string())) {
|
||||
if (asset->out) {
|
||||
const std::filesystem::path cookedRelative = ValidateRelativePath(*asset->out);
|
||||
const std::filesystem::path cookedPath = ResolvePath(*mount, cookedRelative);
|
||||
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_regular_file(cookedPath, error)) {
|
||||
error.clear();
|
||||
const auto sourceTime = std::filesystem::last_write_time(rawPath, error);
|
||||
error.clear();
|
||||
const auto cookedTime = std::filesystem::last_write_time(cookedPath, error);
|
||||
if (!error && sourceTime > cookedTime) {
|
||||
throw std::runtime_error(
|
||||
"cooked asset output is stale: " + cookedPath.string());
|
||||
}
|
||||
return cookedPath;
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"cooked asset output is missing: " + cookedPath.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assets without a cooked output, such as shader includes, use the source path.
|
||||
return rawPath;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> AssetFS::ReadFile(const std::filesystem::path& fullPath) {
|
||||
@@ -99,10 +208,14 @@ std::vector<uint8_t> AssetFS::ReadFile(const std::filesystem::path& fullPath) {
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
std::streamsize size = file.tellg();
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size < 0) {
|
||||
throw std::runtime_error("failed to determine file size: " + fullPath.string());
|
||||
}
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<uint8_t> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
|
||||
std::vector<uint8_t> buffer(static_cast<std::size_t>(size));
|
||||
if (size > 0 && !file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
throw std::runtime_error("failed to read file: " + fullPath.string());
|
||||
}
|
||||
return buffer;
|
||||
|
||||
@@ -2,8 +2,30 @@
|
||||
#include <destrum/FS/Manifest.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] std::string NormalizeRelativePath(const std::string& value, const char* field) {
|
||||
const std::filesystem::path path(value);
|
||||
if (value.empty() || path.empty() || path.is_absolute() || path.has_root_path()) {
|
||||
throw std::runtime_error(std::string("Invalid manifest ") + field + " path: " + value);
|
||||
}
|
||||
|
||||
for (const auto& part : path) {
|
||||
if (part == "..") {
|
||||
throw std::runtime_error(std::string("Manifest ") + field + " path escapes its mount: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string normalized = path.lexically_normal().generic_string();
|
||||
if (normalized.empty() || normalized == ".") {
|
||||
throw std::runtime_error(std::string("Invalid manifest ") + field + " path: " + value);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
AssetManifest FS::LoadAssetManifest(const std::filesystem::path& manifestPath) {
|
||||
std::ifstream f(manifestPath);
|
||||
@@ -25,18 +47,25 @@ AssetManifest FS::LoadAssetManifest(const std::filesystem::path& manifestPath) {
|
||||
ManifestAsset asset;
|
||||
asset.src = a.at("src").get<std::string>();
|
||||
asset.type = a.at("type").get<std::string>();
|
||||
asset.mtime_epoch_ns = a.value("mtime_epoch_ns", 0);
|
||||
asset.size_bytes = a.value("size_bytes", 0);
|
||||
if (asset.type.empty()) {
|
||||
throw std::runtime_error("Invalid manifest asset type");
|
||||
}
|
||||
asset.mtime_epoch_ns = a.value("mtime_epoch_ns", std::int64_t{0});
|
||||
asset.size_bytes = a.value("size_bytes", std::uint64_t{0});
|
||||
|
||||
if (a.contains("out") && !a["out"].is_null()) {
|
||||
asset.out = a["out"].get<std::string>();
|
||||
}
|
||||
|
||||
// Normalize to forward slashes for cross-platform matching
|
||||
std::filesystem::path p(asset.src);
|
||||
asset.src = p.generic_string();
|
||||
asset.src = NormalizeRelativePath(asset.src, "source");
|
||||
if (asset.out) {
|
||||
*asset.out = NormalizeRelativePath(*asset.out, "output");
|
||||
}
|
||||
|
||||
manifest.assetsBySrc.emplace(asset.src, std::move(asset));
|
||||
const std::string source = asset.src;
|
||||
if (!manifest.assetsBySrc.emplace(source, std::move(asset)).second) {
|
||||
throw std::runtime_error("Duplicate manifest source path: " + source);
|
||||
}
|
||||
}
|
||||
|
||||
return manifest;
|
||||
|
||||
@@ -1,21 +1,64 @@
|
||||
#include <destrum/Graphics/BindlessSetManager.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <volk.h>
|
||||
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr std::uint32_t maxBindlessResources = 16536;
|
||||
constexpr std::uint32_t requestedMaxBindlessResources = 16536;
|
||||
constexpr std::uint32_t maxSamplers = 32;
|
||||
|
||||
constexpr std::uint32_t texturesBinding = 0;
|
||||
constexpr std::uint32_t samplersBinding = 1;
|
||||
}
|
||||
|
||||
void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
void BindlessSetManager::init(
|
||||
VkDevice device,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
float maxAnisotropy)
|
||||
{
|
||||
try {
|
||||
VkPhysicalDeviceDescriptorIndexingProperties indexingProperties{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES,
|
||||
};
|
||||
VkPhysicalDeviceMaintenance3Properties maintenanceProperties{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES,
|
||||
.pNext = &indexingProperties,
|
||||
};
|
||||
VkPhysicalDeviceProperties2 properties{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
.pNext = &maintenanceProperties,
|
||||
};
|
||||
vkGetPhysicalDeviceProperties2(physicalDevice, &properties);
|
||||
|
||||
if (indexingProperties.maxDescriptorSetUpdateAfterBindSamplers < maxSamplers ||
|
||||
indexingProperties.maxPerStageDescriptorUpdateAfterBindSamplers < maxSamplers) {
|
||||
throw std::runtime_error("The device exposes too few bindless samplers");
|
||||
}
|
||||
|
||||
const auto descriptorCapacityAfterSamplers = [](std::uint32_t capacity) {
|
||||
return capacity > maxSamplers ? capacity - maxSamplers : 0u;
|
||||
};
|
||||
const auto maxPerSetImages = descriptorCapacityAfterSamplers(
|
||||
maintenanceProperties.maxPerSetDescriptors);
|
||||
const auto maxAllPoolsImages = descriptorCapacityAfterSamplers(
|
||||
indexingProperties.maxUpdateAfterBindDescriptorsInAllPools);
|
||||
maxBindlessResources = std::min(
|
||||
requestedMaxBindlessResources,
|
||||
std::min({
|
||||
indexingProperties.maxDescriptorSetUpdateAfterBindSampledImages,
|
||||
indexingProperties.maxPerStageDescriptorUpdateAfterBindSampledImages,
|
||||
maxPerSetImages,
|
||||
maxAllPoolsImages,
|
||||
}));
|
||||
if (maxBindlessResources == 0) {
|
||||
throw std::runtime_error("The device exposes no bindless sampled-image capacity");
|
||||
}
|
||||
|
||||
{ // create pool
|
||||
const auto poolSizesBindless = std::array<VkDescriptorPoolSize, 2>{{
|
||||
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, maxBindlessResources},
|
||||
@@ -25,7 +68,7 @@ void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
const auto poolInfo = VkDescriptorPoolCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT,
|
||||
.maxSets = 10,
|
||||
.maxSets = 1,
|
||||
.poolSizeCount = static_cast<std::uint32_t>(poolSizesBindless.size()),
|
||||
.pPoolSizes = poolSizesBindless.data(),
|
||||
};
|
||||
@@ -78,17 +121,14 @@ void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
.pSetLayouts = &descSetLayout,
|
||||
};
|
||||
|
||||
std::uint32_t maxBinding = maxBindlessResources - 1;
|
||||
const auto countInfo = VkDescriptorSetVariableDescriptorCountAllocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO,
|
||||
.descriptorSetCount = 1,
|
||||
.pDescriptorCounts = &maxBinding,
|
||||
};
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(device, &allocInfo, &descSet));
|
||||
}
|
||||
|
||||
initDefaultSamplers(device, maxAnisotropy);
|
||||
initDefaultSamplers(device, maxAnisotropy);
|
||||
} catch (...) {
|
||||
cleanup(device);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotropy)
|
||||
@@ -96,7 +136,8 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
|
||||
// Keep in sync with bindless.glsl
|
||||
static const std::uint32_t nearestSamplerId = 0;
|
||||
static const std::uint32_t linearSamplerId = 1;
|
||||
static const std::uint32_t shadowSamplerId = 2;
|
||||
static const std::uint32_t anisotropicSamplerId = 2;
|
||||
static const std::uint32_t shadowSamplerId = 3;
|
||||
|
||||
{ // init nearest sampler
|
||||
const auto samplerCreateInfo = VkSamplerCreateInfo{
|
||||
@@ -115,15 +156,26 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
// TODO: make possible to disable anisotropy or set other values?
|
||||
.anisotropyEnable = VK_TRUE,
|
||||
.maxAnisotropy = maxAnisotropy,
|
||||
};
|
||||
VK_CHECK(vkCreateSampler(device, &samplerCreateInfo, nullptr, &linearSampler));
|
||||
vkutil::addDebugLabel(device, linearSampler, "linear");
|
||||
addSampler(device, linearSamplerId, linearSampler);
|
||||
}
|
||||
|
||||
{ // init anisotropic sampler
|
||||
const auto samplerCreateInfo = VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.anisotropyEnable = VK_TRUE,
|
||||
.maxAnisotropy = maxAnisotropy,
|
||||
};
|
||||
VK_CHECK(vkCreateSampler(device, &samplerCreateInfo, nullptr, &anisotropicSampler));
|
||||
vkutil::addDebugLabel(device, anisotropicSampler, "anisotropic");
|
||||
addSampler(device, anisotropicSamplerId, anisotropicSampler);
|
||||
}
|
||||
|
||||
{ // init shadow map sampler
|
||||
const auto samplerCreateInfo = VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
@@ -140,12 +192,47 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
|
||||
|
||||
void BindlessSetManager::cleanup(VkDevice device)
|
||||
{
|
||||
vkDestroySampler(device, nearestSampler, nullptr);
|
||||
vkDestroySampler(device, linearSampler, nullptr);
|
||||
vkDestroySampler(device, shadowMapSampler, nullptr);
|
||||
if (descPool != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(device, descPool, nullptr);
|
||||
}
|
||||
descPool = VK_NULL_HANDLE;
|
||||
}
|
||||
descSet = VK_NULL_HANDLE;
|
||||
|
||||
vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr);
|
||||
vkDestroyDescriptorPool(device, descPool, nullptr);
|
||||
if (descSetLayout != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr);
|
||||
}
|
||||
descSetLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (nearestSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, nearestSampler, nullptr);
|
||||
}
|
||||
nearestSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
if (linearSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, linearSampler, nullptr);
|
||||
}
|
||||
linearSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
if (anisotropicSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, anisotropicSampler, nullptr);
|
||||
}
|
||||
anisotropicSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
if (shadowMapSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, shadowMapSampler, nullptr);
|
||||
}
|
||||
shadowMapSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
maxBindlessResources = 0;
|
||||
}
|
||||
|
||||
void BindlessSetManager::addImage(
|
||||
@@ -153,8 +240,12 @@ void BindlessSetManager::addImage(
|
||||
std::uint32_t id,
|
||||
const VkImageView imageView)
|
||||
{
|
||||
if (id >= maxBindlessResources) {
|
||||
throw std::out_of_range("Bindless image descriptor capacity exceeded");
|
||||
}
|
||||
|
||||
const auto imageInfo = VkDescriptorImageInfo{
|
||||
.imageView = imageView, .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};
|
||||
.imageView = imageView, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
const auto writeSet = VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descSet,
|
||||
@@ -170,7 +261,7 @@ void BindlessSetManager::addImage(
|
||||
void BindlessSetManager::addSampler(const VkDevice device, std::uint32_t id, VkSampler sampler)
|
||||
{
|
||||
const auto imageInfo =
|
||||
VkDescriptorImageInfo{.sampler = sampler, .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};
|
||||
VkDescriptorImageInfo{.sampler = sampler, .imageLayout = VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
const auto writeSet = VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descSet,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
ImageCache::ImageCache(GfxDevice& gfxDevice) : gfxDevice(gfxDevice) {
|
||||
}
|
||||
@@ -36,44 +38,68 @@ ImageID ImageCache::loadImageFromFile(
|
||||
|
||||
auto image = std::move(imageOpt.value());
|
||||
|
||||
const auto id = getFreeImageId();
|
||||
|
||||
addImage(id, std::move(image));
|
||||
|
||||
loadedImagesInfo.emplace(
|
||||
id,
|
||||
LoadedImageInfo{
|
||||
.path = path,
|
||||
.intent = intent,
|
||||
.usage = usage,
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
|
||||
return id;
|
||||
ImageID id = NULL_IMAGE_ID;
|
||||
bool infoInserted = false;
|
||||
try {
|
||||
id = getFreeImageId();
|
||||
loadedImagesInfo.emplace(
|
||||
id,
|
||||
LoadedImageInfo{
|
||||
.path = path,
|
||||
.intent = intent,
|
||||
.usage = usage,
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
infoInserted = true;
|
||||
addImage(id, std::move(image));
|
||||
return id;
|
||||
} catch (...) {
|
||||
if (infoInserted) {
|
||||
loadedImagesInfo.erase(id);
|
||||
}
|
||||
gfxDevice.destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ImageID ImageCache::addImage(GPUImage image) {
|
||||
return addImage(getFreeImageId(), std::move(image));
|
||||
try {
|
||||
return addImage(getFreeImageId(), std::move(image));
|
||||
} catch (...) {
|
||||
gfxDevice.destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ImageID ImageCache::addImage(ImageID id, GPUImage image) {
|
||||
image.setBindlessId(static_cast<std::uint32_t>(id));
|
||||
|
||||
if (id < images.size()) {
|
||||
gfxDevice.destroyImage(images[id]);
|
||||
images[id] = std::move(image);
|
||||
} else {
|
||||
assert(id == images.size());
|
||||
images.push_back(std::move(image));
|
||||
if (id >= getMaxImageCount()) {
|
||||
throw std::out_of_range("ImageCache bindless capacity exhausted");
|
||||
}
|
||||
if (id > images.size()) {
|
||||
throw std::out_of_range("ImageCache image ID is not contiguous");
|
||||
}
|
||||
|
||||
bindlessSetManager.addImage(
|
||||
gfxDevice.getDevice(),
|
||||
id,
|
||||
images[id].imageView
|
||||
);
|
||||
try {
|
||||
image.setBindlessId(static_cast<std::uint32_t>(id));
|
||||
|
||||
return id;
|
||||
if (id < images.size()) {
|
||||
gfxDevice.destroyImage(images[id]);
|
||||
images[id] = std::move(image);
|
||||
} else {
|
||||
images.push_back(std::move(image));
|
||||
}
|
||||
|
||||
bindlessSetManager.addImage(
|
||||
gfxDevice.getDevice(),
|
||||
id,
|
||||
images[id].imageView
|
||||
);
|
||||
|
||||
return id;
|
||||
} catch (...) {
|
||||
gfxDevice.destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
const GPUImage& ImageCache::getImage(ImageID id) const {
|
||||
@@ -82,11 +108,17 @@ const GPUImage& ImageCache::getImage(ImageID id) const {
|
||||
}
|
||||
|
||||
ImageID ImageCache::getFreeImageId() const {
|
||||
return images.size();
|
||||
if (images.size() >= getMaxImageCount()) {
|
||||
throw std::out_of_range("ImageCache bindless capacity exhausted");
|
||||
}
|
||||
if (images.size() >= std::numeric_limits<ImageID>::max()) {
|
||||
throw std::out_of_range("ImageCache ID range exhausted");
|
||||
}
|
||||
return static_cast<ImageID>(images.size());
|
||||
}
|
||||
|
||||
void ImageCache::destroyImages() {
|
||||
for (const auto& image: images) {
|
||||
for (auto& image: images) {
|
||||
gfxDevice.destroyImage(image);
|
||||
}
|
||||
images.clear();
|
||||
|
||||
@@ -4,36 +4,58 @@
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
void MaterialCache::init(
|
||||
GfxDevice& gfxDevice,
|
||||
MaterialDefaultTextures defaults)
|
||||
{
|
||||
defaultTextures = defaults;
|
||||
try {
|
||||
defaultTextures = defaults;
|
||||
device = &gfxDevice;
|
||||
|
||||
materialDataBuffer = gfxDevice.createBuffer(
|
||||
MAX_MATERIALS * sizeof(MaterialData),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
materialDataBuffer = gfxDevice.createBuffer(
|
||||
MAX_MATERIALS * sizeof(MaterialData),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
vkutil::addDebugLabel(
|
||||
gfxDevice.getDevice(),
|
||||
materialDataBuffer.buffer,
|
||||
"material data");
|
||||
vkutil::addDebugLabel(
|
||||
gfxDevice.getDevice(),
|
||||
materialDataBuffer.buffer,
|
||||
"material data");
|
||||
|
||||
Material placeholderMaterial{};
|
||||
placeholderMaterial.name = "PLACEHOLDER_MATERIAL";
|
||||
placeholderMaterial.diffuseTexture = defaultTextures.white;
|
||||
Material placeholderMaterial{};
|
||||
placeholderMaterial.name = "PLACEHOLDER_MATERIAL";
|
||||
placeholderMaterial.diffuseTexture = defaultTextures.white;
|
||||
|
||||
placeholderMaterialId = addMaterial(placeholderMaterial);
|
||||
placeholderMaterialId = addMaterial(placeholderMaterial);
|
||||
gfxDevice.getMemoryManager().flushAllocation(materialDataBuffer);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialCache::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
gfxDevice.destroyBuffer(materialDataBuffer);
|
||||
materialDataBuffer = {};
|
||||
materials.clear();
|
||||
materialKeys.clear();
|
||||
placeholderMaterialId = NULL_MATERIAL_ID;
|
||||
device = nullptr;
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::addMaterial(Material material)
|
||||
{
|
||||
if (!materials.empty() && device != nullptr) {
|
||||
// The material buffer is shared by all in-flight frames. A newly
|
||||
// appended entry may be written while an older frame is reading it.
|
||||
device->waitIdle();
|
||||
}
|
||||
|
||||
const auto getTextureOrElse = [](ImageID imageId, ImageID placeholder)
|
||||
{
|
||||
return imageId != NULL_IMAGE_ID ? imageId : placeholder;
|
||||
@@ -42,7 +64,12 @@ MaterialID MaterialCache::addMaterial(Material material)
|
||||
MaterialData* data = static_cast<MaterialData*>(materialDataBuffer.info.pMappedData);
|
||||
|
||||
const auto id = getFreeMaterialId();
|
||||
assert(id < MAX_MATERIALS);
|
||||
if (id >= MAX_MATERIALS) {
|
||||
throw std::runtime_error("MaterialCache capacity exhausted");
|
||||
}
|
||||
if (data == nullptr) {
|
||||
throw std::runtime_error("MaterialCache material buffer is not mapped");
|
||||
}
|
||||
|
||||
data[id] = MaterialData{
|
||||
.baseColor = glm::vec4(material.baseColor, 1.0f),
|
||||
@@ -66,10 +93,30 @@ MaterialID MaterialCache::addMaterial(Material material)
|
||||
|
||||
.emissiveTex = defaultTextures.emissive,
|
||||
};
|
||||
device->getMemoryManager().flushAllocation(
|
||||
materialDataBuffer,
|
||||
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
|
||||
sizeof(MaterialData));
|
||||
|
||||
// The caller may update multiple materials in one frame; the range flush is
|
||||
// cheap for coherent allocations and required for non-coherent memory.
|
||||
// The cache does not own a device reference, so the renderer flushes the
|
||||
// exact range after batching updates.
|
||||
|
||||
const auto materialId = static_cast<MaterialID>(materials.size());
|
||||
std::string key = material.name.empty()
|
||||
? "material:" + std::to_string(materialId)
|
||||
: material.name;
|
||||
const std::string baseKey = key;
|
||||
std::size_t suffix = 1;
|
||||
while (std::find(materialKeys.begin(), materialKeys.end(), key) != materialKeys.end()) {
|
||||
key = baseKey + "#" + std::to_string(suffix++);
|
||||
}
|
||||
|
||||
materials.push_back(std::move(material));
|
||||
materialKeys.push_back(std::move(key));
|
||||
|
||||
return id;
|
||||
return materialId;
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::addSimpleTextureMaterial(ImageID textureID)
|
||||
@@ -88,9 +135,23 @@ const Material& MaterialCache::getMaterial(MaterialID id) const
|
||||
return materials.at(id);
|
||||
}
|
||||
|
||||
const std::string& MaterialCache::getMaterialKey(MaterialID id) const
|
||||
{
|
||||
return materialKeys.at(id);
|
||||
}
|
||||
|
||||
std::optional<MaterialID> MaterialCache::findMaterialByKey(std::string_view key) const
|
||||
{
|
||||
const auto it = std::find(materialKeys.begin(), materialKeys.end(), key);
|
||||
if (it == materialKeys.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<MaterialID>(std::distance(materialKeys.begin(), it));
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::getFreeMaterialId() const
|
||||
{
|
||||
return materials.size();
|
||||
return static_cast<MaterialID>(materials.size());
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::getPlaceholderMaterialId() const
|
||||
@@ -136,4 +197,8 @@ void MaterialCache::updateMaterialGPU(MaterialID id)
|
||||
.metallicRoughnessTex = defaultTextures.metallicRoughness,
|
||||
.emissiveTex = defaultTextures.emissive,
|
||||
};
|
||||
device->getMemoryManager().flushAllocation(
|
||||
materialDataBuffer,
|
||||
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
|
||||
sizeof(MaterialData));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "volk.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
@@ -12,15 +13,25 @@ ComputePipeline::ComputePipeline(GfxDevice& device,
|
||||
const std::string& compPath,
|
||||
const ComputePipelineConfigInfo& configInfo)
|
||||
: m_device(device) {
|
||||
CreateComputePipeline(compPath, configInfo);
|
||||
try {
|
||||
CreateComputePipeline(compPath, configInfo);
|
||||
} catch (...) {
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_compShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_computePipeline, nullptr);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ComputePipeline::~ComputePipeline() {
|
||||
if (m_compShaderModule != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_compShaderModule, nullptr);
|
||||
}
|
||||
if (m_computePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(m_device.getDevice(), m_computePipeline, nullptr);
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
if (m_compShaderModule != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_compShaderModule, nullptr);
|
||||
}
|
||||
if (m_computePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(m_device.getDevice(), m_computePipeline, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+123
-576
@@ -1,260 +1,158 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
#define VOLK_IMPLEMENTATION
|
||||
#include <volk.h>
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <filesystem>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Init.h>
|
||||
#include <destrum/Graphics/Pipelines/ImguiPass.h>
|
||||
|
||||
|
||||
#include "destrum/Graphics/imageLoader.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include "tracy/TracyVulkan.hpp"
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/TracyVulkan.hpp>
|
||||
|
||||
GfxDevice::GfxDevice() {
|
||||
}
|
||||
|
||||
GfxDevice::~GfxDevice() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync) {
|
||||
VK_CHECK(volkInitialize());
|
||||
m_vSync = vSync;
|
||||
instance = vkb::InstanceBuilder{}
|
||||
.set_app_name(appName.c_str())
|
||||
.set_app_version(1, 0, 0)
|
||||
.request_validation_layers()
|
||||
.use_default_debug_messenger()
|
||||
.require_api_version(1, 3, 0)
|
||||
.build()
|
||||
.value();
|
||||
|
||||
volkLoadInstance(instance);
|
||||
|
||||
const auto res = SDL_Vulkan_CreateSurface(window, instance, &surface);
|
||||
if (res != SDL_TRUE) {
|
||||
spdlog::error("Failed to create Vulkan surface: {}", SDL_GetError());
|
||||
std::exit(1);
|
||||
if (initialized) {
|
||||
throw std::logic_error("GfxDevice::init called twice");
|
||||
}
|
||||
if (window == nullptr) {
|
||||
throw std::invalid_argument("GfxDevice::init requires a valid window");
|
||||
}
|
||||
m_vSync = vSync;
|
||||
|
||||
constexpr auto deviceFeatures = VkPhysicalDeviceFeatures{
|
||||
.imageCubeArray = VK_TRUE,
|
||||
.geometryShader = VK_TRUE, // for im3d
|
||||
.depthClamp = VK_TRUE,
|
||||
.fillModeNonSolid = VK_TRUE,
|
||||
.samplerAnisotropy = VK_TRUE
|
||||
};
|
||||
instanceManager.init(window, appName);
|
||||
|
||||
constexpr auto features12 = VkPhysicalDeviceVulkan12Features{
|
||||
.descriptorIndexing = true,
|
||||
.descriptorBindingSampledImageUpdateAfterBind = true,
|
||||
.descriptorBindingStorageImageUpdateAfterBind = true,
|
||||
.descriptorBindingPartiallyBound = true,
|
||||
.descriptorBindingVariableDescriptorCount = true,
|
||||
.runtimeDescriptorArray = true,
|
||||
.scalarBlockLayout = true,
|
||||
.bufferDeviceAddress = true,
|
||||
};
|
||||
constexpr auto features13 = VkPhysicalDeviceVulkan13Features{
|
||||
.synchronization2 = true,
|
||||
.dynamicRendering = true,
|
||||
};
|
||||
const auto extendedDynamicState3Features =
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
.extendedDynamicState3PolygonMode = VK_TRUE,
|
||||
};
|
||||
memoryManager.init(instanceManager);
|
||||
|
||||
physicalDevice = vkb::PhysicalDeviceSelector{instance}
|
||||
.set_minimum_version(1, 3)
|
||||
.set_required_features(deviceFeatures)
|
||||
.set_required_features_12(features12)
|
||||
.set_required_features_13(features13)
|
||||
.add_required_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
|
||||
.add_required_extension_features(extendedDynamicState3Features)
|
||||
.set_surface(surface)
|
||||
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
||||
.select()
|
||||
.value();
|
||||
executor.init(instanceManager.getDevice(), instanceManager.getGraphicsQueueFamily(), instanceManager.getGraphicsQueue());
|
||||
|
||||
device = vkb::DeviceBuilder{physicalDevice}.build().value();
|
||||
volkLoadDevice(device);
|
||||
frameManager.init(instanceManager.getDevice(), instanceManager.getGraphicsQueueFamily());
|
||||
|
||||
#if defined(TRACY_ENABLE)
|
||||
instanceManager.initTracy(executor.getCommandBuffer());
|
||||
#endif
|
||||
|
||||
graphicsQueueFamily = device.get_queue_index(vkb::QueueType::graphics).value();
|
||||
graphicsQueue = device.get_queue(vkb::QueueType::graphics).value();
|
||||
|
||||
//Vma
|
||||
const auto vulkanFunctions = VmaVulkanFunctions{
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
};
|
||||
|
||||
const auto allocatorInfo = VmaAllocatorCreateInfo{
|
||||
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
|
||||
.physicalDevice = physicalDevice,
|
||||
.device = device,
|
||||
.pVulkanFunctions = &vulkanFunctions,
|
||||
.instance = instance,
|
||||
};
|
||||
vmaCreateAllocator(&allocatorInfo, &allocator);
|
||||
|
||||
executor.init(device, graphicsQueueFamily, graphicsQueue);
|
||||
|
||||
imageManager.init(
|
||||
instanceManager.getDevice(),
|
||||
instanceManager.getPhysicalDevice(),
|
||||
memoryManager,
|
||||
executor);
|
||||
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
swapchainFormat = VK_FORMAT_B8G8R8A8_SRGB;
|
||||
swapchain.createSwapchain(this, swapchainFormat, w, h, vSync);
|
||||
|
||||
VkPhysicalDeviceProperties props{};
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &props);
|
||||
swapchain.createSwapchain(
|
||||
instanceManager.getDevice(),
|
||||
instanceManager.getVkbDevice(),
|
||||
instanceManager.getSurface(),
|
||||
swapchainFormat,
|
||||
static_cast<std::uint32_t>(w),
|
||||
static_cast<std::uint32_t>(h),
|
||||
vSync);
|
||||
|
||||
// imageCache.bindlessSetManager.init(device, props.limits.maxSamplerAnisotropy);
|
||||
swapchainFormat = swapchain.getFormat();
|
||||
|
||||
swapchain.initSync(device);
|
||||
|
||||
const auto poolCreateInfo = vkinit::commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||
|
||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
auto& commandPool = frames[i].commandPool;
|
||||
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool));
|
||||
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
|
||||
auto& mainCommandBuffer = frames[i].commandBuffer;
|
||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
||||
}
|
||||
|
||||
#if defined(TRACY_ENABLE)
|
||||
{
|
||||
VkCommandBuffer tracyInitCmd = frames[0].commandBuffer;
|
||||
|
||||
#if defined(TRACY_VK_USE_SYMBOL_TABLE)
|
||||
tracyVkCtx = TracyVkContext(
|
||||
instance,
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd,
|
||||
vkGetInstanceProcAddr,
|
||||
vkGetDeviceProcAddr
|
||||
);
|
||||
#else
|
||||
tracyVkCtx = TracyVkContext(
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd
|
||||
);
|
||||
#endif
|
||||
|
||||
static constexpr char ctxName[] = "Graphics Queue";
|
||||
TracyVkContextName(tracyVkCtx, ctxName, sizeof(ctxName) - 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// { // create white texture
|
||||
// std::uint32_t pixel = 0xFFFFFFFF;
|
||||
// whiteImageId = createImage(
|
||||
// {
|
||||
// .format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
// .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
// .extent = VkExtent3D{1, 1, 1},
|
||||
// },
|
||||
// "white texture",
|
||||
// &pixel);
|
||||
// }
|
||||
//
|
||||
// { // create error texture (black/magenta checker)
|
||||
// constexpr auto black = 0xFF000000;
|
||||
// constexpr auto magenta = 0xFFFF00FF;
|
||||
//
|
||||
// std::array<std::uint32_t, 4> pixels{black, magenta, magenta, black};
|
||||
// errorImageId = createImage(
|
||||
// {
|
||||
// .format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
// .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
// VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
|
||||
// .extent = VkExtent3D{2, 2, 1},
|
||||
// },
|
||||
// "error texture",
|
||||
// pixels.data());
|
||||
// imageCache.setErrorImageId(errorImageId);
|
||||
// }
|
||||
swapchain.initSync(instanceManager.getDevice());
|
||||
|
||||
GameState::GetInstance().SetGfxDevice(this);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void GfxDevice::cleanup() {
|
||||
if (!initialized &&
|
||||
instanceManager.getDevice() == VK_NULL_HANDLE &&
|
||||
instanceManager.getInstance() == VK_NULL_HANDLE &&
|
||||
memoryManager.getAllocator() == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (instanceManager.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(instanceManager.getDevice());
|
||||
}
|
||||
|
||||
swapchain.cleanup(instanceManager.getDevice());
|
||||
frameManager.cleanup(instanceManager.getDevice());
|
||||
executor.cleanup(instanceManager.getDevice());
|
||||
memoryManager.cleanup(instanceManager.getDevice());
|
||||
instanceManager.cleanup();
|
||||
|
||||
GameState::GetInstance().SetGfxDevice(nullptr);
|
||||
frameActive = false;
|
||||
swapchainFormat = VK_FORMAT_UNDEFINED;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void GfxDevice::recreateSwapchain(int width, int height) {
|
||||
assert(width != 0 && height != 0);
|
||||
if (!initialized) {
|
||||
throw std::logic_error("GfxDevice::recreateSwapchain called before init");
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw std::invalid_argument("GfxDevice::recreateSwapchain requires a non-zero extent");
|
||||
}
|
||||
waitIdle();
|
||||
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, m_vSync);
|
||||
|
||||
swapchain.recreateSwapchain(
|
||||
instanceManager.getDevice(),
|
||||
instanceManager.getVkbDevice(),
|
||||
instanceManager.getSurface(),
|
||||
swapchainFormat,
|
||||
static_cast<std::uint32_t>(width),
|
||||
static_cast<std::uint32_t>(height),
|
||||
m_vSync);
|
||||
|
||||
swapchainFormat = swapchain.getFormat();
|
||||
}
|
||||
|
||||
VkCommandBuffer GfxDevice::beginFrame()
|
||||
{
|
||||
ZoneScopedN("GfxDevice::beginFrame");
|
||||
|
||||
const auto frameIndex = getCurrentFrameIndex();
|
||||
|
||||
{
|
||||
ZoneScopedN("Swapchain BeginFrame");
|
||||
swapchain.beginFrame(getCurrentFrameIndex());
|
||||
swapchain.beginFrame(instanceManager.getDevice(), frameIndex);
|
||||
}
|
||||
|
||||
const auto& frame = getCurrentFrame();
|
||||
const auto& cmd = frame.commandBuffer;
|
||||
|
||||
const auto cmdBeginInfo = VkCommandBufferBeginInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
|
||||
{
|
||||
ZoneScopedN("vkBeginCommandBuffer");
|
||||
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
|
||||
const auto acquire = swapchain.acquireNextImage(instanceManager.getDevice(), frameIndex);
|
||||
if (!acquire.hasImage()) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
return cmd;
|
||||
activeFrameIndex = frameIndex;
|
||||
activeSwapchainImageIndex = acquire.imageIndex;
|
||||
frameActive = true;
|
||||
|
||||
return frameManager.beginFrame();
|
||||
}
|
||||
|
||||
VulkanImmediateExecutor& GfxDevice::GetImmediateExecuter() {
|
||||
VulkanImmediateExecutor& GfxDevice::getImmediateExecuter() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const EndFrameProps& props) {
|
||||
ZoneScopedN("GfxDevice::endFrame");
|
||||
|
||||
// get swapchain image
|
||||
VkImage swapchainImage = VK_NULL_HANDLE;
|
||||
std::uint32_t swapchainImageIndex = 0;
|
||||
|
||||
{
|
||||
ZoneScopedN("Swapchain AcquireNextImage");
|
||||
|
||||
const auto result = swapchain.acquireNextImage(getCurrentFrameIndex());
|
||||
swapchainImage = result.first;
|
||||
swapchainImageIndex = result.second;
|
||||
if (!frameActive || cmd == VK_NULL_HANDLE) {
|
||||
throw std::logic_error("GfxDevice::endFrame called without an active frame");
|
||||
}
|
||||
|
||||
if (swapchainImage == VK_NULL_HANDLE) {
|
||||
spdlog::info("Swapchain is freaky, skipping frame...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fences are reset here to prevent the deadlock in case swapchain becomes dirty
|
||||
{
|
||||
ZoneScopedN("Swapchain ResetFences");
|
||||
swapchain.resetFences(getCurrentFrameIndex());
|
||||
}
|
||||
const VkImage swapchainImage = swapchain.getImages().at(activeSwapchainImageIndex);
|
||||
const auto swapchainImageIndex = activeSwapchainImageIndex;
|
||||
|
||||
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED; {
|
||||
ZoneScopedN("Clear Swapchain Image");
|
||||
@@ -270,45 +168,26 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
||||
if (true) {
|
||||
ZoneScopedN("Copy DrawImage To Swapchain");
|
||||
|
||||
// copy from draw image into swapchain
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
|
||||
drawImage.layout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
drawImage.setLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
vkutil::transitionImage(
|
||||
cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
swapchainLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
|
||||
auto filter = false ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
||||
filter = VK_FILTER_NEAREST;
|
||||
if (false) {
|
||||
vkutil::copyImageToImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
swapchainImage,
|
||||
drawImage.getExtent2D(),
|
||||
props.drawImageBlitRect.x,
|
||||
props.drawImageBlitRect.y,
|
||||
props.drawImageBlitRect.z,
|
||||
props.drawImageBlitRect.w,
|
||||
filter);
|
||||
} else {
|
||||
// will stretch image to swapchain
|
||||
vkutil::copyImageToImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
swapchainImage,
|
||||
drawImage.getExtent2D(),
|
||||
getSwapchainExtent(),
|
||||
filter);
|
||||
}
|
||||
auto filter = VK_FILTER_NEAREST;
|
||||
vkutil::copyImageToImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
swapchainImage,
|
||||
drawImage.getExtent2D(),
|
||||
getSwapchainExtent(),
|
||||
filter);
|
||||
}
|
||||
|
||||
// prepare for present
|
||||
// vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
// swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
if (props.imguiPass) {
|
||||
ZoneScopedN("ImGui Render");
|
||||
|
||||
@@ -321,7 +200,6 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
||||
);
|
||||
}
|
||||
|
||||
// prepare for present
|
||||
{
|
||||
ZoneScopedN("Transition Swapchain To Present");
|
||||
|
||||
@@ -329,369 +207,38 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
||||
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
}
|
||||
#if defined(TRACY_ENABLE)
|
||||
TracyVkCollect(tracyVkCtx, cmd);
|
||||
TracyVkCollect(instanceManager.getTracyVkCtx(), cmd);
|
||||
#endif
|
||||
{
|
||||
ZoneScopedN("vkEndCommandBuffer");
|
||||
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||
}
|
||||
|
||||
// swapchain.submitAndPresent(cmd, graphicsQueue, getCurrentFrameIndex(), swapchainImageIndex);
|
||||
frameManager.endFrame(cmd);
|
||||
|
||||
{
|
||||
ZoneScopedN("Swapchain SubmitAndPresent");
|
||||
swapchain.submitAndPresent(cmd, graphicsQueue, swapchainImageIndex, getCurrentFrameIndex());
|
||||
try {
|
||||
swapchain.submitAndPresent(
|
||||
instanceManager.getDevice(),
|
||||
cmd,
|
||||
instanceManager.getGraphicsQueue(),
|
||||
instanceManager.getPresentQueue(),
|
||||
swapchainImageIndex,
|
||||
activeFrameIndex);
|
||||
} catch (...) {
|
||||
frameManager.nextFrame();
|
||||
frameActive = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
frameNumber++;
|
||||
frameManager.nextFrame();
|
||||
frameActive = false;
|
||||
|
||||
FrameMark;
|
||||
}
|
||||
|
||||
void GfxDevice::cleanup() {
|
||||
#if defined(TRACY_ENABLE)
|
||||
if (tracyVkCtx) {
|
||||
TracyVkDestroy(tracyVkCtx);
|
||||
tracyVkCtx = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GfxDevice::waitIdle() {
|
||||
VK_CHECK(vkDeviceWaitIdle(device));
|
||||
frameManager.waitIdle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GfxDevice::immediateSubmit(ImmediateExecuteFunction&& f) const {
|
||||
executor.immediateSubmit(std::move(f));
|
||||
}
|
||||
|
||||
GPUBuffer GfxDevice::createBuffer(
|
||||
std::size_t allocSize,
|
||||
VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage) const {
|
||||
const auto bufferInfo = VkBufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = allocSize,
|
||||
.usage = usage,
|
||||
};
|
||||
|
||||
const auto allocInfo = VmaAllocationCreateInfo{
|
||||
.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
||||
// TODO: allow to set VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT when needed
|
||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
|
||||
.usage = memoryUsage,
|
||||
};
|
||||
|
||||
GPUBuffer buffer{};
|
||||
VK_CHECK(vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer.buffer, &buffer.allocation, &buffer.info));
|
||||
if ((usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0) {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
buffer.address = vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
VkDeviceAddress GfxDevice::getBufferAddress(const GPUBuffer& buffer) const {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
return vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
void GfxDevice::destroyBuffer(const GPUBuffer& buffer) const {
|
||||
vmaDestroyBuffer(allocator, buffer.buffer, buffer.allocation);
|
||||
}
|
||||
|
||||
//
|
||||
// GPUImage GfxDevice::loadImageFromFileRaw(
|
||||
// const std::filesystem::path& path,
|
||||
// VkFormat format,
|
||||
// VkImageUsageFlags usage,
|
||||
// bool mipMap) const
|
||||
// {
|
||||
// auto data = util::loadImage(path);
|
||||
// if (!data.pixels) {
|
||||
// fmt::println("[error] failed to load image from '{}'", path.string());
|
||||
// return getImage(errorImageId);
|
||||
// }
|
||||
//
|
||||
// auto image = createImageRaw({
|
||||
// .format = format,
|
||||
// .usage = usage | //
|
||||
// VK_IMAGE_USAGE_TRANSFER_DST_BIT | // for uploading pixel data to image
|
||||
// VK_IMAGE_USAGE_TRANSFER_SRC_BIT, // for generating mips
|
||||
// .extent =
|
||||
// VkExtent3D{
|
||||
// .width = (std::uint32_t)data.width,
|
||||
// .height = (std::uint32_t)data.height,
|
||||
// .depth = 1,
|
||||
// },
|
||||
// .mipMap = mipMap,
|
||||
// });
|
||||
// uploadImageData(image, data.pixels);
|
||||
//
|
||||
// image.debugName = path.string();
|
||||
// vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
//
|
||||
// return image;
|
||||
// }
|
||||
|
||||
|
||||
GPUImage GfxDevice::createImageRaw(
|
||||
const vkutil::CreateImageInfo& createInfo,
|
||||
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo) const {
|
||||
std::uint32_t mipLevels = 1;
|
||||
if (createInfo.mipMap) {
|
||||
const auto maxExtent = std::max(createInfo.extent.width, createInfo.extent.height);
|
||||
mipLevels = (std::uint32_t)std::floor(std::log2(maxExtent)) + 1;
|
||||
}
|
||||
|
||||
if (createInfo.isCubemap) {
|
||||
assert(createInfo.numLayers % 6 == 0);
|
||||
// assert(!createInfo.mipMap);
|
||||
assert((createInfo.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) != 0);
|
||||
}
|
||||
|
||||
auto imgInfo = VkImageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.flags = createInfo.flags,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = createInfo.format,
|
||||
.extent = createInfo.extent,
|
||||
.mipLevels = mipLevels,
|
||||
.arrayLayers = createInfo.numLayers,
|
||||
.samples = createInfo.samples,
|
||||
.tiling = createInfo.tiling,
|
||||
.usage = createInfo.usage,
|
||||
};
|
||||
|
||||
static const auto defaultAllocInfo = VmaAllocationCreateInfo{
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
};
|
||||
const auto allocInfo = customAllocationCreateInfo.has_value() ? customAllocationCreateInfo.value() : defaultAllocInfo;
|
||||
|
||||
GPUImage image{};
|
||||
image.format = createInfo.format;
|
||||
image.usage = createInfo.usage;
|
||||
image.extent = createInfo.extent;
|
||||
image.mipLevels = mipLevels;
|
||||
image.numLayers = createInfo.numLayers;
|
||||
image.isCubemap = createInfo.isCubemap;
|
||||
|
||||
VK_CHECK(vmaCreateImage(allocator, &imgInfo, &allocInfo, &image.image, &image.allocation, nullptr));
|
||||
|
||||
// create view only when usage flags allow it
|
||||
bool shouldCreateView = ((createInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0);
|
||||
|
||||
if (shouldCreateView) {
|
||||
VkImageAspectFlags aspectFlag = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
if (createInfo.format == VK_FORMAT_D32_SFLOAT) {
|
||||
// TODO: support other depth formats
|
||||
aspectFlag = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
|
||||
auto viewType =
|
||||
createInfo.numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
if (createInfo.isCubemap && createInfo.numLayers == 6) {
|
||||
viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
}
|
||||
|
||||
const auto viewCreateInfo = VkImageViewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image.image,
|
||||
.viewType = viewType,
|
||||
.format = createInfo.format,
|
||||
.subresourceRange =
|
||||
VkImageSubresourceRange{
|
||||
.aspectMask = aspectFlag,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = mipLevels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = createInfo.numLayers,
|
||||
},
|
||||
};
|
||||
|
||||
VK_CHECK(vkCreateImageView(device, &viewCreateInfo, nullptr, &image.imageView));
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::optional<GPUImage> GfxDevice::loadImageFromFileRaw(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent) const
|
||||
{
|
||||
const auto data = util::loadImage(path, intent);
|
||||
|
||||
if (data.vkFormat == VK_FORMAT_UNDEFINED ||
|
||||
data.byteSize == 0 ||
|
||||
(data.hdr ? data.hdrPixels == nullptr : data.pixels == nullptr))
|
||||
{
|
||||
spdlog::error("Failed to load image '{}'", path.string());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto image = createImageRaw({
|
||||
.format = data.vkFormat,
|
||||
.usage = usage |
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
(mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
||||
.extent = VkExtent3D{
|
||||
.width = static_cast<std::uint32_t>(data.width),
|
||||
.height = static_cast<std::uint32_t>(data.height),
|
||||
.depth = 1,
|
||||
},
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
|
||||
const void* src =
|
||||
data.hdr
|
||||
? static_cast<const void*>(data.hdrPixels)
|
||||
: static_cast<const void*>(data.pixels);
|
||||
|
||||
uploadImageDataSized(image, src, data.byteSize, 0);
|
||||
|
||||
image.debugName = path.string();
|
||||
vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// void GfxDevice::uploadImageData(const GPUImage& image, void* pixelData, std::uint32_t layer) const {
|
||||
// VkDeviceSize dataSize =
|
||||
// VkDeviceSize(image.extent.depth) *
|
||||
// image.extent.width *
|
||||
// image.extent.height *
|
||||
// BytesPerTexel(image.format);
|
||||
//
|
||||
// auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
// memcpy(uploadBuffer.info.pMappedData, pixelData, size_t(dataSize));
|
||||
//
|
||||
// executor.immediateSubmit([&] (VkCommandBuffer cmd) {
|
||||
// assert(
|
||||
// (image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
|
||||
// "Image needs to have VK_IMAGE_USAGE_TRANSFER_DST_BIT to upload data to it");
|
||||
// vkutil::transitionImage(
|
||||
// cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
//
|
||||
// const auto copyRegion = VkBufferImageCopy{
|
||||
// .bufferOffset = 0,
|
||||
// .bufferRowLength = 0,
|
||||
// .bufferImageHeight = 0,
|
||||
// .imageSubresource =
|
||||
// {
|
||||
// .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
// .mipLevel = 0,
|
||||
// .baseArrayLayer = layer,
|
||||
// .layerCount = 1,
|
||||
// },
|
||||
// .imageExtent = image.extent,
|
||||
// };
|
||||
//
|
||||
// vkCmdCopyBufferToImage(
|
||||
// cmd,
|
||||
// uploadBuffer.buffer,
|
||||
// image.image,
|
||||
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
// 1,
|
||||
// ©Region);
|
||||
//
|
||||
// if (image.mipLevels > 1) {
|
||||
// assert(
|
||||
// (image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
|
||||
// (image.usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0 &&
|
||||
// "Image needs to have VK_IMAGE_USAGE_TRANSFER_{DST,SRC}_BIT to generate mip maps");
|
||||
// // graphics::generateMipmaps(
|
||||
// // cmd,
|
||||
// // image.image,
|
||||
// // VkExtent2D{image.extent.width, image.extent.height},
|
||||
// // image.mipLevels);
|
||||
// spdlog::warn("Yea dawg, i ain't written this yet :pray:");
|
||||
// } else {
|
||||
// vkutil::transitionImage(
|
||||
// cmd,
|
||||
// image.image,
|
||||
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// destroyBuffer(uploadBuffer);
|
||||
// }
|
||||
|
||||
void GfxDevice::uploadImageDataSized(const GPUImage& image, const void* pixelData, std::size_t byteSize, std::uint32_t layer) const {
|
||||
auto uploadBuffer = createBuffer(byteSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// safety checks
|
||||
assert(uploadBuffer.info.pMappedData);
|
||||
assert(pixelData);
|
||||
assert(byteSize > 0);
|
||||
|
||||
std::memcpy(uploadBuffer.info.pMappedData, pixelData, byteSize);
|
||||
|
||||
executor.immediateSubmit([&] (VkCommandBuffer cmd) {
|
||||
vkutil::transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
VkBufferImageCopy copyRegion{};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copyRegion.imageSubresource.mipLevel = 0;
|
||||
copyRegion.imageSubresource.baseArrayLayer = layer;
|
||||
copyRegion.imageSubresource.layerCount = 1;
|
||||
copyRegion.imageExtent = image.extent;
|
||||
|
||||
vkCmdCopyBufferToImage(cmd, uploadBuffer.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region);
|
||||
|
||||
vkutil::transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
});
|
||||
|
||||
destroyBuffer(uploadBuffer);
|
||||
}
|
||||
|
||||
// GPUImage GfxDevice::loadImageFromFileRaw(const std::filesystem::path& path, VkImageUsageFlags usage, bool mipMap) const {
|
||||
// const auto data = util::loadImage(path);
|
||||
// const bool isHdr = data.hdr && data.hdrPixels;
|
||||
// const bool isLdr = !data.hdr && data.pixels;
|
||||
//
|
||||
// if (!isHdr && !isLdr || data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0) {
|
||||
// spdlog::error("failed to load image from '{}'", path.string());
|
||||
// return getImage(errorImageId);
|
||||
// }
|
||||
//
|
||||
// auto image = createImageRaw({
|
||||
// .format = data.vkFormat,
|
||||
// .usage = usage |
|
||||
// VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
// (mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
||||
// .extent = VkExtent3D{
|
||||
// .width = (std::uint32_t)data.width,
|
||||
// .height = (std::uint32_t)data.height,
|
||||
// .depth = 1,
|
||||
// },
|
||||
// .mipMap = mipMap,
|
||||
// });
|
||||
//
|
||||
// const void* src = isHdr ? (const void*)data.hdrPixels : (const void*)data.pixels;
|
||||
// uploadImageDataSized(image, src, data.byteSize, 0);
|
||||
//
|
||||
// image.debugName = path.string();
|
||||
// vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
// return image;
|
||||
// }
|
||||
|
||||
void GfxDevice::destroyImage(const GPUImage& image) const {
|
||||
vkDestroyImageView(device, image.imageView, nullptr);
|
||||
vmaDestroyImage(allocator, image.image, image.allocation);
|
||||
// TODO: if image has bindless id, update the set
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <destrum/Graphics/ImmediateExecuter.h>
|
||||
|
||||
#include <volk.h>
|
||||
@@ -14,36 +15,61 @@ constexpr auto NO_TIMEOUT = std::numeric_limits<std::uint64_t>::max();
|
||||
}
|
||||
|
||||
void VulkanImmediateExecutor::init(
|
||||
VkDevice device,
|
||||
VkDevice deviceHandle,
|
||||
std::uint32_t graphicsQueueFamily,
|
||||
VkQueue graphicsQueue)
|
||||
VkQueue graphicsQueueHandle)
|
||||
{
|
||||
assert(!initialized);
|
||||
if (initialized) {
|
||||
throw std::logic_error("VulkanImmediateExecutor::init called twice");
|
||||
}
|
||||
|
||||
this->device = device;
|
||||
this->graphicsQueue = graphicsQueue;
|
||||
this->device = deviceHandle;
|
||||
this->graphicsQueue = graphicsQueueHandle;
|
||||
|
||||
const auto poolCreateInfo = vkinit::
|
||||
commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &immCommandPool));
|
||||
try {
|
||||
const auto poolCreateInfo = vkinit::
|
||||
commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||
VK_CHECK(vkCreateCommandPool(deviceHandle, &poolCreateInfo, nullptr, &immCommandPool));
|
||||
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(immCommandPool, 1);
|
||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &immCommandBuffer));
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(immCommandPool, 1);
|
||||
VK_CHECK(vkAllocateCommandBuffers(deviceHandle, &cmdAllocInfo, &immCommandBuffer));
|
||||
|
||||
constexpr auto fenceCreateInfo = VkFenceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &immFence));
|
||||
constexpr auto fenceCreateInfo = VkFenceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
VK_CHECK(vkCreateFence(deviceHandle, &fenceCreateInfo, nullptr, &immFence));
|
||||
|
||||
initialized = true;
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup(deviceHandle);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanImmediateExecutor::cleanup(VkDevice device)
|
||||
void VulkanImmediateExecutor::cleanup(VkDevice deviceHandle)
|
||||
{
|
||||
assert(initialized);
|
||||
vkDestroyCommandPool(device, immCommandPool, nullptr);
|
||||
vkDestroyFence(device, immFence, nullptr);
|
||||
if (deviceHandle == VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
deviceHandle = device;
|
||||
}
|
||||
|
||||
if (deviceHandle == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (immCommandPool != VK_NULL_HANDLE) {
|
||||
vkDestroyCommandPool(deviceHandle, immCommandPool, nullptr);
|
||||
}
|
||||
if (immFence != VK_NULL_HANDLE) {
|
||||
vkDestroyFence(deviceHandle, immFence, nullptr);
|
||||
}
|
||||
|
||||
immCommandPool = VK_NULL_HANDLE;
|
||||
immCommandBuffer = VK_NULL_HANDLE;
|
||||
immFence = VK_NULL_HANDLE;
|
||||
this->device = VK_NULL_HANDLE;
|
||||
graphicsQueue = VK_NULL_HANDLE;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void VulkanImmediateExecutor::immediateSubmit(
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <destrum/Graphics/Managers/FrameManager.h>
|
||||
|
||||
#include <volk.h>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#include <destrum/Graphics/Init.h>
|
||||
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
FrameManager::~FrameManager() {
|
||||
}
|
||||
|
||||
void FrameManager::init(VkDevice dev, std::uint32_t queueFamily) {
|
||||
device = dev;
|
||||
|
||||
try {
|
||||
const auto poolCreateInfo = vkinit::commandPoolCreateInfo(
|
||||
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
queueFamily);
|
||||
|
||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
auto& commandPool = frames[i].commandPool;
|
||||
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool));
|
||||
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
|
||||
auto& mainCommandBuffer = frames[i].commandBuffer;
|
||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
||||
}
|
||||
} catch (...) {
|
||||
cleanup(dev);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameManager::cleanup(VkDevice dev) {
|
||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
if (frames[i].commandPool != VK_NULL_HANDLE && dev != VK_NULL_HANDLE) {
|
||||
vkDestroyCommandPool(dev, frames[i].commandPool, nullptr);
|
||||
}
|
||||
frames[i].commandPool = VK_NULL_HANDLE;
|
||||
frames[i].commandBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
frameNumber = 0;
|
||||
device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkCommandBuffer FrameManager::beginFrame() {
|
||||
ZoneScopedN("FrameManager::beginFrame");
|
||||
|
||||
const auto& frame = getCurrentFrame();
|
||||
const auto& cmd = frame.commandBuffer;
|
||||
|
||||
const auto cmdBeginInfo = VkCommandBufferBeginInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
|
||||
{
|
||||
ZoneScopedN("vkBeginCommandBuffer");
|
||||
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void FrameManager::endFrame(VkCommandBuffer cmd) {
|
||||
ZoneScopedN("FrameManager::endFrame");
|
||||
|
||||
{
|
||||
ZoneScopedN("vkEndCommandBuffer");
|
||||
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FrameManager::nextFrame() {
|
||||
frameNumber++;
|
||||
FrameMark;
|
||||
}
|
||||
|
||||
void FrameManager::waitIdle() const {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
VK_CHECK(vkDeviceWaitIdle(device));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
#include <destrum/Graphics/Managers/ImageManager.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <volk.h>
|
||||
|
||||
#include <destrum/Graphics/ImmediateExecuter.h>
|
||||
#include <destrum/Graphics/Managers/MemoryManager.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
#include <destrum/Graphics/imageLoader.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
ImageManager::~ImageManager()
|
||||
{
|
||||
}
|
||||
|
||||
void ImageManager::init(
|
||||
VkDevice dev,
|
||||
VkPhysicalDevice physicalDev,
|
||||
const MemoryManager& memoryManagerRef,
|
||||
VulkanImmediateExecutor& exec)
|
||||
{
|
||||
device = dev;
|
||||
physicalDevice = physicalDev;
|
||||
this->memoryManager = &memoryManagerRef;
|
||||
executor = &exec;
|
||||
}
|
||||
|
||||
GPUImage ImageManager::createImage(
|
||||
const vkutil::CreateImageInfo& createInfo,
|
||||
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo) const
|
||||
{
|
||||
if (createInfo.extent.width == 0 || createInfo.extent.height == 0 ||
|
||||
createInfo.extent.depth == 0 || createInfo.numLayers == 0) {
|
||||
throw std::invalid_argument("Cannot create an empty Vulkan image");
|
||||
}
|
||||
|
||||
std::uint32_t mipLevels = 1;
|
||||
if (createInfo.mipMap)
|
||||
{
|
||||
const auto maxExtent = std::max(createInfo.extent.width, createInfo.extent.height);
|
||||
mipLevels = static_cast<std::uint32_t>(std::floor(std::log2(maxExtent))) + 1;
|
||||
}
|
||||
|
||||
if (createInfo.isCubemap)
|
||||
{
|
||||
assert(createInfo.numLayers % 6 == 0);
|
||||
assert((createInfo.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) != 0);
|
||||
}
|
||||
|
||||
auto imgInfo = VkImageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.flags = createInfo.flags,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = createInfo.format,
|
||||
.extent = createInfo.extent,
|
||||
.mipLevels = mipLevels,
|
||||
.arrayLayers = createInfo.numLayers,
|
||||
.samples = createInfo.samples,
|
||||
.tiling = createInfo.tiling,
|
||||
.usage = createInfo.usage,
|
||||
};
|
||||
|
||||
static const auto defaultAllocInfo = VmaAllocationCreateInfo{
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
};
|
||||
const auto allocInfo = customAllocationCreateInfo.has_value()
|
||||
? customAllocationCreateInfo.value()
|
||||
: defaultAllocInfo;
|
||||
|
||||
GPUImage image{};
|
||||
image.format = createInfo.format;
|
||||
image.usage = createInfo.usage;
|
||||
image.extent = createInfo.extent;
|
||||
image.mipLevels = mipLevels;
|
||||
image.numLayers = createInfo.numLayers;
|
||||
image.isCubemap = createInfo.isCubemap;
|
||||
image.layerLayouts.assign(createInfo.numLayers, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
|
||||
try {
|
||||
VK_CHECK(
|
||||
vmaCreateImage(memoryManager->getAllocator(), &imgInfo, &allocInfo, &image.image, &image.allocation, nullptr));
|
||||
|
||||
const bool shouldCreateView = ((createInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0);
|
||||
|
||||
if (shouldCreateView) {
|
||||
VkImageAspectFlags aspectFlag = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
if (createInfo.format == VK_FORMAT_D32_SFLOAT) {
|
||||
aspectFlag = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
|
||||
auto viewType = createInfo.numLayers == 1
|
||||
? VK_IMAGE_VIEW_TYPE_2D
|
||||
: VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
if (createInfo.isCubemap && createInfo.numLayers == 6) {
|
||||
viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
}
|
||||
|
||||
const auto viewCreateInfo = VkImageViewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image.image,
|
||||
.viewType = viewType,
|
||||
.format = createInfo.format,
|
||||
.subresourceRange = VkImageSubresourceRange{
|
||||
.aspectMask = aspectFlag,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = mipLevels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = createInfo.numLayers,
|
||||
},
|
||||
};
|
||||
|
||||
VK_CHECK(vkCreateImageView(device, &viewCreateInfo, nullptr, &image.imageView));
|
||||
}
|
||||
|
||||
return image;
|
||||
} catch (...) {
|
||||
destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<GPUImage> ImageManager::loadImageFromFile(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent) const
|
||||
{
|
||||
const auto data = util::loadImage(path, intent);
|
||||
|
||||
if (data.vkFormat == VK_FORMAT_UNDEFINED ||
|
||||
data.byteSize == 0 ||
|
||||
(data.hdr ? data.hdrPixels == nullptr : data.pixels == nullptr))
|
||||
{
|
||||
spdlog::error("Failed to load image '{}'", path.string());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (mipMap) {
|
||||
VkFormatProperties formatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, data.vkFormat, &formatProperties);
|
||||
const auto requiredFeatures =
|
||||
VK_FORMAT_FEATURE_BLIT_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_DST_BIT |
|
||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
||||
if ((formatProperties.optimalTilingFeatures & requiredFeatures) != requiredFeatures) {
|
||||
spdlog::error(
|
||||
"Format {} does not support linear mip generation",
|
||||
static_cast<int>(data.vkFormat));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto image = createImage({
|
||||
.format = data.vkFormat,
|
||||
.usage = usage | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
||||
.extent = VkExtent3D{
|
||||
.width = static_cast<std::uint32_t>(data.width),
|
||||
.height = static_cast<std::uint32_t>(data.height),
|
||||
.depth = 1,
|
||||
},
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
|
||||
const void* src = data.hdr
|
||||
? static_cast<const void*>(data.hdrPixels)
|
||||
: static_cast<const void*>(data.pixels);
|
||||
|
||||
try {
|
||||
uploadImageData(image, src, data.byteSize, 0);
|
||||
} catch (...) {
|
||||
destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
|
||||
image.debugName = path.string();
|
||||
vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void ImageManager::uploadImageData(
|
||||
const GPUImage& image,
|
||||
const void* pixelData,
|
||||
std::size_t byteSize,
|
||||
std::uint32_t layer) const
|
||||
{
|
||||
if (layer >= image.numLayers) {
|
||||
throw std::out_of_range("Image upload layer is out of range");
|
||||
}
|
||||
if (pixelData == nullptr || byteSize == 0) {
|
||||
throw std::invalid_argument("Image upload requires non-empty pixel data");
|
||||
}
|
||||
|
||||
GPUBuffer uploadBuffer = memoryManager->createBuffer(byteSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
if (uploadBuffer.info.pMappedData == nullptr) {
|
||||
memoryManager->destroyBuffer(uploadBuffer);
|
||||
throw std::runtime_error("Image upload staging buffer is not mapped");
|
||||
}
|
||||
|
||||
std::memcpy(uploadBuffer.info.pMappedData, pixelData, byteSize);
|
||||
memoryManager->flushAllocation(uploadBuffer);
|
||||
|
||||
try {
|
||||
executor->immediateSubmit([&](VkCommandBuffer cmd)
|
||||
{
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
uploadBuffer.buffer,
|
||||
0,
|
||||
VK_WHOLE_SIZE);
|
||||
|
||||
const VkImageSubresourceRange uploadRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = image.mipLevels,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
image.getLayout(layer),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
uploadRange);
|
||||
|
||||
VkBufferImageCopy copyRegion{};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copyRegion.imageSubresource.mipLevel = 0;
|
||||
copyRegion.imageSubresource.baseArrayLayer = layer;
|
||||
copyRegion.imageSubresource.layerCount = 1;
|
||||
copyRegion.imageExtent = image.extent;
|
||||
|
||||
vkCmdCopyBufferToImage(
|
||||
cmd,
|
||||
uploadBuffer.buffer,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1,
|
||||
©Region);
|
||||
|
||||
if (image.mipLevels == 1) {
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
uploadRange);
|
||||
} else {
|
||||
for (std::uint32_t mip = 1; mip < image.mipLevels; ++mip) {
|
||||
const VkImageSubresourceRange previousRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = mip - 1,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
previousRange);
|
||||
|
||||
const VkImageBlit2 blitRegion{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
|
||||
.srcSubresource = VkImageSubresourceLayers{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = mip - 1,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.srcOffsets = {
|
||||
{0, 0, 0},
|
||||
{
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.width >> (mip - 1))),
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.height >> (mip - 1))),
|
||||
1,
|
||||
},
|
||||
},
|
||||
.dstSubresource = VkImageSubresourceLayers{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = mip,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.dstOffsets = {
|
||||
{0, 0, 0},
|
||||
{
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.width >> mip)),
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.height >> mip)),
|
||||
1,
|
||||
},
|
||||
},
|
||||
};
|
||||
const VkBlitImageInfo2 blitInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
|
||||
.srcImage = image.image,
|
||||
.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.dstImage = image.image,
|
||||
.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.regionCount = 1,
|
||||
.pRegions = &blitRegion,
|
||||
.filter = VK_FILTER_LINEAR,
|
||||
};
|
||||
vkCmdBlitImage2(cmd, &blitInfo);
|
||||
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
previousRange);
|
||||
}
|
||||
|
||||
const VkImageSubresourceRange lastRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = image.mipLevels - 1,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
lastRange);
|
||||
}
|
||||
});
|
||||
} catch (...) {
|
||||
memoryManager->destroyBuffer(uploadBuffer);
|
||||
throw;
|
||||
}
|
||||
|
||||
image.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, layer);
|
||||
|
||||
memoryManager->destroyBuffer(uploadBuffer);
|
||||
}
|
||||
|
||||
void ImageManager::destroyImage(GPUImage& image) const
|
||||
{
|
||||
if (image.imageView != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(device, image.imageView, nullptr);
|
||||
}
|
||||
image.imageView = VK_NULL_HANDLE;
|
||||
if (image.image != VK_NULL_HANDLE && memoryManager != nullptr &&
|
||||
memoryManager->getAllocator() != VK_NULL_HANDLE) {
|
||||
vmaDestroyImage(memoryManager->getAllocator(), image.image, image.allocation);
|
||||
}
|
||||
if (image.image != VK_NULL_HANDLE) {
|
||||
image.image = VK_NULL_HANDLE;
|
||||
image.allocation = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#include <destrum/Graphics/Managers/MemoryManager.h>
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <volk.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Managers/VulkanInstanceManager.h>
|
||||
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
MemoryManager::~MemoryManager() {
|
||||
}
|
||||
|
||||
void MemoryManager::init(const VulkanInstanceManager& instanceManager) {
|
||||
const auto vulkanFunctions = VmaVulkanFunctions{
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
};
|
||||
|
||||
const auto allocatorInfo = VmaAllocatorCreateInfo{
|
||||
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
|
||||
.physicalDevice = instanceManager.getPhysicalDevice(),
|
||||
.device = instanceManager.getDevice(),
|
||||
.pVulkanFunctions = &vulkanFunctions,
|
||||
.instance = instanceManager.getInstance(),
|
||||
};
|
||||
VK_CHECK(vmaCreateAllocator(&allocatorInfo, &allocator));
|
||||
device = instanceManager.getDevice();
|
||||
}
|
||||
|
||||
void MemoryManager::cleanup(VkDevice) {
|
||||
if (allocator != VK_NULL_HANDLE) {
|
||||
vmaDestroyAllocator(allocator);
|
||||
allocator = VK_NULL_HANDLE;
|
||||
}
|
||||
device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
GPUBuffer MemoryManager::createBuffer(
|
||||
std::size_t allocSize,
|
||||
VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage) const
|
||||
{
|
||||
if (allocSize == 0) {
|
||||
throw std::invalid_argument("Cannot create a zero-sized Vulkan buffer");
|
||||
}
|
||||
const auto bufferInfo = VkBufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = allocSize,
|
||||
.usage = usage,
|
||||
};
|
||||
|
||||
const bool hostVisible =
|
||||
memoryUsage == VMA_MEMORY_USAGE_CPU_ONLY ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_CPU_TO_GPU ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_GPU_TO_CPU ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_CPU_COPY ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
||||
|
||||
const auto hostAccessFlag =
|
||||
memoryUsage == VMA_MEMORY_USAGE_GPU_TO_CPU || memoryUsage == VMA_MEMORY_USAGE_CPU_COPY
|
||||
? VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT
|
||||
: VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
const auto allocInfo = VmaAllocationCreateInfo{
|
||||
.flags = hostVisible
|
||||
? static_cast<VmaAllocationCreateFlags>(
|
||||
VMA_ALLOCATION_CREATE_MAPPED_BIT | hostAccessFlag)
|
||||
: VmaAllocationCreateFlags{},
|
||||
.usage = memoryUsage,
|
||||
};
|
||||
|
||||
GPUBuffer buffer{};
|
||||
VK_CHECK(vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer.buffer, &buffer.allocation, &buffer.info));
|
||||
if ((usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0) {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
buffer.address = vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
buffer.hostVisible = hostVisible;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
VkDeviceAddress MemoryManager::getBufferAddress(const GPUBuffer& buffer) const {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
return vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
void MemoryManager::destroyBuffer(GPUBuffer& buffer) const {
|
||||
if (allocator != VK_NULL_HANDLE && buffer.buffer != VK_NULL_HANDLE) {
|
||||
vmaDestroyBuffer(allocator, buffer.buffer, buffer.allocation);
|
||||
}
|
||||
buffer = {};
|
||||
}
|
||||
|
||||
void MemoryManager::flushAllocation(
|
||||
const GPUBuffer& buffer,
|
||||
VkDeviceSize offset,
|
||||
VkDeviceSize size) const
|
||||
{
|
||||
if (!buffer.hostVisible || buffer.allocation == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
VK_CHECK(vmaFlushAllocation(allocator, buffer.allocation, offset, size));
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
#include <destrum/Graphics/Managers/VulkanInstanceManager.h>
|
||||
|
||||
#include <volk.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/TracyVulkan.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
VulkanInstanceManager::~VulkanInstanceManager()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::init(
|
||||
SDL_Window* window,
|
||||
const std::string& appName,
|
||||
const DeviceFeatures& features)
|
||||
{
|
||||
if (initialized) {
|
||||
throw std::logic_error("VulkanInstanceManager::init called twice");
|
||||
}
|
||||
|
||||
VK_CHECK(volkInitialize());
|
||||
|
||||
const auto instanceResult = vkb::InstanceBuilder{}
|
||||
.set_app_name(appName.c_str())
|
||||
.set_app_version(1, 0, 0)
|
||||
.request_validation_layers()
|
||||
.use_default_debug_messenger()
|
||||
.require_api_version(1, 3, 0)
|
||||
.build();
|
||||
if (!instanceResult.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"Failed to create Vulkan instance: " + instanceResult.error().message());
|
||||
}
|
||||
vkbInstance = instanceResult.value();
|
||||
|
||||
instance = vkbInstance;
|
||||
|
||||
volkLoadInstance(instance);
|
||||
|
||||
const auto res = SDL_Vulkan_CreateSurface(window, instance, &surface);
|
||||
if (res != SDL_TRUE)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to create Vulkan surface: " + std::string{SDL_GetError()});
|
||||
}
|
||||
|
||||
const auto physicalDeviceResult =
|
||||
vkb::PhysicalDeviceSelector{vkbInstance}
|
||||
.set_minimum_version(1, 3)
|
||||
.set_required_features(features.device)
|
||||
.set_required_features_12(features.features12)
|
||||
.set_required_features_13(features.features13)
|
||||
.add_required_extension(
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
|
||||
.add_required_extension_features(
|
||||
features.extendedDynamicState3)
|
||||
.set_surface(surface)
|
||||
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
||||
.select();
|
||||
if (!physicalDeviceResult.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"Failed to select Vulkan physical device: " +
|
||||
physicalDeviceResult.error().message());
|
||||
}
|
||||
vkbPhysicalDevice = physicalDeviceResult.value();
|
||||
|
||||
physicalDevice = vkbPhysicalDevice;
|
||||
|
||||
const auto deviceResult = vkb::DeviceBuilder{vkbPhysicalDevice}.build();
|
||||
if (!deviceResult.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"Failed to create Vulkan device: " + deviceResult.error().message());
|
||||
}
|
||||
vkbDevice = deviceResult.value();
|
||||
|
||||
device = vkbDevice;
|
||||
|
||||
volkLoadDevice(vkbDevice);
|
||||
|
||||
const auto graphicsQueueFamilyResult =
|
||||
vkbDevice.get_queue_index(vkb::QueueType::graphics);
|
||||
const auto graphicsQueueResult = vkbDevice.get_queue(vkb::QueueType::graphics);
|
||||
const auto presentQueueFamilyResult =
|
||||
vkbDevice.get_queue_index(vkb::QueueType::present);
|
||||
const auto presentQueueResult = vkbDevice.get_queue(vkb::QueueType::present);
|
||||
if (!graphicsQueueFamilyResult.has_value() ||
|
||||
!graphicsQueueResult.has_value() ||
|
||||
!presentQueueFamilyResult.has_value() ||
|
||||
!presentQueueResult.has_value()) {
|
||||
throw std::runtime_error("Failed to retrieve graphics/present queues");
|
||||
}
|
||||
|
||||
graphicsQueueFamily = graphicsQueueFamilyResult.value();
|
||||
graphicsQueue = graphicsQueueResult.value();
|
||||
presentQueueFamily = presentQueueFamilyResult.value();
|
||||
presentQueue = presentQueueResult.value();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::initTracy(VkCommandBuffer tracyInitCmd)
|
||||
{
|
||||
#if defined(TRACY_ENABLE)
|
||||
|
||||
#if defined(TRACY_VK_USE_SYMBOL_TABLE)
|
||||
tracyVkCtx = TracyVkContext(
|
||||
instance,
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd,
|
||||
vkGetInstanceProcAddr,
|
||||
vkGetDeviceProcAddr);
|
||||
#else
|
||||
tracyVkCtx = TracyVkContext(
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd);
|
||||
#endif
|
||||
|
||||
static constexpr char ctxName[] = "Graphics Queue";
|
||||
|
||||
TracyVkContextName(
|
||||
tracyVkCtx,
|
||||
ctxName,
|
||||
sizeof(ctxName) - 1);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::cleanup()
|
||||
{
|
||||
#if defined(TRACY_ENABLE)
|
||||
if (tracyVkCtx)
|
||||
{
|
||||
TracyVkDestroy(tracyVkCtx);
|
||||
tracyVkCtx = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroyDevice(device, nullptr);
|
||||
device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (surface != VK_NULL_HANDLE && instance != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||
surface = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (instance != VK_NULL_HANDLE) {
|
||||
vkb::destroy_instance(vkbInstance);
|
||||
instance = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
physicalDevice = VK_NULL_HANDLE;
|
||||
graphicsQueueFamily = VK_QUEUE_FAMILY_IGNORED;
|
||||
graphicsQueue = VK_NULL_HANDLE;
|
||||
presentQueueFamily = VK_QUEUE_FAMILY_IGNORED;
|
||||
presentQueue = VK_NULL_HANDLE;
|
||||
vkbInstance = {};
|
||||
vkbPhysicalDevice = {};
|
||||
vkbDevice = {};
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::waitIdle() const
|
||||
{
|
||||
if (device != VK_NULL_HANDLE)
|
||||
{
|
||||
VK_CHECK(vkDeviceWaitIdle(device));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include <destrum/Util/MathUtils.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include "volk.h"
|
||||
// #include <destrum/Math/Util.h>
|
||||
|
||||
MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
|
||||
@@ -27,49 +32,110 @@ MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
|
||||
const auto id = meshes.size();
|
||||
meshes.push_back(std::move(gpuMesh));
|
||||
cpuMeshes.push_back(cpuMesh); // store a copy of the CPU mesh
|
||||
std::string key = cpuMesh.name.empty()
|
||||
? "mesh:" + std::to_string(id)
|
||||
: cpuMesh.name;
|
||||
const std::string baseKey = key;
|
||||
std::size_t suffix = 1;
|
||||
while (std::find(meshKeys.begin(), meshKeys.end(), key) != meshKeys.end()) {
|
||||
key = baseKey + "#" + std::to_string(suffix++);
|
||||
}
|
||||
meshKeys.push_back(std::move(key));
|
||||
return id;
|
||||
}
|
||||
|
||||
void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) const
|
||||
{
|
||||
try {
|
||||
// create index buffer
|
||||
const auto indexBufferSize = cpuMesh.indices.size() * sizeof(std::uint32_t);
|
||||
gpuMesh.indexBuffer = gfxDevice.createBuffer(
|
||||
indexBufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
indexBufferSize,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
|
||||
// create vertex buffer
|
||||
const auto vertexBufferSize = cpuMesh.vertices.size() * sizeof(CPUMesh::Vertex);
|
||||
gpuMesh.vertexBuffer = gfxDevice.createBuffer(
|
||||
vertexBufferSize,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
|
||||
const auto staging =
|
||||
auto vertexIndexStaging =
|
||||
gfxDevice
|
||||
.createBuffer(vertexBufferSize + indexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
.createBuffer(
|
||||
vertexBufferSize + indexBufferSize,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_ONLY);
|
||||
|
||||
// copy data
|
||||
void* data = staging.info.pMappedData;
|
||||
memcpy(data, cpuMesh.vertices.data(), vertexBufferSize);
|
||||
memcpy((char*)data + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize);
|
||||
try {
|
||||
// copy data
|
||||
void* vertexIndexData = vertexIndexStaging.info.pMappedData;
|
||||
memcpy(vertexIndexData, cpuMesh.vertices.data(), vertexBufferSize);
|
||||
memcpy(static_cast<char*>(vertexIndexData) + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize);
|
||||
gfxDevice.getMemoryManager().flushAllocation(vertexIndexStaging);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
vertexIndexStaging.buffer,
|
||||
0,
|
||||
VK_WHOLE_SIZE);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
const auto vertexCopy = VkBufferCopy{
|
||||
.srcOffset = 0,
|
||||
.dstOffset = 0,
|
||||
.size = vertexBufferSize,
|
||||
};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, gpuMesh.vertexBuffer.buffer, 1, &vertexCopy);
|
||||
vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, gpuMesh.vertexBuffer.buffer, 1, &vertexCopy);
|
||||
|
||||
const auto indexCopy = VkBufferCopy{
|
||||
.srcOffset = vertexBufferSize,
|
||||
.dstOffset = 0,
|
||||
.size = indexBufferSize,
|
||||
};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, gpuMesh.indexBuffer.buffer, 1, &indexCopy);
|
||||
});
|
||||
vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, gpuMesh.indexBuffer.buffer, 1, &indexCopy);
|
||||
|
||||
gfxDevice.destroyBuffer(staging);
|
||||
const std::array<VkBufferMemoryBarrier2, 2> destinationBarriers{{
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = gpuMesh.vertexBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
},
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = gpuMesh.indexBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
}
|
||||
}};
|
||||
const VkDependencyInfo destinationDependency{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.bufferMemoryBarrierCount = static_cast<std::uint32_t>(destinationBarriers.size()),
|
||||
.pBufferMemoryBarriers = destinationBarriers.data(),
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &destinationDependency);
|
||||
});
|
||||
} catch (...) {
|
||||
gfxDevice.destroyBuffer(vertexIndexStaging);
|
||||
throw;
|
||||
}
|
||||
|
||||
gfxDevice.destroyBuffer(vertexIndexStaging);
|
||||
|
||||
const auto vtxBufferName = cpuMesh.name + " (vtx)";
|
||||
const auto idxBufferName = cpuMesh.name + " (idx)";
|
||||
@@ -82,25 +148,67 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
||||
gpuMesh.skinningDataBuffer = gfxDevice.createBuffer(
|
||||
skinningDataSize,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
|
||||
const auto staging =
|
||||
gfxDevice.createBuffer(skinningDataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
auto skinningStaging =
|
||||
gfxDevice.createBuffer(
|
||||
skinningDataSize,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_ONLY);
|
||||
|
||||
// copy data
|
||||
void* data = staging.info.pMappedData;
|
||||
memcpy(data, cpuMesh.skinningData.data(), skinningDataSize);
|
||||
try {
|
||||
// copy data
|
||||
void* skinningData = skinningStaging.info.pMappedData;
|
||||
memcpy(skinningData, cpuMesh.skinningData.data(), skinningDataSize);
|
||||
gfxDevice.getMemoryManager().flushAllocation(skinningStaging);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
skinningStaging.buffer,
|
||||
0,
|
||||
VK_WHOLE_SIZE);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
const auto vertexCopy = VkBufferCopy{
|
||||
.srcOffset = 0,
|
||||
.dstOffset = 0,
|
||||
.size = skinningDataSize,
|
||||
};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, gpuMesh.skinningDataBuffer.buffer, 1, &vertexCopy);
|
||||
});
|
||||
vkCmdCopyBuffer(cmd, skinningStaging.buffer, gpuMesh.skinningDataBuffer.buffer, 1, &vertexCopy);
|
||||
|
||||
gfxDevice.destroyBuffer(staging);
|
||||
const VkBufferMemoryBarrier2 destinationBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = gpuMesh.skinningDataBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
};
|
||||
const VkDependencyInfo destinationDependency{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.bufferMemoryBarrierCount = 1,
|
||||
.pBufferMemoryBarriers = &destinationBarrier,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &destinationDependency);
|
||||
});
|
||||
} catch (...) {
|
||||
gfxDevice.destroyBuffer(skinningStaging);
|
||||
throw;
|
||||
}
|
||||
|
||||
gfxDevice.destroyBuffer(skinningStaging);
|
||||
}
|
||||
} catch (...) {
|
||||
gfxDevice.destroyBuffer(gpuMesh.skinningDataBuffer);
|
||||
gfxDevice.destroyBuffer(gpuMesh.vertexBuffer);
|
||||
gfxDevice.destroyBuffer(gpuMesh.indexBuffer);
|
||||
gpuMesh = {};
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,10 +222,28 @@ const CPUMesh& MeshCache::getCPUMesh(MeshID id) const
|
||||
return cpuMeshes.at(id);
|
||||
}
|
||||
|
||||
const std::string& MeshCache::getMeshKey(MeshID id) const
|
||||
{
|
||||
return meshKeys.at(id);
|
||||
}
|
||||
|
||||
std::optional<MeshID> MeshCache::findMeshByKey(std::string_view key) const
|
||||
{
|
||||
const auto it = std::find(meshKeys.begin(), meshKeys.end(), key);
|
||||
if (it == meshKeys.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<MeshID>(std::distance(meshKeys.begin(), it));
|
||||
}
|
||||
|
||||
void MeshCache::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
for (const auto& mesh : meshes) {
|
||||
for (auto& mesh : meshes) {
|
||||
gfxDevice.destroyBuffer(mesh.indexBuffer);
|
||||
gfxDevice.destroyBuffer(mesh.vertexBuffer);
|
||||
gfxDevice.destroyBuffer(mesh.skinningDataBuffer);
|
||||
}
|
||||
meshes.clear();
|
||||
cpuMeshes.clear();
|
||||
meshKeys.clear();
|
||||
}
|
||||
|
||||
@@ -5,19 +5,31 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "volk.h"
|
||||
#include "destrum/Graphics/Util.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
|
||||
Pipeline::Pipeline(GfxDevice& device, const std::string& vertPath, const std::string& fragPath,
|
||||
const PipelineConfigInfo& configInfo): m_device(device) {
|
||||
CreateGraphicsPipeline(vertPath, fragPath, configInfo);
|
||||
try {
|
||||
CreateGraphicsPipeline(vertPath, fragPath, configInfo);
|
||||
} catch (...) {
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_vertShaderModule, nullptr);
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_fragShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_graphicsPipeline, nullptr);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline::~Pipeline() {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_vertShaderModule, nullptr);
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_fragShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_graphicsPipeline, nullptr);
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_vertShaderModule, nullptr);
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_fragShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_graphicsPipeline, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Pipeline::bind(VkCommandBuffer buffer) const {
|
||||
|
||||
@@ -16,12 +16,14 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
gfx = &gfxDevice;
|
||||
sdlWindow = window;
|
||||
colorFormat = gfx->getSwapchainFormat();
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
contextCreated = true;
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
@@ -32,6 +34,7 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
if (!ImGui_ImplSDL2_InitForVulkan(sdlWindow)) {
|
||||
throw std::runtime_error("ImGui_ImplSDL2_InitForVulkan failed");
|
||||
}
|
||||
sdlInitialized = true;
|
||||
|
||||
const std::array<VkDescriptorPoolSize, 11> poolSizes{{
|
||||
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
|
||||
@@ -56,6 +59,21 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(gfx->getVkDevice(), &poolInfo, nullptr, &descriptorPool));
|
||||
|
||||
initVulkanBackend();
|
||||
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void ImguiPass::initVulkanBackend()
|
||||
{
|
||||
if (gfx == nullptr) {
|
||||
throw std::runtime_error("Cannot initialize ImGui Vulkan backend without a device");
|
||||
}
|
||||
|
||||
VkPipelineRenderingCreateInfoKHR pipelineRenderingInfo{};
|
||||
pipelineRenderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
|
||||
pipelineRenderingInfo.colorAttachmentCount = 1;
|
||||
@@ -69,21 +87,26 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
|
||||
ImGui_ImplVulkan_InitInfo initInfo{};
|
||||
initInfo.ApiVersion = VK_API_VERSION_1_3;
|
||||
initInfo.Instance = gfxDevice.getVkInstance();
|
||||
initInfo.PhysicalDevice = gfxDevice.getVkPhysicalDevice();
|
||||
initInfo.Device = gfxDevice.getDevice();
|
||||
initInfo.Queue = gfxDevice.getGraphicsQueue();
|
||||
initInfo.Instance = gfx->getVkInstance();
|
||||
initInfo.PhysicalDevice = gfx->getVkPhysicalDevice();
|
||||
initInfo.Device = gfx->getDevice();
|
||||
initInfo.Queue = gfx->getGraphicsQueue();
|
||||
initInfo.QueueFamily = gfx->getGraphicsQueueFamily();
|
||||
initInfo.DescriptorPool = descriptorPool;
|
||||
initInfo.MinImageCount = 3;
|
||||
initInfo.ImageCount = 3;
|
||||
const auto imageCount = gfx->getSwapchainImageCount();
|
||||
if (imageCount < 2) {
|
||||
throw std::runtime_error("ImGui Vulkan backend requires at least two swapchain images");
|
||||
}
|
||||
initInfo.MinImageCount = imageCount;
|
||||
initInfo.ImageCount = imageCount;
|
||||
initInfo.UseDynamicRendering = true;
|
||||
initInfo.PipelineInfoMain = pipelineInfo;
|
||||
|
||||
vulkanInitialized = true;
|
||||
if (!ImGui_ImplVulkan_Init(&initInfo)) {
|
||||
vulkanInitialized = false;
|
||||
throw std::runtime_error("ImGui_ImplVulkan_Init failed");
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void ImguiPass::handleEvent(const SDL_Event& event) {
|
||||
@@ -168,25 +191,52 @@ void ImguiPass::onSwapchainRecreated() {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_ImplVulkan_SetMinImageCount(2);
|
||||
const auto imageCount = gfx->getSwapchainImageCount();
|
||||
const auto newFormat = gfx->getSwapchainFormat();
|
||||
if (newFormat != colorFormat) {
|
||||
if (vulkanInitialized) {
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
vulkanInitialized = false;
|
||||
}
|
||||
colorFormat = newFormat;
|
||||
initVulkanBackend();
|
||||
} else {
|
||||
if (imageCount < 2) {
|
||||
throw std::runtime_error("ImGui Vulkan backend requires at least two swapchain images");
|
||||
}
|
||||
ImGui_ImplVulkan_SetMinImageCount(std::max(2u, imageCount));
|
||||
}
|
||||
}
|
||||
|
||||
void ImguiPass::cleanup() {
|
||||
if (!initialized || gfx == nullptr) {
|
||||
if (gfx == nullptr && !contextCreated && !sdlInitialized &&
|
||||
!vulkanInitialized && descriptorPool == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
vkDeviceWaitIdle(gfx->getVkDevice());
|
||||
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
if (descriptorPool != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(gfx->getVkDevice(), descriptorPool, nullptr);
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
if (gfx != nullptr && gfx->getVkDevice() != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(gfx->getVkDevice());
|
||||
}
|
||||
|
||||
if (vulkanInitialized) {
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
vulkanInitialized = false;
|
||||
}
|
||||
if (sdlInitialized) {
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
sdlInitialized = false;
|
||||
}
|
||||
if (contextCreated) {
|
||||
ImGui::DestroyContext();
|
||||
contextCreated = false;
|
||||
}
|
||||
|
||||
if (descriptorPool != VK_NULL_HANDLE && gfx != nullptr &&
|
||||
gfx->getVkDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(gfx->getVkDevice(), descriptorPool, nullptr);
|
||||
}
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
|
||||
gfx = nullptr;
|
||||
sdlWindow = nullptr;
|
||||
colorFormat = VK_FORMAT_UNDEFINED;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||
#include <destrum/Graphics/Frustum.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
MeshPipeline::MeshPipeline() = default;
|
||||
@@ -22,6 +23,7 @@ void MeshPipeline::init(
|
||||
VkFormat drawImageFormat,
|
||||
VkFormat depthImageFormat)
|
||||
{
|
||||
try {
|
||||
const auto vertexShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
|
||||
|
||||
@@ -49,7 +51,7 @@ void MeshPipeline::init(
|
||||
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
|
||||
|
||||
if (vkCreatePipelineLayout(
|
||||
gfxDevice.getDevice().device,
|
||||
gfxDevice.getDevice(),
|
||||
&pipelineLayoutInfo,
|
||||
nullptr,
|
||||
&m_pipelineLayout) != VK_SUCCESS)
|
||||
@@ -69,12 +71,16 @@ void MeshPipeline::init(
|
||||
pipelineConfig.colorAttachments = {drawImageFormat};
|
||||
pipelineConfig.depthAttachment = depthImageFormat;
|
||||
|
||||
m_pipeline = std::make_unique<Pipeline>(
|
||||
m_pipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
fragShader.string(),
|
||||
pipelineConfig
|
||||
);
|
||||
);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice.getDevice());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshPipeline::draw(
|
||||
@@ -188,8 +194,8 @@ void MeshPipeline::cleanup(VkDevice device)
|
||||
{
|
||||
m_pipeline.reset();
|
||||
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE) {
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
|
||||
m_pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
m_pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "destrum/FS/AssetFS.h"
|
||||
#include "../../../include/destrum/Graphics/Caches/MeshCache.h"
|
||||
#include "destrum/Graphics/MeshDrawCommand.h"
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
@@ -10,7 +11,10 @@
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "volk.h"
|
||||
|
||||
void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
||||
try {
|
||||
const auto& device = gfxDevice.getDevice();
|
||||
|
||||
const auto pushConstant = VkPushConstantRange{
|
||||
@@ -26,7 +30,7 @@ void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
||||
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||
pipelineLayoutInfo.pPushConstantRanges = pushConstants.data();
|
||||
|
||||
if (vkCreatePipelineLayout(device.device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) {
|
||||
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Could not make pipeline layout");
|
||||
}
|
||||
|
||||
@@ -42,21 +46,31 @@ void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
||||
pipelineConfig
|
||||
);
|
||||
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
auto& jointMatricesBuffer = framesData[i].jointMatricesBuffer;
|
||||
jointMatricesBuffer.capacity = MAX_JOINT_MATRICES;
|
||||
jointMatricesBuffer.buffer = gfxDevice.createBuffer(
|
||||
MAX_JOINT_MATRICES * sizeof(glm::mat4),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningPipeline::cleanup(GfxDevice& gfxDevice) {
|
||||
for (auto& frame : framesData) {
|
||||
gfxDevice.destroyBuffer(frame.jointMatricesBuffer.buffer);
|
||||
frame.jointMatricesBuffer.buffer = {};
|
||||
frame.jointMatricesBuffer.size = 0;
|
||||
}
|
||||
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice().device, m_pipelineLayout, nullptr);
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice(), m_pipelineLayout, nullptr);
|
||||
}
|
||||
m_pipelineLayout = VK_NULL_HANDLE;
|
||||
skinningPipeline.reset();
|
||||
}
|
||||
|
||||
@@ -94,31 +108,45 @@ void SkinningPipeline::doSkinning(VkCommandBuffer cmd,
|
||||
|
||||
vkCmdDispatch(cmd, groupSizeX, 1, 1);
|
||||
|
||||
// Required before the graphics pass reads skinnedVertexBuffer as a vertex buffer.
|
||||
// Without this, the draw can see stale/partial data from before the compute dispatch.
|
||||
VkBufferMemoryBarrier skinnedVertexBarrier{};
|
||||
skinnedVertexBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
skinnedVertexBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
skinnedVertexBarrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
|
||||
skinnedVertexBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
skinnedVertexBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
skinnedVertexBarrier.buffer = dc.skinnedMesh->skinnedVertexBuffer.buffer;
|
||||
skinnedVertexBarrier.offset = 0;
|
||||
skinnedVertexBarrier.size = VK_WHOLE_SIZE;
|
||||
const VkBufferMemoryBarrier2 skinnedVertexBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = dc.skinnedMesh->skinnedVertexBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(cmd,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &skinnedVertexBarrier,
|
||||
0, nullptr);
|
||||
const VkDependencyInfo dependencyInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.bufferMemoryBarrierCount = 1,
|
||||
.pBufferMemoryBarriers = &skinnedVertexBarrier,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
|
||||
}
|
||||
|
||||
void SkinningPipeline::beginDrawing(std::size_t frameIndex) {
|
||||
getCurrentFrameData(frameIndex).jointMatricesBuffer.clear();
|
||||
}
|
||||
|
||||
void SkinningPipeline::flushCurrentFrame(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
std::size_t frameIndex)
|
||||
{
|
||||
auto& buffer = getCurrentFrameData(frameIndex).jointMatricesBuffer.buffer;
|
||||
gfxDevice.getMemoryManager().flushAllocation(buffer, 0, VK_WHOLE_SIZE);
|
||||
vkutil::bufferHostWriteToShaderReadBarrier(
|
||||
cmd,
|
||||
buffer.buffer,
|
||||
0,
|
||||
static_cast<VkDeviceSize>(MAX_JOINT_MATRICES * sizeof(glm::mat4)));
|
||||
}
|
||||
|
||||
std::size_t SkinningPipeline::appendJointMatrices(std::span<const glm::mat4> jointMatrices,
|
||||
std::size_t frameIndex) {
|
||||
auto& jointMatricesBuffer = getCurrentFrameData(frameIndex).jointMatricesBuffer;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <glm/glm.hpp>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
@@ -19,6 +20,7 @@ void SkyboxPipeline::init(
|
||||
VkFormat drawImageFormat,
|
||||
VkFormat depthImageFormat)
|
||||
{
|
||||
try {
|
||||
const auto vertexShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/fullscreen_triangle.vert");
|
||||
|
||||
@@ -45,7 +47,7 @@ void SkyboxPipeline::init(
|
||||
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
|
||||
|
||||
if (vkCreatePipelineLayout(
|
||||
gfxDevice.getDevice().device,
|
||||
gfxDevice.getDevice(),
|
||||
&pipelineLayoutInfo,
|
||||
nullptr,
|
||||
&pipelineLayout) != VK_SUCCESS)
|
||||
@@ -71,22 +73,26 @@ void SkyboxPipeline::init(
|
||||
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
|
||||
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
|
||||
pipeline = std::make_unique<Pipeline>(
|
||||
pipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
fragShader.string(),
|
||||
pipelineConfig
|
||||
);
|
||||
);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice.getDevice());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SkyboxPipeline::cleanup(VkDevice device)
|
||||
{
|
||||
pipeline.reset();
|
||||
|
||||
if (pipelineLayout != VK_NULL_HANDLE) {
|
||||
if (pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void SkyboxPipeline::draw(
|
||||
|
||||
@@ -3,24 +3,31 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
RenderResources::RenderResources() = default;
|
||||
|
||||
void RenderResources::init(GfxDevice& gfxDevice)
|
||||
{
|
||||
imageCache = std::make_unique<ImageCache>(gfxDevice);
|
||||
meshCache = std::make_unique<MeshCache>();
|
||||
materialCache = std::make_unique<MaterialCache>();
|
||||
if (imageCache || meshCache || materialCache) {
|
||||
cleanup(gfxDevice);
|
||||
}
|
||||
|
||||
VkPhysicalDeviceProperties props{};
|
||||
vkGetPhysicalDeviceProperties(gfxDevice.getVkPhysicalDevice(), &props);
|
||||
try {
|
||||
imageCache = std::make_unique<ImageCache>(gfxDevice);
|
||||
meshCache = std::make_unique<MeshCache>();
|
||||
materialCache = std::make_unique<MaterialCache>();
|
||||
|
||||
imageCache->bindlessSetManager.init(
|
||||
gfxDevice.getVkDevice(),
|
||||
props.limits.maxSamplerAnisotropy);
|
||||
VkPhysicalDeviceProperties props{};
|
||||
vkGetPhysicalDeviceProperties(gfxDevice.getVkPhysicalDevice(), &props);
|
||||
|
||||
{
|
||||
imageCache->bindlessSetManager.init(
|
||||
gfxDevice.getVkDevice(),
|
||||
gfxDevice.getVkPhysicalDevice(),
|
||||
props.limits.maxSamplerAnisotropy);
|
||||
|
||||
{
|
||||
std::uint32_t white = 0xFFFFFFFF;
|
||||
|
||||
whiteImageId = createImage(
|
||||
@@ -35,7 +42,7 @@ void RenderResources::init(GfxDevice& gfxDevice)
|
||||
&white);
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
std::uint32_t normal = 0xFFFF8080; // tangent-space normal: 0.5, 0.5, 1.0, 1.0
|
||||
|
||||
defaultNormalImageId = createImage(
|
||||
@@ -50,7 +57,7 @@ void RenderResources::init(GfxDevice& gfxDevice)
|
||||
&normal);
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
constexpr auto black = 0xFF000000;
|
||||
constexpr auto magenta = 0xFFFF00FF;
|
||||
|
||||
@@ -74,14 +81,18 @@ void RenderResources::init(GfxDevice& gfxDevice)
|
||||
imageCache->setErrorImageId(errorImageId);
|
||||
}
|
||||
|
||||
materialCache->init(
|
||||
gfxDevice,
|
||||
MaterialDefaultTextures{
|
||||
.white = whiteImageId,
|
||||
.normal = defaultNormalImageId,
|
||||
.metallicRoughness = whiteImageId,
|
||||
.emissive = whiteImageId,
|
||||
});
|
||||
materialCache->init(
|
||||
gfxDevice,
|
||||
MaterialDefaultTextures{
|
||||
.white = whiteImageId,
|
||||
.normal = defaultNormalImageId,
|
||||
.metallicRoughness = whiteImageId,
|
||||
.emissive = whiteImageId,
|
||||
});
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ImageID RenderResources::createImage(
|
||||
@@ -152,6 +163,7 @@ ImageID RenderResources::loadImageFromFile(
|
||||
bool mipMap,
|
||||
TextureIntent intent)
|
||||
{
|
||||
(void)gfxDevice;
|
||||
return imageCache->loadImageFromFile(path, usage, mipMap, intent);
|
||||
}
|
||||
|
||||
@@ -188,6 +200,33 @@ void RenderResources::bindBindlessDescSet(VkCommandBuffer cmd, VkPipelineLayout
|
||||
);
|
||||
}
|
||||
|
||||
void RenderResources::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
if (!imageCache && !meshCache && !materialCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
gfxDevice.waitIdle();
|
||||
|
||||
if (materialCache) {
|
||||
materialCache->cleanup(gfxDevice);
|
||||
}
|
||||
if (meshCache) {
|
||||
meshCache->cleanup(gfxDevice);
|
||||
}
|
||||
if (imageCache) {
|
||||
imageCache->bindlessSetManager.cleanup(gfxDevice.getVkDevice());
|
||||
imageCache->destroyImages();
|
||||
}
|
||||
|
||||
materialCache.reset();
|
||||
meshCache.reset();
|
||||
imageCache.reset();
|
||||
whiteImageId = NULL_IMAGE_ID;
|
||||
errorImageId = NULL_IMAGE_ID;
|
||||
defaultNormalImageId = NULL_IMAGE_ID;
|
||||
}
|
||||
|
||||
std::uint32_t RenderResources::BytesPerTexel(VkFormat fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "volk.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
@@ -13,26 +17,31 @@ GameRenderer::GameRenderer()
|
||||
|
||||
void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::ivec2 drawImageSize)
|
||||
{
|
||||
resources = &_resources;
|
||||
sceneDataBuffer.init(
|
||||
gfxDevice,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
sizeof(GPUSceneData),
|
||||
"scene data");
|
||||
try {
|
||||
resources = &_resources;
|
||||
sceneDataBuffer.init(
|
||||
gfxDevice,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
sizeof(GPUSceneData),
|
||||
"scene data");
|
||||
|
||||
createDrawImage(gfxDevice, drawImageSize, true);
|
||||
createDrawImage(gfxDevice, drawImageSize, true);
|
||||
|
||||
meshPipeline = std::make_unique<MeshPipeline>();
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
meshPipeline = std::make_unique<MeshPipeline>();
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
|
||||
skinningPipeline = std::make_unique<SkinningPipeline>();
|
||||
skinningPipeline->init(gfxDevice);
|
||||
skinningPipeline = std::make_unique<SkinningPipeline>();
|
||||
skinningPipeline->init(gfxDevice);
|
||||
|
||||
|
||||
GameState::GetInstance().SetRenderer(this);
|
||||
GameState::GetInstance().SetRenderer(this);
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
@@ -44,7 +53,23 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
|
||||
void GameRenderer::endDrawing()
|
||||
{
|
||||
//Sort the drawlist
|
||||
sortedMeshDrawCommands.resize(meshDrawCommands.size());
|
||||
std::iota(sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.end(), 0);
|
||||
|
||||
std::stable_sort(
|
||||
sortedMeshDrawCommands.begin(),
|
||||
sortedMeshDrawCommands.end(),
|
||||
[this](std::size_t left, std::size_t right) {
|
||||
const auto& lhs = meshDrawCommands[left];
|
||||
const auto& rhs = meshDrawCommands[right];
|
||||
if (lhs.meshId != rhs.meshId) {
|
||||
return lhs.meshId < rhs.meshId;
|
||||
}
|
||||
if (lhs.materialId != rhs.materialId) {
|
||||
return lhs.materialId < rhs.materialId;
|
||||
}
|
||||
return (lhs.skinnedMesh != nullptr) < (rhs.skinnedMesh != nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& camera, const SceneData& sceneData)
|
||||
@@ -55,6 +80,11 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
{
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Skinning");
|
||||
|
||||
skinningPipeline->flushCurrentFrame(
|
||||
cmd,
|
||||
gfxDevice,
|
||||
gfxDevice.getCurrentFrameIndex());
|
||||
|
||||
for (const auto& dc : meshDrawCommands)
|
||||
{
|
||||
if (!dc.skinnedMesh)
|
||||
@@ -108,8 +138,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
drawImage.layout,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
drawImage.setLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -118,17 +149,20 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
depthImage.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
depthImage.layout,
|
||||
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
|
||||
const auto renderInfo = vkutil::createRenderingInfo({
|
||||
auto renderInfo = vkutil::createRenderingInfo({
|
||||
.renderExtent = drawImage.getExtent2D(),
|
||||
.colorImageView = drawImage.imageView,
|
||||
.colorImageClearValue = glm::vec4{0.f, 0.f, 0.f, 1.f},
|
||||
.depthImageView = depthImage.imageView,
|
||||
.depthImageClearValue = 1.f,
|
||||
});
|
||||
renderInfo.renderingInfo.pColorAttachments = &renderInfo.colorAttachment;
|
||||
renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment;
|
||||
|
||||
{
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "vkCmdBeginRendering");
|
||||
@@ -165,9 +199,11 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
|
||||
void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
VkDevice device = gfxDevice.getDevice().device;
|
||||
VkDevice device = gfxDevice.getDevice();
|
||||
|
||||
vkDeviceWaitIdle(device);
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(device);
|
||||
}
|
||||
|
||||
if (skinningPipeline)
|
||||
skinningPipeline->cleanup(gfxDevice);
|
||||
@@ -188,6 +224,15 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
|
||||
drawImageId = NULL_IMAGE_ID;
|
||||
depthImageId = NULL_IMAGE_ID;
|
||||
resources = nullptr;
|
||||
meshPipeline.reset();
|
||||
skyboxPipeline.reset();
|
||||
skinningPipeline.reset();
|
||||
pendingMaterialUploads.clear();
|
||||
meshDrawCommands.clear();
|
||||
sortedMeshDrawCommands.clear();
|
||||
initialized = false;
|
||||
GameState::GetInstance().SetRenderer(nullptr);
|
||||
}
|
||||
|
||||
void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId)
|
||||
@@ -199,6 +244,7 @@ void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID ma
|
||||
meshDrawCommands.push_back(MeshDrawCommand{
|
||||
.meshId = id,
|
||||
.transformMatrix = transform,
|
||||
.worldBoundingSphere = worldBoundingSphere,
|
||||
.materialId = materialId,
|
||||
});
|
||||
}
|
||||
@@ -210,13 +256,14 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
|
||||
std::size_t jointMatricesStartIndex)
|
||||
{
|
||||
const auto& mesh = resources->meshes().getMesh(id);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, false);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, true);
|
||||
assert(materialId != NULL_MATERIAL_ID);
|
||||
assert(skinnedMesh != nullptr);
|
||||
|
||||
meshDrawCommands.push_back(MeshDrawCommand{
|
||||
.meshId = id,
|
||||
.transformMatrix = transform,
|
||||
.worldBoundingSphere = worldBoundingSphere,
|
||||
.materialId = materialId,
|
||||
.skinnedMesh = skinnedMesh,
|
||||
.jointMatricesStartIndex = static_cast<std::uint32_t>(jointMatricesStartIndex),
|
||||
@@ -249,10 +296,20 @@ void GameRenderer::setSkyboxTexture(ImageID skyboxImageId)
|
||||
|
||||
void GameRenderer::flushMaterialUpdates(GfxDevice& gfxDevice)
|
||||
{
|
||||
if (!pendingMaterialUploads.empty()) {
|
||||
// Material data is currently shared by all frame contexts. Wait before
|
||||
// mutating it so an older in-flight frame cannot read the overwritten
|
||||
// range. This can later be replaced with per-frame material buffers.
|
||||
gfxDevice.waitIdle();
|
||||
}
|
||||
|
||||
for (MaterialID id : pendingMaterialUploads)
|
||||
{
|
||||
resources->materials().updateMaterialGPU(id);
|
||||
// if non-coherent: flush mapped range for that id here
|
||||
gfxDevice.getMemoryManager().flushAllocation(
|
||||
resources->materials().getMaterialDataBuffer(),
|
||||
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
|
||||
sizeof(MaterialData));
|
||||
}
|
||||
pendingMaterialUploads.clear();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "volk.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
CubeMap::CubeMap()
|
||||
@@ -69,7 +70,7 @@ void CubeMap::RenderToCubemap(
|
||||
);
|
||||
}
|
||||
|
||||
gfxDevice.GetImmediateExecuter().immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
gfxDevice.getImmediateExecuter().immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
@@ -201,45 +202,57 @@ void CubeMap::CreateCubeMap(
|
||||
|
||||
std::array<VkImageView, 6> faceViews{};
|
||||
|
||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = cubeMapImage.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = face;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
try {
|
||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = cubeMapImage.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = face;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
|
||||
if (vkCreateImageView(
|
||||
VK_CHECK(vkCreateImageView(
|
||||
gfxDevice.getDevice(),
|
||||
&viewInfo,
|
||||
nullptr,
|
||||
&faceViews[face]) != VK_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Failed to create cubemap face image view.");
|
||||
&faceViews[face]));
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("HDRI image id = {}", m_hdrImage);
|
||||
spdlog::info("HDRI image id = {}", m_hdrImage);
|
||||
|
||||
RenderToCubemap(
|
||||
gfxDevice,
|
||||
resources,
|
||||
m_hdrImage,
|
||||
cubeMapImage.image,
|
||||
faceViews,
|
||||
m_cubeMapSize
|
||||
);
|
||||
RenderToCubemap(
|
||||
gfxDevice,
|
||||
resources,
|
||||
m_hdrImage,
|
||||
cubeMapImage.image,
|
||||
faceViews,
|
||||
m_cubeMapSize
|
||||
);
|
||||
|
||||
m_cubemapImageID = resources.addImageToCache(std::move(cubeMapImage));
|
||||
|
||||
for (VkImageView view : faceViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(gfxDevice.getDevice(), view, nullptr);
|
||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||
cubeMapImage.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, face);
|
||||
}
|
||||
|
||||
m_cubemapImageID = resources.addImageToCache(std::move(cubeMapImage));
|
||||
|
||||
for (VkImageView& view : faceViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(gfxDevice.getDevice(), view, nullptr);
|
||||
view = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
for (VkImageView& view : faceViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(gfxDevice.getDevice(), view, nullptr);
|
||||
}
|
||||
}
|
||||
gfxDevice.destroyImage(cubeMapImage);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <destrum/Graphics/Resources/NBuffer.h>
|
||||
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Managers/MemoryManager.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
@@ -18,29 +19,37 @@ void NBuffer::init(
|
||||
assert(FRAMES_IN_FLIGHT > 0);
|
||||
assert(dataSize > 0);
|
||||
|
||||
framesInFlight = FRAMES_IN_FLIGHT;
|
||||
gpuBufferSize = dataSize;
|
||||
try {
|
||||
framesInFlight = FRAMES_IN_FLIGHT;
|
||||
gpuBufferSize = dataSize;
|
||||
memoryManager = &gfxDevice.getMemoryManager();
|
||||
|
||||
gpuBuffer = gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuBuffer.buffer, debugName.c_str());
|
||||
gpuBuffer = gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuBuffer.buffer, debugName.c_str());
|
||||
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
stagingBuffers.push_back(gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST));
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
stagingBuffers.push_back(gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST));
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void NBuffer::cleanup(GfxDevice& device)
|
||||
{
|
||||
for (const auto& stagingBuffer : stagingBuffers) {
|
||||
for (auto& stagingBuffer : stagingBuffers) {
|
||||
device.destroyBuffer(stagingBuffer);
|
||||
}
|
||||
stagingBuffers.clear();
|
||||
|
||||
device.destroyBuffer(gpuBuffer);
|
||||
gpuBuffer = {};
|
||||
memoryManager = nullptr;
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
@@ -66,7 +75,7 @@ void NBuffer::uploadNewData(
|
||||
const auto bufferBarrier = VkBufferMemoryBarrier2{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_READ_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||
.buffer = gpuBuffer.buffer,
|
||||
@@ -84,6 +93,10 @@ void NBuffer::uploadNewData(
|
||||
auto& staging = stagingBuffers[frameIndex];
|
||||
auto* mappedData = reinterpret_cast<std::uint8_t*>(staging.info.pMappedData);
|
||||
memcpy((void*)&mappedData[offset], newData, dataSize);
|
||||
memoryManager->flushAllocation(
|
||||
staging,
|
||||
static_cast<VkDeviceSize>(offset),
|
||||
static_cast<VkDeviceSize>(dataSize));
|
||||
|
||||
const auto region = VkBufferCopy2{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2,
|
||||
@@ -91,6 +104,12 @@ void NBuffer::uploadNewData(
|
||||
.dstOffset = (VkDeviceSize)offset,
|
||||
.size = dataSize,
|
||||
};
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
staging.buffer,
|
||||
static_cast<VkDeviceSize>(offset),
|
||||
static_cast<VkDeviceSize>(dataSize));
|
||||
|
||||
const auto bufCopyInfo = VkCopyBufferInfo2{
|
||||
.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2,
|
||||
.srcBuffer = staging.buffer,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include <format>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Swapchain.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Init.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include <volk.h>
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
|
||||
void Swapchain::initSync(VkDevice device) {
|
||||
@@ -23,31 +24,39 @@ void Swapchain::initSync(VkDevice device) {
|
||||
}
|
||||
}
|
||||
|
||||
void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync) {
|
||||
void Swapchain::createSwapchain(
|
||||
VkDevice,
|
||||
vkb::Device vkbDevice,
|
||||
VkSurfaceKHR surf,
|
||||
VkFormat format,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
bool vSync)
|
||||
{
|
||||
ZoneScopedN("Swapchain::createSwapchain");
|
||||
|
||||
m_gfxDevice = gfxDevice;
|
||||
assert(format == VK_FORMAT_B8G8R8A8_SRGB && "TODO: test other formats");
|
||||
// vSync = true;
|
||||
surface = surf;
|
||||
|
||||
{
|
||||
ZoneScopedN("vkb::SwapchainBuilder::build");
|
||||
|
||||
auto res = vkb::SwapchainBuilder{gfxDevice->getDevice()}
|
||||
auto res = vkb::SwapchainBuilder{vkbDevice, surface}
|
||||
.set_desired_format(VkSurfaceFormatKHR{
|
||||
.format = format,
|
||||
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||
})
|
||||
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.add_image_usage_flags(
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.set_desired_present_mode(
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR
|
||||
: VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
.set_desired_extent(width, height)
|
||||
.build();
|
||||
|
||||
if (!res.has_value()) {
|
||||
// throw std::runtime_error(std::format(
|
||||
// "failed to create swapchain: error = {}, vk result = {}",
|
||||
// res.full_error().type.message(),
|
||||
// string_VkResult(res.full_error().vk_result)));
|
||||
throw std::runtime_error(
|
||||
"Failed to create swapchain: " + res.error().message());
|
||||
}
|
||||
m_swapchain = res.value();
|
||||
}
|
||||
@@ -55,8 +64,13 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
|
||||
{
|
||||
ZoneScopedN("Get Swapchain Images / Views");
|
||||
|
||||
images = m_swapchain.get_images().value();
|
||||
imageViews = m_swapchain.get_image_views().value();
|
||||
const auto imageResult = m_swapchain.get_images();
|
||||
const auto viewResult = m_swapchain.get_image_views();
|
||||
if (!imageResult.has_value() || !viewResult.has_value()) {
|
||||
throw std::runtime_error("Failed to retrieve swapchain images or views");
|
||||
}
|
||||
images = imageResult.value();
|
||||
imageViews = viewResult.value();
|
||||
}
|
||||
|
||||
imageRenderSemaphores.resize(images.size());
|
||||
@@ -65,20 +79,20 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
|
||||
};
|
||||
|
||||
for (auto& sem: imageRenderSemaphores) {
|
||||
for (auto& sem : imageRenderSemaphores) {
|
||||
ZoneScopedN("Create Image Render Semaphore");
|
||||
|
||||
VK_CHECK(vkCreateSemaphore(m_gfxDevice->getDevice(), &sci, nullptr, &sem));
|
||||
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
||||
}
|
||||
|
||||
// TODO: if re-creation of swapchain is supported, don't forget to call
|
||||
// vkutil::initSwapchainViews here.
|
||||
|
||||
extent = m_swapchain.extent;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Swapchain::recreateSwapchain(
|
||||
const GfxDevice& gfxDevice,
|
||||
VkDevice,
|
||||
vkb::Device vkbDevice,
|
||||
VkSurfaceKHR surf,
|
||||
VkFormat format,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
@@ -91,11 +105,11 @@ void Swapchain::recreateSwapchain(
|
||||
return;
|
||||
}
|
||||
|
||||
VkDevice device = gfxDevice.getDevice();
|
||||
surface = surf;
|
||||
|
||||
{
|
||||
ZoneScopedN("vkDeviceWaitIdle");
|
||||
vkDeviceWaitIdle(device);
|
||||
vkDeviceWaitIdle(vkbDevice);
|
||||
}
|
||||
|
||||
auto oldSwapchain = m_swapchain;
|
||||
@@ -103,23 +117,24 @@ void Swapchain::recreateSwapchain(
|
||||
{
|
||||
ZoneScopedN("vkb::SwapchainBuilder::rebuild");
|
||||
|
||||
auto res = vkb::SwapchainBuilder{gfxDevice.getVkbDevice()}
|
||||
auto res = vkb::SwapchainBuilder{vkbDevice, surface}
|
||||
.set_old_swapchain(oldSwapchain)
|
||||
.set_desired_format(VkSurfaceFormatKHR{
|
||||
.format = format,
|
||||
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||
})
|
||||
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.add_image_usage_flags(
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.set_desired_present_mode(
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR
|
||||
: VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
.set_desired_extent(width, height)
|
||||
.build();
|
||||
|
||||
if (!res.has_value()) {
|
||||
// throw std::runtime_error(std::format(
|
||||
// "failed to create swapchain: error = {}, vk result = {}",
|
||||
// res.full_error().type.message(),
|
||||
// string_VkResult(res.full_error().vk_result)));
|
||||
throw std::runtime_error(
|
||||
"Failed to recreate swapchain: " + res.error().message());
|
||||
}
|
||||
|
||||
m_swapchain = res.value();
|
||||
@@ -129,7 +144,7 @@ void Swapchain::recreateSwapchain(
|
||||
ZoneScopedN("Destroy Old Image Render Semaphores");
|
||||
|
||||
for (auto sem : imageRenderSemaphores) {
|
||||
vkDestroySemaphore(device, sem, nullptr);
|
||||
vkDestroySemaphore(vkbDevice, sem, nullptr);
|
||||
}
|
||||
imageRenderSemaphores.clear();
|
||||
}
|
||||
@@ -138,7 +153,7 @@ void Swapchain::recreateSwapchain(
|
||||
ZoneScopedN("Destroy Old Image Views");
|
||||
|
||||
for (auto imageView : imageViews) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
vkDestroyImageView(vkbDevice, imageView, nullptr);
|
||||
}
|
||||
imageViews.clear();
|
||||
}
|
||||
@@ -151,8 +166,13 @@ void Swapchain::recreateSwapchain(
|
||||
{
|
||||
ZoneScopedN("Get New Swapchain Images / Views");
|
||||
|
||||
images = m_swapchain.get_images().value();
|
||||
imageViews = m_swapchain.get_image_views().value();
|
||||
const auto imageResult = m_swapchain.get_images();
|
||||
const auto viewResult = m_swapchain.get_image_views();
|
||||
if (!imageResult.has_value() || !viewResult.has_value()) {
|
||||
throw std::runtime_error("Failed to retrieve recreated swapchain images or views");
|
||||
}
|
||||
images = imageResult.value();
|
||||
imageViews = viewResult.value();
|
||||
}
|
||||
|
||||
VkSemaphoreCreateInfo sci{
|
||||
@@ -164,57 +184,68 @@ void Swapchain::recreateSwapchain(
|
||||
for (auto& sem : imageRenderSemaphores) {
|
||||
ZoneScopedN("Create New Image Render Semaphore");
|
||||
|
||||
VK_CHECK(vkCreateSemaphore(device, &sci, nullptr, &sem));
|
||||
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
||||
}
|
||||
|
||||
extent = m_swapchain.extent;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Swapchain::cleanup() {
|
||||
for (auto& frame: frames) {
|
||||
vkDestroyFence(m_gfxDevice->getDevice(), frame.renderFence, nullptr);
|
||||
vkDestroySemaphore(m_gfxDevice->getDevice(), frame.swapchainSemaphore, nullptr);
|
||||
} {
|
||||
// destroy swapchain and its views
|
||||
for (auto imageView: imageViews) {
|
||||
vkDestroyImageView(m_gfxDevice->getDevice(), imageView, nullptr);
|
||||
void Swapchain::cleanup(VkDevice device) {
|
||||
for (auto& frame : frames) {
|
||||
if (frame.renderFence != VK_NULL_HANDLE) {
|
||||
vkDestroyFence(device, frame.renderFence, nullptr);
|
||||
frame.renderFence = VK_NULL_HANDLE;
|
||||
}
|
||||
if (frame.swapchainSemaphore != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(device, frame.swapchainSemaphore, nullptr);
|
||||
frame.swapchainSemaphore = VK_NULL_HANDLE;
|
||||
}
|
||||
imageViews.clear();
|
||||
|
||||
vkb::destroy_swapchain(m_swapchain);
|
||||
}
|
||||
|
||||
for (auto& semaphore : imageRenderSemaphores) {
|
||||
if (semaphore != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(device, semaphore, nullptr);
|
||||
semaphore = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
imageRenderSemaphores.clear();
|
||||
|
||||
for (auto imageView : imageViews) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
}
|
||||
imageViews.clear();
|
||||
|
||||
vkb::destroy_swapchain(m_swapchain);
|
||||
m_swapchain = {};
|
||||
images.clear();
|
||||
extent = {};
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Swapchain::beginFrame(int index) const {
|
||||
void Swapchain::beginFrame(VkDevice device, int index) const {
|
||||
ZoneScopedN("Swapchain::beginFrame");
|
||||
|
||||
auto& frame = frames[index];
|
||||
|
||||
{
|
||||
ZoneScopedN("vkWaitForFences");
|
||||
VK_CHECK(vkWaitForFences(m_gfxDevice->getDevice(), 1, &frame.renderFence, true, std::numeric_limits<std::uint64_t>::max()));
|
||||
VK_CHECK(vkWaitForFences(device, 1, &frame.renderFence, true, std::numeric_limits<std::uint64_t>::max()));
|
||||
}
|
||||
}
|
||||
|
||||
void Swapchain::resetFences(int index) const {
|
||||
void Swapchain::resetFences(VkDevice device, int index) const {
|
||||
ZoneScopedN("Swapchain::resetFences");
|
||||
|
||||
auto& frame = frames[index];
|
||||
|
||||
{
|
||||
ZoneScopedN("vkResetFences");
|
||||
VK_CHECK(vkResetFences(m_gfxDevice->getDevice(), 1, &frame.renderFence));
|
||||
VK_CHECK(vkResetFences(device, 1, &frame.renderFence));
|
||||
}
|
||||
}
|
||||
|
||||
struct SwapchainAcquireResult {
|
||||
VkResult result = VK_SUCCESS;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
uint32_t imageIndex = 0;
|
||||
};
|
||||
|
||||
std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||
Swapchain::AcquireResult Swapchain::acquireNextImage(VkDevice device, std::uint32_t index) {
|
||||
ZoneScopedN("Swapchain::acquireNextImage");
|
||||
|
||||
std::uint32_t swapchainImageIndex{};
|
||||
@@ -225,7 +256,7 @@ std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||
ZoneScopedN("vkAcquireNextImageKHR");
|
||||
|
||||
result = vkAcquireNextImageKHR(
|
||||
m_gfxDevice->getDevice(),
|
||||
device,
|
||||
m_swapchain,
|
||||
std::numeric_limits<std::uint64_t>::max(),
|
||||
frames[index].swapchainSemaphore,
|
||||
@@ -233,29 +264,42 @@ std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||
&swapchainImageIndex);
|
||||
}
|
||||
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||
dirty = true;
|
||||
return {images[swapchainImageIndex], swapchainImageIndex};
|
||||
} else if (result != VK_SUCCESS) {
|
||||
return {.result = result};
|
||||
}
|
||||
if (result == VK_SUBOPTIMAL_KHR) {
|
||||
dirty = true;
|
||||
return {
|
||||
.result = result,
|
||||
.image = images.at(swapchainImageIndex),
|
||||
.imageIndex = swapchainImageIndex,
|
||||
};
|
||||
}
|
||||
if (result != VK_SUCCESS) {
|
||||
throw std::runtime_error("failed to acquire swap chain image!");
|
||||
}
|
||||
|
||||
return {images[swapchainImageIndex], swapchainImageIndex};
|
||||
return {
|
||||
.result = result,
|
||||
.image = images.at(swapchainImageIndex),
|
||||
.imageIndex = swapchainImageIndex,
|
||||
};
|
||||
}
|
||||
|
||||
void Swapchain::submitAndPresent(
|
||||
VkDevice device,
|
||||
VkCommandBuffer cmd,
|
||||
VkQueue graphicsQueue,
|
||||
uint32_t imageIndex, // from vkAcquireNextImageKHR
|
||||
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
|
||||
VkQueue presentQueue,
|
||||
std::uint32_t imageIndex,
|
||||
std::uint32_t frameIndex)
|
||||
{
|
||||
ZoneScopedN("Swapchain::submitAndPresent");
|
||||
|
||||
auto& frame = frames[frameIndex]; // ✅ per-frame
|
||||
auto& frame = frames[frameIndex];
|
||||
VkSemaphore renderFinished = imageRenderSemaphores[imageIndex];
|
||||
|
||||
VkSemaphore renderFinished = imageRenderSemaphores[imageIndex]; // ✅ per-image
|
||||
|
||||
// submit
|
||||
VkCommandBufferSubmitInfo cmdInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
|
||||
.commandBuffer = cmd,
|
||||
@@ -263,38 +307,51 @@ void Swapchain::submitAndPresent(
|
||||
|
||||
VkSemaphoreSubmitInfo waitInfo =
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
||||
frame.swapchainSemaphore); // ✅ acquire semaphore (per-frame)
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
frame.swapchainSemaphore);
|
||||
|
||||
VkSemaphoreSubmitInfo signalInfo =
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
|
||||
renderFinished); // ✅ signal semaphore (per-image)
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
renderFinished);
|
||||
|
||||
VkSubmitInfo2 submit = vkinit::submitInfo(&cmdInfo, &waitInfo, &signalInfo);
|
||||
|
||||
VK_CHECK(vkResetFences(device, 1, &frame.renderFence));
|
||||
|
||||
{
|
||||
ZoneScopedN("vkQueueSubmit2");
|
||||
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence)); // ✅ fence (per-frame)
|
||||
const VkResult submitResult = vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence);
|
||||
if (submitResult != VK_SUCCESS) {
|
||||
vkDestroyFence(device, frame.renderFence, nullptr);
|
||||
constexpr VkFenceCreateInfo fenceInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
VK_CHECK(vkCreateFence(device, &fenceInfo, nullptr, &frame.renderFence));
|
||||
checkVkResult(submitResult, "vkQueueSubmit2", __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// present
|
||||
VkPresentInfoKHR presentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &renderFinished,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &m_swapchain.swapchain,
|
||||
.pImageIndices = &imageIndex, // ✅ imageIndex, NOT frameIndex
|
||||
.pImageIndices = &imageIndex,
|
||||
};
|
||||
|
||||
VkResult res = VK_SUCCESS;
|
||||
|
||||
{
|
||||
ZoneScopedN("vkQueuePresentKHR");
|
||||
res = vkQueuePresentKHR(graphicsQueue, &presentInfo);
|
||||
res = vkQueuePresentKHR(presentQueue, &presentInfo);
|
||||
}
|
||||
|
||||
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) dirty = true;
|
||||
else if (res != VK_SUCCESS) dirty = true;
|
||||
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) {
|
||||
dirty = true;
|
||||
} else if (res != VK_SUCCESS) {
|
||||
throw std::runtime_error("failed to present swap chain image");
|
||||
}
|
||||
}
|
||||
|
||||
+120
-81
@@ -6,23 +6,87 @@
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
void vkutil::transitionImage(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout) {
|
||||
VkImageAspectFlags aspectMask =
|
||||
(currentLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL)
|
||||
? VK_IMAGE_ASPECT_DEPTH_BIT
|
||||
: VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
namespace {
|
||||
VkImageAspectFlags AspectForLayout(VkImageLayout currentLayout, VkImageLayout newLayout)
|
||||
{
|
||||
return (currentLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL)
|
||||
? VK_IMAGE_ASPECT_DEPTH_BIT
|
||||
: VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
|
||||
struct LayoutSync {
|
||||
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
|
||||
VkAccessFlags2 access{VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
};
|
||||
|
||||
LayoutSync SyncForLayout(VkImageLayout layout, bool source)
|
||||
{
|
||||
switch (layout) {
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
return {VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, 0};
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
return {VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT};
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
return {VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT};
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
return {
|
||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT};
|
||||
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
|
||||
return {
|
||||
VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT,
|
||||
VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT};
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL:
|
||||
return {VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_READ_BIT};
|
||||
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
||||
return {VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_READ_BIT};
|
||||
default:
|
||||
return source
|
||||
? LayoutSync{}
|
||||
: LayoutSync{VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vkutil::transitionImage(
|
||||
VkCommandBuffer cmd,
|
||||
VkImage image,
|
||||
VkImageLayout currentLayout,
|
||||
VkImageLayout newLayout)
|
||||
{
|
||||
transitionImage(
|
||||
cmd,
|
||||
image,
|
||||
currentLayout,
|
||||
newLayout,
|
||||
vkinit::imageSubresourceRange(AspectForLayout(currentLayout, newLayout)));
|
||||
}
|
||||
|
||||
void vkutil::transitionImage(
|
||||
VkCommandBuffer cmd,
|
||||
VkImage image,
|
||||
VkImageLayout currentLayout,
|
||||
VkImageLayout newLayout,
|
||||
const VkImageSubresourceRange& subresourceRange)
|
||||
{
|
||||
const LayoutSync sourceSync = SyncForLayout(currentLayout, true);
|
||||
const LayoutSync destinationSync = SyncForLayout(newLayout, false);
|
||||
VkImageMemoryBarrier2 imageBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT,
|
||||
.srcStageMask = sourceSync.stage,
|
||||
.srcAccessMask = sourceSync.access,
|
||||
.dstStageMask = destinationSync.stage,
|
||||
.dstAccessMask = destinationSync.access,
|
||||
.oldLayout = currentLayout,
|
||||
.newLayout = newLayout,
|
||||
.image = image,
|
||||
.subresourceRange = vkinit::imageSubresourceRange(aspectMask),
|
||||
.subresourceRange = subresourceRange,
|
||||
};
|
||||
|
||||
VkDependencyInfo depInfo{
|
||||
@@ -101,6 +165,28 @@ void vkutil::bufferHostWriteToShaderReadBarrier(
|
||||
vkCmdPipelineBarrier2(cmd, &dep);
|
||||
}
|
||||
|
||||
void vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
VkCommandBuffer cmd,
|
||||
VkBuffer buffer,
|
||||
VkDeviceSize offset,
|
||||
VkDeviceSize size)
|
||||
{
|
||||
VkBufferMemoryBarrier2 barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2};
|
||||
barrier.srcStageMask = VK_PIPELINE_STAGE_2_HOST_BIT;
|
||||
barrier.srcAccessMask = VK_ACCESS_2_HOST_WRITE_BIT;
|
||||
barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
|
||||
barrier.buffer = buffer;
|
||||
barrier.offset = offset;
|
||||
barrier.size = size;
|
||||
|
||||
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
|
||||
dep.bufferMemoryBarrierCount = 1;
|
||||
dep.pBufferMemoryBarriers = &barrier;
|
||||
|
||||
vkCmdPipelineBarrier2(cmd, &dep);
|
||||
}
|
||||
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
||||
@@ -109,7 +195,7 @@ void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
||||
.objectHandle = (std::uint64_t)image,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
|
||||
@@ -119,7 +205,7 @@ void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const c
|
||||
.objectHandle = (std::uint64_t)shaderModule,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||
@@ -129,7 +215,7 @@ void vkutil::addDebugLabel(VkDevice device, VkPipeline pipeline, const char* lab
|
||||
.objectHandle = (std::uint64_t)pipeline,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||
@@ -139,7 +225,7 @@ void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label)
|
||||
.objectHandle = (std::uint64_t)buffer,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
||||
@@ -149,7 +235,7 @@ void vkutil::addDebugLabel(VkDevice device, VkSampler sampler, const char* label
|
||||
.objectHandle = (std::uint64_t)sampler,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params) {
|
||||
@@ -207,15 +293,19 @@ vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params
|
||||
VkShaderModule vkutil::loadShaderModule(const std::filesystem::path& path, VkDevice device) {
|
||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
spdlog::error("failed to open shader");
|
||||
std::exit(1);
|
||||
throw std::runtime_error("Failed to open shader: " + path.string());
|
||||
}
|
||||
|
||||
const auto fileSize = file.tellg();
|
||||
if (fileSize < 0 || fileSize % static_cast<std::streamoff>(sizeof(std::uint32_t)) != 0) {
|
||||
throw std::runtime_error("Shader file has an invalid size: " + path.string());
|
||||
}
|
||||
std::vector<std::uint32_t> buffer(fileSize / sizeof(std::uint32_t));
|
||||
|
||||
file.seekg(0);
|
||||
file.read((char*)buffer.data(), fileSize);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), fileSize)) {
|
||||
throw std::runtime_error("Failed to read shader: " + path.string());
|
||||
}
|
||||
file.close();
|
||||
|
||||
auto info = VkShaderModuleCreateInfo{
|
||||
@@ -224,11 +314,8 @@ VkShaderModule vkutil::loadShaderModule(const std::filesystem::path& path, VkDev
|
||||
.pCode = buffer.data(),
|
||||
};
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
if (vkCreateShaderModule(device, &info, nullptr, &shaderModule) != VK_SUCCESS) {
|
||||
spdlog::error("Failed to load");
|
||||
std::exit(1);
|
||||
}
|
||||
VkShaderModule shaderModule{VK_NULL_HANDLE};
|
||||
VK_CHECK(vkCreateShaderModule(device, &info, nullptr, &shaderModule));
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
@@ -239,7 +326,7 @@ void addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
||||
.objectHandle = (std::uint64_t)image,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label) {
|
||||
@@ -249,7 +336,7 @@ void addDebugLabel(VkDevice device, VkImageView imageView, const char* label) {
|
||||
.objectHandle = (std::uint64_t)imageView,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
|
||||
@@ -259,7 +346,7 @@ void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* lab
|
||||
.objectHandle = (std::uint64_t)shaderModule,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||
@@ -269,7 +356,7 @@ void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||
.objectHandle = (std::uint64_t)pipeline,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label) {
|
||||
@@ -279,7 +366,7 @@ void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label)
|
||||
.objectHandle = (std::uint64_t)layout,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||
@@ -289,7 +376,7 @@ void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||
.objectHandle = (std::uint64_t)buffer,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
||||
@@ -299,57 +386,9 @@ void addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
||||
.objectHandle = (std::uint64_t)sampler,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params) {
|
||||
assert(
|
||||
(params.colorImageView || params.depthImageView != nullptr) &&
|
||||
"Either draw or depth image should be present");
|
||||
assert(
|
||||
params.renderExtent.width != 0.f && params.renderExtent.height != 0.f &&
|
||||
"renderExtent not specified");
|
||||
|
||||
vkutil::RenderInfo ri;
|
||||
if (params.colorImageView) {
|
||||
ri.colorAttachment = VkRenderingAttachmentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
|
||||
.imageView = params.colorImageView,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
.loadOp = params.colorImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
};
|
||||
if (params.colorImageClearValue) {
|
||||
const auto col = params.colorImageClearValue.value();
|
||||
ri.colorAttachment.clearValue.color = {col[0], col[1], col[2], col[3]};
|
||||
}
|
||||
}
|
||||
|
||||
if (params.depthImageView) {
|
||||
ri.depthAttachment = VkRenderingAttachmentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
|
||||
.imageView = params.depthImageView,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
|
||||
.loadOp = params.depthImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
};
|
||||
if (params.depthImageClearValue) {
|
||||
ri.depthAttachment.clearValue.depthStencil.depth = params.depthImageClearValue.value();
|
||||
}
|
||||
}
|
||||
|
||||
ri.renderingInfo = VkRenderingInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
|
||||
.renderArea =
|
||||
VkRect2D{
|
||||
.offset = {},
|
||||
.extent = params.renderExtent,
|
||||
},
|
||||
.layerCount = 1,
|
||||
.colorAttachmentCount = params.colorImageView ? 1u : 0u,
|
||||
.pColorAttachments = params.colorImageView ? &ri.colorAttachment : nullptr,
|
||||
.pDepthAttachment = params.depthImageView ? &ri.depthAttachment : nullptr,
|
||||
};
|
||||
|
||||
return ri;
|
||||
return vkutil::createRenderingInfo(params);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <stdexcept>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
|
||||
Component::Component(GameObject& pParent, const std::string& name)
|
||||
: Object(name),
|
||||
@@ -14,6 +16,7 @@ Transform& Component::GetTransform() const {
|
||||
|
||||
void Component::Destroy() {
|
||||
Object::Destroy();
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
void Component::Start() {
|
||||
@@ -21,12 +24,30 @@ void Component::Start() {
|
||||
|
||||
void Component::SetEnabled(bool enabled) {
|
||||
m_IsEnabled = enabled;
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
void Component::LateUpdate() {
|
||||
void Component::NotifyPhysicsChanged() {
|
||||
if (m_ParentGameObjectPtr != nullptr) {
|
||||
if (auto* rigidbody = m_ParentGameObjectPtr->GetComponent<Rigidbody>();
|
||||
rigidbody != nullptr && rigidbody->GetPhysicsWorld() != nullptr) {
|
||||
rigidbody->GetPhysicsWorld()->RefreshRigidbody(*rigidbody);
|
||||
} else {
|
||||
m_ParentGameObjectPtr->RefreshPhysics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Component::FixedUpdate() {
|
||||
void Component::Update(float dt) {
|
||||
m_LastUpdateDeltaTime = dt;
|
||||
}
|
||||
|
||||
void Component::LateUpdate(float dt) {
|
||||
m_LastUpdateDeltaTime = dt;
|
||||
}
|
||||
|
||||
void Component::FixedUpdate(float fixedDt) {
|
||||
m_LastFixedDeltaTime = fixedDt;
|
||||
}
|
||||
|
||||
void Component::ImGuiInspector() {
|
||||
@@ -35,5 +56,5 @@ void Component::ImGuiInspector() {
|
||||
void Component::ImGuiRender() {
|
||||
}
|
||||
|
||||
void Component::Render(const RenderContext& ctx) {
|
||||
void Component::Render(const RenderContext&) {
|
||||
}
|
||||
@@ -1,32 +1,72 @@
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] std::vector<Component*> SnapshotComponents(const GameObject& object) {
|
||||
std::vector<Component*> components;
|
||||
components.reserve(object.GetComponents().size());
|
||||
for (const auto& component : object.GetComponents()) {
|
||||
if (component != nullptr) {
|
||||
components.push_back(component.get());
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
bool EnsureComponentStarted(Component& component) {
|
||||
if (!component.HasStarted) {
|
||||
component.Start();
|
||||
component.HasStarted = true;
|
||||
}
|
||||
return !component.IsBeingDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
GameObject::~GameObject() {
|
||||
// spdlog::debug("GameObject destroyed: {}", GetName());
|
||||
if (!IsBeingDestroyed()) {
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
GameObject::GameObject(const std::string& name)
|
||||
: Object(name),
|
||||
m_Id(s_NextId++) {
|
||||
m_Id(AllocateId()) {
|
||||
}
|
||||
|
||||
void GameObject::SetIdForDeserialization(ObjectId id) {
|
||||
if (id == InvalidObjectId || id >= std::numeric_limits<ObjectId>::max() - 1) {
|
||||
throw std::out_of_range("Object ID is reserved or cannot be incremented");
|
||||
}
|
||||
|
||||
m_Id = id;
|
||||
|
||||
// Prevent newly created objects from reusing loaded IDs.
|
||||
if (id >= s_NextId) {
|
||||
if (id != std::numeric_limits<ObjectId>::max() && id >= s_NextId) {
|
||||
s_NextId = id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectId GameObject::AllocateId() {
|
||||
if (s_NextId == InvalidObjectId ||
|
||||
s_NextId >= std::numeric_limits<ObjectId>::max() - 1) {
|
||||
throw std::overflow_error("Object ID range exhausted");
|
||||
}
|
||||
return s_NextId++;
|
||||
}
|
||||
|
||||
void GameObject::SetActiveDirty() {
|
||||
m_ActiveDirty = true;
|
||||
|
||||
for (const Transform* child : m_TransformPtr.GetChildren()) {
|
||||
child->GetOwner()->SetActiveDirty();
|
||||
if (child != nullptr && child->GetOwner() != nullptr) {
|
||||
child->GetOwner()->SetActiveDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +75,10 @@ void GameObject::UpdateActiveState() {
|
||||
|
||||
if (parentPtr == nullptr) {
|
||||
m_ActiveInHierarchy = m_Active;
|
||||
} else {
|
||||
} else if (parentPtr->GetOwner() != nullptr) {
|
||||
m_ActiveInHierarchy = m_Active && parentPtr->GetOwner()->IsActiveInHierarchy();
|
||||
} else {
|
||||
m_ActiveInHierarchy = m_Active;
|
||||
}
|
||||
|
||||
m_ActiveDirty = false;
|
||||
@@ -50,61 +92,112 @@ bool GameObject::IsActiveInHierarchy() {
|
||||
return m_ActiveInHierarchy;
|
||||
}
|
||||
|
||||
void GameObject::Update() {
|
||||
for (const auto& component : m_Components) {
|
||||
void GameObject::Update(float dt) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component->HasStarted) {
|
||||
component->Start();
|
||||
component->HasStarted = true;
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
component->Update();
|
||||
component->Update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::LateUpdate() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (component->isEnabled()) {
|
||||
component->LateUpdate();
|
||||
void GameObject::LateUpdate(float dt) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->isEnabled() && !component->IsBeingDestroyed()) {
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
component->LateUpdate(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::FixedUpdate() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (component->isEnabled()) {
|
||||
component->FixedUpdate();
|
||||
void GameObject::FixedUpdate(float fixedDt) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->isEnabled() && !component->IsBeingDestroyed()) {
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
component->FixedUpdate(fixedDt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::Render(const RenderContext& ctx) const {
|
||||
for (const auto& component : m_Components) {
|
||||
if (component->isEnabled()) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->isEnabled() && !component->IsBeingDestroyed()) {
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
component->Render(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::Destroy() {
|
||||
if (IsBeingDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object::Destroy();
|
||||
|
||||
if (m_Scene != nullptr) {
|
||||
m_Scene->GetPhysics().UnregisterGameObject(*this);
|
||||
}
|
||||
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (auto* rigidbody = dynamic_cast<Rigidbody*>(component);
|
||||
rigidbody != nullptr && rigidbody->GetPhysicsWorld() != nullptr) {
|
||||
rigidbody->GetPhysicsWorld()->UnregisterRigidbody(*rigidbody);
|
||||
}
|
||||
}
|
||||
|
||||
m_TransformPtr.SetParent(nullptr);
|
||||
|
||||
for (const auto& component : m_Components) {
|
||||
component->Destroy();
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component != nullptr) {
|
||||
component->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto child : m_TransformPtr.GetChildren()) {
|
||||
child->GetOwner()->Destroy();
|
||||
// Destroying a child detaches it from this transform, so iterate over a
|
||||
// snapshot rather than the live child vector.
|
||||
const std::vector<Transform*> children = m_TransformPtr.GetChildren();
|
||||
for (Transform* child : children) {
|
||||
if (child != nullptr && child->GetOwner() != nullptr) {
|
||||
child->GetOwner()->Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::CleanupComponents() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (!component || !component->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* rigidbody = dynamic_cast<Rigidbody*>(component.get()); rigidbody != nullptr &&
|
||||
rigidbody->GetPhysicsWorld() != nullptr) {
|
||||
rigidbody->GetPhysicsWorld()->UnregisterRigidbody(*rigidbody);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(m_Components, [](const std::unique_ptr<Component>& component) {
|
||||
return component->IsBeingDestroyed();
|
||||
return component == nullptr || component->IsBeingDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
void GameObject::RefreshPhysics() {
|
||||
if (m_Scene != nullptr) {
|
||||
m_Scene->RefreshPhysics();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
#include <destrum/ObjectModel/Object.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
Object::~Object() {
|
||||
if (!m_BeingDestroyed) {
|
||||
assert(false && "Objects destructor called before destroy");
|
||||
}
|
||||
// spdlog::debug("Object Destroyed: {}", m_Name);
|
||||
}
|
||||
|
||||
void Object::Destroy() {
|
||||
// spdlog::debug("Object marked for destruction: {}", m_Name);
|
||||
if (m_BeingDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_BeingDestroyed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,119 @@
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
namespace {
|
||||
bool IsChildRecursive(const Transform* parent,
|
||||
const Transform* target,
|
||||
std::unordered_set<const Transform*>& visited) {
|
||||
if (parent == nullptr || !visited.insert(parent).second) {
|
||||
return false;
|
||||
}
|
||||
for (const Transform* candidate : parent->GetChildren()) {
|
||||
if (candidate == target || IsChildRecursive(candidate, target, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] glm::mat4 ComposeMatrix(const glm::vec3& position,
|
||||
const glm::quat& rotation,
|
||||
const glm::vec3& scale) {
|
||||
return glm::translate(glm::mat4{1.0f}, position)
|
||||
* glm::mat4_cast(rotation)
|
||||
* glm::scale(glm::mat4{1.0f}, scale);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DecomposeMatrix(const glm::mat4& matrix,
|
||||
glm::vec3& position,
|
||||
glm::quat& rotation,
|
||||
glm::vec3& scale) {
|
||||
glm::vec3 skew{};
|
||||
glm::vec4 perspective{};
|
||||
if (!glm::decompose(matrix, scale, rotation, position, skew, perspective)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (glm::length(skew) > 0.0001f ||
|
||||
glm::length(perspective - glm::vec4{0.0f, 0.0f, 0.0f, 1.0f}) > 0.0001f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float rotationLength = glm::length(rotation);
|
||||
if (rotationLength <= 0.000001f || !std::isfinite(rotationLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rotation = glm::normalize(rotation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Transform::Transform(GameObject* owner): m_Owner(owner) {
|
||||
}
|
||||
|
||||
Transform::~Transform() {
|
||||
SetParent(nullptr);
|
||||
if (m_Parent != nullptr) {
|
||||
Transform* parent = m_Parent;
|
||||
m_Parent = nullptr;
|
||||
parent->RemoveChild(this);
|
||||
}
|
||||
|
||||
for (auto it = m_Children.begin(); it != m_Children.end();) {
|
||||
Transform* child = *it;
|
||||
it = m_Children.erase(it);
|
||||
child->SetParent(nullptr);
|
||||
// Do not call SetParent while the owning object is being torn down. The
|
||||
// parent may already be in its destructor, so update the links directly.
|
||||
const std::vector<Transform*> children = std::move(m_Children);
|
||||
m_Children.clear();
|
||||
for (Transform* child : children) {
|
||||
if (child == nullptr || child->m_Parent != this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
child->m_Parent = nullptr;
|
||||
child->SetPositionDirty();
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3& Transform::GetWorldPosition() {
|
||||
if (m_PositionDirty) {
|
||||
UpdateWorldPosition();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldPosition;
|
||||
}
|
||||
|
||||
const glm::quat& Transform::GetWorldRotation() {
|
||||
if (m_RotationDirty) {
|
||||
UpdateWorldRotation();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldRotation;
|
||||
}
|
||||
|
||||
const glm::vec3& Transform::GetWorldScale() {
|
||||
if (m_ScaleDirty) {
|
||||
UpdateWorldScale();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldScale;
|
||||
}
|
||||
|
||||
const glm::mat4& Transform::GetWorldMatrix() {
|
||||
if (m_MatrixDirty) {
|
||||
UpdateWorldMatrix();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldMatrix;
|
||||
}
|
||||
|
||||
void Transform::SetWorldPosition(const glm::vec3& position) {
|
||||
if (m_Parent == nullptr) {
|
||||
SetLocalPosition(position);
|
||||
} else {
|
||||
SetLocalPosition(position - m_Parent->GetWorldPosition());
|
||||
}
|
||||
glm::mat4 worldMatrix = GetWorldMatrix();
|
||||
worldMatrix[3] = glm::vec4(position, 1.0f);
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
}
|
||||
|
||||
void Transform::SetWorldPosition(float x, float y, float z) {
|
||||
@@ -55,11 +121,10 @@ void Transform::SetWorldPosition(float x, float y, float z) {
|
||||
}
|
||||
|
||||
void Transform::SetWorldRotation(const glm::quat& rotation) {
|
||||
if(m_Parent == nullptr) {
|
||||
SetLocalRotation(rotation);
|
||||
} else {
|
||||
SetLocalRotation(glm::inverse(m_Parent->GetWorldRotation()) * rotation);
|
||||
}
|
||||
const glm::vec3 worldPosition = GetWorldPosition();
|
||||
const glm::vec3 worldScale = GetWorldScale();
|
||||
const glm::mat4 worldMatrix = ComposeMatrix(worldPosition, rotation, worldScale);
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
}
|
||||
|
||||
void Transform::SetWorldRotation(const glm::vec3& rotation) {
|
||||
@@ -75,11 +140,10 @@ void Transform::SetWorldScale(double x, double y, double z) {
|
||||
}
|
||||
|
||||
void Transform::SetWorldScale(const glm::vec3& scale) {
|
||||
if (m_Parent == nullptr) {
|
||||
SetLocalScale(scale);
|
||||
} else {
|
||||
SetLocalScale(scale - m_Parent->GetWorldScale());
|
||||
}
|
||||
const glm::vec3 worldPosition = GetWorldPosition();
|
||||
const glm::quat worldRotation = GetWorldRotation();
|
||||
const glm::mat4 worldMatrix = ComposeMatrix(worldPosition, worldRotation, scale);
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
}
|
||||
|
||||
void Transform::Move(const glm::vec3& move) {
|
||||
@@ -87,7 +151,7 @@ void Transform::Move(const glm::vec3& move) {
|
||||
}
|
||||
|
||||
void Transform::Move(double x, double y, double z) {
|
||||
this->Move(glm::vec3(x, y, z));
|
||||
Move(glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
void Transform::SetLocalPosition(const glm::vec3& position) {
|
||||
@@ -108,7 +172,10 @@ void Transform::SetLocalRotation(const glm::vec3& rotation) {
|
||||
}
|
||||
|
||||
void Transform::SetLocalRotation(const glm::quat& rotation) {
|
||||
m_LocalRotation = rotation;
|
||||
const float length = glm::length(rotation);
|
||||
m_LocalRotation = length > 0.000001f
|
||||
? glm::normalize(rotation)
|
||||
: glm::quat{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
SetRotationDirty();
|
||||
}
|
||||
|
||||
@@ -122,132 +189,196 @@ void Transform::SetLocalScale(const glm::vec3& scale) {
|
||||
}
|
||||
|
||||
void Transform::RemoveChild(Transform* transform) {
|
||||
std::erase(m_Children, transform);
|
||||
const auto it = std::find(m_Children.begin(), m_Children.end(), transform);
|
||||
if (it != m_Children.end()) {
|
||||
m_Children.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::AddChild(Transform* transform) {
|
||||
// if (transform == this or transform == nullptr) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (transform->m_Parent) {
|
||||
// transform->m_Parent->RemoveChild(transform);
|
||||
// }
|
||||
//
|
||||
// transform->SetParent(this);
|
||||
m_Children.push_back(transform);
|
||||
//
|
||||
// transform->SetPositionDirty();
|
||||
}
|
||||
|
||||
void Transform::SetParent(Transform* parent, bool useWorldPosition) {
|
||||
if (parent == m_Parent or parent == this or IsChild(parent)) {
|
||||
if (transform == nullptr || transform == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent == nullptr) {
|
||||
SetLocalPosition(GetWorldPosition());
|
||||
} else {
|
||||
if (std::find(m_Children.begin(), m_Children.end(), transform) != m_Children.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Children.push_back(transform);
|
||||
}
|
||||
|
||||
void Transform::SetParent(Transform* parent, bool useWorldPosition) {
|
||||
if (parent == m_Parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A transform cannot be parented to itself or to one of its descendants.
|
||||
if (parent == this || IsChild(parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent != nullptr) {
|
||||
GameObject* parentOwner = parent->GetOwner();
|
||||
if (parentOwner == nullptr || parentOwner->IsBeingDestroyed() ||
|
||||
(m_Owner != nullptr && m_Owner->GetScene() != parentOwner->GetScene())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 worldMatrix{1.0f};
|
||||
if (useWorldPosition) {
|
||||
worldMatrix = GetWorldMatrix();
|
||||
}
|
||||
|
||||
Transform* previousParent = m_Parent;
|
||||
if (previousParent != nullptr) {
|
||||
previousParent->RemoveChild(this);
|
||||
}
|
||||
|
||||
m_Parent = parent;
|
||||
if (m_Parent != nullptr) {
|
||||
m_Parent->AddChild(this);
|
||||
}
|
||||
|
||||
if (m_Owner != nullptr) {
|
||||
m_Owner->SetActiveDirty();
|
||||
m_Owner->RefreshPhysics();
|
||||
}
|
||||
|
||||
try {
|
||||
if (useWorldPosition) {
|
||||
SetLocalPosition(GetWorldPosition() - parent->GetWorldPosition());
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
} else {
|
||||
SetPositionDirty();
|
||||
}
|
||||
} catch (...) {
|
||||
if (m_Parent != nullptr) {
|
||||
m_Parent->RemoveChild(this);
|
||||
}
|
||||
m_Parent = previousParent;
|
||||
if (m_Parent != nullptr) {
|
||||
m_Parent->AddChild(this);
|
||||
}
|
||||
SetPositionDirty();
|
||||
}
|
||||
if (m_Parent) {
|
||||
m_Parent->RemoveChild(this);
|
||||
}
|
||||
m_Parent = parent;
|
||||
if (m_Parent) {
|
||||
m_Parent->AddChild(this);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Transform::IsChild(Transform* child) const {
|
||||
return std::ranges::find(m_Children, child) != m_Children.end();
|
||||
if (child == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::unordered_set<const Transform*> visited;
|
||||
return IsChildRecursive(this, child, visited);
|
||||
}
|
||||
|
||||
const std::vector<Transform*>& Transform::GetChildren() const {
|
||||
// std::vector<Transform*> validChildren;
|
||||
// for (auto* child : m_Children) {
|
||||
// if (child && !child->GetOwner()->IsBeingDestroyed()) {
|
||||
// validChildren.push_back(child);
|
||||
// }
|
||||
// }
|
||||
// return validChildren;
|
||||
return m_Children;
|
||||
}
|
||||
|
||||
GameObject *Transform::GetOwner() const {
|
||||
GameObject* Transform::GetOwner() const {
|
||||
return m_Owner;
|
||||
}
|
||||
|
||||
void Transform::SetPositionDirty() {
|
||||
m_PositionDirty = true;
|
||||
m_RotationDirty = true;
|
||||
m_ScaleDirty = true;
|
||||
m_MatrixDirty = true;
|
||||
for (const auto child: m_Children) {
|
||||
child->SetPositionDirty();
|
||||
|
||||
for (Transform* child : m_Children) {
|
||||
if (child != nullptr) {
|
||||
child->SetPositionDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::SetRotationDirty() {
|
||||
m_PositionDirty = true;
|
||||
m_RotationDirty = true;
|
||||
m_ScaleDirty = true;
|
||||
m_MatrixDirty = true;
|
||||
|
||||
for(Transform* childPtr : m_Children) {
|
||||
if(not childPtr->m_RotationDirty) {
|
||||
childPtr->SetRotationDirty();
|
||||
for (Transform* child : m_Children) {
|
||||
if (child != nullptr) {
|
||||
child->SetRotationDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::SetScaleDirty() {
|
||||
m_PositionDirty = true;
|
||||
m_RotationDirty = true;
|
||||
m_ScaleDirty = true;
|
||||
m_MatrixDirty = true;
|
||||
|
||||
for(Transform* childPtr : m_Children) {
|
||||
if(not childPtr->m_ScaleDirty) {
|
||||
childPtr->SetScaleDirty();
|
||||
for (Transform* child : m_Children) {
|
||||
if (child != nullptr) {
|
||||
child->SetScaleDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldPosition() {
|
||||
if (m_Parent) {
|
||||
m_WorldPosition = m_Parent->GetWorldPosition() + m_LocalPosition;
|
||||
} else {
|
||||
m_WorldPosition = m_LocalPosition;
|
||||
}
|
||||
m_PositionDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldRotation() {
|
||||
if (m_Parent == nullptr) {
|
||||
m_WorldRotation = m_LocalRotation;
|
||||
} else {
|
||||
m_WorldRotation = m_LocalRotation * m_Parent->GetWorldRotation();
|
||||
}
|
||||
|
||||
m_RotationDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldScale() {
|
||||
if(m_Parent == nullptr) {
|
||||
m_WorldScale = m_LocalScale;
|
||||
} else {
|
||||
m_WorldScale = m_LocalScale * m_Parent->GetWorldScale();
|
||||
}
|
||||
|
||||
m_ScaleDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldMatrix() {
|
||||
const glm::mat4 trans = glm::translate(glm::mat4(1.0f), GetWorldPosition());
|
||||
const glm::mat4 rot = glm::mat4_cast(GetWorldRotation());
|
||||
const glm::mat4 scale = glm::scale(glm::mat4(1.0f), GetWorldScale());
|
||||
|
||||
m_WorldMatrix = trans * rot * scale;
|
||||
m_MatrixDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::SetLocalFromWorldMatrix(const glm::mat4& worldMatrix) {
|
||||
glm::mat4 localMatrix = worldMatrix;
|
||||
if (m_Parent != nullptr) {
|
||||
const glm::mat4 parentMatrix = m_Parent->GetWorldMatrix();
|
||||
if (std::abs(glm::determinant(parentMatrix)) <= 0.000001f) {
|
||||
throw std::runtime_error("Cannot set a world transform under a singular parent");
|
||||
}
|
||||
localMatrix = glm::inverse(parentMatrix) * worldMatrix;
|
||||
}
|
||||
|
||||
glm::vec3 position{};
|
||||
glm::quat rotation{};
|
||||
glm::vec3 scale{};
|
||||
if (DecomposeMatrix(localMatrix, position, rotation, scale)) {
|
||||
m_LocalPosition = position;
|
||||
m_LocalRotation = rotation;
|
||||
m_LocalScale = scale;
|
||||
} else {
|
||||
throw std::runtime_error("World transform cannot be represented as position/rotation/scale");
|
||||
}
|
||||
|
||||
SetPositionDirty();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldCache() {
|
||||
if (!m_PositionDirty && !m_RotationDirty && !m_ScaleDirty && !m_MatrixDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const glm::mat4 localMatrix = ComposeMatrix(m_LocalPosition, m_LocalRotation, m_LocalScale);
|
||||
m_WorldMatrix = m_Parent != nullptr
|
||||
? m_Parent->GetWorldMatrix() * localMatrix
|
||||
: localMatrix;
|
||||
|
||||
if (!DecomposeMatrix(m_WorldMatrix, m_WorldPosition, m_WorldRotation, m_WorldScale)) {
|
||||
m_WorldPosition = glm::vec3(m_WorldMatrix[3]);
|
||||
m_WorldRotation = m_Parent != nullptr
|
||||
? glm::normalize(m_Parent->GetWorldRotation() * m_LocalRotation)
|
||||
: m_LocalRotation;
|
||||
m_WorldScale = m_LocalScale;
|
||||
}
|
||||
|
||||
m_PositionDirty = false;
|
||||
m_RotationDirty = false;
|
||||
m_ScaleDirty = false;
|
||||
m_MatrixDirty = false;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <Jolt/Physics/Collision/RayCast.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/PhysicsSystem.h>
|
||||
#include <Jolt/RegisterTypes.h>
|
||||
@@ -333,8 +334,24 @@ public:
|
||||
{
|
||||
ShapeRefC shape = CreateJoltShape(desc.shape);
|
||||
|
||||
// This first backend ignores centerOffset for now.
|
||||
// Later, use JPH::OffsetCenterOfMassShapeSettings for offset colliders.
|
||||
// Jolt's offset shape stores the offset from the shape to the center
|
||||
// of mass, so negate the component's center offset. This keeps the
|
||||
// body transform aligned with the owning GameObject.
|
||||
if (glm::length2(desc.shape.centerOffset) > 0.0000001f)
|
||||
{
|
||||
OffsetCenterOfMassShapeSettings offsetSettings(
|
||||
ToJoltVec3(-desc.shape.centerOffset),
|
||||
shape.GetPtr());
|
||||
ShapeSettings::ShapeResult result = offsetSettings.Create();
|
||||
|
||||
if (!result.IsValid())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string("Failed to create Jolt offset shape: ") + std::string(result.GetError()));
|
||||
}
|
||||
|
||||
shape = result.Get();
|
||||
}
|
||||
|
||||
BodyCreationSettings settings(
|
||||
shape,
|
||||
@@ -391,6 +408,7 @@ public:
|
||||
bodyInterface.RemoveBody(it->second.bodyID);
|
||||
bodyInterface.DestroyBody(it->second.bodyID);
|
||||
|
||||
it->second.desc.owner = nullptr;
|
||||
m_Bodies.erase(it);
|
||||
}
|
||||
|
||||
@@ -416,7 +434,8 @@ public:
|
||||
|
||||
for (auto& [handleId, record] : m_Bodies)
|
||||
{
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Kinematic || !record.desc.owner)
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Kinematic || !record.desc.owner ||
|
||||
record.desc.owner->IsBeingDestroyed() || !record.desc.owner->IsActiveInHierarchy())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -437,7 +456,8 @@ public:
|
||||
|
||||
for (auto& [handleId, record] : m_Bodies)
|
||||
{
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Dynamic || !record.desc.owner)
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Dynamic || !record.desc.owner ||
|
||||
record.desc.owner->IsBeingDestroyed() || !record.desc.owner->IsActiveInHierarchy())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ PhysicsSceneBridge::PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world)
|
||||
|
||||
void PhysicsSceneBridge::RegisterGameObject(GameObject& object) {
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
if (!rb->HasPhysicsBody()) {
|
||||
if (!rb->HasPhysicsBody() || rb->GetPhysicsWorld() != &GetWorld()) {
|
||||
m_World->RegisterRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,18 @@ void PhysicsSceneBridge::RegisterGameObject(GameObject& object) {
|
||||
|
||||
void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) {
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
if (rb->HasPhysicsBody()) {
|
||||
if (rb->GetPhysicsWorld() == m_World.get()) {
|
||||
m_World->UnregisterRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::RefreshGameObject(GameObject& object) {
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
m_World->RefreshRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::FixedUpdate(float fixedDt) {
|
||||
m_World->SyncKinematicBodiesToPhysics();
|
||||
m_World->Step(fixedDt);
|
||||
|
||||
@@ -1,20 +1,59 @@
|
||||
#include <destrum/Physics/PhysicsWorld.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
|
||||
PhysicsWorld::~PhysicsWorld() {
|
||||
// Derived backends have already released their body storage by the time
|
||||
// the base destructor runs. Clear component-side handles without calling
|
||||
// a virtual backend method from here.
|
||||
for (Rigidbody* rigidbody : m_RegisteredRigidbodies) {
|
||||
if (rigidbody != nullptr && rigidbody->GetPhysicsWorld() == this) {
|
||||
rigidbody->DetachPhysicsBody();
|
||||
}
|
||||
}
|
||||
m_RegisteredRigidbodies.clear();
|
||||
}
|
||||
|
||||
void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
|
||||
if (rigidbody.IsBeingDestroyed() || !rigidbody.isEnabled()) {
|
||||
if (rigidbody.GetPhysicsWorld() == this) {
|
||||
UnregisterRigidbody(rigidbody);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (rigidbody.GetPhysicsWorld() != nullptr) {
|
||||
if (rigidbody.GetPhysicsWorld() == this && rigidbody.HasPhysicsBody()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicsWorld* previousWorld = rigidbody.GetPhysicsWorld();
|
||||
if (previousWorld != this) {
|
||||
previousWorld->UnregisterRigidbody(rigidbody);
|
||||
} else {
|
||||
rigidbody.DetachPhysicsBody();
|
||||
}
|
||||
}
|
||||
|
||||
GameObject* owner = rigidbody.GetGameObject();
|
||||
if (owner == nullptr || owner->IsBeingDestroyed() || !owner->IsActiveInHierarchy()) {
|
||||
if (rigidbody.GetPhysicsWorld() == this) {
|
||||
UnregisterRigidbody(rigidbody);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Transform& transform = owner->GetTransform();
|
||||
|
||||
auto* collider = owner->GetComponent<Collider>();
|
||||
if (!collider) {
|
||||
throw std::runtime_error("Rigidbody requires a Collider on the same GameObject for now.");
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicsBodyDesc desc{};
|
||||
@@ -23,6 +62,10 @@ void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
|
||||
desc.transform.rotation = transform.GetWorldRotation();
|
||||
desc.shape = collider->BuildPhysicsShape();
|
||||
|
||||
// Collider dimensions are expressed in world units. Render meshes may
|
||||
// use a transform scale to normalize imported asset units, so applying
|
||||
// that scale here would shrink the physics shape a second time.
|
||||
|
||||
desc.type = rigidbody.GetType();
|
||||
desc.mass = rigidbody.GetMass();
|
||||
desc.useGravity = rigidbody.UsesGravity();
|
||||
@@ -31,32 +74,52 @@ void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
|
||||
desc.material.restitution = rigidbody.GetRestitution();
|
||||
|
||||
PhysicsBodyHandle handle = CreateBody(desc);
|
||||
if (!handle.IsValid()) {
|
||||
rigidbody.DetachPhysicsBody();
|
||||
return;
|
||||
}
|
||||
|
||||
rigidbody.AttachPhysicsBody(this, handle);
|
||||
m_RegisteredRigidbodies.insert(&rigidbody);
|
||||
}
|
||||
|
||||
void PhysicsWorld::RefreshRigidbody(Rigidbody& rigidbody) {
|
||||
const bool hadBody = rigidbody.GetPhysicsWorld() == this && rigidbody.HasPhysicsBody();
|
||||
const glm::vec3 previousVelocity = hadBody
|
||||
? GetLinearVelocity(rigidbody.GetBody())
|
||||
: glm::vec3{0.0f};
|
||||
|
||||
if (rigidbody.GetPhysicsWorld() == this) {
|
||||
UnregisterRigidbody(rigidbody);
|
||||
}
|
||||
RegisterRigidbody(rigidbody);
|
||||
|
||||
if (hadBody && rigidbody.HasPhysicsBody()) {
|
||||
SetLinearVelocity(rigidbody.GetBody(), previousVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsWorld::UnregisterRigidbody(Rigidbody& rigidbody) {
|
||||
PhysicsBodyHandle handle = rigidbody.GetBody();
|
||||
if (rigidbody.GetPhysicsWorld() != this) {
|
||||
m_RegisteredRigidbodies.erase(&rigidbody);
|
||||
return;
|
||||
}
|
||||
|
||||
const PhysicsBodyHandle handle = rigidbody.GetBody();
|
||||
if (handle.IsValid()) {
|
||||
DestroyBody(handle);
|
||||
}
|
||||
|
||||
m_RegisteredRigidbodies.erase(&rigidbody);
|
||||
rigidbody.DetachPhysicsBody();
|
||||
}
|
||||
|
||||
void PhysicsWorld::SyncKinematicBodiesToPhysics() {
|
||||
// Backend-independent sync is intentionally not possible here because
|
||||
// PhysicsWorld does not own the list of active rigidbodies.
|
||||
//
|
||||
// Use SimplePhysicsWorld as a reference implementation.
|
||||
//
|
||||
// If you use Jolt/PhysX/Bullet later, keep a body table in the backend:
|
||||
// handle -> { GameObject*, Rigidbody* }.
|
||||
}
|
||||
|
||||
void PhysicsWorld::SyncDynamicBodiesToTransforms() {
|
||||
// Backend-independent sync is intentionally not possible here because
|
||||
// PhysicsWorld does not own the list of active rigidbodies.
|
||||
//
|
||||
// Use SimplePhysicsWorld as a reference implementation.
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ void SimplePhysicsWorld::DestroyBody(PhysicsBodyHandle body) {
|
||||
if (BodyRecord* record = FindBody(body)) {
|
||||
record->alive = false;
|
||||
record->rigidbody = nullptr;
|
||||
record->desc.owner = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +98,9 @@ void SimplePhysicsWorld::Step(float fixedDt) {
|
||||
}
|
||||
|
||||
for (BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive) {
|
||||
if (!body.alive || !body.desc.owner ||
|
||||
body.desc.owner->IsBeingDestroyed() ||
|
||||
!body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -133,7 +136,8 @@ void SimplePhysicsWorld::Step(float fixedDt) {
|
||||
|
||||
void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() {
|
||||
for (BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner ||
|
||||
body.desc.owner->IsBeingDestroyed() || !body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -147,7 +151,8 @@ void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() {
|
||||
|
||||
void SimplePhysicsWorld::SyncDynamicBodiesToTransforms() {
|
||||
for (BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Dynamic || !body.desc.owner) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Dynamic || !body.desc.owner ||
|
||||
body.desc.owner->IsBeingDestroyed() || !body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -176,13 +181,16 @@ bool SimplePhysicsWorld::Raycast(const glm::vec3& origin,
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (const BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive || body.desc.shape.type == PhysicsShapeType::None) {
|
||||
if (!body.alive || body.desc.shape.type == PhysicsShapeType::None ||
|
||||
!body.desc.owner || body.desc.owner->IsBeingDestroyed() ||
|
||||
!body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = 0.0f;
|
||||
const float radius = GetApproxBoundingRadius(body);
|
||||
const glm::vec3 center = body.currentTransform.position + body.desc.shape.centerOffset;
|
||||
const glm::vec3 center = body.currentTransform.position
|
||||
+ body.currentTransform.rotation * body.desc.shape.centerOffset;
|
||||
|
||||
if (RaySphere(origin, dir, center, radius, maxDistance, distance)) {
|
||||
if (distance < bestDistance) {
|
||||
|
||||
+185
-85
@@ -1,163 +1,263 @@
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <SDL_scancode.h>
|
||||
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
// #include "ServiceLocator.h"
|
||||
// #include "Input/InputManager.h"
|
||||
// #include "Managers/Renderer.h"
|
||||
|
||||
|
||||
unsigned int Scene::m_idCounter = 0;
|
||||
|
||||
Scene::Scene(const std::string& name) : m_name(name) {
|
||||
namespace {
|
||||
class IterationGuard {
|
||||
public:
|
||||
explicit IterationGuard(Scene& scene) : m_Scene(scene) {
|
||||
m_Scene.BeginIteration();
|
||||
}
|
||||
|
||||
~IterationGuard() {
|
||||
m_Scene.EndIteration();
|
||||
}
|
||||
|
||||
private:
|
||||
Scene& m_Scene;
|
||||
};
|
||||
}
|
||||
|
||||
Scene::~Scene() = default;
|
||||
Scene::Scene(const std::string& name)
|
||||
: m_name(name),
|
||||
m_id(++m_idCounter) {
|
||||
}
|
||||
|
||||
// void Scene::Add(std::shared_ptr<GameObject> object) {
|
||||
// // m_objects.emplace_back(std::move(object));
|
||||
// m_pendingAdditions.emplace_back(std::move(object));
|
||||
// }
|
||||
Scene::~Scene() {
|
||||
Unload();
|
||||
RemoveAll();
|
||||
}
|
||||
|
||||
GameObject* Scene::CreateGameObject(std::string name)
|
||||
{
|
||||
auto obj = std::make_shared<GameObject>(std::move(name));
|
||||
|
||||
GameObject* rawPtr = obj.get();
|
||||
|
||||
obj->SetScene(this);
|
||||
m_pendingAdditions.emplace_back(std::move(obj));
|
||||
GameObject* Scene::CreateGameObject(std::string name) {
|
||||
auto object = std::make_shared<GameObject>(std::move(name));
|
||||
GameObject* rawPtr = object.get();
|
||||
|
||||
object->SetScene(this);
|
||||
m_pendingAdditions.emplace_back(std::move(object));
|
||||
return rawPtr;
|
||||
}
|
||||
|
||||
void Scene::Remove(GameObject* object) {
|
||||
std::erase_if(m_objects, [object](const std::shared_ptr<GameObject>& obj) {
|
||||
return obj.get() == object;
|
||||
});
|
||||
if (object == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::erase_if(m_pendingAdditions, [object](const std::shared_ptr<GameObject>& obj) {
|
||||
return obj.get() == object;
|
||||
});
|
||||
const auto containsObject = [object](const auto& objects) {
|
||||
return std::any_of(objects.begin(), objects.end(), [object](const auto& candidate) {
|
||||
return candidate != nullptr && candidate.get() == object;
|
||||
});
|
||||
};
|
||||
|
||||
if (!containsObject(m_objects) && !containsObject(m_pendingAdditions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
object->Destroy();
|
||||
|
||||
if (IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto eraseDestroyed = [](auto& objects) {
|
||||
for (const auto& candidate : objects) {
|
||||
if (candidate != nullptr && candidate->IsBeingDestroyed()) {
|
||||
candidate->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(objects, [](const auto& candidate) {
|
||||
return candidate == nullptr || candidate->IsBeingDestroyed();
|
||||
});
|
||||
};
|
||||
|
||||
// Destroying an object also marks its descendants. Remove all of those
|
||||
// marked entries together, after destruction has finished.
|
||||
eraseDestroyed(m_objects);
|
||||
eraseDestroyed(m_pendingAdditions);
|
||||
}
|
||||
|
||||
void Scene::RemoveAll() {
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
if (IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
object->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
object->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
m_objects.clear();
|
||||
m_pendingAdditions.clear();
|
||||
}
|
||||
|
||||
void Scene::Load() {
|
||||
OnSceneLoaded.Invoke();
|
||||
if (m_registerBindings) {
|
||||
m_registerBindings();
|
||||
}
|
||||
LoadBindings();
|
||||
}
|
||||
|
||||
void Scene::Update() {
|
||||
void Scene::Update(float dt) {
|
||||
CommitPendingAdditions();
|
||||
IterationGuard iteration(*this);
|
||||
|
||||
for (const auto& object : m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
object->Update();
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->Update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Scene::FixedUpdate(float dt) {
|
||||
for (const auto& object: m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
object->FixedUpdate();
|
||||
CommitPendingAdditions();
|
||||
IterationGuard iteration(*this);
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->FixedUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
m_Physics.FixedUpdate(dt);
|
||||
}
|
||||
|
||||
void Scene::LateUpdate() {
|
||||
for (const auto& object: m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
object->LateUpdate();
|
||||
void Scene::LateUpdate(float dt) {
|
||||
IterationGuard iteration(*this);
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->LateUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Scene::Render(const RenderContext& ctx) const {
|
||||
for (const auto& object: m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
void Scene::Render(const RenderContext& ctx) {
|
||||
IterationGuard iteration(*this);
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->Render(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Scene::RenderImgui() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Scene::CleanupDestroyedGameObjects() {
|
||||
if (m_BeingUnloaded) {
|
||||
//Scene is gone anyways, kill everything
|
||||
m_objects.clear();
|
||||
if (IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& gameObject: m_objects) {
|
||||
//First check if a gameobjects components needs to be destroyed
|
||||
gameObject->CleanupComponents();
|
||||
const auto cleanup = [](auto& objects) {
|
||||
for (const auto& object : objects) {
|
||||
if (object != nullptr && object->IsBeingDestroyed()) {
|
||||
object->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& object : objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed()) {
|
||||
object->CleanupComponents();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& object : objects) {
|
||||
if (object != nullptr && object->IsBeingDestroyed()) {
|
||||
object->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(objects, [](const auto& object) {
|
||||
return object == nullptr || object->IsBeingDestroyed();
|
||||
});
|
||||
};
|
||||
|
||||
cleanup(m_objects);
|
||||
cleanup(m_pendingAdditions);
|
||||
}
|
||||
|
||||
void Scene::BeginIteration() {
|
||||
++m_IterationDepth;
|
||||
}
|
||||
|
||||
void Scene::EndIteration() {
|
||||
if (m_IterationDepth == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// //Strange for loop since im deleting during looping over it
|
||||
// for (auto it = m_objects.begin(); it != m_objects.end();) {
|
||||
// if ((*it)->IsBeingDestroyed()) {
|
||||
// it = m_objects.erase(it);
|
||||
// } else {
|
||||
// ++it;
|
||||
// }
|
||||
// }
|
||||
|
||||
std::erase_if(m_objects, [] (const std::shared_ptr<GameObject>& gameObject) {
|
||||
return gameObject->IsBeingDestroyed();
|
||||
});
|
||||
--m_IterationDepth;
|
||||
if (m_IterationDepth == 0) {
|
||||
CleanupDestroyedGameObjects();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::Unload() {
|
||||
if (m_unregisterBindings) {
|
||||
m_unregisterBindings();
|
||||
if (m_BeingUnloaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
UnloadBindings();
|
||||
m_BeingUnloaded = true;
|
||||
}
|
||||
|
||||
void Scene::DestroyGameObjects() {
|
||||
if (m_BeingUnloaded) {
|
||||
|
||||
for (auto& obj : m_pendingAdditions) {
|
||||
m_objects.emplace_back(std::move(obj));
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
m_pendingAdditions.clear();
|
||||
|
||||
//Scene is gone anyways, kill everything
|
||||
for (const auto& gameObject: m_objects) {
|
||||
gameObject->Destroy();
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
} else {
|
||||
assert(m_BeingUnloaded && "Scene is being cleared but not unloaded? Weird");
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::CommitPendingAdditions() {
|
||||
if (m_pendingAdditions.empty()) {
|
||||
if (m_pendingAdditions.empty() || IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& obj : m_pendingAdditions) {
|
||||
m_objects.emplace_back(std::move(obj));
|
||||
for (auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed()) {
|
||||
m_Physics.RegisterGameObject(*object);
|
||||
m_objects.emplace_back(std::move(object));
|
||||
}
|
||||
}
|
||||
|
||||
m_pendingAdditions.clear();
|
||||
std::erase_if(m_pendingAdditions, [](const auto& object) {
|
||||
return object == nullptr || object->IsBeingDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
void Scene::RefreshPhysics() {
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
m_Physics.RefreshGameObject(*object);
|
||||
}
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
m_Physics.RefreshGameObject(*object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,57 @@
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
void SceneManager::Update() {
|
||||
m_scenes[m_ActiveSceneIndex]->Update();
|
||||
Scene& SceneManager::GetCurrentScene() const {
|
||||
if (m_scenes.empty()) {
|
||||
throw std::out_of_range("No scenes are available");
|
||||
}
|
||||
|
||||
if (m_ActiveSceneIndex < 0 || m_ActiveSceneIndex >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Active scene index is invalid");
|
||||
}
|
||||
|
||||
return *m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)];
|
||||
}
|
||||
|
||||
void SceneManager::Update(float dt) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->Update(dt);
|
||||
}
|
||||
|
||||
void SceneManager::FixedUpdate(float dt) {
|
||||
m_scenes[m_ActiveSceneIndex]->FixedUpdate(dt);
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->FixedUpdate(dt);
|
||||
}
|
||||
|
||||
void SceneManager::LateUpdate() {
|
||||
m_scenes[m_ActiveSceneIndex]->LateUpdate();
|
||||
void SceneManager::LateUpdate(float dt) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->LateUpdate(dt);
|
||||
}
|
||||
|
||||
void SceneManager::Render(const RenderContext& ctx) {
|
||||
m_scenes[m_ActiveSceneIndex]->Render(ctx);
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->Render(ctx);
|
||||
}
|
||||
|
||||
void SceneManager::RenderImgui() {
|
||||
m_scenes[m_ActiveSceneIndex]->RenderImgui();
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->RenderImgui();
|
||||
}
|
||||
|
||||
void SceneManager::HandleGameObjectDestroy() {
|
||||
@@ -43,6 +73,12 @@ void SceneManager::UnloadAllScenes() {
|
||||
}
|
||||
|
||||
void SceneManager::HandleSceneDestroy() {
|
||||
const std::shared_ptr<Scene> activeScene =
|
||||
m_ActiveSceneIndex >= 0 &&
|
||||
m_ActiveSceneIndex < static_cast<int>(m_scenes.size())
|
||||
? m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]
|
||||
: nullptr;
|
||||
|
||||
for (auto it = m_scenes.begin(); it != m_scenes.end();) {
|
||||
if ((*it)->IsBeingUnloaded()) {
|
||||
it = m_scenes.erase(it);
|
||||
@@ -50,15 +86,37 @@ void SceneManager::HandleSceneDestroy() {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_scenes.empty()) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeScene != nullptr) {
|
||||
const auto activeIt = std::find(m_scenes.begin(), m_scenes.end(), activeScene);
|
||||
if (activeIt != m_scenes.end()) {
|
||||
m_ActiveSceneIndex = static_cast<int>(std::distance(m_scenes.begin(), activeIt));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_ActiveSceneIndex = std::clamp(
|
||||
m_ActiveSceneIndex,
|
||||
0,
|
||||
static_cast<int>(m_scenes.size()) - 1);
|
||||
}
|
||||
|
||||
void SceneManager::HandleScene() {
|
||||
DestroyGameObjects();
|
||||
HandleGameObjectDestroy();
|
||||
HandleSceneDestroy();
|
||||
}
|
||||
|
||||
void SceneManager::Destroy() {
|
||||
if (m_scenes.empty()) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
UnloadAllScenes();
|
||||
DestroyGameObjects();
|
||||
HandleGameObjectDestroy();
|
||||
@@ -71,13 +129,20 @@ void SceneManager::SwitchScene(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Scene index out of range");
|
||||
}
|
||||
m_scenes[m_ActiveSceneIndex]->UnloadBindings();
|
||||
if (index == m_ActiveSceneIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]->UnloadBindings();
|
||||
m_ActiveSceneIndex = index;
|
||||
m_scenes[m_ActiveSceneIndex]->LoadBindings();
|
||||
m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]->LoadBindings();
|
||||
}
|
||||
|
||||
Scene &SceneManager::CreateScene(const std::string &name) {
|
||||
const auto &scene = std::shared_ptr<Scene>(new Scene(name));
|
||||
const auto scene = std::shared_ptr<Scene>(new Scene(name));
|
||||
m_scenes.push_back(scene);
|
||||
if (m_scenes.size() == 1) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
}
|
||||
return *scene;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
#include <destrum/Components/Physics/CapsuleCollider.h>
|
||||
#include <destrum/Components/Physics/SphereCollider.h>
|
||||
|
||||
void RegisterEngineComponents()
|
||||
@@ -22,21 +23,26 @@ void RegisterEngineComponents()
|
||||
|
||||
registered = true;
|
||||
|
||||
ComponentFactory::Register("MeshRendererComponent", [](GameObject& owner) {
|
||||
return owner.AddComponent<MeshRendererComponent>();
|
||||
});
|
||||
// Keep the old serialized spelling readable while writing the canonical
|
||||
// component name returned by MeshRendererComponent.
|
||||
ComponentFactory::Register("MeshRenderer", [](GameObject& owner) {
|
||||
return owner.AddComponent<MeshRendererComponent>();
|
||||
});
|
||||
|
||||
// ComponentFactory::Register("Rotator", [](GameObject& owner) {
|
||||
// return owner.AddComponent<Rotator>();
|
||||
// });
|
||||
ComponentFactory::Register("Rotator", [](GameObject& owner) {
|
||||
return owner.AddComponent<Rotator>();
|
||||
});
|
||||
|
||||
// ComponentFactory::Register("Spinner", [](GameObject& owner) {
|
||||
// return owner.AddComponent<Spinner>();
|
||||
// });
|
||||
ComponentFactory::Register("Spinner", [](GameObject& owner) {
|
||||
return owner.AddComponent<Spinner>();
|
||||
});
|
||||
|
||||
// ComponentFactory::Register("OrbitAndSpin", [](GameObject& owner) {
|
||||
// return owner.AddComponent<OrbitAndSpin>();
|
||||
// });
|
||||
ComponentFactory::Register("OrbitAndSpin", [](GameObject& owner) {
|
||||
return owner.AddComponent<OrbitAndSpin>();
|
||||
});
|
||||
|
||||
ComponentFactory::Register("Animator", [](GameObject& owner) {
|
||||
return owner.AddComponent<Animator>();
|
||||
@@ -45,6 +51,9 @@ void RegisterEngineComponents()
|
||||
ComponentFactory::Register("Rigidbody", [](GameObject& owner) {
|
||||
return owner.AddComponent<Rigidbody>();
|
||||
});
|
||||
ComponentFactory::Register("RigidBody", [](GameObject& owner) {
|
||||
return owner.AddComponent<Rigidbody>();
|
||||
});
|
||||
|
||||
ComponentFactory::Register("BoxCollider", [](GameObject& owner) {
|
||||
return owner.AddComponent<BoxCollider>();
|
||||
@@ -53,4 +62,8 @@ void RegisterEngineComponents()
|
||||
ComponentFactory::Register("SphereCollider", [](GameObject& owner) {
|
||||
return owner.AddComponent<SphereCollider>();
|
||||
});
|
||||
|
||||
ComponentFactory::Register("CapsuleCollider", [](GameObject& owner) {
|
||||
return owner.AddComponent<CapsuleCollider>();
|
||||
});
|
||||
}
|
||||
@@ -1,75 +1,278 @@
|
||||
#include <destrum/Serialization/SceneSerializer.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/Component.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/ObjectId.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Serialization/ComponentFactory.h>
|
||||
#include <destrum/Serialization/ComponentRegistry.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] bool IsFiniteNumber(const json& value) {
|
||||
if (!value.is_number()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::isfinite(value.get<double>());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsFiniteArray(const json& value, std::size_t size) {
|
||||
if (!value.is_array() || value.size() != size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& element : value) {
|
||||
if (!IsFiniteNumber(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsFiniteJson(const json& value) {
|
||||
if (value.is_number()) {
|
||||
return IsFiniteNumber(value);
|
||||
}
|
||||
if (value.is_array()) {
|
||||
for (const auto& element : value) {
|
||||
if (!IsFiniteJson(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (value.is_object()) {
|
||||
for (auto it = value.begin(); it != value.end(); ++it) {
|
||||
if (!IsFiniteJson(it.value())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ValidateTransform(const json& objectJson, int version) {
|
||||
if (!objectJson.contains("transform")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& transform = objectJson.at("transform");
|
||||
if (!transform.is_object()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Version 1 wrote an empty placeholder transform object.
|
||||
if (version <= 1 && transform.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!transform.contains("position") || !transform.contains("rotation") ||
|
||||
!transform.contains("scale")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsFiniteArray(transform.at("position"), 3) ||
|
||||
!IsFiniteArray(transform.at("scale"), 3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& rotation = transform.at("rotation");
|
||||
return IsFiniteArray(rotation, 3) || IsFiniteArray(rotation, 4);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ValidateSceneJson(const json& root) {
|
||||
if (!root.is_object() || !root.contains("objects") || !root.at("objects").is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root.contains("name") && !root.at("name").is_string()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int version = 1;
|
||||
if (root.contains("version")) {
|
||||
if (!root.at("version").is_number_integer()) {
|
||||
return false;
|
||||
}
|
||||
version = root.at("version").get<int>();
|
||||
if (version < 1 || version > 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<ObjectId> ids;
|
||||
std::unordered_map<ObjectId, ObjectId> parents;
|
||||
|
||||
for (const auto& objectJson : root.at("objects")) {
|
||||
if (!objectJson.is_object() || !objectJson.contains("id")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ObjectId id = objectJson.at("id").get<ObjectId>();
|
||||
if (id == InvalidObjectId ||
|
||||
id >= std::numeric_limits<ObjectId>::max() - 1 ||
|
||||
!ids.insert(id).second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objectJson.contains("name") && !objectJson.at("name").is_string()) {
|
||||
return false;
|
||||
}
|
||||
if (objectJson.contains("active") && !objectJson.at("active").is_boolean()) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateTransform(objectJson, version)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ObjectId parent = objectJson.value("parent", InvalidObjectId);
|
||||
if (parent == id) {
|
||||
return false;
|
||||
}
|
||||
parents.emplace(id, parent);
|
||||
|
||||
if (objectJson.contains("components")) {
|
||||
const auto& components = objectJson.at("components");
|
||||
if (!components.is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& componentJson : components) {
|
||||
if (!componentJson.is_object() || !componentJson.contains("type") ||
|
||||
!componentJson.at("type").is_string()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (componentJson.contains("enabled") &&
|
||||
!componentJson.at("enabled").is_boolean()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (componentJson.contains("data") &&
|
||||
(!componentJson.at("data").is_object() ||
|
||||
!IsFiniteJson(componentJson.at("data")))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComponentFactory::IsRegistered(componentJson.at("type").get<std::string>())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [id, parent] : parents) {
|
||||
if (parent != InvalidObjectId && !ids.contains(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_set<ObjectId> visited;
|
||||
ObjectId current = id;
|
||||
while (current != InvalidObjectId) {
|
||||
if (!visited.insert(current).second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto parentIt = parents.find(current);
|
||||
if (parentIt == parents.end()) {
|
||||
return false;
|
||||
}
|
||||
current = parentIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
|
||||
scene.CommitPendingAdditions();
|
||||
if (scene.IsIterating()) {
|
||||
std::cerr << "Cannot save a scene during an update or render phase: "
|
||||
<< scene.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterEngineComponents();
|
||||
try {
|
||||
scene.CommitPendingAdditions();
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to commit scene additions before save: "
|
||||
<< exception.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
json root;
|
||||
|
||||
root["version"] = 1;
|
||||
root["version"] = 2;
|
||||
root["name"] = scene.GetName();
|
||||
root["objects"] = json::array();
|
||||
|
||||
for (const auto& objectPtr : scene.GetObjects()) {
|
||||
if (!objectPtr) {
|
||||
if (!objectPtr || objectPtr->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GameObject& object = *objectPtr;
|
||||
if (object.GetComponent<Rigidbody>() != nullptr &&
|
||||
object.GetComponent<Collider>() == nullptr) {
|
||||
std::cerr << "Cannot save object with a Rigidbody but no Collider: "
|
||||
<< object.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
const Transform& transform = object.GetTransform();
|
||||
const glm::vec3& position = transform.GetLocalPosition();
|
||||
const glm::quat& rotation = transform.GetLocalRotation();
|
||||
const glm::vec3& scale = transform.GetLocalScale();
|
||||
|
||||
json objectJson;
|
||||
objectJson["id"] = object.GetId();
|
||||
objectJson["name"] = object.GetName();
|
||||
objectJson["active"] = object.IsActive();
|
||||
|
||||
// Transform should be saved separately because in your engine it is not a component.
|
||||
// Replace this with your actual Transform getters.
|
||||
objectJson["transform"] = {
|
||||
// Example:
|
||||
// { "position", { object.GetTransform().GetLocalPosition().x,
|
||||
// object.GetTransform().GetLocalPosition().y,
|
||||
// object.GetTransform().GetLocalPosition().z } },
|
||||
// { "rotation", { ... } },
|
||||
// { "scale", { ... } }
|
||||
{"position", {position.x, position.y, position.z}},
|
||||
{"rotation", {rotation.x, rotation.y, rotation.z, rotation.w}},
|
||||
{"scale", {scale.x, scale.y, scale.z}}
|
||||
};
|
||||
|
||||
const Transform* parent = object.GetTransform().GetParent();
|
||||
objectJson["parent"] = parent
|
||||
const Transform* parent = transform.GetParent();
|
||||
objectJson["parent"] = parent != nullptr && parent->GetOwner() != nullptr &&
|
||||
parent->GetOwner()->GetScene() == &scene &&
|
||||
!parent->GetOwner()->IsBeingDestroyed()
|
||||
? parent->GetOwner()->GetId()
|
||||
: InvalidObjectId;
|
||||
|
||||
objectJson["components"] = json::array();
|
||||
|
||||
for (const auto& componentPtr : object.GetComponents()) {
|
||||
if (!componentPtr) {
|
||||
if (!componentPtr || componentPtr->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Component& component = *componentPtr;
|
||||
|
||||
json componentJson;
|
||||
componentJson["type"] = component.GetTypeName();
|
||||
componentJson["enabled"] = component.isEnabled();
|
||||
componentJson["data"] = component.Serialize();
|
||||
|
||||
objectJson["components"].push_back(componentJson);
|
||||
objectJson["components"].push_back({
|
||||
{"type", component.GetTypeName()},
|
||||
{"enabled", component.isEnabled()},
|
||||
{"data", component.Serialize()}
|
||||
});
|
||||
}
|
||||
|
||||
root["objects"].push_back(objectJson);
|
||||
root["objects"].push_back(std::move(objectJson));
|
||||
}
|
||||
|
||||
std::ofstream file(path);
|
||||
@@ -79,10 +282,23 @@ bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
file << root.dump(4);
|
||||
if (!file.good()) {
|
||||
std::cerr << "Failed to write scene file: " << path << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
if (scene.IsIterating()) {
|
||||
std::cerr << "Cannot load a scene during an update or render phase: "
|
||||
<< scene.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterEngineComponents();
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open scene file for reading: " << path << '\n';
|
||||
@@ -90,134 +306,166 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
json root;
|
||||
|
||||
try {
|
||||
file >> root;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Failed to parse scene file: " << e.what() << '\n';
|
||||
if (!ValidateSceneJson(root)) {
|
||||
std::cerr << "Invalid scene data: " << path << '\n';
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to parse scene file: " << exception.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
scene.RemoveAll();
|
||||
if (scene.IsBeingUnloaded()) {
|
||||
std::cerr << "Cannot load a scene that is being unloaded: " << scene.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build the replacement separately. The current scene is not touched
|
||||
// until all objects, transforms, and components have loaded successfully.
|
||||
Scene stagingScene(scene.GetName());
|
||||
std::unordered_map<ObjectId, GameObject*> idMap;
|
||||
std::vector<const json*> objectJsonList;
|
||||
std::vector<GameObject*> registeredObjects;
|
||||
|
||||
try {
|
||||
const auto& objectsJson = root.at("objects");
|
||||
|
||||
// Pass 1:
|
||||
// Create all GameObjects first.
|
||||
for (const auto& objectJson : objectsJson) {
|
||||
const auto id = objectJson.at("id").get<ObjectId>();
|
||||
for (const auto& objectJson : root.at("objects")) {
|
||||
const ObjectId id = objectJson.at("id").get<ObjectId>();
|
||||
const std::string name = objectJson.value("name", "GameObject");
|
||||
const bool active = objectJson.value("active", true);
|
||||
|
||||
GameObject* object = scene.CreateGameObject(name);
|
||||
GameObject* object = stagingScene.CreateGameObject(name);
|
||||
object->SetIdForDeserialization(id);
|
||||
object->SetActive(active);
|
||||
|
||||
idMap[id] = object;
|
||||
idMap.emplace(id, object);
|
||||
objectJsonList.push_back(&objectJson);
|
||||
}
|
||||
|
||||
scene.CommitPendingAdditions();
|
||||
stagingScene.CommitPendingAdditions();
|
||||
|
||||
// Pass 2:
|
||||
// Restore transforms and parent-child hierarchy.
|
||||
for (const json* objectJson : objectJsonList) {
|
||||
const auto id = objectJson->at("id").get<ObjectId>();
|
||||
|
||||
const ObjectId id = objectJson->at("id").get<ObjectId>();
|
||||
GameObject* object = idMap.at(id);
|
||||
|
||||
// Replace this with your actual Transform deserialization.
|
||||
if (objectJson->contains("transform")) {
|
||||
const json& transformJson = objectJson->at("transform");
|
||||
if (objectJson->contains("transform") &&
|
||||
!objectJson->at("transform").empty()) {
|
||||
const auto& transformJson = objectJson->at("transform");
|
||||
const auto& position = transformJson.at("position");
|
||||
const auto& rotation = transformJson.at("rotation");
|
||||
const auto& scale = transformJson.at("scale");
|
||||
|
||||
auto position = transformJson.at("position");
|
||||
object->GetTransform().SetLocalPosition({
|
||||
position[0].get<float>(),
|
||||
position[1].get<float>(),
|
||||
position[2].get<float>()
|
||||
position.at(0).get<float>(),
|
||||
position.at(1).get<float>(),
|
||||
position.at(2).get<float>()
|
||||
});
|
||||
|
||||
auto rotation = transformJson.at("rotation");
|
||||
object->GetTransform().SetLocalRotation({
|
||||
rotation[0].get<float>(),
|
||||
rotation[1].get<float>(),
|
||||
rotation[2].get<float>()
|
||||
});
|
||||
if (rotation.size() == 4) {
|
||||
object->GetTransform().SetLocalRotation({
|
||||
rotation.at(3).get<float>(),
|
||||
rotation.at(0).get<float>(),
|
||||
rotation.at(1).get<float>(),
|
||||
rotation.at(2).get<float>()
|
||||
});
|
||||
} else {
|
||||
// Version 1 scene files used three Euler angles in
|
||||
// degrees. Continue reading that format safely.
|
||||
object->GetTransform().SetLocalRotation({
|
||||
rotation.at(0).get<float>(),
|
||||
rotation.at(1).get<float>(),
|
||||
rotation.at(2).get<float>()
|
||||
});
|
||||
}
|
||||
|
||||
auto scale = transformJson.at("scale");
|
||||
object->GetTransform().SetLocalScale({
|
||||
scale[0].get<float>(),
|
||||
scale[1].get<float>(),
|
||||
scale[2].get<float>()
|
||||
scale.at(0).get<float>(),
|
||||
scale.at(1).get<float>(),
|
||||
scale.at(2).get<float>()
|
||||
});
|
||||
}
|
||||
|
||||
const ObjectId parentId = objectJson->value("parent", InvalidObjectId);
|
||||
|
||||
if (parentId != InvalidObjectId) {
|
||||
auto parentIt = idMap.find(parentId);
|
||||
|
||||
if (parentIt != idMap.end()) {
|
||||
object->GetTransform().SetParent(&parentIt->second->GetTransform());
|
||||
} else {
|
||||
std::cerr << "Parent not found while loading object " << id << '\n';
|
||||
}
|
||||
object->GetTransform().SetParent(&idMap.at(parentId)->GetTransform(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3:
|
||||
// Create and deserialize components.
|
||||
for (const json* objectJson : objectJsonList) {
|
||||
const auto id = objectJson->at("id").get<ObjectId>();
|
||||
GameObject* object = idMap.at(id);
|
||||
|
||||
GameObject* object = idMap.at(objectJson->at("id").get<ObjectId>());
|
||||
if (!objectJson->contains("components")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& componentJson : objectJson->at("components")) {
|
||||
const std::string type = componentJson.at("type").get<std::string>();
|
||||
|
||||
Component* component = ComponentFactory::Create(type, *object);
|
||||
|
||||
if (!component) {
|
||||
std::cerr << "Unknown component type while loading scene: " << type << '\n';
|
||||
continue;
|
||||
if (component == nullptr) {
|
||||
throw std::runtime_error("Unknown component type: " + type);
|
||||
}
|
||||
|
||||
component->SetEnabled(componentJson.value("enabled", true));
|
||||
|
||||
if (componentJson.contains("data")) {
|
||||
component->Deserialize(componentJson.at("data"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 4:
|
||||
// Resolve object references now that all objects and components exist.
|
||||
for (const auto& objectPtr : scene.GetObjects()) {
|
||||
for (const auto& objectPtr : stagingScene.GetObjects()) {
|
||||
if (!objectPtr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& componentPtr : objectPtr->GetComponents()) {
|
||||
if (!componentPtr) {
|
||||
continue;
|
||||
if (componentPtr) {
|
||||
componentPtr->ResolveReferences(idMap);
|
||||
}
|
||||
|
||||
componentPtr->ResolveReferences(idMap);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Failed to load scene: " << e.what() << '\n';
|
||||
scene.RemoveAll();
|
||||
// Register the replacement bodies in the live physics world before
|
||||
// touching the current scene. If any body fails, the old scene and
|
||||
// its physics state can remain intact.
|
||||
for (const auto& objectPtr : stagingScene.GetObjects()) {
|
||||
if (objectPtr) {
|
||||
if (objectPtr->GetComponent<Rigidbody>() != nullptr &&
|
||||
objectPtr->GetComponent<Collider>() == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"Rigidbody requires a collider on object " + objectPtr->GetName());
|
||||
}
|
||||
scene.GetPhysics().RegisterGameObject(*objectPtr);
|
||||
registeredObjects.push_back(objectPtr.get());
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to load scene: " << exception.what() << '\n';
|
||||
for (GameObject* object : registeredObjects) {
|
||||
if (object != nullptr) {
|
||||
scene.GetPhysics().UnregisterGameObject(*object);
|
||||
}
|
||||
}
|
||||
stagingScene.RemoveAll();
|
||||
return false;
|
||||
}
|
||||
|
||||
scene.RemoveAll();
|
||||
scene.m_objects = std::move(stagingScene.m_objects);
|
||||
scene.m_pendingAdditions = std::move(stagingScene.m_pendingAdditions);
|
||||
if (root.contains("name")) {
|
||||
scene.m_name = root.at("name").get<std::string>();
|
||||
}
|
||||
|
||||
for (const auto& object : scene.m_objects) {
|
||||
if (object) {
|
||||
object->SetScene(&scene);
|
||||
}
|
||||
}
|
||||
for (const auto& object : scene.m_pendingAdditions) {
|
||||
if (object) {
|
||||
object->SetScene(&scene);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
+37
-13
@@ -35,28 +35,52 @@ target_link_libraries(lightkeeper PRIVATE destrum::destrum)
|
||||
|
||||
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_src")
|
||||
set(ASSETS_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_runtime")
|
||||
set(OUTPUT_GAME_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/assets/game")
|
||||
set(OUTPUT_GAME_ASSETS_DIR "$<TARGET_FILE_DIR:lightkeeper>/assets/game")
|
||||
|
||||
if (WIN32)
|
||||
set(GAME_ASSET_INSTALL_COMMANDS
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${ASSETS_RUNTIME_DIR}" "${OUTPUT_GAME_ASSETS_DIR}")
|
||||
else()
|
||||
set(GAME_ASSET_INSTALL_COMMANDS
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
"${ASSETS_RUNTIME_DIR}" "${OUTPUT_GAME_ASSETS_DIR}")
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET lightkeeper POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/assets"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_GAME_ASSETS_DIR}/.."
|
||||
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${CMAKE_CURRENT_BINARY_DIR}/assets/game"
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink "${ASSETS_RUNTIME_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/assets/game"
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUTPUT_GAME_ASSETS_DIR}"
|
||||
${GAME_ASSET_INSTALL_COMMANDS}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE GAME_ASSET_SOURCES CONFIGURE_DEPENDS
|
||||
"${ASSETS_SRC_DIR}/*")
|
||||
|
||||
add_custom_target(_internal_clean_game_assets
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
--clean
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
--clean
|
||||
DEPENDS TheChef
|
||||
)
|
||||
|
||||
add_custom_target(_internal_cook_game_assets ALL
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
DEPENDS TheChef
|
||||
add_custom_target(_internal_cook_game_assets
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
--clean
|
||||
COMMAND TheChef
|
||||
--input "${ASSETS_SRC_DIR}"
|
||||
--output "${ASSETS_RUNTIME_DIR}"
|
||||
DEPENDS TheChef ${GAME_ASSET_SOURCES}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_dependencies(lightkeeper _internal_cook_game_assets)
|
||||
|
||||
destrum_cook_engine_assets(lightkeeper "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
install(TARGETS lightkeeper RUNTIME DESTINATION bin)
|
||||
install(DIRECTORY "${ASSETS_RUNTIME_DIR}/" DESTINATION "bin/assets/game")
|
||||
|
||||
@@ -30,6 +30,12 @@ LightKeeper::LightKeeper() : App()
|
||||
|
||||
LightKeeper::~LightKeeper()
|
||||
{
|
||||
if (!cleanedUp) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightKeeper::customInit()
|
||||
@@ -442,8 +448,11 @@ void LightKeeper::customInit()
|
||||
|
||||
void LightKeeper::customUpdate(float dt)
|
||||
{
|
||||
// LightKeeper owns a camera that hides App::camera; update this instance
|
||||
// after SDL events have been consumed.
|
||||
camera.Update(dt);
|
||||
SceneManager::GetInstance().Update();
|
||||
SceneManager::GetInstance().Update(dt);
|
||||
SceneManager::GetInstance().LateUpdate(dt);
|
||||
|
||||
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_1))
|
||||
{
|
||||
@@ -462,17 +471,22 @@ void LightKeeper::customUpdate(float dt)
|
||||
meshRenderComp->SetMaterialID(sphereMaterial);
|
||||
meshRenderComp->SetMeshID(sphereMesh);
|
||||
|
||||
sphere->GetTransform().SetWorldPosition(0, 100, 0);
|
||||
sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f);
|
||||
sphere->GetTransform().SetWorldScale(glm::vec3{0.015f});
|
||||
|
||||
|
||||
SceneManager::GetInstance().GetCurrentScene().GetPhysics().RegisterGameObject(*sphere);
|
||||
SceneManager::GetInstance().GetCurrentScene().GetPhysics().RefreshGameObject(*sphere);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void LightKeeper::customDraw()
|
||||
{
|
||||
const auto cmd = gfxDevice.beginFrame();
|
||||
if (cmd == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.beginDrawing(gfxDevice);
|
||||
|
||||
const RenderContext ctx{
|
||||
@@ -490,7 +504,6 @@ void LightKeeper::customDraw()
|
||||
SceneManager::GetInstance().Render(ctx);
|
||||
renderer.endDrawing();
|
||||
|
||||
const auto cmd = gfxDevice.beginFrame();
|
||||
const auto& drawImage = renderer.getDrawImage();
|
||||
|
||||
renderer.draw(
|
||||
@@ -508,14 +521,17 @@ void LightKeeper::customDraw()
|
||||
|
||||
void LightKeeper::customCleanup()
|
||||
{
|
||||
auto device = gfxDevice.getDevice().device;
|
||||
// auto device = gfxDevice.getDevice().device;
|
||||
|
||||
vkDeviceWaitIdle(device);
|
||||
// vkDeviceWaitIdle(device);
|
||||
|
||||
gfxDevice.waitIdle();
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
|
||||
if (skyboxCubemap)
|
||||
{
|
||||
skyboxCubemap->cleanup(gfxDevice);
|
||||
skyboxCubemap.reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <SDL.h>
|
||||
|
||||
#include "Lightkeeper.h"
|
||||
@@ -9,8 +11,13 @@ int main(int argc, char* argv[]) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", SDL_GetError(), nullptr);
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
std::filesystem::path exeDir = SDL_GetBasePath();
|
||||
try {
|
||||
char* basePath = SDL_GetBasePath();
|
||||
if (basePath == nullptr) {
|
||||
throw std::runtime_error(std::string{"SDL_GetBasePath failed: "} + SDL_GetError());
|
||||
}
|
||||
const std::filesystem::path exeDir = basePath;
|
||||
SDL_free(basePath);
|
||||
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
|
||||
@@ -24,6 +31,10 @@ int main(int argc, char* argv[]) {
|
||||
});
|
||||
app.run();
|
||||
app.cleanup();
|
||||
} catch (const std::exception& exception) {
|
||||
spdlog::critical("Lightkeeper terminated with an error: {}", exception.what());
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
add_executable(destrum_tests
|
||||
destrum_tests.cpp
|
||||
)
|
||||
|
||||
set_target_properties(destrum_tests PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS OFF
|
||||
)
|
||||
|
||||
target_compile_definitions(destrum_tests PRIVATE SDL_MAIN_HANDLED)
|
||||
target_link_libraries(destrum_tests PRIVATE destrum::destrum)
|
||||
|
||||
destrum_enable_warnings(destrum_tests)
|
||||
|
||||
add_test(NAME destrum_tests COMMAND destrum_tests)
|
||||
@@ -0,0 +1,363 @@
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <destrum/Event.h>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/ObjectModel/Component.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
#include <destrum/Serialization/SceneSerializer.h>
|
||||
#include <destrum/Physics/SimplePhysicsWorld.h>
|
||||
#include <destrum/Physics/JoltPhysicsWorld.h>
|
||||
#include <destrum/Components/Physics/SphereCollider.h>
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
|
||||
namespace {
|
||||
class TestComponent final : public Component {
|
||||
public:
|
||||
explicit TestComponent(GameObject& owner) : Component(owner, "TestComponent") {}
|
||||
|
||||
void Update() override {}
|
||||
std::string GetTypeName() const override { return "TestComponent"; }
|
||||
};
|
||||
|
||||
class RemovingListener final : public EventListener {
|
||||
public:
|
||||
explicit RemovingListener(std::unique_ptr<RemovingListener>* victim)
|
||||
: victim(victim) {}
|
||||
|
||||
void OnEvent(int) {
|
||||
if (victim != nullptr) {
|
||||
victim->reset();
|
||||
victim = nullptr;
|
||||
}
|
||||
++calls;
|
||||
}
|
||||
|
||||
int calls{0};
|
||||
|
||||
private:
|
||||
std::unique_ptr<RemovingListener>* victim;
|
||||
};
|
||||
|
||||
void check(bool condition, const char* message)
|
||||
{
|
||||
if (!condition) {
|
||||
std::cerr << "FAILED: " << message << '\n';
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
bool close(float a, float b)
|
||||
{
|
||||
return std::abs(a - b) < 0.001f;
|
||||
}
|
||||
|
||||
void testTransforms()
|
||||
{
|
||||
GameObject parent{"parent"};
|
||||
GameObject child{"child"};
|
||||
|
||||
parent.GetTransform().SetLocalPosition({10.f, 2.f, -4.f});
|
||||
parent.GetTransform().SetLocalRotation({0.f, 0.f, 90.f});
|
||||
parent.GetTransform().SetLocalScale({2.f, 2.f, 2.f});
|
||||
child.GetTransform().SetLocalPosition({1.f, 0.f, 0.f});
|
||||
|
||||
child.GetTransform().SetParent(&parent.GetTransform(), true);
|
||||
const auto worldPosition = child.GetTransform().GetWorldPosition();
|
||||
check(close(worldPosition.x, 1.f) && close(worldPosition.y, 0.f),
|
||||
"reparenting must preserve world position");
|
||||
|
||||
child.GetTransform().SetWorldPosition({20.f, 10.f, 0.f});
|
||||
const auto movedPosition = child.GetTransform().GetWorldPosition();
|
||||
check(close(movedPosition.x, 20.f) && close(movedPosition.y, 10.f),
|
||||
"world position must account for parent rotation and scale");
|
||||
|
||||
parent.Destroy();
|
||||
check(parent.IsBeingDestroyed() && child.IsBeingDestroyed(),
|
||||
"destroying a parent must mark descendants");
|
||||
}
|
||||
|
||||
void testSceneRoundTrip()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("round-trip");
|
||||
GameObject* parent = scene.CreateGameObject("parent");
|
||||
GameObject* child = scene.CreateGameObject("child");
|
||||
child->GetTransform().SetLocalPosition({1.f, 2.f, 3.f});
|
||||
child->GetTransform().SetParent(&parent->GetTransform(), false);
|
||||
scene.CommitPendingAdditions();
|
||||
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_scene_test.json";
|
||||
check(SceneSerializer::Save(scene, path), "scene save must succeed");
|
||||
check(SceneSerializer::Load(scene, path), "scene load must succeed");
|
||||
std::filesystem::remove(path);
|
||||
|
||||
check(scene.GetObjects().size() == 2, "scene round-trip must preserve objects");
|
||||
const auto* loadedChild = scene.GetObjects().at(1).get();
|
||||
check(loadedChild->GetTransform().GetParent() != nullptr,
|
||||
"scene round-trip must preserve hierarchy");
|
||||
check(close(loadedChild->GetTransform().GetLocalPosition().x, 1.f),
|
||||
"scene round-trip must preserve local transforms");
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testEventMutation()
|
||||
{
|
||||
Event<int> event;
|
||||
int calls = 0;
|
||||
event.AddListener([&](int) { ++calls; });
|
||||
event.AddListener([&](int) {
|
||||
++calls;
|
||||
event.AddListener([&](int) { ++calls; });
|
||||
});
|
||||
|
||||
event.Invoke(1);
|
||||
check(calls == 2, "event mutation must not invalidate the current invocation");
|
||||
event.Invoke(1);
|
||||
check(calls == 5, "new event listeners must run on subsequent invocations");
|
||||
}
|
||||
|
||||
void testEventRemoval()
|
||||
{
|
||||
Event<int> event;
|
||||
std::unique_ptr<RemovingListener> victim;
|
||||
RemovingListener remover{&victim};
|
||||
victim = std::make_unique<RemovingListener>(nullptr);
|
||||
|
||||
event.AddListener(&remover, &RemovingListener::OnEvent);
|
||||
event.AddListener(victim.get(), &RemovingListener::OnEvent);
|
||||
event.Invoke(1);
|
||||
|
||||
check(remover.calls == 1, "event remover must run once");
|
||||
check(victim == nullptr, "event listener must be removable during invocation");
|
||||
}
|
||||
|
||||
void testComponentRemoval()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("component-removal");
|
||||
GameObject* object = scene.CreateGameObject("object");
|
||||
object->AddComponent<TestComponent>();
|
||||
scene.CommitPendingAdditions();
|
||||
|
||||
check(object->GetComponent<TestComponent>() != nullptr,
|
||||
"test component must be discoverable before removal");
|
||||
check(object->DestroyComponent<TestComponent>() != nullptr,
|
||||
"destroying a component must find it");
|
||||
scene.CleanupDestroyedGameObjects();
|
||||
check(object->GetComponent<TestComponent>() == nullptr,
|
||||
"destroyed components must be removed at the scene boundary");
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testAssetPathValidation()
|
||||
{
|
||||
const auto root = std::filesystem::temp_directory_path() / "destrum_asset_test";
|
||||
std::filesystem::create_directories(root / "assets" / "engine");
|
||||
std::filesystem::create_directories(root / "assets" / "game");
|
||||
std::ofstream{root / "assets" / "engine" / "asset.txt"} << "asset";
|
||||
std::ofstream{root / "assets" / "engine" / "cooked.asset"} << "cooked";
|
||||
std::ofstream{root / "assets" / "engine" / "manifest.json"}
|
||||
<< R"({"version":1,"assets":[{"src":"asset.txt","out":"cooked.asset","type":"raw","mtime_epoch_ns":-5020137004799419216,"size_bytes":355972}]})";
|
||||
|
||||
auto& assetFs = AssetFS::GetInstance();
|
||||
assetFs.Init(root);
|
||||
check(std::filesystem::exists(assetFs.GetFullPath("engine://asset.txt")),
|
||||
"valid virtual asset paths must resolve");
|
||||
check(assetFs.GetCookedPathForFile("engine://asset.txt").filename() == "cooked.asset",
|
||||
"manifest output paths must resolve to cooked assets");
|
||||
|
||||
bool rejected = false;
|
||||
try {
|
||||
(void)assetFs.GetFullPath("engine://../outside.txt");
|
||||
} catch (const std::exception&) {
|
||||
rejected = true;
|
||||
}
|
||||
check(rejected, "asset traversal must be rejected");
|
||||
|
||||
const auto outside = root / "outside";
|
||||
const auto link = root / "assets" / "engine" / "link";
|
||||
std::filesystem::create_directories(outside);
|
||||
std::error_code symlinkError;
|
||||
std::filesystem::create_directory_symlink(outside, link, symlinkError);
|
||||
if (!symlinkError) {
|
||||
bool symlinkEscapeRejected = false;
|
||||
try {
|
||||
(void)assetFs.GetFullPath("engine://link/missing.txt");
|
||||
} catch (const std::exception&) {
|
||||
symlinkEscapeRejected = true;
|
||||
}
|
||||
check(symlinkEscapeRejected,
|
||||
"symlink traversal must be rejected for missing targets");
|
||||
}
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
assetFs.Reset();
|
||||
}
|
||||
|
||||
void testLegacySceneLoad()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("legacy");
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_legacy_scene.json";
|
||||
std::ofstream{path}
|
||||
<< R"({"version":1,"name":"legacy","objects":[{"id":9001,"name":"object","active":true,"transform":{},"parent":0,"components":[]}]})";
|
||||
|
||||
check(SceneSerializer::Load(scene, path),
|
||||
"version 1 placeholder transforms must remain loadable");
|
||||
check(scene.GetObjects().size() == 1,
|
||||
"legacy scene load must create its object");
|
||||
|
||||
std::filesystem::remove(path);
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testSceneLoadRollback()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("rollback");
|
||||
scene.CreateGameObject("original");
|
||||
scene.CommitPendingAdditions();
|
||||
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_bad_scene.json";
|
||||
std::ofstream{path}
|
||||
<< R"({"version":2,"name":"bad","objects":[{"id":9101,"name":"bad","transform":{"position":[0,0,0],"rotation":[0,0,0,1],"scale":[1,1,1]},"parent":0,"components":[{"type":"Rigidbody","enabled":true,"data":{}}]}]})";
|
||||
|
||||
check(!SceneSerializer::Load(scene, path),
|
||||
"scene loading must reject a rigidbody without a collider");
|
||||
check(scene.GetObjects().size() == 1 && scene.GetObjects().at(0)->GetName() == "original",
|
||||
"failed scene loading must preserve the live scene");
|
||||
|
||||
std::filesystem::remove(path);
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testPhysicsWorldUnits()
|
||||
{
|
||||
SimplePhysicsWorld world{glm::vec3{0.0f}};
|
||||
GameObject object{"scaled-physics"};
|
||||
object.GetTransform().SetLocalScale(glm::vec3{0.5f});
|
||||
object.AddComponent<SphereCollider>(1.0f);
|
||||
Rigidbody* rigidbody = object.AddComponent<Rigidbody>();
|
||||
world.RegisterRigidbody(*rigidbody);
|
||||
|
||||
PhysicsRaycastHit hit;
|
||||
check(world.Raycast({-2.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 5.0f, hit),
|
||||
"scaled physics bodies must remain raycastable");
|
||||
check(close(hit.distance, 1.0f),
|
||||
"physics collider dimensions must remain in world units");
|
||||
|
||||
object.GetTransform().SetLocalPosition({10.0f, 0.0f, 0.0f});
|
||||
world.RefreshRigidbody(*rigidbody);
|
||||
check(world.Raycast({8.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 5.0f, hit),
|
||||
"refreshing a physics body must use the current transform position");
|
||||
check(close(hit.distance, 1.0f),
|
||||
"refreshed physics bodies must remain aligned with rendered transforms");
|
||||
}
|
||||
|
||||
void testJoltSphereCollisions()
|
||||
{
|
||||
JoltPhysicsWorld world{{
|
||||
.workerThreadCount = 1,
|
||||
.gravity = {0.0f, -9.81f, 0.0f}
|
||||
}};
|
||||
|
||||
GameObject floor{"floor"};
|
||||
floor.GetTransform().SetLocalPosition({0.0f, -1.0f, 0.0f});
|
||||
floor.AddComponent<BoxCollider>(glm::vec3{10.0f, 0.5f, 10.0f});
|
||||
auto* floorBody = floor.AddComponent<Rigidbody>();
|
||||
floorBody->SetType(RigidbodyType::Static);
|
||||
world.RegisterRigidbody(*floorBody);
|
||||
|
||||
GameObject first{"first"};
|
||||
first.AddComponent<SphereCollider>(1.5f);
|
||||
auto* firstBody = first.AddComponent<Rigidbody>();
|
||||
world.RegisterRigidbody(*firstBody);
|
||||
first.GetTransform().SetLocalPosition({0.0f, 100.0f, 0.0f});
|
||||
first.GetTransform().SetLocalScale(glm::vec3{0.015f});
|
||||
world.RefreshRigidbody(*firstBody);
|
||||
|
||||
GameObject second{"second"};
|
||||
second.AddComponent<SphereCollider>(1.5f);
|
||||
auto* secondBody = second.AddComponent<Rigidbody>();
|
||||
world.RegisterRigidbody(*secondBody);
|
||||
second.GetTransform().SetLocalPosition({0.0f, 100.0f, 0.0f});
|
||||
second.GetTransform().SetLocalScale(glm::vec3{0.015f});
|
||||
world.RefreshRigidbody(*secondBody);
|
||||
|
||||
for (int step = 0; step < 240; ++step) {
|
||||
world.Step(1.0f / 60.0f);
|
||||
world.SyncDynamicBodiesToTransforms();
|
||||
}
|
||||
|
||||
check(first.GetTransform().GetWorldPosition().y > 0.8f &&
|
||||
second.GetTransform().GetWorldPosition().y > 0.8f,
|
||||
"Jolt spheres must collide with the ground plane");
|
||||
check(glm::distance(first.GetTransform().GetWorldPosition(),
|
||||
second.GetTransform().GetWorldPosition()) > 2.5f,
|
||||
"Jolt spheres must collide with one another instead of occupying the same point");
|
||||
}
|
||||
|
||||
void testSceneJoltSphereCollisions()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("scene-physics");
|
||||
|
||||
GameObject* floor = scene.CreateGameObject("floor");
|
||||
floor->GetTransform().SetLocalPosition({0.0f, -1.0f, 0.0f});
|
||||
floor->AddComponent<BoxCollider>(glm::vec3{10.0f, 0.5f, 10.0f});
|
||||
auto* floorBody = floor->AddComponent<Rigidbody>();
|
||||
floorBody->SetType(RigidbodyType::Static);
|
||||
|
||||
GameObject* first = scene.CreateGameObject("first");
|
||||
first->AddComponent<SphereCollider>(1.5f);
|
||||
first->AddComponent<Rigidbody>();
|
||||
first->GetTransform().SetWorldPosition({0.0f, 100.0f, 0.0f});
|
||||
first->GetTransform().SetWorldScale(glm::vec3{0.015f});
|
||||
|
||||
GameObject* second = scene.CreateGameObject("second");
|
||||
second->AddComponent<SphereCollider>(1.5f);
|
||||
second->AddComponent<Rigidbody>();
|
||||
second->GetTransform().SetWorldPosition({0.0f, 100.0f, 0.0f});
|
||||
second->GetTransform().SetWorldScale(glm::vec3{0.015f});
|
||||
|
||||
scene.CommitPendingAdditions();
|
||||
scene.GetPhysics().RefreshGameObject(*first);
|
||||
scene.GetPhysics().RefreshGameObject(*second);
|
||||
|
||||
for (int step = 0; step < 240; ++step) {
|
||||
scene.FixedUpdate(1.0f / 60.0f);
|
||||
}
|
||||
|
||||
check(first->GetTransform().GetWorldPosition().y > 0.8f &&
|
||||
second->GetTransform().GetWorldPosition().y > 0.8f,
|
||||
"scene physics spheres must collide with the ground plane");
|
||||
check(glm::distance(first->GetTransform().GetWorldPosition(),
|
||||
second->GetTransform().GetWorldPosition()) > 2.5f,
|
||||
"scene physics spheres must not overlap after settling");
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
testTransforms();
|
||||
testSceneRoundTrip();
|
||||
testEventMutation();
|
||||
testEventRemoval();
|
||||
testComponentRemoval();
|
||||
testAssetPathValidation();
|
||||
testLegacySceneLoad();
|
||||
testSceneLoadRollback();
|
||||
testPhysicsWorldUnits();
|
||||
testJoltSphereCollisions();
|
||||
testSceneJoltSphereCollisions();
|
||||
std::cout << "destrum tests passed\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user