fix: alot of stuff changed. Mostly bugfixxes / architechture changes

This commit is contained in:
2026-08-09 02:24:17 +02:00
parent 4b6f773c0c
commit 05384debbf
98 changed files with 5461 additions and 1751 deletions
+62 -7
View File
@@ -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)
+2 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+4
View File
@@ -16,4 +16,8 @@ layout (buffer_reference, std430) readonly buffer VertexBuffer {
Vertex vertices[];
};
layout (buffer_reference, std430) buffer WritableVertexBuffer {
Vertex vertices[];
};
#endif // VERTEX_GLSL
+7 -2
View File
@@ -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);
@@ -66,4 +69,4 @@ private:
glm::vec3 sampleScale (const SkeletalAnimation::Track& track, float t);
};
#endif // ANIMATOR_H
#endif // ANIMATOR_H
@@ -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,11 +29,12 @@ public:
}
void SetHalfExtents(const glm::vec3& halfExtents) {
m_HalfExtents = halfExtents;
m_HalfExtents = glm::max(halfExtents, glm::vec3{0.0001f});
NotifyPhysicsChanged();
}
private:
glm::vec3 m_HalfExtents{0.5f};
};
#endif
#endif
@@ -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 -2
View File
@@ -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; }
+16 -4
View File
@@ -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:
+34 -10
View File
@@ -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};
};
+4
View File
@@ -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);
+4 -1
View File
@@ -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
+62 -9
View File
@@ -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; }
+85 -64
View File
@@ -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};
};
+53 -17
View File
@@ -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
+85 -7
View File
@@ -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
#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};
@@ -157,4 +171,4 @@ private:
bool m_ActiveDirty{true};
bool m_ActiveInHierarchy{true};
};
};
@@ -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;
};
+38 -7
View File
@@ -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
+4 -5
View File
@@ -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;
+2
View File
@@ -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;
+98 -34
View File
@@ -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)
@@ -336,4 +400,4 @@ void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
}
ImGui::End();
}
}
+223 -9
View File
@@ -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 = {};
@@ -184,4 +398,4 @@ glm::vec3 Animator::sampleScale(const SkeletalAnimation::Track& track, float t)
}
}
return kf.back().scale;
}
}
@@ -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();
+68 -7
View File
@@ -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();
}
+19 -1
View File
@@ -1 +1,19 @@
#include <destrum/Components/Physics/BoxCollider.h>
#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>
#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>());
}
}
+38 -3
View File
@@ -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>()};
}
}
+23 -14
View File
@@ -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 Ill 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
View File
@@ -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;
+35 -6
View File
@@ -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;
+113 -22
View File
@@ -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,
+63 -31
View File
@@ -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();
+80 -15
View File
@@ -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));
}
+17 -6
View File
@@ -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
View File
@@ -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,
// &copyRegion);
//
// 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, &copyRegion);
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
}
+46 -20
View File
@@ -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,
&copyRegion);
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));
}
}
+150 -24
View File
@@ -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();
}
+16 -4
View File
@@ -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 {
+69 -19
View File
@@ -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(
+59 -20
View File
@@ -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) {
@@ -208,4 +247,4 @@ std::uint32_t RenderResources::BytesPerTexel(VkFormat fmt)
default:
throw std::runtime_error("RenderResources::BytesPerTexel: unsupported format");
}
}
}
+80 -23
View File
@@ -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();
}
+45 -32
View File
@@ -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;
}
}
@@ -308,4 +321,4 @@ void CubeMap::InitCubemapPipeline(
fragPath,
pipelineConfig
);
}
}
+31 -12
View File
@@ -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,
+136 -79
View File
@@ -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
View File
@@ -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);
}
+25 -4
View File
@@ -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&) {
}
+123 -30
View File
@@ -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();
}
}
+4 -10
View File
@@ -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;
}
+233 -102
View File
@@ -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;
}
+24 -4
View File
@@ -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;
}
+8 -2
View File
@@ -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);
+74 -11
View File
@@ -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.
}
+13 -5
View File
@@ -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) {
+186 -86
View File
@@ -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);
}
}
}
+76 -11
View File
@@ -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;
}
+23 -10
View File
@@ -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>();
});
}
+338 -90
View File
@@ -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;
}
}