fix: alot of stuff changed. Mostly bugfixxes / architechture changes
This commit is contained in:
+98
-34
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "destrum/ObjectModel/Transform.h"
|
||||
#include "destrum/Components/MeshRendererComponent.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "destrum/Util/DeltaTime.h"
|
||||
|
||||
static glm::vec3 RandomUnitVector(std::mt19937& rng)
|
||||
{
|
||||
@@ -54,6 +55,9 @@ void OrbitAndSpin::Randomize(uint32_t seed)
|
||||
|
||||
void OrbitAndSpin::BuildOrbitBasis()
|
||||
{
|
||||
if (glm::dot(m_OrbitAxis, m_OrbitAxis) < 1e-8f)
|
||||
m_OrbitAxis = glm::vec3(0, 1, 0);
|
||||
|
||||
m_OrbitAxis = glm::normalize(m_OrbitAxis);
|
||||
|
||||
// pick any vector not parallel to axis
|
||||
@@ -63,9 +67,8 @@ void OrbitAndSpin::BuildOrbitBasis()
|
||||
m_V = glm::normalize(glm::cross(m_OrbitAxis, m_U));
|
||||
}
|
||||
|
||||
void OrbitAndSpin::Update()
|
||||
void OrbitAndSpin::Update(float dt)
|
||||
{
|
||||
float dt = 1.0f / 60.0f;
|
||||
|
||||
// orbit
|
||||
m_OrbitAngle += m_OrbitSpeed * dt;
|
||||
@@ -84,8 +87,6 @@ void OrbitAndSpin::Update()
|
||||
// grow (always positive)
|
||||
m_GrowPhase += m_GrowSpeed * dt;
|
||||
|
||||
m_GrowPhase += m_GrowSpeed * dt;
|
||||
|
||||
// 0..1
|
||||
float t = 0.5f * (std::sin(m_GrowPhase) + 1.0f);
|
||||
|
||||
@@ -93,7 +94,7 @@ void OrbitAndSpin::Update()
|
||||
float s = glm::mix(m_GrowMin, m_GrowMax, t);
|
||||
|
||||
// respect original scale
|
||||
GetTransform().SetLocalScale(glm::vec3(s));
|
||||
GetTransform().SetLocalScale(m_BaseScale * s);
|
||||
|
||||
// GetTransform().SetLocalScale(glm::vec3(std::sin(m_GrowPhase)));
|
||||
|
||||
@@ -109,8 +110,68 @@ void OrbitAndSpin::Update()
|
||||
|
||||
void OrbitAndSpin::Start() {
|
||||
auto meshComp = this->GetGameObject()->GetComponent<MeshRendererComponent>();
|
||||
m_MaterialID = meshComp->GetMaterialID();
|
||||
if (meshComp != nullptr) {
|
||||
m_MaterialID = meshComp->GetMaterialID();
|
||||
}
|
||||
|
||||
m_BaseScale = GetTransform().GetLocalScale(); // <-- important
|
||||
if (!m_BaseScaleLoaded) {
|
||||
m_BaseScale = GetTransform().GetLocalScale();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nlohmann::json OrbitAndSpin::Serialize() const {
|
||||
return {
|
||||
{"radius", m_Radius},
|
||||
{"center", {m_Center.x, m_Center.y, m_Center.z}},
|
||||
{"orbitAxis", {m_OrbitAxis.x, m_OrbitAxis.y, m_OrbitAxis.z}},
|
||||
{"orbitSpeed", m_OrbitSpeed},
|
||||
{"orbitAngle", m_OrbitAngle},
|
||||
{"orbitPhase", m_OrbitPhase},
|
||||
{"growPhase", m_GrowPhase},
|
||||
{"growSpeed", m_GrowSpeed},
|
||||
{"growMin", m_GrowMin},
|
||||
{"growMax", m_GrowMax},
|
||||
{"spinAxis", {m_SpinAxis.x, m_SpinAxis.y, m_SpinAxis.z}},
|
||||
{"spinSpeed", m_SpinSpeed},
|
||||
{"baseScale", {m_BaseScale.x, m_BaseScale.y, m_BaseScale.z}},
|
||||
{"materialId", m_MaterialID}
|
||||
};
|
||||
}
|
||||
|
||||
void OrbitAndSpin::Deserialize(const nlohmann::json& data) {
|
||||
const auto readVec3 = [&data](const char* key, glm::vec3& value) {
|
||||
if (!data.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& array = data.at(key);
|
||||
value = {array.at(0).get<float>(), array.at(1).get<float>(), array.at(2).get<float>()};
|
||||
};
|
||||
|
||||
if (data.contains("radius")) m_Radius = data.at("radius").get<float>();
|
||||
readVec3("center", m_Center);
|
||||
readVec3("orbitAxis", m_OrbitAxis);
|
||||
if (data.contains("orbitSpeed")) m_OrbitSpeed = data.at("orbitSpeed").get<float>();
|
||||
if (data.contains("orbitAngle")) m_OrbitAngle = data.at("orbitAngle").get<float>();
|
||||
if (data.contains("orbitPhase")) m_OrbitPhase = data.at("orbitPhase").get<float>();
|
||||
if (data.contains("growPhase")) m_GrowPhase = data.at("growPhase").get<float>();
|
||||
if (data.contains("growSpeed")) m_GrowSpeed = data.at("growSpeed").get<float>();
|
||||
if (data.contains("growMin")) m_GrowMin = data.at("growMin").get<float>();
|
||||
if (data.contains("growMax")) m_GrowMax = data.at("growMax").get<float>();
|
||||
readVec3("spinAxis", m_SpinAxis);
|
||||
readVec3("baseScale", m_BaseScale);
|
||||
m_BaseScaleLoaded = data.contains("baseScale");
|
||||
if (data.contains("spinSpeed")) m_SpinSpeed = data.at("spinSpeed").get<float>();
|
||||
if (data.contains("materialId")) m_MaterialID = data.at("materialId").get<MaterialID>();
|
||||
|
||||
if (m_GrowMin > m_GrowMax) {
|
||||
std::swap(m_GrowMin, m_GrowMax);
|
||||
}
|
||||
if (glm::dot(m_SpinAxis, m_SpinAxis) < 1e-8f) {
|
||||
m_SpinAxis = glm::vec3(0, 1, 0);
|
||||
} else {
|
||||
m_SpinAxis = glm::normalize(m_SpinAxis);
|
||||
}
|
||||
BuildOrbitBasis();
|
||||
}
|
||||
|
||||
@@ -1 +1,19 @@
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
#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>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <glm/gtc/quaternion.hpp> // glm::quat, glm::angleAxis
|
||||
#include <glm/gtx/quaternion.hpp> // operator*(quat, vec3)
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
glm::vec3 Rotator::MakePerpendicularUnitVector(const glm::vec3& axis)
|
||||
{
|
||||
@@ -84,12 +85,46 @@ void Rotator::SetDistance(float distance)
|
||||
m_InitialOffset = MakePerpendicularUnitVector(m_Axis) * m_Distance;
|
||||
}
|
||||
|
||||
void Rotator::Update()
|
||||
void Rotator::Update(float dt)
|
||||
{
|
||||
// Replace 0.001f with your engine delta time if you have one.
|
||||
m_CurrentAngle += m_Speed * 0.001f;
|
||||
m_CurrentAngle += m_Speed * dt;
|
||||
|
||||
const glm::quat q = glm::angleAxis(m_CurrentAngle, glm::normalize(m_Axis));
|
||||
const glm::vec3 rotatedOffset = q * m_InitialOffset;
|
||||
GetTransform().SetLocalPosition(m_Pivot + rotatedOffset);
|
||||
}
|
||||
|
||||
nlohmann::json Rotator::Serialize() const {
|
||||
return {
|
||||
{"distance", m_Distance},
|
||||
{"speed", m_Speed},
|
||||
{"currentAngle", m_CurrentAngle},
|
||||
{"pivot", {m_Pivot.x, m_Pivot.y, m_Pivot.z}},
|
||||
{"axis", {m_Axis.x, m_Axis.y, m_Axis.z}},
|
||||
{"initialOffset", {m_InitialOffset.x, m_InitialOffset.y, m_InitialOffset.z}}
|
||||
};
|
||||
}
|
||||
|
||||
void Rotator::Deserialize(const nlohmann::json& data) {
|
||||
if (data.contains("distance")) {
|
||||
m_Distance = data.at("distance").get<float>();
|
||||
}
|
||||
if (data.contains("speed")) {
|
||||
m_Speed = data.at("speed").get<float>();
|
||||
}
|
||||
if (data.contains("currentAngle")) {
|
||||
m_CurrentAngle = data.at("currentAngle").get<float>();
|
||||
}
|
||||
if (data.contains("pivot")) {
|
||||
const auto& value = data.at("pivot");
|
||||
m_Pivot = {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
||||
}
|
||||
if (data.contains("axis")) {
|
||||
const auto& value = data.at("axis");
|
||||
SetAxis({value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()});
|
||||
}
|
||||
if (data.contains("initialOffset")) {
|
||||
const auto& value = data.at("initialOffset");
|
||||
m_InitialOffset = {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,33 @@
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include "destrum/ObjectModel/Transform.h"
|
||||
#include "destrum/Util/DeltaTime.h"
|
||||
|
||||
void Spinner::Update()
|
||||
void Spinner::Update(float dt)
|
||||
{
|
||||
// Replace with your engine dt if you have it available in Component.
|
||||
const float dt = 1.0f / 60.0f;
|
||||
|
||||
|
||||
m_Angle += m_Speed * dt;
|
||||
|
||||
// If you already have SetLocalRotation / SetWorldRotation, use that.
|
||||
// Here I'm assuming you can set rotation as a quaternion or Euler somewhere.
|
||||
// If not, tell me your Transform rotation API and I’ll adjust.
|
||||
|
||||
const glm::quat q = glm::angleAxis(m_Angle, m_Axis);
|
||||
|
||||
// Example APIs you might have:
|
||||
// GetTransform().SetLocalRotation(q);
|
||||
// or GetTransform().SetWorldRotation(q);
|
||||
|
||||
GetTransform().SetLocalRotation(q);
|
||||
}
|
||||
|
||||
nlohmann::json Spinner::Serialize() const {
|
||||
return {
|
||||
{"axis", {m_Axis.x, m_Axis.y, m_Axis.z}},
|
||||
{"speed", m_Speed},
|
||||
{"angle", m_Angle}
|
||||
};
|
||||
}
|
||||
|
||||
void Spinner::Deserialize(const nlohmann::json& data) {
|
||||
if (data.contains("axis")) {
|
||||
const auto& value = data.at("axis");
|
||||
SetAxis({value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()});
|
||||
}
|
||||
if (data.contains("speed")) {
|
||||
m_Speed = data.at("speed").get<float>();
|
||||
}
|
||||
if (data.contains("angle")) {
|
||||
m_Angle = data.at("angle").get<float>();
|
||||
}
|
||||
}
|
||||
|
||||
+194
-81
@@ -1,95 +1,204 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
void AssetFS::Init(std::filesystem::path exeDir) {
|
||||
Mount("engine", exeDir / "assets/engine");
|
||||
Mount("game", exeDir / "assets/game");
|
||||
initialized = true;
|
||||
}
|
||||
namespace {
|
||||
struct ParsedVirtualPath {
|
||||
std::string scheme;
|
||||
std::filesystem::path relativePath;
|
||||
};
|
||||
|
||||
void AssetFS::Mount(std::string scheme, std::filesystem::path root) {
|
||||
spdlog::debug("Mounting assetfs scheme '{}' to root '{}'", scheme, root.string());
|
||||
|
||||
FSMount m;
|
||||
m.scheme = std::move(scheme);
|
||||
m.root = std::move(root);
|
||||
|
||||
const auto manifestPath = m.root / "manifest.json";
|
||||
if (std::filesystem::exists(manifestPath)) {
|
||||
m.manifest = FS::LoadAssetManifest(manifestPath);
|
||||
}
|
||||
|
||||
mounts.push_back(std::move(m));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> AssetFS::ReadBytes(std::string_view vpath) {
|
||||
assert(initialized && "AssetFS not initialized");
|
||||
// parse "engine://path/inside"
|
||||
auto pos = vpath.find("://");
|
||||
if (pos == std::string_view::npos) throw std::runtime_error("bad vpath");
|
||||
|
||||
std::string scheme(vpath.substr(0, pos));
|
||||
std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||
|
||||
for (auto& m : mounts) {
|
||||
if (m.scheme == scheme) {
|
||||
auto full = m.root / rel;
|
||||
return ReadFile(full);
|
||||
[[nodiscard]] std::filesystem::path ValidateRelativePath(std::string_view value) {
|
||||
if (value.empty()) {
|
||||
throw std::runtime_error("asset path is empty");
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("mount not found");
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetFullPath(std::string_view vpath) const {
|
||||
assert(initialized && "AssetFS not initialized");
|
||||
// parse "engine://path/inside"
|
||||
auto pos = vpath.find("://");
|
||||
if (pos == std::string_view::npos) throw std::runtime_error("bad vpath");
|
||||
|
||||
std::string scheme(vpath.substr(0, pos));
|
||||
std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||
|
||||
for (auto& m : mounts) {
|
||||
if (m.scheme == scheme) {
|
||||
auto full = m.root / rel;
|
||||
return full;
|
||||
const std::filesystem::path path{std::string(value)};
|
||||
if (path.empty() || path.is_absolute() || path.has_root_path()) {
|
||||
throw std::runtime_error("asset path must be relative");
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("mount not found");
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetCookedPathForFile(std::string_view vpath) const {
|
||||
assert(initialized && "AssetFS not initialized");
|
||||
|
||||
const auto pos = vpath.find("://");
|
||||
if (pos == std::string_view::npos)
|
||||
throw std::runtime_error("bad vpath");
|
||||
|
||||
const std::string scheme(vpath.substr(0, pos));
|
||||
const std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||
const std::string relStr = rel.generic_string();
|
||||
|
||||
for (const auto& m : mounts) {
|
||||
if (m.scheme != scheme) continue;
|
||||
|
||||
// If we have a manifest, consult it
|
||||
if (m.manifest) {
|
||||
if (const ManifestAsset* asset = m.manifest->FindBySrc(relStr)) {
|
||||
if (asset->out) {
|
||||
return m.root / *asset->out;
|
||||
}
|
||||
for (const auto& part : path) {
|
||||
if (part == "..") {
|
||||
throw std::runtime_error("asset path traversal is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to raw file
|
||||
return m.root / rel;
|
||||
const std::filesystem::path normalized = path.lexically_normal();
|
||||
if (normalized.empty() || normalized == ".") {
|
||||
throw std::runtime_error("asset path is invalid");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
throw std::runtime_error("mount not found");
|
||||
return {};
|
||||
[[nodiscard]] ParsedVirtualPath ParseVirtualPath(std::string_view value) {
|
||||
const std::size_t separator = value.find("://");
|
||||
if (separator == std::string_view::npos || separator == 0) {
|
||||
throw std::runtime_error("asset path must use scheme://relative/path syntax");
|
||||
}
|
||||
|
||||
const std::string scheme(value.substr(0, separator));
|
||||
if (scheme.find_first_of("/\\:") != std::string::npos) {
|
||||
throw std::runtime_error("asset scheme is invalid");
|
||||
}
|
||||
|
||||
return {
|
||||
scheme,
|
||||
ValidateRelativePath(value.substr(separator + 3))
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::filesystem::path ResolvePath(const FSMount& mount,
|
||||
const std::filesystem::path& relativePath) {
|
||||
const std::filesystem::path root = mount.root.lexically_normal();
|
||||
const std::filesystem::path full = (root / relativePath).lexically_normal();
|
||||
const std::filesystem::path relative = full.lexically_relative(root);
|
||||
|
||||
if (relative.empty() || relative.is_absolute() || relative == ".." ||
|
||||
relative.generic_string().starts_with("../")) {
|
||||
throw std::runtime_error("asset path escapes its mount");
|
||||
}
|
||||
|
||||
std::error_code error;
|
||||
const std::filesystem::path canonicalRoot = std::filesystem::weakly_canonical(root, error);
|
||||
if (error) {
|
||||
throw std::runtime_error("failed to resolve asset mount: " + root.string());
|
||||
}
|
||||
|
||||
error.clear();
|
||||
const std::filesystem::path canonicalFull = std::filesystem::weakly_canonical(full, error);
|
||||
if (error) {
|
||||
throw std::runtime_error("failed to resolve asset path: " + full.string());
|
||||
}
|
||||
|
||||
const std::filesystem::path canonicalRelative =
|
||||
canonicalFull.lexically_relative(canonicalRoot);
|
||||
if (canonicalRelative.empty() || canonicalRelative.is_absolute() ||
|
||||
canonicalRelative == ".." || canonicalRelative.generic_string().starts_with("../")) {
|
||||
throw std::runtime_error("asset path escapes its mount");
|
||||
}
|
||||
|
||||
return full;
|
||||
}
|
||||
|
||||
[[nodiscard]] const FSMount* FindMount(const std::vector<FSMount>& mounts,
|
||||
const std::string& scheme) {
|
||||
for (const FSMount& mount : mounts) {
|
||||
if (mount.scheme == scheme) {
|
||||
return &mount;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AssetFS::Init(std::filesystem::path exeDir) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t originalMountCount = mounts.size();
|
||||
try {
|
||||
Mount("engine", exeDir / "assets/engine");
|
||||
Mount("game", exeDir / "assets/game");
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
mounts.resize(originalMountCount);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void AssetFS::Reset() {
|
||||
mounts.clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void AssetFS::Mount(std::string scheme, std::filesystem::path root) {
|
||||
if (scheme.empty() || scheme.find_first_of("/\\:") != std::string::npos) {
|
||||
throw std::runtime_error("asset mount scheme is invalid");
|
||||
}
|
||||
if (FindMount(mounts, scheme) != nullptr) {
|
||||
throw std::runtime_error("asset mount already exists: " + scheme);
|
||||
}
|
||||
|
||||
FSMount mount;
|
||||
mount.scheme = std::move(scheme);
|
||||
mount.root = std::filesystem::absolute(std::move(root)).lexically_normal();
|
||||
|
||||
const std::filesystem::path manifestPath = mount.root / "manifest.json";
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_regular_file(manifestPath, error)) {
|
||||
mount.manifest = FS::LoadAssetManifest(manifestPath);
|
||||
}
|
||||
|
||||
spdlog::debug("Mounting assetfs scheme '{}' to root '{}'", mount.scheme, mount.root.string());
|
||||
mounts.push_back(std::move(mount));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> AssetFS::ReadBytes(std::string_view vpath) {
|
||||
return ReadFile(GetFullPath(vpath));
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetFullPath(std::string_view vpath) const {
|
||||
if (!initialized) {
|
||||
throw std::runtime_error("AssetFS is not initialized");
|
||||
}
|
||||
|
||||
const ParsedVirtualPath parsed = ParseVirtualPath(vpath);
|
||||
const FSMount* mount = FindMount(mounts, parsed.scheme);
|
||||
if (mount == nullptr) {
|
||||
throw std::runtime_error("asset mount not found: " + parsed.scheme);
|
||||
}
|
||||
|
||||
return ResolvePath(*mount, parsed.relativePath);
|
||||
}
|
||||
|
||||
std::filesystem::path AssetFS::GetCookedPathForFile(std::string_view vpath) const {
|
||||
if (!initialized) {
|
||||
throw std::runtime_error("AssetFS is not initialized");
|
||||
}
|
||||
|
||||
const ParsedVirtualPath parsed = ParseVirtualPath(vpath);
|
||||
const FSMount* mount = FindMount(mounts, parsed.scheme);
|
||||
if (mount == nullptr) {
|
||||
throw std::runtime_error("asset mount not found: " + parsed.scheme);
|
||||
}
|
||||
|
||||
const std::filesystem::path rawPath = ResolvePath(*mount, parsed.relativePath);
|
||||
if (mount->manifest) {
|
||||
if (const ManifestAsset* asset = mount->manifest->FindBySrc(parsed.relativePath.generic_string())) {
|
||||
if (asset->out) {
|
||||
const std::filesystem::path cookedRelative = ValidateRelativePath(*asset->out);
|
||||
const std::filesystem::path cookedPath = ResolvePath(*mount, cookedRelative);
|
||||
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_regular_file(cookedPath, error)) {
|
||||
error.clear();
|
||||
const auto sourceTime = std::filesystem::last_write_time(rawPath, error);
|
||||
error.clear();
|
||||
const auto cookedTime = std::filesystem::last_write_time(cookedPath, error);
|
||||
if (!error && sourceTime > cookedTime) {
|
||||
throw std::runtime_error(
|
||||
"cooked asset output is stale: " + cookedPath.string());
|
||||
}
|
||||
return cookedPath;
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"cooked asset output is missing: " + cookedPath.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assets without a cooked output, such as shader includes, use the source path.
|
||||
return rawPath;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> AssetFS::ReadFile(const std::filesystem::path& fullPath) {
|
||||
@@ -99,10 +208,14 @@ std::vector<uint8_t> AssetFS::ReadFile(const std::filesystem::path& fullPath) {
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
std::streamsize size = file.tellg();
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size < 0) {
|
||||
throw std::runtime_error("failed to determine file size: " + fullPath.string());
|
||||
}
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<uint8_t> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
|
||||
std::vector<uint8_t> buffer(static_cast<std::size_t>(size));
|
||||
if (size > 0 && !file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
throw std::runtime_error("failed to read file: " + fullPath.string());
|
||||
}
|
||||
return buffer;
|
||||
|
||||
@@ -2,8 +2,30 @@
|
||||
#include <destrum/FS/Manifest.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] std::string NormalizeRelativePath(const std::string& value, const char* field) {
|
||||
const std::filesystem::path path(value);
|
||||
if (value.empty() || path.empty() || path.is_absolute() || path.has_root_path()) {
|
||||
throw std::runtime_error(std::string("Invalid manifest ") + field + " path: " + value);
|
||||
}
|
||||
|
||||
for (const auto& part : path) {
|
||||
if (part == "..") {
|
||||
throw std::runtime_error(std::string("Manifest ") + field + " path escapes its mount: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string normalized = path.lexically_normal().generic_string();
|
||||
if (normalized.empty() || normalized == ".") {
|
||||
throw std::runtime_error(std::string("Invalid manifest ") + field + " path: " + value);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
AssetManifest FS::LoadAssetManifest(const std::filesystem::path& manifestPath) {
|
||||
std::ifstream f(manifestPath);
|
||||
@@ -25,18 +47,25 @@ AssetManifest FS::LoadAssetManifest(const std::filesystem::path& manifestPath) {
|
||||
ManifestAsset asset;
|
||||
asset.src = a.at("src").get<std::string>();
|
||||
asset.type = a.at("type").get<std::string>();
|
||||
asset.mtime_epoch_ns = a.value("mtime_epoch_ns", 0);
|
||||
asset.size_bytes = a.value("size_bytes", 0);
|
||||
if (asset.type.empty()) {
|
||||
throw std::runtime_error("Invalid manifest asset type");
|
||||
}
|
||||
asset.mtime_epoch_ns = a.value("mtime_epoch_ns", std::int64_t{0});
|
||||
asset.size_bytes = a.value("size_bytes", std::uint64_t{0});
|
||||
|
||||
if (a.contains("out") && !a["out"].is_null()) {
|
||||
asset.out = a["out"].get<std::string>();
|
||||
}
|
||||
|
||||
// Normalize to forward slashes for cross-platform matching
|
||||
std::filesystem::path p(asset.src);
|
||||
asset.src = p.generic_string();
|
||||
asset.src = NormalizeRelativePath(asset.src, "source");
|
||||
if (asset.out) {
|
||||
*asset.out = NormalizeRelativePath(*asset.out, "output");
|
||||
}
|
||||
|
||||
manifest.assetsBySrc.emplace(asset.src, std::move(asset));
|
||||
const std::string source = asset.src;
|
||||
if (!manifest.assetsBySrc.emplace(source, std::move(asset)).second) {
|
||||
throw std::runtime_error("Duplicate manifest source path: " + source);
|
||||
}
|
||||
}
|
||||
|
||||
return manifest;
|
||||
|
||||
@@ -1,21 +1,64 @@
|
||||
#include <destrum/Graphics/BindlessSetManager.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <volk.h>
|
||||
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr std::uint32_t maxBindlessResources = 16536;
|
||||
constexpr std::uint32_t requestedMaxBindlessResources = 16536;
|
||||
constexpr std::uint32_t maxSamplers = 32;
|
||||
|
||||
constexpr std::uint32_t texturesBinding = 0;
|
||||
constexpr std::uint32_t samplersBinding = 1;
|
||||
}
|
||||
|
||||
void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
void BindlessSetManager::init(
|
||||
VkDevice device,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
float maxAnisotropy)
|
||||
{
|
||||
try {
|
||||
VkPhysicalDeviceDescriptorIndexingProperties indexingProperties{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES,
|
||||
};
|
||||
VkPhysicalDeviceMaintenance3Properties maintenanceProperties{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES,
|
||||
.pNext = &indexingProperties,
|
||||
};
|
||||
VkPhysicalDeviceProperties2 properties{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
.pNext = &maintenanceProperties,
|
||||
};
|
||||
vkGetPhysicalDeviceProperties2(physicalDevice, &properties);
|
||||
|
||||
if (indexingProperties.maxDescriptorSetUpdateAfterBindSamplers < maxSamplers ||
|
||||
indexingProperties.maxPerStageDescriptorUpdateAfterBindSamplers < maxSamplers) {
|
||||
throw std::runtime_error("The device exposes too few bindless samplers");
|
||||
}
|
||||
|
||||
const auto descriptorCapacityAfterSamplers = [](std::uint32_t capacity) {
|
||||
return capacity > maxSamplers ? capacity - maxSamplers : 0u;
|
||||
};
|
||||
const auto maxPerSetImages = descriptorCapacityAfterSamplers(
|
||||
maintenanceProperties.maxPerSetDescriptors);
|
||||
const auto maxAllPoolsImages = descriptorCapacityAfterSamplers(
|
||||
indexingProperties.maxUpdateAfterBindDescriptorsInAllPools);
|
||||
maxBindlessResources = std::min(
|
||||
requestedMaxBindlessResources,
|
||||
std::min({
|
||||
indexingProperties.maxDescriptorSetUpdateAfterBindSampledImages,
|
||||
indexingProperties.maxPerStageDescriptorUpdateAfterBindSampledImages,
|
||||
maxPerSetImages,
|
||||
maxAllPoolsImages,
|
||||
}));
|
||||
if (maxBindlessResources == 0) {
|
||||
throw std::runtime_error("The device exposes no bindless sampled-image capacity");
|
||||
}
|
||||
|
||||
{ // create pool
|
||||
const auto poolSizesBindless = std::array<VkDescriptorPoolSize, 2>{{
|
||||
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, maxBindlessResources},
|
||||
@@ -25,7 +68,7 @@ void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
const auto poolInfo = VkDescriptorPoolCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT,
|
||||
.maxSets = 10,
|
||||
.maxSets = 1,
|
||||
.poolSizeCount = static_cast<std::uint32_t>(poolSizesBindless.size()),
|
||||
.pPoolSizes = poolSizesBindless.data(),
|
||||
};
|
||||
@@ -78,17 +121,14 @@ void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
.pSetLayouts = &descSetLayout,
|
||||
};
|
||||
|
||||
std::uint32_t maxBinding = maxBindlessResources - 1;
|
||||
const auto countInfo = VkDescriptorSetVariableDescriptorCountAllocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO,
|
||||
.descriptorSetCount = 1,
|
||||
.pDescriptorCounts = &maxBinding,
|
||||
};
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(device, &allocInfo, &descSet));
|
||||
}
|
||||
|
||||
initDefaultSamplers(device, maxAnisotropy);
|
||||
initDefaultSamplers(device, maxAnisotropy);
|
||||
} catch (...) {
|
||||
cleanup(device);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotropy)
|
||||
@@ -96,7 +136,8 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
|
||||
// Keep in sync with bindless.glsl
|
||||
static const std::uint32_t nearestSamplerId = 0;
|
||||
static const std::uint32_t linearSamplerId = 1;
|
||||
static const std::uint32_t shadowSamplerId = 2;
|
||||
static const std::uint32_t anisotropicSamplerId = 2;
|
||||
static const std::uint32_t shadowSamplerId = 3;
|
||||
|
||||
{ // init nearest sampler
|
||||
const auto samplerCreateInfo = VkSamplerCreateInfo{
|
||||
@@ -115,15 +156,26 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
// TODO: make possible to disable anisotropy or set other values?
|
||||
.anisotropyEnable = VK_TRUE,
|
||||
.maxAnisotropy = maxAnisotropy,
|
||||
};
|
||||
VK_CHECK(vkCreateSampler(device, &samplerCreateInfo, nullptr, &linearSampler));
|
||||
vkutil::addDebugLabel(device, linearSampler, "linear");
|
||||
addSampler(device, linearSamplerId, linearSampler);
|
||||
}
|
||||
|
||||
{ // init anisotropic sampler
|
||||
const auto samplerCreateInfo = VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.anisotropyEnable = VK_TRUE,
|
||||
.maxAnisotropy = maxAnisotropy,
|
||||
};
|
||||
VK_CHECK(vkCreateSampler(device, &samplerCreateInfo, nullptr, &anisotropicSampler));
|
||||
vkutil::addDebugLabel(device, anisotropicSampler, "anisotropic");
|
||||
addSampler(device, anisotropicSamplerId, anisotropicSampler);
|
||||
}
|
||||
|
||||
{ // init shadow map sampler
|
||||
const auto samplerCreateInfo = VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
@@ -140,12 +192,47 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
|
||||
|
||||
void BindlessSetManager::cleanup(VkDevice device)
|
||||
{
|
||||
vkDestroySampler(device, nearestSampler, nullptr);
|
||||
vkDestroySampler(device, linearSampler, nullptr);
|
||||
vkDestroySampler(device, shadowMapSampler, nullptr);
|
||||
if (descPool != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(device, descPool, nullptr);
|
||||
}
|
||||
descPool = VK_NULL_HANDLE;
|
||||
}
|
||||
descSet = VK_NULL_HANDLE;
|
||||
|
||||
vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr);
|
||||
vkDestroyDescriptorPool(device, descPool, nullptr);
|
||||
if (descSetLayout != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr);
|
||||
}
|
||||
descSetLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (nearestSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, nearestSampler, nullptr);
|
||||
}
|
||||
nearestSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
if (linearSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, linearSampler, nullptr);
|
||||
}
|
||||
linearSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
if (anisotropicSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, anisotropicSampler, nullptr);
|
||||
}
|
||||
anisotropicSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
if (shadowMapSampler != VK_NULL_HANDLE) {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(device, shadowMapSampler, nullptr);
|
||||
}
|
||||
shadowMapSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
maxBindlessResources = 0;
|
||||
}
|
||||
|
||||
void BindlessSetManager::addImage(
|
||||
@@ -153,8 +240,12 @@ void BindlessSetManager::addImage(
|
||||
std::uint32_t id,
|
||||
const VkImageView imageView)
|
||||
{
|
||||
if (id >= maxBindlessResources) {
|
||||
throw std::out_of_range("Bindless image descriptor capacity exceeded");
|
||||
}
|
||||
|
||||
const auto imageInfo = VkDescriptorImageInfo{
|
||||
.imageView = imageView, .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};
|
||||
.imageView = imageView, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
const auto writeSet = VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descSet,
|
||||
@@ -170,7 +261,7 @@ void BindlessSetManager::addImage(
|
||||
void BindlessSetManager::addSampler(const VkDevice device, std::uint32_t id, VkSampler sampler)
|
||||
{
|
||||
const auto imageInfo =
|
||||
VkDescriptorImageInfo{.sampler = sampler, .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL};
|
||||
VkDescriptorImageInfo{.sampler = sampler, .imageLayout = VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
const auto writeSet = VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descSet,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
ImageCache::ImageCache(GfxDevice& gfxDevice) : gfxDevice(gfxDevice) {
|
||||
}
|
||||
@@ -36,44 +38,68 @@ ImageID ImageCache::loadImageFromFile(
|
||||
|
||||
auto image = std::move(imageOpt.value());
|
||||
|
||||
const auto id = getFreeImageId();
|
||||
|
||||
addImage(id, std::move(image));
|
||||
|
||||
loadedImagesInfo.emplace(
|
||||
id,
|
||||
LoadedImageInfo{
|
||||
.path = path,
|
||||
.intent = intent,
|
||||
.usage = usage,
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
|
||||
return id;
|
||||
ImageID id = NULL_IMAGE_ID;
|
||||
bool infoInserted = false;
|
||||
try {
|
||||
id = getFreeImageId();
|
||||
loadedImagesInfo.emplace(
|
||||
id,
|
||||
LoadedImageInfo{
|
||||
.path = path,
|
||||
.intent = intent,
|
||||
.usage = usage,
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
infoInserted = true;
|
||||
addImage(id, std::move(image));
|
||||
return id;
|
||||
} catch (...) {
|
||||
if (infoInserted) {
|
||||
loadedImagesInfo.erase(id);
|
||||
}
|
||||
gfxDevice.destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ImageID ImageCache::addImage(GPUImage image) {
|
||||
return addImage(getFreeImageId(), std::move(image));
|
||||
try {
|
||||
return addImage(getFreeImageId(), std::move(image));
|
||||
} catch (...) {
|
||||
gfxDevice.destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ImageID ImageCache::addImage(ImageID id, GPUImage image) {
|
||||
image.setBindlessId(static_cast<std::uint32_t>(id));
|
||||
|
||||
if (id < images.size()) {
|
||||
gfxDevice.destroyImage(images[id]);
|
||||
images[id] = std::move(image);
|
||||
} else {
|
||||
assert(id == images.size());
|
||||
images.push_back(std::move(image));
|
||||
if (id >= getMaxImageCount()) {
|
||||
throw std::out_of_range("ImageCache bindless capacity exhausted");
|
||||
}
|
||||
if (id > images.size()) {
|
||||
throw std::out_of_range("ImageCache image ID is not contiguous");
|
||||
}
|
||||
|
||||
bindlessSetManager.addImage(
|
||||
gfxDevice.getDevice(),
|
||||
id,
|
||||
images[id].imageView
|
||||
);
|
||||
try {
|
||||
image.setBindlessId(static_cast<std::uint32_t>(id));
|
||||
|
||||
return id;
|
||||
if (id < images.size()) {
|
||||
gfxDevice.destroyImage(images[id]);
|
||||
images[id] = std::move(image);
|
||||
} else {
|
||||
images.push_back(std::move(image));
|
||||
}
|
||||
|
||||
bindlessSetManager.addImage(
|
||||
gfxDevice.getDevice(),
|
||||
id,
|
||||
images[id].imageView
|
||||
);
|
||||
|
||||
return id;
|
||||
} catch (...) {
|
||||
gfxDevice.destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
const GPUImage& ImageCache::getImage(ImageID id) const {
|
||||
@@ -82,11 +108,17 @@ const GPUImage& ImageCache::getImage(ImageID id) const {
|
||||
}
|
||||
|
||||
ImageID ImageCache::getFreeImageId() const {
|
||||
return images.size();
|
||||
if (images.size() >= getMaxImageCount()) {
|
||||
throw std::out_of_range("ImageCache bindless capacity exhausted");
|
||||
}
|
||||
if (images.size() >= std::numeric_limits<ImageID>::max()) {
|
||||
throw std::out_of_range("ImageCache ID range exhausted");
|
||||
}
|
||||
return static_cast<ImageID>(images.size());
|
||||
}
|
||||
|
||||
void ImageCache::destroyImages() {
|
||||
for (const auto& image: images) {
|
||||
for (auto& image: images) {
|
||||
gfxDevice.destroyImage(image);
|
||||
}
|
||||
images.clear();
|
||||
|
||||
@@ -4,36 +4,58 @@
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
void MaterialCache::init(
|
||||
GfxDevice& gfxDevice,
|
||||
MaterialDefaultTextures defaults)
|
||||
{
|
||||
defaultTextures = defaults;
|
||||
try {
|
||||
defaultTextures = defaults;
|
||||
device = &gfxDevice;
|
||||
|
||||
materialDataBuffer = gfxDevice.createBuffer(
|
||||
MAX_MATERIALS * sizeof(MaterialData),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
materialDataBuffer = gfxDevice.createBuffer(
|
||||
MAX_MATERIALS * sizeof(MaterialData),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
vkutil::addDebugLabel(
|
||||
gfxDevice.getDevice(),
|
||||
materialDataBuffer.buffer,
|
||||
"material data");
|
||||
vkutil::addDebugLabel(
|
||||
gfxDevice.getDevice(),
|
||||
materialDataBuffer.buffer,
|
||||
"material data");
|
||||
|
||||
Material placeholderMaterial{};
|
||||
placeholderMaterial.name = "PLACEHOLDER_MATERIAL";
|
||||
placeholderMaterial.diffuseTexture = defaultTextures.white;
|
||||
Material placeholderMaterial{};
|
||||
placeholderMaterial.name = "PLACEHOLDER_MATERIAL";
|
||||
placeholderMaterial.diffuseTexture = defaultTextures.white;
|
||||
|
||||
placeholderMaterialId = addMaterial(placeholderMaterial);
|
||||
placeholderMaterialId = addMaterial(placeholderMaterial);
|
||||
gfxDevice.getMemoryManager().flushAllocation(materialDataBuffer);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialCache::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
gfxDevice.destroyBuffer(materialDataBuffer);
|
||||
materialDataBuffer = {};
|
||||
materials.clear();
|
||||
materialKeys.clear();
|
||||
placeholderMaterialId = NULL_MATERIAL_ID;
|
||||
device = nullptr;
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::addMaterial(Material material)
|
||||
{
|
||||
if (!materials.empty() && device != nullptr) {
|
||||
// The material buffer is shared by all in-flight frames. A newly
|
||||
// appended entry may be written while an older frame is reading it.
|
||||
device->waitIdle();
|
||||
}
|
||||
|
||||
const auto getTextureOrElse = [](ImageID imageId, ImageID placeholder)
|
||||
{
|
||||
return imageId != NULL_IMAGE_ID ? imageId : placeholder;
|
||||
@@ -42,7 +64,12 @@ MaterialID MaterialCache::addMaterial(Material material)
|
||||
MaterialData* data = static_cast<MaterialData*>(materialDataBuffer.info.pMappedData);
|
||||
|
||||
const auto id = getFreeMaterialId();
|
||||
assert(id < MAX_MATERIALS);
|
||||
if (id >= MAX_MATERIALS) {
|
||||
throw std::runtime_error("MaterialCache capacity exhausted");
|
||||
}
|
||||
if (data == nullptr) {
|
||||
throw std::runtime_error("MaterialCache material buffer is not mapped");
|
||||
}
|
||||
|
||||
data[id] = MaterialData{
|
||||
.baseColor = glm::vec4(material.baseColor, 1.0f),
|
||||
@@ -66,10 +93,30 @@ MaterialID MaterialCache::addMaterial(Material material)
|
||||
|
||||
.emissiveTex = defaultTextures.emissive,
|
||||
};
|
||||
device->getMemoryManager().flushAllocation(
|
||||
materialDataBuffer,
|
||||
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
|
||||
sizeof(MaterialData));
|
||||
|
||||
// The caller may update multiple materials in one frame; the range flush is
|
||||
// cheap for coherent allocations and required for non-coherent memory.
|
||||
// The cache does not own a device reference, so the renderer flushes the
|
||||
// exact range after batching updates.
|
||||
|
||||
const auto materialId = static_cast<MaterialID>(materials.size());
|
||||
std::string key = material.name.empty()
|
||||
? "material:" + std::to_string(materialId)
|
||||
: material.name;
|
||||
const std::string baseKey = key;
|
||||
std::size_t suffix = 1;
|
||||
while (std::find(materialKeys.begin(), materialKeys.end(), key) != materialKeys.end()) {
|
||||
key = baseKey + "#" + std::to_string(suffix++);
|
||||
}
|
||||
|
||||
materials.push_back(std::move(material));
|
||||
materialKeys.push_back(std::move(key));
|
||||
|
||||
return id;
|
||||
return materialId;
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::addSimpleTextureMaterial(ImageID textureID)
|
||||
@@ -88,9 +135,23 @@ const Material& MaterialCache::getMaterial(MaterialID id) const
|
||||
return materials.at(id);
|
||||
}
|
||||
|
||||
const std::string& MaterialCache::getMaterialKey(MaterialID id) const
|
||||
{
|
||||
return materialKeys.at(id);
|
||||
}
|
||||
|
||||
std::optional<MaterialID> MaterialCache::findMaterialByKey(std::string_view key) const
|
||||
{
|
||||
const auto it = std::find(materialKeys.begin(), materialKeys.end(), key);
|
||||
if (it == materialKeys.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<MaterialID>(std::distance(materialKeys.begin(), it));
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::getFreeMaterialId() const
|
||||
{
|
||||
return materials.size();
|
||||
return static_cast<MaterialID>(materials.size());
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::getPlaceholderMaterialId() const
|
||||
@@ -136,4 +197,8 @@ void MaterialCache::updateMaterialGPU(MaterialID id)
|
||||
.metallicRoughnessTex = defaultTextures.metallicRoughness,
|
||||
.emissiveTex = defaultTextures.emissive,
|
||||
};
|
||||
device->getMemoryManager().flushAllocation(
|
||||
materialDataBuffer,
|
||||
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
|
||||
sizeof(MaterialData));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "volk.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
@@ -12,15 +13,25 @@ ComputePipeline::ComputePipeline(GfxDevice& device,
|
||||
const std::string& compPath,
|
||||
const ComputePipelineConfigInfo& configInfo)
|
||||
: m_device(device) {
|
||||
CreateComputePipeline(compPath, configInfo);
|
||||
try {
|
||||
CreateComputePipeline(compPath, configInfo);
|
||||
} catch (...) {
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_compShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_computePipeline, nullptr);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ComputePipeline::~ComputePipeline() {
|
||||
if (m_compShaderModule != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_compShaderModule, nullptr);
|
||||
}
|
||||
if (m_computePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(m_device.getDevice(), m_computePipeline, nullptr);
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
if (m_compShaderModule != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_compShaderModule, nullptr);
|
||||
}
|
||||
if (m_computePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(m_device.getDevice(), m_computePipeline, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+123
-576
@@ -1,260 +1,158 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
#define VOLK_IMPLEMENTATION
|
||||
#include <volk.h>
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <filesystem>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Init.h>
|
||||
#include <destrum/Graphics/Pipelines/ImguiPass.h>
|
||||
|
||||
|
||||
#include "destrum/Graphics/imageLoader.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include "tracy/TracyVulkan.hpp"
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/TracyVulkan.hpp>
|
||||
|
||||
GfxDevice::GfxDevice() {
|
||||
}
|
||||
|
||||
GfxDevice::~GfxDevice() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync) {
|
||||
VK_CHECK(volkInitialize());
|
||||
m_vSync = vSync;
|
||||
instance = vkb::InstanceBuilder{}
|
||||
.set_app_name(appName.c_str())
|
||||
.set_app_version(1, 0, 0)
|
||||
.request_validation_layers()
|
||||
.use_default_debug_messenger()
|
||||
.require_api_version(1, 3, 0)
|
||||
.build()
|
||||
.value();
|
||||
|
||||
volkLoadInstance(instance);
|
||||
|
||||
const auto res = SDL_Vulkan_CreateSurface(window, instance, &surface);
|
||||
if (res != SDL_TRUE) {
|
||||
spdlog::error("Failed to create Vulkan surface: {}", SDL_GetError());
|
||||
std::exit(1);
|
||||
if (initialized) {
|
||||
throw std::logic_error("GfxDevice::init called twice");
|
||||
}
|
||||
if (window == nullptr) {
|
||||
throw std::invalid_argument("GfxDevice::init requires a valid window");
|
||||
}
|
||||
m_vSync = vSync;
|
||||
|
||||
constexpr auto deviceFeatures = VkPhysicalDeviceFeatures{
|
||||
.imageCubeArray = VK_TRUE,
|
||||
.geometryShader = VK_TRUE, // for im3d
|
||||
.depthClamp = VK_TRUE,
|
||||
.fillModeNonSolid = VK_TRUE,
|
||||
.samplerAnisotropy = VK_TRUE
|
||||
};
|
||||
instanceManager.init(window, appName);
|
||||
|
||||
constexpr auto features12 = VkPhysicalDeviceVulkan12Features{
|
||||
.descriptorIndexing = true,
|
||||
.descriptorBindingSampledImageUpdateAfterBind = true,
|
||||
.descriptorBindingStorageImageUpdateAfterBind = true,
|
||||
.descriptorBindingPartiallyBound = true,
|
||||
.descriptorBindingVariableDescriptorCount = true,
|
||||
.runtimeDescriptorArray = true,
|
||||
.scalarBlockLayout = true,
|
||||
.bufferDeviceAddress = true,
|
||||
};
|
||||
constexpr auto features13 = VkPhysicalDeviceVulkan13Features{
|
||||
.synchronization2 = true,
|
||||
.dynamicRendering = true,
|
||||
};
|
||||
const auto extendedDynamicState3Features =
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
.extendedDynamicState3PolygonMode = VK_TRUE,
|
||||
};
|
||||
memoryManager.init(instanceManager);
|
||||
|
||||
physicalDevice = vkb::PhysicalDeviceSelector{instance}
|
||||
.set_minimum_version(1, 3)
|
||||
.set_required_features(deviceFeatures)
|
||||
.set_required_features_12(features12)
|
||||
.set_required_features_13(features13)
|
||||
.add_required_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
|
||||
.add_required_extension_features(extendedDynamicState3Features)
|
||||
.set_surface(surface)
|
||||
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
||||
.select()
|
||||
.value();
|
||||
executor.init(instanceManager.getDevice(), instanceManager.getGraphicsQueueFamily(), instanceManager.getGraphicsQueue());
|
||||
|
||||
device = vkb::DeviceBuilder{physicalDevice}.build().value();
|
||||
volkLoadDevice(device);
|
||||
frameManager.init(instanceManager.getDevice(), instanceManager.getGraphicsQueueFamily());
|
||||
|
||||
#if defined(TRACY_ENABLE)
|
||||
instanceManager.initTracy(executor.getCommandBuffer());
|
||||
#endif
|
||||
|
||||
graphicsQueueFamily = device.get_queue_index(vkb::QueueType::graphics).value();
|
||||
graphicsQueue = device.get_queue(vkb::QueueType::graphics).value();
|
||||
|
||||
//Vma
|
||||
const auto vulkanFunctions = VmaVulkanFunctions{
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
};
|
||||
|
||||
const auto allocatorInfo = VmaAllocatorCreateInfo{
|
||||
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
|
||||
.physicalDevice = physicalDevice,
|
||||
.device = device,
|
||||
.pVulkanFunctions = &vulkanFunctions,
|
||||
.instance = instance,
|
||||
};
|
||||
vmaCreateAllocator(&allocatorInfo, &allocator);
|
||||
|
||||
executor.init(device, graphicsQueueFamily, graphicsQueue);
|
||||
|
||||
imageManager.init(
|
||||
instanceManager.getDevice(),
|
||||
instanceManager.getPhysicalDevice(),
|
||||
memoryManager,
|
||||
executor);
|
||||
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
swapchainFormat = VK_FORMAT_B8G8R8A8_SRGB;
|
||||
swapchain.createSwapchain(this, swapchainFormat, w, h, vSync);
|
||||
|
||||
VkPhysicalDeviceProperties props{};
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &props);
|
||||
swapchain.createSwapchain(
|
||||
instanceManager.getDevice(),
|
||||
instanceManager.getVkbDevice(),
|
||||
instanceManager.getSurface(),
|
||||
swapchainFormat,
|
||||
static_cast<std::uint32_t>(w),
|
||||
static_cast<std::uint32_t>(h),
|
||||
vSync);
|
||||
|
||||
// imageCache.bindlessSetManager.init(device, props.limits.maxSamplerAnisotropy);
|
||||
swapchainFormat = swapchain.getFormat();
|
||||
|
||||
swapchain.initSync(device);
|
||||
|
||||
const auto poolCreateInfo = vkinit::commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||
|
||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
auto& commandPool = frames[i].commandPool;
|
||||
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool));
|
||||
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
|
||||
auto& mainCommandBuffer = frames[i].commandBuffer;
|
||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
||||
}
|
||||
|
||||
#if defined(TRACY_ENABLE)
|
||||
{
|
||||
VkCommandBuffer tracyInitCmd = frames[0].commandBuffer;
|
||||
|
||||
#if defined(TRACY_VK_USE_SYMBOL_TABLE)
|
||||
tracyVkCtx = TracyVkContext(
|
||||
instance,
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd,
|
||||
vkGetInstanceProcAddr,
|
||||
vkGetDeviceProcAddr
|
||||
);
|
||||
#else
|
||||
tracyVkCtx = TracyVkContext(
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd
|
||||
);
|
||||
#endif
|
||||
|
||||
static constexpr char ctxName[] = "Graphics Queue";
|
||||
TracyVkContextName(tracyVkCtx, ctxName, sizeof(ctxName) - 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// { // create white texture
|
||||
// std::uint32_t pixel = 0xFFFFFFFF;
|
||||
// whiteImageId = createImage(
|
||||
// {
|
||||
// .format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
// .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
// .extent = VkExtent3D{1, 1, 1},
|
||||
// },
|
||||
// "white texture",
|
||||
// &pixel);
|
||||
// }
|
||||
//
|
||||
// { // create error texture (black/magenta checker)
|
||||
// constexpr auto black = 0xFF000000;
|
||||
// constexpr auto magenta = 0xFFFF00FF;
|
||||
//
|
||||
// std::array<std::uint32_t, 4> pixels{black, magenta, magenta, black};
|
||||
// errorImageId = createImage(
|
||||
// {
|
||||
// .format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
// .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
// VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
|
||||
// .extent = VkExtent3D{2, 2, 1},
|
||||
// },
|
||||
// "error texture",
|
||||
// pixels.data());
|
||||
// imageCache.setErrorImageId(errorImageId);
|
||||
// }
|
||||
swapchain.initSync(instanceManager.getDevice());
|
||||
|
||||
GameState::GetInstance().SetGfxDevice(this);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void GfxDevice::cleanup() {
|
||||
if (!initialized &&
|
||||
instanceManager.getDevice() == VK_NULL_HANDLE &&
|
||||
instanceManager.getInstance() == VK_NULL_HANDLE &&
|
||||
memoryManager.getAllocator() == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (instanceManager.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(instanceManager.getDevice());
|
||||
}
|
||||
|
||||
swapchain.cleanup(instanceManager.getDevice());
|
||||
frameManager.cleanup(instanceManager.getDevice());
|
||||
executor.cleanup(instanceManager.getDevice());
|
||||
memoryManager.cleanup(instanceManager.getDevice());
|
||||
instanceManager.cleanup();
|
||||
|
||||
GameState::GetInstance().SetGfxDevice(nullptr);
|
||||
frameActive = false;
|
||||
swapchainFormat = VK_FORMAT_UNDEFINED;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void GfxDevice::recreateSwapchain(int width, int height) {
|
||||
assert(width != 0 && height != 0);
|
||||
if (!initialized) {
|
||||
throw std::logic_error("GfxDevice::recreateSwapchain called before init");
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw std::invalid_argument("GfxDevice::recreateSwapchain requires a non-zero extent");
|
||||
}
|
||||
waitIdle();
|
||||
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, m_vSync);
|
||||
|
||||
swapchain.recreateSwapchain(
|
||||
instanceManager.getDevice(),
|
||||
instanceManager.getVkbDevice(),
|
||||
instanceManager.getSurface(),
|
||||
swapchainFormat,
|
||||
static_cast<std::uint32_t>(width),
|
||||
static_cast<std::uint32_t>(height),
|
||||
m_vSync);
|
||||
|
||||
swapchainFormat = swapchain.getFormat();
|
||||
}
|
||||
|
||||
VkCommandBuffer GfxDevice::beginFrame()
|
||||
{
|
||||
ZoneScopedN("GfxDevice::beginFrame");
|
||||
|
||||
const auto frameIndex = getCurrentFrameIndex();
|
||||
|
||||
{
|
||||
ZoneScopedN("Swapchain BeginFrame");
|
||||
swapchain.beginFrame(getCurrentFrameIndex());
|
||||
swapchain.beginFrame(instanceManager.getDevice(), frameIndex);
|
||||
}
|
||||
|
||||
const auto& frame = getCurrentFrame();
|
||||
const auto& cmd = frame.commandBuffer;
|
||||
|
||||
const auto cmdBeginInfo = VkCommandBufferBeginInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
|
||||
{
|
||||
ZoneScopedN("vkBeginCommandBuffer");
|
||||
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
|
||||
const auto acquire = swapchain.acquireNextImage(instanceManager.getDevice(), frameIndex);
|
||||
if (!acquire.hasImage()) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
return cmd;
|
||||
activeFrameIndex = frameIndex;
|
||||
activeSwapchainImageIndex = acquire.imageIndex;
|
||||
frameActive = true;
|
||||
|
||||
return frameManager.beginFrame();
|
||||
}
|
||||
|
||||
VulkanImmediateExecutor& GfxDevice::GetImmediateExecuter() {
|
||||
VulkanImmediateExecutor& GfxDevice::getImmediateExecuter() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const EndFrameProps& props) {
|
||||
ZoneScopedN("GfxDevice::endFrame");
|
||||
|
||||
// get swapchain image
|
||||
VkImage swapchainImage = VK_NULL_HANDLE;
|
||||
std::uint32_t swapchainImageIndex = 0;
|
||||
|
||||
{
|
||||
ZoneScopedN("Swapchain AcquireNextImage");
|
||||
|
||||
const auto result = swapchain.acquireNextImage(getCurrentFrameIndex());
|
||||
swapchainImage = result.first;
|
||||
swapchainImageIndex = result.second;
|
||||
if (!frameActive || cmd == VK_NULL_HANDLE) {
|
||||
throw std::logic_error("GfxDevice::endFrame called without an active frame");
|
||||
}
|
||||
|
||||
if (swapchainImage == VK_NULL_HANDLE) {
|
||||
spdlog::info("Swapchain is freaky, skipping frame...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fences are reset here to prevent the deadlock in case swapchain becomes dirty
|
||||
{
|
||||
ZoneScopedN("Swapchain ResetFences");
|
||||
swapchain.resetFences(getCurrentFrameIndex());
|
||||
}
|
||||
const VkImage swapchainImage = swapchain.getImages().at(activeSwapchainImageIndex);
|
||||
const auto swapchainImageIndex = activeSwapchainImageIndex;
|
||||
|
||||
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED; {
|
||||
ZoneScopedN("Clear Swapchain Image");
|
||||
@@ -270,45 +168,26 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
||||
if (true) {
|
||||
ZoneScopedN("Copy DrawImage To Swapchain");
|
||||
|
||||
// copy from draw image into swapchain
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
|
||||
drawImage.layout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
drawImage.setLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
vkutil::transitionImage(
|
||||
cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
swapchainLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
|
||||
auto filter = false ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
||||
filter = VK_FILTER_NEAREST;
|
||||
if (false) {
|
||||
vkutil::copyImageToImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
swapchainImage,
|
||||
drawImage.getExtent2D(),
|
||||
props.drawImageBlitRect.x,
|
||||
props.drawImageBlitRect.y,
|
||||
props.drawImageBlitRect.z,
|
||||
props.drawImageBlitRect.w,
|
||||
filter);
|
||||
} else {
|
||||
// will stretch image to swapchain
|
||||
vkutil::copyImageToImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
swapchainImage,
|
||||
drawImage.getExtent2D(),
|
||||
getSwapchainExtent(),
|
||||
filter);
|
||||
}
|
||||
auto filter = VK_FILTER_NEAREST;
|
||||
vkutil::copyImageToImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
swapchainImage,
|
||||
drawImage.getExtent2D(),
|
||||
getSwapchainExtent(),
|
||||
filter);
|
||||
}
|
||||
|
||||
// prepare for present
|
||||
// vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
// swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
if (props.imguiPass) {
|
||||
ZoneScopedN("ImGui Render");
|
||||
|
||||
@@ -321,7 +200,6 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
||||
);
|
||||
}
|
||||
|
||||
// prepare for present
|
||||
{
|
||||
ZoneScopedN("Transition Swapchain To Present");
|
||||
|
||||
@@ -329,369 +207,38 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
||||
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
}
|
||||
#if defined(TRACY_ENABLE)
|
||||
TracyVkCollect(tracyVkCtx, cmd);
|
||||
TracyVkCollect(instanceManager.getTracyVkCtx(), cmd);
|
||||
#endif
|
||||
{
|
||||
ZoneScopedN("vkEndCommandBuffer");
|
||||
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||
}
|
||||
|
||||
// swapchain.submitAndPresent(cmd, graphicsQueue, getCurrentFrameIndex(), swapchainImageIndex);
|
||||
frameManager.endFrame(cmd);
|
||||
|
||||
{
|
||||
ZoneScopedN("Swapchain SubmitAndPresent");
|
||||
swapchain.submitAndPresent(cmd, graphicsQueue, swapchainImageIndex, getCurrentFrameIndex());
|
||||
try {
|
||||
swapchain.submitAndPresent(
|
||||
instanceManager.getDevice(),
|
||||
cmd,
|
||||
instanceManager.getGraphicsQueue(),
|
||||
instanceManager.getPresentQueue(),
|
||||
swapchainImageIndex,
|
||||
activeFrameIndex);
|
||||
} catch (...) {
|
||||
frameManager.nextFrame();
|
||||
frameActive = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
frameNumber++;
|
||||
frameManager.nextFrame();
|
||||
frameActive = false;
|
||||
|
||||
FrameMark;
|
||||
}
|
||||
|
||||
void GfxDevice::cleanup() {
|
||||
#if defined(TRACY_ENABLE)
|
||||
if (tracyVkCtx) {
|
||||
TracyVkDestroy(tracyVkCtx);
|
||||
tracyVkCtx = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GfxDevice::waitIdle() {
|
||||
VK_CHECK(vkDeviceWaitIdle(device));
|
||||
frameManager.waitIdle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GfxDevice::immediateSubmit(ImmediateExecuteFunction&& f) const {
|
||||
executor.immediateSubmit(std::move(f));
|
||||
}
|
||||
|
||||
GPUBuffer GfxDevice::createBuffer(
|
||||
std::size_t allocSize,
|
||||
VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage) const {
|
||||
const auto bufferInfo = VkBufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = allocSize,
|
||||
.usage = usage,
|
||||
};
|
||||
|
||||
const auto allocInfo = VmaAllocationCreateInfo{
|
||||
.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
||||
// TODO: allow to set VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT when needed
|
||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
|
||||
.usage = memoryUsage,
|
||||
};
|
||||
|
||||
GPUBuffer buffer{};
|
||||
VK_CHECK(vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer.buffer, &buffer.allocation, &buffer.info));
|
||||
if ((usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0) {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
buffer.address = vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
VkDeviceAddress GfxDevice::getBufferAddress(const GPUBuffer& buffer) const {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
return vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
void GfxDevice::destroyBuffer(const GPUBuffer& buffer) const {
|
||||
vmaDestroyBuffer(allocator, buffer.buffer, buffer.allocation);
|
||||
}
|
||||
|
||||
//
|
||||
// GPUImage GfxDevice::loadImageFromFileRaw(
|
||||
// const std::filesystem::path& path,
|
||||
// VkFormat format,
|
||||
// VkImageUsageFlags usage,
|
||||
// bool mipMap) const
|
||||
// {
|
||||
// auto data = util::loadImage(path);
|
||||
// if (!data.pixels) {
|
||||
// fmt::println("[error] failed to load image from '{}'", path.string());
|
||||
// return getImage(errorImageId);
|
||||
// }
|
||||
//
|
||||
// auto image = createImageRaw({
|
||||
// .format = format,
|
||||
// .usage = usage | //
|
||||
// VK_IMAGE_USAGE_TRANSFER_DST_BIT | // for uploading pixel data to image
|
||||
// VK_IMAGE_USAGE_TRANSFER_SRC_BIT, // for generating mips
|
||||
// .extent =
|
||||
// VkExtent3D{
|
||||
// .width = (std::uint32_t)data.width,
|
||||
// .height = (std::uint32_t)data.height,
|
||||
// .depth = 1,
|
||||
// },
|
||||
// .mipMap = mipMap,
|
||||
// });
|
||||
// uploadImageData(image, data.pixels);
|
||||
//
|
||||
// image.debugName = path.string();
|
||||
// vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
//
|
||||
// return image;
|
||||
// }
|
||||
|
||||
|
||||
GPUImage GfxDevice::createImageRaw(
|
||||
const vkutil::CreateImageInfo& createInfo,
|
||||
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo) const {
|
||||
std::uint32_t mipLevels = 1;
|
||||
if (createInfo.mipMap) {
|
||||
const auto maxExtent = std::max(createInfo.extent.width, createInfo.extent.height);
|
||||
mipLevels = (std::uint32_t)std::floor(std::log2(maxExtent)) + 1;
|
||||
}
|
||||
|
||||
if (createInfo.isCubemap) {
|
||||
assert(createInfo.numLayers % 6 == 0);
|
||||
// assert(!createInfo.mipMap);
|
||||
assert((createInfo.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) != 0);
|
||||
}
|
||||
|
||||
auto imgInfo = VkImageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.flags = createInfo.flags,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = createInfo.format,
|
||||
.extent = createInfo.extent,
|
||||
.mipLevels = mipLevels,
|
||||
.arrayLayers = createInfo.numLayers,
|
||||
.samples = createInfo.samples,
|
||||
.tiling = createInfo.tiling,
|
||||
.usage = createInfo.usage,
|
||||
};
|
||||
|
||||
static const auto defaultAllocInfo = VmaAllocationCreateInfo{
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
};
|
||||
const auto allocInfo = customAllocationCreateInfo.has_value() ? customAllocationCreateInfo.value() : defaultAllocInfo;
|
||||
|
||||
GPUImage image{};
|
||||
image.format = createInfo.format;
|
||||
image.usage = createInfo.usage;
|
||||
image.extent = createInfo.extent;
|
||||
image.mipLevels = mipLevels;
|
||||
image.numLayers = createInfo.numLayers;
|
||||
image.isCubemap = createInfo.isCubemap;
|
||||
|
||||
VK_CHECK(vmaCreateImage(allocator, &imgInfo, &allocInfo, &image.image, &image.allocation, nullptr));
|
||||
|
||||
// create view only when usage flags allow it
|
||||
bool shouldCreateView = ((createInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0);
|
||||
|
||||
if (shouldCreateView) {
|
||||
VkImageAspectFlags aspectFlag = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
if (createInfo.format == VK_FORMAT_D32_SFLOAT) {
|
||||
// TODO: support other depth formats
|
||||
aspectFlag = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
|
||||
auto viewType =
|
||||
createInfo.numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
if (createInfo.isCubemap && createInfo.numLayers == 6) {
|
||||
viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
}
|
||||
|
||||
const auto viewCreateInfo = VkImageViewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image.image,
|
||||
.viewType = viewType,
|
||||
.format = createInfo.format,
|
||||
.subresourceRange =
|
||||
VkImageSubresourceRange{
|
||||
.aspectMask = aspectFlag,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = mipLevels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = createInfo.numLayers,
|
||||
},
|
||||
};
|
||||
|
||||
VK_CHECK(vkCreateImageView(device, &viewCreateInfo, nullptr, &image.imageView));
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::optional<GPUImage> GfxDevice::loadImageFromFileRaw(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent) const
|
||||
{
|
||||
const auto data = util::loadImage(path, intent);
|
||||
|
||||
if (data.vkFormat == VK_FORMAT_UNDEFINED ||
|
||||
data.byteSize == 0 ||
|
||||
(data.hdr ? data.hdrPixels == nullptr : data.pixels == nullptr))
|
||||
{
|
||||
spdlog::error("Failed to load image '{}'", path.string());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto image = createImageRaw({
|
||||
.format = data.vkFormat,
|
||||
.usage = usage |
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
(mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
||||
.extent = VkExtent3D{
|
||||
.width = static_cast<std::uint32_t>(data.width),
|
||||
.height = static_cast<std::uint32_t>(data.height),
|
||||
.depth = 1,
|
||||
},
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
|
||||
const void* src =
|
||||
data.hdr
|
||||
? static_cast<const void*>(data.hdrPixels)
|
||||
: static_cast<const void*>(data.pixels);
|
||||
|
||||
uploadImageDataSized(image, src, data.byteSize, 0);
|
||||
|
||||
image.debugName = path.string();
|
||||
vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// void GfxDevice::uploadImageData(const GPUImage& image, void* pixelData, std::uint32_t layer) const {
|
||||
// VkDeviceSize dataSize =
|
||||
// VkDeviceSize(image.extent.depth) *
|
||||
// image.extent.width *
|
||||
// image.extent.height *
|
||||
// BytesPerTexel(image.format);
|
||||
//
|
||||
// auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
// memcpy(uploadBuffer.info.pMappedData, pixelData, size_t(dataSize));
|
||||
//
|
||||
// executor.immediateSubmit([&] (VkCommandBuffer cmd) {
|
||||
// assert(
|
||||
// (image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
|
||||
// "Image needs to have VK_IMAGE_USAGE_TRANSFER_DST_BIT to upload data to it");
|
||||
// vkutil::transitionImage(
|
||||
// cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
//
|
||||
// const auto copyRegion = VkBufferImageCopy{
|
||||
// .bufferOffset = 0,
|
||||
// .bufferRowLength = 0,
|
||||
// .bufferImageHeight = 0,
|
||||
// .imageSubresource =
|
||||
// {
|
||||
// .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
// .mipLevel = 0,
|
||||
// .baseArrayLayer = layer,
|
||||
// .layerCount = 1,
|
||||
// },
|
||||
// .imageExtent = image.extent,
|
||||
// };
|
||||
//
|
||||
// vkCmdCopyBufferToImage(
|
||||
// cmd,
|
||||
// uploadBuffer.buffer,
|
||||
// image.image,
|
||||
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
// 1,
|
||||
// ©Region);
|
||||
//
|
||||
// if (image.mipLevels > 1) {
|
||||
// assert(
|
||||
// (image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
|
||||
// (image.usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0 &&
|
||||
// "Image needs to have VK_IMAGE_USAGE_TRANSFER_{DST,SRC}_BIT to generate mip maps");
|
||||
// // graphics::generateMipmaps(
|
||||
// // cmd,
|
||||
// // image.image,
|
||||
// // VkExtent2D{image.extent.width, image.extent.height},
|
||||
// // image.mipLevels);
|
||||
// spdlog::warn("Yea dawg, i ain't written this yet :pray:");
|
||||
// } else {
|
||||
// vkutil::transitionImage(
|
||||
// cmd,
|
||||
// image.image,
|
||||
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// destroyBuffer(uploadBuffer);
|
||||
// }
|
||||
|
||||
void GfxDevice::uploadImageDataSized(const GPUImage& image, const void* pixelData, std::size_t byteSize, std::uint32_t layer) const {
|
||||
auto uploadBuffer = createBuffer(byteSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// safety checks
|
||||
assert(uploadBuffer.info.pMappedData);
|
||||
assert(pixelData);
|
||||
assert(byteSize > 0);
|
||||
|
||||
std::memcpy(uploadBuffer.info.pMappedData, pixelData, byteSize);
|
||||
|
||||
executor.immediateSubmit([&] (VkCommandBuffer cmd) {
|
||||
vkutil::transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
VkBufferImageCopy copyRegion{};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copyRegion.imageSubresource.mipLevel = 0;
|
||||
copyRegion.imageSubresource.baseArrayLayer = layer;
|
||||
copyRegion.imageSubresource.layerCount = 1;
|
||||
copyRegion.imageExtent = image.extent;
|
||||
|
||||
vkCmdCopyBufferToImage(cmd, uploadBuffer.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region);
|
||||
|
||||
vkutil::transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
});
|
||||
|
||||
destroyBuffer(uploadBuffer);
|
||||
}
|
||||
|
||||
// GPUImage GfxDevice::loadImageFromFileRaw(const std::filesystem::path& path, VkImageUsageFlags usage, bool mipMap) const {
|
||||
// const auto data = util::loadImage(path);
|
||||
// const bool isHdr = data.hdr && data.hdrPixels;
|
||||
// const bool isLdr = !data.hdr && data.pixels;
|
||||
//
|
||||
// if (!isHdr && !isLdr || data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0) {
|
||||
// spdlog::error("failed to load image from '{}'", path.string());
|
||||
// return getImage(errorImageId);
|
||||
// }
|
||||
//
|
||||
// auto image = createImageRaw({
|
||||
// .format = data.vkFormat,
|
||||
// .usage = usage |
|
||||
// VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
// (mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
||||
// .extent = VkExtent3D{
|
||||
// .width = (std::uint32_t)data.width,
|
||||
// .height = (std::uint32_t)data.height,
|
||||
// .depth = 1,
|
||||
// },
|
||||
// .mipMap = mipMap,
|
||||
// });
|
||||
//
|
||||
// const void* src = isHdr ? (const void*)data.hdrPixels : (const void*)data.pixels;
|
||||
// uploadImageDataSized(image, src, data.byteSize, 0);
|
||||
//
|
||||
// image.debugName = path.string();
|
||||
// vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
// return image;
|
||||
// }
|
||||
|
||||
void GfxDevice::destroyImage(const GPUImage& image) const {
|
||||
vkDestroyImageView(device, image.imageView, nullptr);
|
||||
vmaDestroyImage(allocator, image.image, image.allocation);
|
||||
// TODO: if image has bindless id, update the set
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <destrum/Graphics/ImmediateExecuter.h>
|
||||
|
||||
#include <volk.h>
|
||||
@@ -14,36 +15,61 @@ constexpr auto NO_TIMEOUT = std::numeric_limits<std::uint64_t>::max();
|
||||
}
|
||||
|
||||
void VulkanImmediateExecutor::init(
|
||||
VkDevice device,
|
||||
VkDevice deviceHandle,
|
||||
std::uint32_t graphicsQueueFamily,
|
||||
VkQueue graphicsQueue)
|
||||
VkQueue graphicsQueueHandle)
|
||||
{
|
||||
assert(!initialized);
|
||||
if (initialized) {
|
||||
throw std::logic_error("VulkanImmediateExecutor::init called twice");
|
||||
}
|
||||
|
||||
this->device = device;
|
||||
this->graphicsQueue = graphicsQueue;
|
||||
this->device = deviceHandle;
|
||||
this->graphicsQueue = graphicsQueueHandle;
|
||||
|
||||
const auto poolCreateInfo = vkinit::
|
||||
commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &immCommandPool));
|
||||
try {
|
||||
const auto poolCreateInfo = vkinit::
|
||||
commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||
VK_CHECK(vkCreateCommandPool(deviceHandle, &poolCreateInfo, nullptr, &immCommandPool));
|
||||
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(immCommandPool, 1);
|
||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &immCommandBuffer));
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(immCommandPool, 1);
|
||||
VK_CHECK(vkAllocateCommandBuffers(deviceHandle, &cmdAllocInfo, &immCommandBuffer));
|
||||
|
||||
constexpr auto fenceCreateInfo = VkFenceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &immFence));
|
||||
constexpr auto fenceCreateInfo = VkFenceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
VK_CHECK(vkCreateFence(deviceHandle, &fenceCreateInfo, nullptr, &immFence));
|
||||
|
||||
initialized = true;
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup(deviceHandle);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanImmediateExecutor::cleanup(VkDevice device)
|
||||
void VulkanImmediateExecutor::cleanup(VkDevice deviceHandle)
|
||||
{
|
||||
assert(initialized);
|
||||
vkDestroyCommandPool(device, immCommandPool, nullptr);
|
||||
vkDestroyFence(device, immFence, nullptr);
|
||||
if (deviceHandle == VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
deviceHandle = device;
|
||||
}
|
||||
|
||||
if (deviceHandle == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (immCommandPool != VK_NULL_HANDLE) {
|
||||
vkDestroyCommandPool(deviceHandle, immCommandPool, nullptr);
|
||||
}
|
||||
if (immFence != VK_NULL_HANDLE) {
|
||||
vkDestroyFence(deviceHandle, immFence, nullptr);
|
||||
}
|
||||
|
||||
immCommandPool = VK_NULL_HANDLE;
|
||||
immCommandBuffer = VK_NULL_HANDLE;
|
||||
immFence = VK_NULL_HANDLE;
|
||||
this->device = VK_NULL_HANDLE;
|
||||
graphicsQueue = VK_NULL_HANDLE;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void VulkanImmediateExecutor::immediateSubmit(
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <destrum/Graphics/Managers/FrameManager.h>
|
||||
|
||||
#include <volk.h>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#include <destrum/Graphics/Init.h>
|
||||
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
FrameManager::~FrameManager() {
|
||||
}
|
||||
|
||||
void FrameManager::init(VkDevice dev, std::uint32_t queueFamily) {
|
||||
device = dev;
|
||||
|
||||
try {
|
||||
const auto poolCreateInfo = vkinit::commandPoolCreateInfo(
|
||||
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
queueFamily);
|
||||
|
||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
auto& commandPool = frames[i].commandPool;
|
||||
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool));
|
||||
|
||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
|
||||
auto& mainCommandBuffer = frames[i].commandBuffer;
|
||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
||||
}
|
||||
} catch (...) {
|
||||
cleanup(dev);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameManager::cleanup(VkDevice dev) {
|
||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
if (frames[i].commandPool != VK_NULL_HANDLE && dev != VK_NULL_HANDLE) {
|
||||
vkDestroyCommandPool(dev, frames[i].commandPool, nullptr);
|
||||
}
|
||||
frames[i].commandPool = VK_NULL_HANDLE;
|
||||
frames[i].commandBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
frameNumber = 0;
|
||||
device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkCommandBuffer FrameManager::beginFrame() {
|
||||
ZoneScopedN("FrameManager::beginFrame");
|
||||
|
||||
const auto& frame = getCurrentFrame();
|
||||
const auto& cmd = frame.commandBuffer;
|
||||
|
||||
const auto cmdBeginInfo = VkCommandBufferBeginInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
|
||||
{
|
||||
ZoneScopedN("vkBeginCommandBuffer");
|
||||
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void FrameManager::endFrame(VkCommandBuffer cmd) {
|
||||
ZoneScopedN("FrameManager::endFrame");
|
||||
|
||||
{
|
||||
ZoneScopedN("vkEndCommandBuffer");
|
||||
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FrameManager::nextFrame() {
|
||||
frameNumber++;
|
||||
FrameMark;
|
||||
}
|
||||
|
||||
void FrameManager::waitIdle() const {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
VK_CHECK(vkDeviceWaitIdle(device));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
#include <destrum/Graphics/Managers/ImageManager.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <volk.h>
|
||||
|
||||
#include <destrum/Graphics/ImmediateExecuter.h>
|
||||
#include <destrum/Graphics/Managers/MemoryManager.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
#include <destrum/Graphics/imageLoader.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
ImageManager::~ImageManager()
|
||||
{
|
||||
}
|
||||
|
||||
void ImageManager::init(
|
||||
VkDevice dev,
|
||||
VkPhysicalDevice physicalDev,
|
||||
const MemoryManager& memoryManagerRef,
|
||||
VulkanImmediateExecutor& exec)
|
||||
{
|
||||
device = dev;
|
||||
physicalDevice = physicalDev;
|
||||
this->memoryManager = &memoryManagerRef;
|
||||
executor = &exec;
|
||||
}
|
||||
|
||||
GPUImage ImageManager::createImage(
|
||||
const vkutil::CreateImageInfo& createInfo,
|
||||
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo) const
|
||||
{
|
||||
if (createInfo.extent.width == 0 || createInfo.extent.height == 0 ||
|
||||
createInfo.extent.depth == 0 || createInfo.numLayers == 0) {
|
||||
throw std::invalid_argument("Cannot create an empty Vulkan image");
|
||||
}
|
||||
|
||||
std::uint32_t mipLevels = 1;
|
||||
if (createInfo.mipMap)
|
||||
{
|
||||
const auto maxExtent = std::max(createInfo.extent.width, createInfo.extent.height);
|
||||
mipLevels = static_cast<std::uint32_t>(std::floor(std::log2(maxExtent))) + 1;
|
||||
}
|
||||
|
||||
if (createInfo.isCubemap)
|
||||
{
|
||||
assert(createInfo.numLayers % 6 == 0);
|
||||
assert((createInfo.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) != 0);
|
||||
}
|
||||
|
||||
auto imgInfo = VkImageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.flags = createInfo.flags,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = createInfo.format,
|
||||
.extent = createInfo.extent,
|
||||
.mipLevels = mipLevels,
|
||||
.arrayLayers = createInfo.numLayers,
|
||||
.samples = createInfo.samples,
|
||||
.tiling = createInfo.tiling,
|
||||
.usage = createInfo.usage,
|
||||
};
|
||||
|
||||
static const auto defaultAllocInfo = VmaAllocationCreateInfo{
|
||||
.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
};
|
||||
const auto allocInfo = customAllocationCreateInfo.has_value()
|
||||
? customAllocationCreateInfo.value()
|
||||
: defaultAllocInfo;
|
||||
|
||||
GPUImage image{};
|
||||
image.format = createInfo.format;
|
||||
image.usage = createInfo.usage;
|
||||
image.extent = createInfo.extent;
|
||||
image.mipLevels = mipLevels;
|
||||
image.numLayers = createInfo.numLayers;
|
||||
image.isCubemap = createInfo.isCubemap;
|
||||
image.layerLayouts.assign(createInfo.numLayers, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
|
||||
try {
|
||||
VK_CHECK(
|
||||
vmaCreateImage(memoryManager->getAllocator(), &imgInfo, &allocInfo, &image.image, &image.allocation, nullptr));
|
||||
|
||||
const bool shouldCreateView = ((createInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0) ||
|
||||
((createInfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0);
|
||||
|
||||
if (shouldCreateView) {
|
||||
VkImageAspectFlags aspectFlag = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
if (createInfo.format == VK_FORMAT_D32_SFLOAT) {
|
||||
aspectFlag = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
|
||||
auto viewType = createInfo.numLayers == 1
|
||||
? VK_IMAGE_VIEW_TYPE_2D
|
||||
: VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
if (createInfo.isCubemap && createInfo.numLayers == 6) {
|
||||
viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
}
|
||||
|
||||
const auto viewCreateInfo = VkImageViewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image.image,
|
||||
.viewType = viewType,
|
||||
.format = createInfo.format,
|
||||
.subresourceRange = VkImageSubresourceRange{
|
||||
.aspectMask = aspectFlag,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = mipLevels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = createInfo.numLayers,
|
||||
},
|
||||
};
|
||||
|
||||
VK_CHECK(vkCreateImageView(device, &viewCreateInfo, nullptr, &image.imageView));
|
||||
}
|
||||
|
||||
return image;
|
||||
} catch (...) {
|
||||
destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<GPUImage> ImageManager::loadImageFromFile(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent) const
|
||||
{
|
||||
const auto data = util::loadImage(path, intent);
|
||||
|
||||
if (data.vkFormat == VK_FORMAT_UNDEFINED ||
|
||||
data.byteSize == 0 ||
|
||||
(data.hdr ? data.hdrPixels == nullptr : data.pixels == nullptr))
|
||||
{
|
||||
spdlog::error("Failed to load image '{}'", path.string());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (mipMap) {
|
||||
VkFormatProperties formatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, data.vkFormat, &formatProperties);
|
||||
const auto requiredFeatures =
|
||||
VK_FORMAT_FEATURE_BLIT_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_DST_BIT |
|
||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
||||
if ((formatProperties.optimalTilingFeatures & requiredFeatures) != requiredFeatures) {
|
||||
spdlog::error(
|
||||
"Format {} does not support linear mip generation",
|
||||
static_cast<int>(data.vkFormat));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto image = createImage({
|
||||
.format = data.vkFormat,
|
||||
.usage = usage | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
||||
.extent = VkExtent3D{
|
||||
.width = static_cast<std::uint32_t>(data.width),
|
||||
.height = static_cast<std::uint32_t>(data.height),
|
||||
.depth = 1,
|
||||
},
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
|
||||
const void* src = data.hdr
|
||||
? static_cast<const void*>(data.hdrPixels)
|
||||
: static_cast<const void*>(data.pixels);
|
||||
|
||||
try {
|
||||
uploadImageData(image, src, data.byteSize, 0);
|
||||
} catch (...) {
|
||||
destroyImage(image);
|
||||
throw;
|
||||
}
|
||||
|
||||
image.debugName = path.string();
|
||||
vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void ImageManager::uploadImageData(
|
||||
const GPUImage& image,
|
||||
const void* pixelData,
|
||||
std::size_t byteSize,
|
||||
std::uint32_t layer) const
|
||||
{
|
||||
if (layer >= image.numLayers) {
|
||||
throw std::out_of_range("Image upload layer is out of range");
|
||||
}
|
||||
if (pixelData == nullptr || byteSize == 0) {
|
||||
throw std::invalid_argument("Image upload requires non-empty pixel data");
|
||||
}
|
||||
|
||||
GPUBuffer uploadBuffer = memoryManager->createBuffer(byteSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
if (uploadBuffer.info.pMappedData == nullptr) {
|
||||
memoryManager->destroyBuffer(uploadBuffer);
|
||||
throw std::runtime_error("Image upload staging buffer is not mapped");
|
||||
}
|
||||
|
||||
std::memcpy(uploadBuffer.info.pMappedData, pixelData, byteSize);
|
||||
memoryManager->flushAllocation(uploadBuffer);
|
||||
|
||||
try {
|
||||
executor->immediateSubmit([&](VkCommandBuffer cmd)
|
||||
{
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
uploadBuffer.buffer,
|
||||
0,
|
||||
VK_WHOLE_SIZE);
|
||||
|
||||
const VkImageSubresourceRange uploadRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = image.mipLevels,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
image.getLayout(layer),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
uploadRange);
|
||||
|
||||
VkBufferImageCopy copyRegion{};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copyRegion.imageSubresource.mipLevel = 0;
|
||||
copyRegion.imageSubresource.baseArrayLayer = layer;
|
||||
copyRegion.imageSubresource.layerCount = 1;
|
||||
copyRegion.imageExtent = image.extent;
|
||||
|
||||
vkCmdCopyBufferToImage(
|
||||
cmd,
|
||||
uploadBuffer.buffer,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1,
|
||||
©Region);
|
||||
|
||||
if (image.mipLevels == 1) {
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
uploadRange);
|
||||
} else {
|
||||
for (std::uint32_t mip = 1; mip < image.mipLevels; ++mip) {
|
||||
const VkImageSubresourceRange previousRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = mip - 1,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
previousRange);
|
||||
|
||||
const VkImageBlit2 blitRegion{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
|
||||
.srcSubresource = VkImageSubresourceLayers{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = mip - 1,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.srcOffsets = {
|
||||
{0, 0, 0},
|
||||
{
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.width >> (mip - 1))),
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.height >> (mip - 1))),
|
||||
1,
|
||||
},
|
||||
},
|
||||
.dstSubresource = VkImageSubresourceLayers{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = mip,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.dstOffsets = {
|
||||
{0, 0, 0},
|
||||
{
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.width >> mip)),
|
||||
static_cast<std::int32_t>(std::max(1u, image.extent.height >> mip)),
|
||||
1,
|
||||
},
|
||||
},
|
||||
};
|
||||
const VkBlitImageInfo2 blitInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
|
||||
.srcImage = image.image,
|
||||
.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.dstImage = image.image,
|
||||
.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.regionCount = 1,
|
||||
.pRegions = &blitRegion,
|
||||
.filter = VK_FILTER_LINEAR,
|
||||
};
|
||||
vkCmdBlitImage2(cmd, &blitInfo);
|
||||
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
previousRange);
|
||||
}
|
||||
|
||||
const VkImageSubresourceRange lastRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = image.mipLevels - 1,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
lastRange);
|
||||
}
|
||||
});
|
||||
} catch (...) {
|
||||
memoryManager->destroyBuffer(uploadBuffer);
|
||||
throw;
|
||||
}
|
||||
|
||||
image.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, layer);
|
||||
|
||||
memoryManager->destroyBuffer(uploadBuffer);
|
||||
}
|
||||
|
||||
void ImageManager::destroyImage(GPUImage& image) const
|
||||
{
|
||||
if (image.imageView != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(device, image.imageView, nullptr);
|
||||
}
|
||||
image.imageView = VK_NULL_HANDLE;
|
||||
if (image.image != VK_NULL_HANDLE && memoryManager != nullptr &&
|
||||
memoryManager->getAllocator() != VK_NULL_HANDLE) {
|
||||
vmaDestroyImage(memoryManager->getAllocator(), image.image, image.allocation);
|
||||
}
|
||||
if (image.image != VK_NULL_HANDLE) {
|
||||
image.image = VK_NULL_HANDLE;
|
||||
image.allocation = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#include <destrum/Graphics/Managers/MemoryManager.h>
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <volk.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Managers/VulkanInstanceManager.h>
|
||||
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
MemoryManager::~MemoryManager() {
|
||||
}
|
||||
|
||||
void MemoryManager::init(const VulkanInstanceManager& instanceManager) {
|
||||
const auto vulkanFunctions = VmaVulkanFunctions{
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
};
|
||||
|
||||
const auto allocatorInfo = VmaAllocatorCreateInfo{
|
||||
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
|
||||
.physicalDevice = instanceManager.getPhysicalDevice(),
|
||||
.device = instanceManager.getDevice(),
|
||||
.pVulkanFunctions = &vulkanFunctions,
|
||||
.instance = instanceManager.getInstance(),
|
||||
};
|
||||
VK_CHECK(vmaCreateAllocator(&allocatorInfo, &allocator));
|
||||
device = instanceManager.getDevice();
|
||||
}
|
||||
|
||||
void MemoryManager::cleanup(VkDevice) {
|
||||
if (allocator != VK_NULL_HANDLE) {
|
||||
vmaDestroyAllocator(allocator);
|
||||
allocator = VK_NULL_HANDLE;
|
||||
}
|
||||
device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
GPUBuffer MemoryManager::createBuffer(
|
||||
std::size_t allocSize,
|
||||
VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage) const
|
||||
{
|
||||
if (allocSize == 0) {
|
||||
throw std::invalid_argument("Cannot create a zero-sized Vulkan buffer");
|
||||
}
|
||||
const auto bufferInfo = VkBufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = allocSize,
|
||||
.usage = usage,
|
||||
};
|
||||
|
||||
const bool hostVisible =
|
||||
memoryUsage == VMA_MEMORY_USAGE_CPU_ONLY ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_CPU_TO_GPU ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_GPU_TO_CPU ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_CPU_COPY ||
|
||||
memoryUsage == VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
||||
|
||||
const auto hostAccessFlag =
|
||||
memoryUsage == VMA_MEMORY_USAGE_GPU_TO_CPU || memoryUsage == VMA_MEMORY_USAGE_CPU_COPY
|
||||
? VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT
|
||||
: VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
const auto allocInfo = VmaAllocationCreateInfo{
|
||||
.flags = hostVisible
|
||||
? static_cast<VmaAllocationCreateFlags>(
|
||||
VMA_ALLOCATION_CREATE_MAPPED_BIT | hostAccessFlag)
|
||||
: VmaAllocationCreateFlags{},
|
||||
.usage = memoryUsage,
|
||||
};
|
||||
|
||||
GPUBuffer buffer{};
|
||||
VK_CHECK(vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer.buffer, &buffer.allocation, &buffer.info));
|
||||
if ((usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0) {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
buffer.address = vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
buffer.hostVisible = hostVisible;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
VkDeviceAddress MemoryManager::getBufferAddress(const GPUBuffer& buffer) const {
|
||||
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer.buffer,
|
||||
};
|
||||
return vkGetBufferDeviceAddress(device, &deviceAdressInfo);
|
||||
}
|
||||
|
||||
void MemoryManager::destroyBuffer(GPUBuffer& buffer) const {
|
||||
if (allocator != VK_NULL_HANDLE && buffer.buffer != VK_NULL_HANDLE) {
|
||||
vmaDestroyBuffer(allocator, buffer.buffer, buffer.allocation);
|
||||
}
|
||||
buffer = {};
|
||||
}
|
||||
|
||||
void MemoryManager::flushAllocation(
|
||||
const GPUBuffer& buffer,
|
||||
VkDeviceSize offset,
|
||||
VkDeviceSize size) const
|
||||
{
|
||||
if (!buffer.hostVisible || buffer.allocation == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
VK_CHECK(vmaFlushAllocation(allocator, buffer.allocation, offset, size));
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
#include <destrum/Graphics/Managers/VulkanInstanceManager.h>
|
||||
|
||||
#include <volk.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/TracyVulkan.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
VulkanInstanceManager::~VulkanInstanceManager()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::init(
|
||||
SDL_Window* window,
|
||||
const std::string& appName,
|
||||
const DeviceFeatures& features)
|
||||
{
|
||||
if (initialized) {
|
||||
throw std::logic_error("VulkanInstanceManager::init called twice");
|
||||
}
|
||||
|
||||
VK_CHECK(volkInitialize());
|
||||
|
||||
const auto instanceResult = vkb::InstanceBuilder{}
|
||||
.set_app_name(appName.c_str())
|
||||
.set_app_version(1, 0, 0)
|
||||
.request_validation_layers()
|
||||
.use_default_debug_messenger()
|
||||
.require_api_version(1, 3, 0)
|
||||
.build();
|
||||
if (!instanceResult.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"Failed to create Vulkan instance: " + instanceResult.error().message());
|
||||
}
|
||||
vkbInstance = instanceResult.value();
|
||||
|
||||
instance = vkbInstance;
|
||||
|
||||
volkLoadInstance(instance);
|
||||
|
||||
const auto res = SDL_Vulkan_CreateSurface(window, instance, &surface);
|
||||
if (res != SDL_TRUE)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to create Vulkan surface: " + std::string{SDL_GetError()});
|
||||
}
|
||||
|
||||
const auto physicalDeviceResult =
|
||||
vkb::PhysicalDeviceSelector{vkbInstance}
|
||||
.set_minimum_version(1, 3)
|
||||
.set_required_features(features.device)
|
||||
.set_required_features_12(features.features12)
|
||||
.set_required_features_13(features.features13)
|
||||
.add_required_extension(
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
|
||||
.add_required_extension_features(
|
||||
features.extendedDynamicState3)
|
||||
.set_surface(surface)
|
||||
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
||||
.select();
|
||||
if (!physicalDeviceResult.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"Failed to select Vulkan physical device: " +
|
||||
physicalDeviceResult.error().message());
|
||||
}
|
||||
vkbPhysicalDevice = physicalDeviceResult.value();
|
||||
|
||||
physicalDevice = vkbPhysicalDevice;
|
||||
|
||||
const auto deviceResult = vkb::DeviceBuilder{vkbPhysicalDevice}.build();
|
||||
if (!deviceResult.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"Failed to create Vulkan device: " + deviceResult.error().message());
|
||||
}
|
||||
vkbDevice = deviceResult.value();
|
||||
|
||||
device = vkbDevice;
|
||||
|
||||
volkLoadDevice(vkbDevice);
|
||||
|
||||
const auto graphicsQueueFamilyResult =
|
||||
vkbDevice.get_queue_index(vkb::QueueType::graphics);
|
||||
const auto graphicsQueueResult = vkbDevice.get_queue(vkb::QueueType::graphics);
|
||||
const auto presentQueueFamilyResult =
|
||||
vkbDevice.get_queue_index(vkb::QueueType::present);
|
||||
const auto presentQueueResult = vkbDevice.get_queue(vkb::QueueType::present);
|
||||
if (!graphicsQueueFamilyResult.has_value() ||
|
||||
!graphicsQueueResult.has_value() ||
|
||||
!presentQueueFamilyResult.has_value() ||
|
||||
!presentQueueResult.has_value()) {
|
||||
throw std::runtime_error("Failed to retrieve graphics/present queues");
|
||||
}
|
||||
|
||||
graphicsQueueFamily = graphicsQueueFamilyResult.value();
|
||||
graphicsQueue = graphicsQueueResult.value();
|
||||
presentQueueFamily = presentQueueFamilyResult.value();
|
||||
presentQueue = presentQueueResult.value();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::initTracy(VkCommandBuffer tracyInitCmd)
|
||||
{
|
||||
#if defined(TRACY_ENABLE)
|
||||
|
||||
#if defined(TRACY_VK_USE_SYMBOL_TABLE)
|
||||
tracyVkCtx = TracyVkContext(
|
||||
instance,
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd,
|
||||
vkGetInstanceProcAddr,
|
||||
vkGetDeviceProcAddr);
|
||||
#else
|
||||
tracyVkCtx = TracyVkContext(
|
||||
physicalDevice,
|
||||
device,
|
||||
graphicsQueue,
|
||||
tracyInitCmd);
|
||||
#endif
|
||||
|
||||
static constexpr char ctxName[] = "Graphics Queue";
|
||||
|
||||
TracyVkContextName(
|
||||
tracyVkCtx,
|
||||
ctxName,
|
||||
sizeof(ctxName) - 1);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::cleanup()
|
||||
{
|
||||
#if defined(TRACY_ENABLE)
|
||||
if (tracyVkCtx)
|
||||
{
|
||||
TracyVkDestroy(tracyVkCtx);
|
||||
tracyVkCtx = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDestroyDevice(device, nullptr);
|
||||
device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (surface != VK_NULL_HANDLE && instance != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||
surface = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (instance != VK_NULL_HANDLE) {
|
||||
vkb::destroy_instance(vkbInstance);
|
||||
instance = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
physicalDevice = VK_NULL_HANDLE;
|
||||
graphicsQueueFamily = VK_QUEUE_FAMILY_IGNORED;
|
||||
graphicsQueue = VK_NULL_HANDLE;
|
||||
presentQueueFamily = VK_QUEUE_FAMILY_IGNORED;
|
||||
presentQueue = VK_NULL_HANDLE;
|
||||
vkbInstance = {};
|
||||
vkbPhysicalDevice = {};
|
||||
vkbDevice = {};
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void VulkanInstanceManager::waitIdle() const
|
||||
{
|
||||
if (device != VK_NULL_HANDLE)
|
||||
{
|
||||
VK_CHECK(vkDeviceWaitIdle(device));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include <destrum/Util/MathUtils.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include "volk.h"
|
||||
// #include <destrum/Math/Util.h>
|
||||
|
||||
MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
|
||||
@@ -27,49 +32,110 @@ MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
|
||||
const auto id = meshes.size();
|
||||
meshes.push_back(std::move(gpuMesh));
|
||||
cpuMeshes.push_back(cpuMesh); // store a copy of the CPU mesh
|
||||
std::string key = cpuMesh.name.empty()
|
||||
? "mesh:" + std::to_string(id)
|
||||
: cpuMesh.name;
|
||||
const std::string baseKey = key;
|
||||
std::size_t suffix = 1;
|
||||
while (std::find(meshKeys.begin(), meshKeys.end(), key) != meshKeys.end()) {
|
||||
key = baseKey + "#" + std::to_string(suffix++);
|
||||
}
|
||||
meshKeys.push_back(std::move(key));
|
||||
return id;
|
||||
}
|
||||
|
||||
void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) const
|
||||
{
|
||||
try {
|
||||
// create index buffer
|
||||
const auto indexBufferSize = cpuMesh.indices.size() * sizeof(std::uint32_t);
|
||||
gpuMesh.indexBuffer = gfxDevice.createBuffer(
|
||||
indexBufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
indexBufferSize,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
|
||||
// create vertex buffer
|
||||
const auto vertexBufferSize = cpuMesh.vertices.size() * sizeof(CPUMesh::Vertex);
|
||||
gpuMesh.vertexBuffer = gfxDevice.createBuffer(
|
||||
vertexBufferSize,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
|
||||
const auto staging =
|
||||
auto vertexIndexStaging =
|
||||
gfxDevice
|
||||
.createBuffer(vertexBufferSize + indexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
.createBuffer(
|
||||
vertexBufferSize + indexBufferSize,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_ONLY);
|
||||
|
||||
// copy data
|
||||
void* data = staging.info.pMappedData;
|
||||
memcpy(data, cpuMesh.vertices.data(), vertexBufferSize);
|
||||
memcpy((char*)data + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize);
|
||||
try {
|
||||
// copy data
|
||||
void* vertexIndexData = vertexIndexStaging.info.pMappedData;
|
||||
memcpy(vertexIndexData, cpuMesh.vertices.data(), vertexBufferSize);
|
||||
memcpy(static_cast<char*>(vertexIndexData) + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize);
|
||||
gfxDevice.getMemoryManager().flushAllocation(vertexIndexStaging);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
vertexIndexStaging.buffer,
|
||||
0,
|
||||
VK_WHOLE_SIZE);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
const auto vertexCopy = VkBufferCopy{
|
||||
.srcOffset = 0,
|
||||
.dstOffset = 0,
|
||||
.size = vertexBufferSize,
|
||||
};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, gpuMesh.vertexBuffer.buffer, 1, &vertexCopy);
|
||||
vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, gpuMesh.vertexBuffer.buffer, 1, &vertexCopy);
|
||||
|
||||
const auto indexCopy = VkBufferCopy{
|
||||
.srcOffset = vertexBufferSize,
|
||||
.dstOffset = 0,
|
||||
.size = indexBufferSize,
|
||||
};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, gpuMesh.indexBuffer.buffer, 1, &indexCopy);
|
||||
});
|
||||
vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, gpuMesh.indexBuffer.buffer, 1, &indexCopy);
|
||||
|
||||
gfxDevice.destroyBuffer(staging);
|
||||
const std::array<VkBufferMemoryBarrier2, 2> destinationBarriers{{
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = gpuMesh.vertexBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
},
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = gpuMesh.indexBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
}
|
||||
}};
|
||||
const VkDependencyInfo destinationDependency{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.bufferMemoryBarrierCount = static_cast<std::uint32_t>(destinationBarriers.size()),
|
||||
.pBufferMemoryBarriers = destinationBarriers.data(),
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &destinationDependency);
|
||||
});
|
||||
} catch (...) {
|
||||
gfxDevice.destroyBuffer(vertexIndexStaging);
|
||||
throw;
|
||||
}
|
||||
|
||||
gfxDevice.destroyBuffer(vertexIndexStaging);
|
||||
|
||||
const auto vtxBufferName = cpuMesh.name + " (vtx)";
|
||||
const auto idxBufferName = cpuMesh.name + " (idx)";
|
||||
@@ -82,25 +148,67 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
||||
gpuMesh.skinningDataBuffer = gfxDevice.createBuffer(
|
||||
skinningDataSize,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
|
||||
const auto staging =
|
||||
gfxDevice.createBuffer(skinningDataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
auto skinningStaging =
|
||||
gfxDevice.createBuffer(
|
||||
skinningDataSize,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_ONLY);
|
||||
|
||||
// copy data
|
||||
void* data = staging.info.pMappedData;
|
||||
memcpy(data, cpuMesh.skinningData.data(), skinningDataSize);
|
||||
try {
|
||||
// copy data
|
||||
void* skinningData = skinningStaging.info.pMappedData;
|
||||
memcpy(skinningData, cpuMesh.skinningData.data(), skinningDataSize);
|
||||
gfxDevice.getMemoryManager().flushAllocation(skinningStaging);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
skinningStaging.buffer,
|
||||
0,
|
||||
VK_WHOLE_SIZE);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
const auto vertexCopy = VkBufferCopy{
|
||||
.srcOffset = 0,
|
||||
.dstOffset = 0,
|
||||
.size = skinningDataSize,
|
||||
};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, gpuMesh.skinningDataBuffer.buffer, 1, &vertexCopy);
|
||||
});
|
||||
vkCmdCopyBuffer(cmd, skinningStaging.buffer, gpuMesh.skinningDataBuffer.buffer, 1, &vertexCopy);
|
||||
|
||||
gfxDevice.destroyBuffer(staging);
|
||||
const VkBufferMemoryBarrier2 destinationBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = gpuMesh.skinningDataBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
};
|
||||
const VkDependencyInfo destinationDependency{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.bufferMemoryBarrierCount = 1,
|
||||
.pBufferMemoryBarriers = &destinationBarrier,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &destinationDependency);
|
||||
});
|
||||
} catch (...) {
|
||||
gfxDevice.destroyBuffer(skinningStaging);
|
||||
throw;
|
||||
}
|
||||
|
||||
gfxDevice.destroyBuffer(skinningStaging);
|
||||
}
|
||||
} catch (...) {
|
||||
gfxDevice.destroyBuffer(gpuMesh.skinningDataBuffer);
|
||||
gfxDevice.destroyBuffer(gpuMesh.vertexBuffer);
|
||||
gfxDevice.destroyBuffer(gpuMesh.indexBuffer);
|
||||
gpuMesh = {};
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,10 +222,28 @@ const CPUMesh& MeshCache::getCPUMesh(MeshID id) const
|
||||
return cpuMeshes.at(id);
|
||||
}
|
||||
|
||||
const std::string& MeshCache::getMeshKey(MeshID id) const
|
||||
{
|
||||
return meshKeys.at(id);
|
||||
}
|
||||
|
||||
std::optional<MeshID> MeshCache::findMeshByKey(std::string_view key) const
|
||||
{
|
||||
const auto it = std::find(meshKeys.begin(), meshKeys.end(), key);
|
||||
if (it == meshKeys.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<MeshID>(std::distance(meshKeys.begin(), it));
|
||||
}
|
||||
|
||||
void MeshCache::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
for (const auto& mesh : meshes) {
|
||||
for (auto& mesh : meshes) {
|
||||
gfxDevice.destroyBuffer(mesh.indexBuffer);
|
||||
gfxDevice.destroyBuffer(mesh.vertexBuffer);
|
||||
gfxDevice.destroyBuffer(mesh.skinningDataBuffer);
|
||||
}
|
||||
meshes.clear();
|
||||
cpuMeshes.clear();
|
||||
meshKeys.clear();
|
||||
}
|
||||
|
||||
@@ -5,19 +5,31 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "volk.h"
|
||||
#include "destrum/Graphics/Util.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
|
||||
Pipeline::Pipeline(GfxDevice& device, const std::string& vertPath, const std::string& fragPath,
|
||||
const PipelineConfigInfo& configInfo): m_device(device) {
|
||||
CreateGraphicsPipeline(vertPath, fragPath, configInfo);
|
||||
try {
|
||||
CreateGraphicsPipeline(vertPath, fragPath, configInfo);
|
||||
} catch (...) {
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_vertShaderModule, nullptr);
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_fragShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_graphicsPipeline, nullptr);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline::~Pipeline() {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_vertShaderModule, nullptr);
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_fragShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_graphicsPipeline, nullptr);
|
||||
if (m_device.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_vertShaderModule, nullptr);
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_fragShaderModule, nullptr);
|
||||
vkDestroyPipeline(m_device.getDevice(), m_graphicsPipeline, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Pipeline::bind(VkCommandBuffer buffer) const {
|
||||
|
||||
@@ -16,12 +16,14 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
gfx = &gfxDevice;
|
||||
sdlWindow = window;
|
||||
colorFormat = gfx->getSwapchainFormat();
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
contextCreated = true;
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
@@ -32,6 +34,7 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
if (!ImGui_ImplSDL2_InitForVulkan(sdlWindow)) {
|
||||
throw std::runtime_error("ImGui_ImplSDL2_InitForVulkan failed");
|
||||
}
|
||||
sdlInitialized = true;
|
||||
|
||||
const std::array<VkDescriptorPoolSize, 11> poolSizes{{
|
||||
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
|
||||
@@ -56,6 +59,21 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(gfx->getVkDevice(), &poolInfo, nullptr, &descriptorPool));
|
||||
|
||||
initVulkanBackend();
|
||||
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void ImguiPass::initVulkanBackend()
|
||||
{
|
||||
if (gfx == nullptr) {
|
||||
throw std::runtime_error("Cannot initialize ImGui Vulkan backend without a device");
|
||||
}
|
||||
|
||||
VkPipelineRenderingCreateInfoKHR pipelineRenderingInfo{};
|
||||
pipelineRenderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
|
||||
pipelineRenderingInfo.colorAttachmentCount = 1;
|
||||
@@ -69,21 +87,26 @@ void ImguiPass::init(SDL_Window* window, GfxDevice& gfxDevice) {
|
||||
|
||||
ImGui_ImplVulkan_InitInfo initInfo{};
|
||||
initInfo.ApiVersion = VK_API_VERSION_1_3;
|
||||
initInfo.Instance = gfxDevice.getVkInstance();
|
||||
initInfo.PhysicalDevice = gfxDevice.getVkPhysicalDevice();
|
||||
initInfo.Device = gfxDevice.getDevice();
|
||||
initInfo.Queue = gfxDevice.getGraphicsQueue();
|
||||
initInfo.Instance = gfx->getVkInstance();
|
||||
initInfo.PhysicalDevice = gfx->getVkPhysicalDevice();
|
||||
initInfo.Device = gfx->getDevice();
|
||||
initInfo.Queue = gfx->getGraphicsQueue();
|
||||
initInfo.QueueFamily = gfx->getGraphicsQueueFamily();
|
||||
initInfo.DescriptorPool = descriptorPool;
|
||||
initInfo.MinImageCount = 3;
|
||||
initInfo.ImageCount = 3;
|
||||
const auto imageCount = gfx->getSwapchainImageCount();
|
||||
if (imageCount < 2) {
|
||||
throw std::runtime_error("ImGui Vulkan backend requires at least two swapchain images");
|
||||
}
|
||||
initInfo.MinImageCount = imageCount;
|
||||
initInfo.ImageCount = imageCount;
|
||||
initInfo.UseDynamicRendering = true;
|
||||
initInfo.PipelineInfoMain = pipelineInfo;
|
||||
|
||||
vulkanInitialized = true;
|
||||
if (!ImGui_ImplVulkan_Init(&initInfo)) {
|
||||
vulkanInitialized = false;
|
||||
throw std::runtime_error("ImGui_ImplVulkan_Init failed");
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void ImguiPass::handleEvent(const SDL_Event& event) {
|
||||
@@ -168,25 +191,52 @@ void ImguiPass::onSwapchainRecreated() {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_ImplVulkan_SetMinImageCount(2);
|
||||
const auto imageCount = gfx->getSwapchainImageCount();
|
||||
const auto newFormat = gfx->getSwapchainFormat();
|
||||
if (newFormat != colorFormat) {
|
||||
if (vulkanInitialized) {
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
vulkanInitialized = false;
|
||||
}
|
||||
colorFormat = newFormat;
|
||||
initVulkanBackend();
|
||||
} else {
|
||||
if (imageCount < 2) {
|
||||
throw std::runtime_error("ImGui Vulkan backend requires at least two swapchain images");
|
||||
}
|
||||
ImGui_ImplVulkan_SetMinImageCount(std::max(2u, imageCount));
|
||||
}
|
||||
}
|
||||
|
||||
void ImguiPass::cleanup() {
|
||||
if (!initialized || gfx == nullptr) {
|
||||
if (gfx == nullptr && !contextCreated && !sdlInitialized &&
|
||||
!vulkanInitialized && descriptorPool == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
vkDeviceWaitIdle(gfx->getVkDevice());
|
||||
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
if (descriptorPool != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(gfx->getVkDevice(), descriptorPool, nullptr);
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
if (gfx != nullptr && gfx->getVkDevice() != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(gfx->getVkDevice());
|
||||
}
|
||||
|
||||
if (vulkanInitialized) {
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
vulkanInitialized = false;
|
||||
}
|
||||
if (sdlInitialized) {
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
sdlInitialized = false;
|
||||
}
|
||||
if (contextCreated) {
|
||||
ImGui::DestroyContext();
|
||||
contextCreated = false;
|
||||
}
|
||||
|
||||
if (descriptorPool != VK_NULL_HANDLE && gfx != nullptr &&
|
||||
gfx->getVkDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(gfx->getVkDevice(), descriptorPool, nullptr);
|
||||
}
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
|
||||
gfx = nullptr;
|
||||
sdlWindow = nullptr;
|
||||
colorFormat = VK_FORMAT_UNDEFINED;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||
#include <destrum/Graphics/Frustum.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
MeshPipeline::MeshPipeline() = default;
|
||||
@@ -22,6 +23,7 @@ void MeshPipeline::init(
|
||||
VkFormat drawImageFormat,
|
||||
VkFormat depthImageFormat)
|
||||
{
|
||||
try {
|
||||
const auto vertexShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
|
||||
|
||||
@@ -49,7 +51,7 @@ void MeshPipeline::init(
|
||||
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
|
||||
|
||||
if (vkCreatePipelineLayout(
|
||||
gfxDevice.getDevice().device,
|
||||
gfxDevice.getDevice(),
|
||||
&pipelineLayoutInfo,
|
||||
nullptr,
|
||||
&m_pipelineLayout) != VK_SUCCESS)
|
||||
@@ -69,12 +71,16 @@ void MeshPipeline::init(
|
||||
pipelineConfig.colorAttachments = {drawImageFormat};
|
||||
pipelineConfig.depthAttachment = depthImageFormat;
|
||||
|
||||
m_pipeline = std::make_unique<Pipeline>(
|
||||
m_pipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
fragShader.string(),
|
||||
pipelineConfig
|
||||
);
|
||||
);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice.getDevice());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshPipeline::draw(
|
||||
@@ -188,8 +194,8 @@ void MeshPipeline::cleanup(VkDevice device)
|
||||
{
|
||||
m_pipeline.reset();
|
||||
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE) {
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
|
||||
m_pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
m_pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "destrum/FS/AssetFS.h"
|
||||
#include "../../../include/destrum/Graphics/Caches/MeshCache.h"
|
||||
#include "destrum/Graphics/MeshDrawCommand.h"
|
||||
#include "destrum/Graphics/Util.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
@@ -10,7 +11,10 @@
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "volk.h"
|
||||
|
||||
void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
||||
try {
|
||||
const auto& device = gfxDevice.getDevice();
|
||||
|
||||
const auto pushConstant = VkPushConstantRange{
|
||||
@@ -26,7 +30,7 @@ void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
||||
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||
pipelineLayoutInfo.pPushConstantRanges = pushConstants.data();
|
||||
|
||||
if (vkCreatePipelineLayout(device.device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) {
|
||||
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Could not make pipeline layout");
|
||||
}
|
||||
|
||||
@@ -42,21 +46,31 @@ void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
||||
pipelineConfig
|
||||
);
|
||||
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
auto& jointMatricesBuffer = framesData[i].jointMatricesBuffer;
|
||||
jointMatricesBuffer.capacity = MAX_JOINT_MATRICES;
|
||||
jointMatricesBuffer.buffer = gfxDevice.createBuffer(
|
||||
MAX_JOINT_MATRICES * sizeof(glm::mat4),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningPipeline::cleanup(GfxDevice& gfxDevice) {
|
||||
for (auto& frame : framesData) {
|
||||
gfxDevice.destroyBuffer(frame.jointMatricesBuffer.buffer);
|
||||
frame.jointMatricesBuffer.buffer = {};
|
||||
frame.jointMatricesBuffer.size = 0;
|
||||
}
|
||||
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice().device, m_pipelineLayout, nullptr);
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice(), m_pipelineLayout, nullptr);
|
||||
}
|
||||
m_pipelineLayout = VK_NULL_HANDLE;
|
||||
skinningPipeline.reset();
|
||||
}
|
||||
|
||||
@@ -94,31 +108,45 @@ void SkinningPipeline::doSkinning(VkCommandBuffer cmd,
|
||||
|
||||
vkCmdDispatch(cmd, groupSizeX, 1, 1);
|
||||
|
||||
// Required before the graphics pass reads skinnedVertexBuffer as a vertex buffer.
|
||||
// Without this, the draw can see stale/partial data from before the compute dispatch.
|
||||
VkBufferMemoryBarrier skinnedVertexBarrier{};
|
||||
skinnedVertexBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
skinnedVertexBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
skinnedVertexBarrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
|
||||
skinnedVertexBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
skinnedVertexBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
skinnedVertexBarrier.buffer = dc.skinnedMesh->skinnedVertexBuffer.buffer;
|
||||
skinnedVertexBarrier.offset = 0;
|
||||
skinnedVertexBarrier.size = VK_WHOLE_SIZE;
|
||||
const VkBufferMemoryBarrier2 skinnedVertexBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = dc.skinnedMesh->skinnedVertexBuffer.buffer,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(cmd,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &skinnedVertexBarrier,
|
||||
0, nullptr);
|
||||
const VkDependencyInfo dependencyInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.bufferMemoryBarrierCount = 1,
|
||||
.pBufferMemoryBarriers = &skinnedVertexBarrier,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
|
||||
}
|
||||
|
||||
void SkinningPipeline::beginDrawing(std::size_t frameIndex) {
|
||||
getCurrentFrameData(frameIndex).jointMatricesBuffer.clear();
|
||||
}
|
||||
|
||||
void SkinningPipeline::flushCurrentFrame(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
std::size_t frameIndex)
|
||||
{
|
||||
auto& buffer = getCurrentFrameData(frameIndex).jointMatricesBuffer.buffer;
|
||||
gfxDevice.getMemoryManager().flushAllocation(buffer, 0, VK_WHOLE_SIZE);
|
||||
vkutil::bufferHostWriteToShaderReadBarrier(
|
||||
cmd,
|
||||
buffer.buffer,
|
||||
0,
|
||||
static_cast<VkDeviceSize>(MAX_JOINT_MATRICES * sizeof(glm::mat4)));
|
||||
}
|
||||
|
||||
std::size_t SkinningPipeline::appendJointMatrices(std::span<const glm::mat4> jointMatrices,
|
||||
std::size_t frameIndex) {
|
||||
auto& jointMatricesBuffer = getCurrentFrameData(frameIndex).jointMatricesBuffer;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <glm/glm.hpp>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
@@ -19,6 +20,7 @@ void SkyboxPipeline::init(
|
||||
VkFormat drawImageFormat,
|
||||
VkFormat depthImageFormat)
|
||||
{
|
||||
try {
|
||||
const auto vertexShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/fullscreen_triangle.vert");
|
||||
|
||||
@@ -45,7 +47,7 @@ void SkyboxPipeline::init(
|
||||
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
|
||||
|
||||
if (vkCreatePipelineLayout(
|
||||
gfxDevice.getDevice().device,
|
||||
gfxDevice.getDevice(),
|
||||
&pipelineLayoutInfo,
|
||||
nullptr,
|
||||
&pipelineLayout) != VK_SUCCESS)
|
||||
@@ -71,22 +73,26 @@ void SkyboxPipeline::init(
|
||||
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
|
||||
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
|
||||
pipeline = std::make_unique<Pipeline>(
|
||||
pipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
fragShader.string(),
|
||||
pipelineConfig
|
||||
);
|
||||
);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice.getDevice());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SkyboxPipeline::cleanup(VkDevice device)
|
||||
{
|
||||
pipeline.reset();
|
||||
|
||||
if (pipelineLayout != VK_NULL_HANDLE) {
|
||||
if (pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void SkyboxPipeline::draw(
|
||||
|
||||
@@ -3,24 +3,31 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
RenderResources::RenderResources() = default;
|
||||
|
||||
void RenderResources::init(GfxDevice& gfxDevice)
|
||||
{
|
||||
imageCache = std::make_unique<ImageCache>(gfxDevice);
|
||||
meshCache = std::make_unique<MeshCache>();
|
||||
materialCache = std::make_unique<MaterialCache>();
|
||||
if (imageCache || meshCache || materialCache) {
|
||||
cleanup(gfxDevice);
|
||||
}
|
||||
|
||||
VkPhysicalDeviceProperties props{};
|
||||
vkGetPhysicalDeviceProperties(gfxDevice.getVkPhysicalDevice(), &props);
|
||||
try {
|
||||
imageCache = std::make_unique<ImageCache>(gfxDevice);
|
||||
meshCache = std::make_unique<MeshCache>();
|
||||
materialCache = std::make_unique<MaterialCache>();
|
||||
|
||||
imageCache->bindlessSetManager.init(
|
||||
gfxDevice.getVkDevice(),
|
||||
props.limits.maxSamplerAnisotropy);
|
||||
VkPhysicalDeviceProperties props{};
|
||||
vkGetPhysicalDeviceProperties(gfxDevice.getVkPhysicalDevice(), &props);
|
||||
|
||||
{
|
||||
imageCache->bindlessSetManager.init(
|
||||
gfxDevice.getVkDevice(),
|
||||
gfxDevice.getVkPhysicalDevice(),
|
||||
props.limits.maxSamplerAnisotropy);
|
||||
|
||||
{
|
||||
std::uint32_t white = 0xFFFFFFFF;
|
||||
|
||||
whiteImageId = createImage(
|
||||
@@ -35,7 +42,7 @@ void RenderResources::init(GfxDevice& gfxDevice)
|
||||
&white);
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
std::uint32_t normal = 0xFFFF8080; // tangent-space normal: 0.5, 0.5, 1.0, 1.0
|
||||
|
||||
defaultNormalImageId = createImage(
|
||||
@@ -50,7 +57,7 @@ void RenderResources::init(GfxDevice& gfxDevice)
|
||||
&normal);
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
constexpr auto black = 0xFF000000;
|
||||
constexpr auto magenta = 0xFFFF00FF;
|
||||
|
||||
@@ -74,14 +81,18 @@ void RenderResources::init(GfxDevice& gfxDevice)
|
||||
imageCache->setErrorImageId(errorImageId);
|
||||
}
|
||||
|
||||
materialCache->init(
|
||||
gfxDevice,
|
||||
MaterialDefaultTextures{
|
||||
.white = whiteImageId,
|
||||
.normal = defaultNormalImageId,
|
||||
.metallicRoughness = whiteImageId,
|
||||
.emissive = whiteImageId,
|
||||
});
|
||||
materialCache->init(
|
||||
gfxDevice,
|
||||
MaterialDefaultTextures{
|
||||
.white = whiteImageId,
|
||||
.normal = defaultNormalImageId,
|
||||
.metallicRoughness = whiteImageId,
|
||||
.emissive = whiteImageId,
|
||||
});
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ImageID RenderResources::createImage(
|
||||
@@ -152,6 +163,7 @@ ImageID RenderResources::loadImageFromFile(
|
||||
bool mipMap,
|
||||
TextureIntent intent)
|
||||
{
|
||||
(void)gfxDevice;
|
||||
return imageCache->loadImageFromFile(path, usage, mipMap, intent);
|
||||
}
|
||||
|
||||
@@ -188,6 +200,33 @@ void RenderResources::bindBindlessDescSet(VkCommandBuffer cmd, VkPipelineLayout
|
||||
);
|
||||
}
|
||||
|
||||
void RenderResources::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
if (!imageCache && !meshCache && !materialCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
gfxDevice.waitIdle();
|
||||
|
||||
if (materialCache) {
|
||||
materialCache->cleanup(gfxDevice);
|
||||
}
|
||||
if (meshCache) {
|
||||
meshCache->cleanup(gfxDevice);
|
||||
}
|
||||
if (imageCache) {
|
||||
imageCache->bindlessSetManager.cleanup(gfxDevice.getVkDevice());
|
||||
imageCache->destroyImages();
|
||||
}
|
||||
|
||||
materialCache.reset();
|
||||
meshCache.reset();
|
||||
imageCache.reset();
|
||||
whiteImageId = NULL_IMAGE_ID;
|
||||
errorImageId = NULL_IMAGE_ID;
|
||||
defaultNormalImageId = NULL_IMAGE_ID;
|
||||
}
|
||||
|
||||
std::uint32_t RenderResources::BytesPerTexel(VkFormat fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
@@ -208,4 +247,4 @@ std::uint32_t RenderResources::BytesPerTexel(VkFormat fmt)
|
||||
default:
|
||||
throw std::runtime_error("RenderResources::BytesPerTexel: unsupported format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "volk.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
@@ -13,26 +17,31 @@ GameRenderer::GameRenderer()
|
||||
|
||||
void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::ivec2 drawImageSize)
|
||||
{
|
||||
resources = &_resources;
|
||||
sceneDataBuffer.init(
|
||||
gfxDevice,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
sizeof(GPUSceneData),
|
||||
"scene data");
|
||||
try {
|
||||
resources = &_resources;
|
||||
sceneDataBuffer.init(
|
||||
gfxDevice,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
sizeof(GPUSceneData),
|
||||
"scene data");
|
||||
|
||||
createDrawImage(gfxDevice, drawImageSize, true);
|
||||
createDrawImage(gfxDevice, drawImageSize, true);
|
||||
|
||||
meshPipeline = std::make_unique<MeshPipeline>();
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
meshPipeline = std::make_unique<MeshPipeline>();
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
|
||||
skinningPipeline = std::make_unique<SkinningPipeline>();
|
||||
skinningPipeline->init(gfxDevice);
|
||||
skinningPipeline = std::make_unique<SkinningPipeline>();
|
||||
skinningPipeline->init(gfxDevice);
|
||||
|
||||
|
||||
GameState::GetInstance().SetRenderer(this);
|
||||
GameState::GetInstance().SetRenderer(this);
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
@@ -44,7 +53,23 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
|
||||
void GameRenderer::endDrawing()
|
||||
{
|
||||
//Sort the drawlist
|
||||
sortedMeshDrawCommands.resize(meshDrawCommands.size());
|
||||
std::iota(sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.end(), 0);
|
||||
|
||||
std::stable_sort(
|
||||
sortedMeshDrawCommands.begin(),
|
||||
sortedMeshDrawCommands.end(),
|
||||
[this](std::size_t left, std::size_t right) {
|
||||
const auto& lhs = meshDrawCommands[left];
|
||||
const auto& rhs = meshDrawCommands[right];
|
||||
if (lhs.meshId != rhs.meshId) {
|
||||
return lhs.meshId < rhs.meshId;
|
||||
}
|
||||
if (lhs.materialId != rhs.materialId) {
|
||||
return lhs.materialId < rhs.materialId;
|
||||
}
|
||||
return (lhs.skinnedMesh != nullptr) < (rhs.skinnedMesh != nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& camera, const SceneData& sceneData)
|
||||
@@ -55,6 +80,11 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
{
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Skinning");
|
||||
|
||||
skinningPipeline->flushCurrentFrame(
|
||||
cmd,
|
||||
gfxDevice,
|
||||
gfxDevice.getCurrentFrameIndex());
|
||||
|
||||
for (const auto& dc : meshDrawCommands)
|
||||
{
|
||||
if (!dc.skinnedMesh)
|
||||
@@ -108,8 +138,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
drawImage.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
drawImage.layout,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
drawImage.setLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -118,17 +149,20 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
depthImage.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
depthImage.layout,
|
||||
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
|
||||
const auto renderInfo = vkutil::createRenderingInfo({
|
||||
auto renderInfo = vkutil::createRenderingInfo({
|
||||
.renderExtent = drawImage.getExtent2D(),
|
||||
.colorImageView = drawImage.imageView,
|
||||
.colorImageClearValue = glm::vec4{0.f, 0.f, 0.f, 1.f},
|
||||
.depthImageView = depthImage.imageView,
|
||||
.depthImageClearValue = 1.f,
|
||||
});
|
||||
renderInfo.renderingInfo.pColorAttachments = &renderInfo.colorAttachment;
|
||||
renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment;
|
||||
|
||||
{
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "vkCmdBeginRendering");
|
||||
@@ -165,9 +199,11 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
|
||||
void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
VkDevice device = gfxDevice.getDevice().device;
|
||||
VkDevice device = gfxDevice.getDevice();
|
||||
|
||||
vkDeviceWaitIdle(device);
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(device);
|
||||
}
|
||||
|
||||
if (skinningPipeline)
|
||||
skinningPipeline->cleanup(gfxDevice);
|
||||
@@ -188,6 +224,15 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
|
||||
drawImageId = NULL_IMAGE_ID;
|
||||
depthImageId = NULL_IMAGE_ID;
|
||||
resources = nullptr;
|
||||
meshPipeline.reset();
|
||||
skyboxPipeline.reset();
|
||||
skinningPipeline.reset();
|
||||
pendingMaterialUploads.clear();
|
||||
meshDrawCommands.clear();
|
||||
sortedMeshDrawCommands.clear();
|
||||
initialized = false;
|
||||
GameState::GetInstance().SetRenderer(nullptr);
|
||||
}
|
||||
|
||||
void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId)
|
||||
@@ -199,6 +244,7 @@ void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID ma
|
||||
meshDrawCommands.push_back(MeshDrawCommand{
|
||||
.meshId = id,
|
||||
.transformMatrix = transform,
|
||||
.worldBoundingSphere = worldBoundingSphere,
|
||||
.materialId = materialId,
|
||||
});
|
||||
}
|
||||
@@ -210,13 +256,14 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
|
||||
std::size_t jointMatricesStartIndex)
|
||||
{
|
||||
const auto& mesh = resources->meshes().getMesh(id);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, false);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, true);
|
||||
assert(materialId != NULL_MATERIAL_ID);
|
||||
assert(skinnedMesh != nullptr);
|
||||
|
||||
meshDrawCommands.push_back(MeshDrawCommand{
|
||||
.meshId = id,
|
||||
.transformMatrix = transform,
|
||||
.worldBoundingSphere = worldBoundingSphere,
|
||||
.materialId = materialId,
|
||||
.skinnedMesh = skinnedMesh,
|
||||
.jointMatricesStartIndex = static_cast<std::uint32_t>(jointMatricesStartIndex),
|
||||
@@ -249,10 +296,20 @@ void GameRenderer::setSkyboxTexture(ImageID skyboxImageId)
|
||||
|
||||
void GameRenderer::flushMaterialUpdates(GfxDevice& gfxDevice)
|
||||
{
|
||||
if (!pendingMaterialUploads.empty()) {
|
||||
// Material data is currently shared by all frame contexts. Wait before
|
||||
// mutating it so an older in-flight frame cannot read the overwritten
|
||||
// range. This can later be replaced with per-frame material buffers.
|
||||
gfxDevice.waitIdle();
|
||||
}
|
||||
|
||||
for (MaterialID id : pendingMaterialUploads)
|
||||
{
|
||||
resources->materials().updateMaterialGPU(id);
|
||||
// if non-coherent: flush mapped range for that id here
|
||||
gfxDevice.getMemoryManager().flushAllocation(
|
||||
resources->materials().getMaterialDataBuffer(),
|
||||
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
|
||||
sizeof(MaterialData));
|
||||
}
|
||||
pendingMaterialUploads.clear();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "volk.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
CubeMap::CubeMap()
|
||||
@@ -69,7 +70,7 @@ void CubeMap::RenderToCubemap(
|
||||
);
|
||||
}
|
||||
|
||||
gfxDevice.GetImmediateExecuter().immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
gfxDevice.getImmediateExecuter().immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
@@ -201,45 +202,57 @@ void CubeMap::CreateCubeMap(
|
||||
|
||||
std::array<VkImageView, 6> faceViews{};
|
||||
|
||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = cubeMapImage.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = face;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
try {
|
||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = cubeMapImage.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = face;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
|
||||
if (vkCreateImageView(
|
||||
VK_CHECK(vkCreateImageView(
|
||||
gfxDevice.getDevice(),
|
||||
&viewInfo,
|
||||
nullptr,
|
||||
&faceViews[face]) != VK_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Failed to create cubemap face image view.");
|
||||
&faceViews[face]));
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("HDRI image id = {}", m_hdrImage);
|
||||
spdlog::info("HDRI image id = {}", m_hdrImage);
|
||||
|
||||
RenderToCubemap(
|
||||
gfxDevice,
|
||||
resources,
|
||||
m_hdrImage,
|
||||
cubeMapImage.image,
|
||||
faceViews,
|
||||
m_cubeMapSize
|
||||
);
|
||||
RenderToCubemap(
|
||||
gfxDevice,
|
||||
resources,
|
||||
m_hdrImage,
|
||||
cubeMapImage.image,
|
||||
faceViews,
|
||||
m_cubeMapSize
|
||||
);
|
||||
|
||||
m_cubemapImageID = resources.addImageToCache(std::move(cubeMapImage));
|
||||
|
||||
for (VkImageView view : faceViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(gfxDevice.getDevice(), view, nullptr);
|
||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||
cubeMapImage.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, face);
|
||||
}
|
||||
|
||||
m_cubemapImageID = resources.addImageToCache(std::move(cubeMapImage));
|
||||
|
||||
for (VkImageView& view : faceViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(gfxDevice.getDevice(), view, nullptr);
|
||||
view = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
for (VkImageView& view : faceViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(gfxDevice.getDevice(), view, nullptr);
|
||||
}
|
||||
}
|
||||
gfxDevice.destroyImage(cubeMapImage);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,4 +321,4 @@ void CubeMap::InitCubemapPipeline(
|
||||
fragPath,
|
||||
pipelineConfig
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <destrum/Graphics/Resources/NBuffer.h>
|
||||
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Managers/MemoryManager.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
@@ -18,29 +19,37 @@ void NBuffer::init(
|
||||
assert(FRAMES_IN_FLIGHT > 0);
|
||||
assert(dataSize > 0);
|
||||
|
||||
framesInFlight = FRAMES_IN_FLIGHT;
|
||||
gpuBufferSize = dataSize;
|
||||
try {
|
||||
framesInFlight = FRAMES_IN_FLIGHT;
|
||||
gpuBufferSize = dataSize;
|
||||
memoryManager = &gfxDevice.getMemoryManager();
|
||||
|
||||
gpuBuffer = gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuBuffer.buffer, debugName.c_str());
|
||||
gpuBuffer = gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuBuffer.buffer, debugName.c_str());
|
||||
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
stagingBuffers.push_back(gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST));
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
stagingBuffers.push_back(gfxDevice.createBuffer(
|
||||
dataSize, usage | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST));
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void NBuffer::cleanup(GfxDevice& device)
|
||||
{
|
||||
for (const auto& stagingBuffer : stagingBuffers) {
|
||||
for (auto& stagingBuffer : stagingBuffers) {
|
||||
device.destroyBuffer(stagingBuffer);
|
||||
}
|
||||
stagingBuffers.clear();
|
||||
|
||||
device.destroyBuffer(gpuBuffer);
|
||||
gpuBuffer = {};
|
||||
memoryManager = nullptr;
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
@@ -66,7 +75,7 @@ void NBuffer::uploadNewData(
|
||||
const auto bufferBarrier = VkBufferMemoryBarrier2{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_READ_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||
.buffer = gpuBuffer.buffer,
|
||||
@@ -84,6 +93,10 @@ void NBuffer::uploadNewData(
|
||||
auto& staging = stagingBuffers[frameIndex];
|
||||
auto* mappedData = reinterpret_cast<std::uint8_t*>(staging.info.pMappedData);
|
||||
memcpy((void*)&mappedData[offset], newData, dataSize);
|
||||
memoryManager->flushAllocation(
|
||||
staging,
|
||||
static_cast<VkDeviceSize>(offset),
|
||||
static_cast<VkDeviceSize>(dataSize));
|
||||
|
||||
const auto region = VkBufferCopy2{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2,
|
||||
@@ -91,6 +104,12 @@ void NBuffer::uploadNewData(
|
||||
.dstOffset = (VkDeviceSize)offset,
|
||||
.size = dataSize,
|
||||
};
|
||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
cmd,
|
||||
staging.buffer,
|
||||
static_cast<VkDeviceSize>(offset),
|
||||
static_cast<VkDeviceSize>(dataSize));
|
||||
|
||||
const auto bufCopyInfo = VkCopyBufferInfo2{
|
||||
.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2,
|
||||
.srcBuffer = staging.buffer,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include <format>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Graphics/Swapchain.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Init.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include <volk.h>
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
|
||||
void Swapchain::initSync(VkDevice device) {
|
||||
@@ -23,31 +24,39 @@ void Swapchain::initSync(VkDevice device) {
|
||||
}
|
||||
}
|
||||
|
||||
void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync) {
|
||||
void Swapchain::createSwapchain(
|
||||
VkDevice,
|
||||
vkb::Device vkbDevice,
|
||||
VkSurfaceKHR surf,
|
||||
VkFormat format,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
bool vSync)
|
||||
{
|
||||
ZoneScopedN("Swapchain::createSwapchain");
|
||||
|
||||
m_gfxDevice = gfxDevice;
|
||||
assert(format == VK_FORMAT_B8G8R8A8_SRGB && "TODO: test other formats");
|
||||
// vSync = true;
|
||||
surface = surf;
|
||||
|
||||
{
|
||||
ZoneScopedN("vkb::SwapchainBuilder::build");
|
||||
|
||||
auto res = vkb::SwapchainBuilder{gfxDevice->getDevice()}
|
||||
auto res = vkb::SwapchainBuilder{vkbDevice, surface}
|
||||
.set_desired_format(VkSurfaceFormatKHR{
|
||||
.format = format,
|
||||
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||
})
|
||||
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.add_image_usage_flags(
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.set_desired_present_mode(
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR
|
||||
: VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
.set_desired_extent(width, height)
|
||||
.build();
|
||||
|
||||
if (!res.has_value()) {
|
||||
// throw std::runtime_error(std::format(
|
||||
// "failed to create swapchain: error = {}, vk result = {}",
|
||||
// res.full_error().type.message(),
|
||||
// string_VkResult(res.full_error().vk_result)));
|
||||
throw std::runtime_error(
|
||||
"Failed to create swapchain: " + res.error().message());
|
||||
}
|
||||
m_swapchain = res.value();
|
||||
}
|
||||
@@ -55,8 +64,13 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
|
||||
{
|
||||
ZoneScopedN("Get Swapchain Images / Views");
|
||||
|
||||
images = m_swapchain.get_images().value();
|
||||
imageViews = m_swapchain.get_image_views().value();
|
||||
const auto imageResult = m_swapchain.get_images();
|
||||
const auto viewResult = m_swapchain.get_image_views();
|
||||
if (!imageResult.has_value() || !viewResult.has_value()) {
|
||||
throw std::runtime_error("Failed to retrieve swapchain images or views");
|
||||
}
|
||||
images = imageResult.value();
|
||||
imageViews = viewResult.value();
|
||||
}
|
||||
|
||||
imageRenderSemaphores.resize(images.size());
|
||||
@@ -65,20 +79,20 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
|
||||
};
|
||||
|
||||
for (auto& sem: imageRenderSemaphores) {
|
||||
for (auto& sem : imageRenderSemaphores) {
|
||||
ZoneScopedN("Create Image Render Semaphore");
|
||||
|
||||
VK_CHECK(vkCreateSemaphore(m_gfxDevice->getDevice(), &sci, nullptr, &sem));
|
||||
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
||||
}
|
||||
|
||||
// TODO: if re-creation of swapchain is supported, don't forget to call
|
||||
// vkutil::initSwapchainViews here.
|
||||
|
||||
extent = m_swapchain.extent;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Swapchain::recreateSwapchain(
|
||||
const GfxDevice& gfxDevice,
|
||||
VkDevice,
|
||||
vkb::Device vkbDevice,
|
||||
VkSurfaceKHR surf,
|
||||
VkFormat format,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
@@ -91,11 +105,11 @@ void Swapchain::recreateSwapchain(
|
||||
return;
|
||||
}
|
||||
|
||||
VkDevice device = gfxDevice.getDevice();
|
||||
surface = surf;
|
||||
|
||||
{
|
||||
ZoneScopedN("vkDeviceWaitIdle");
|
||||
vkDeviceWaitIdle(device);
|
||||
vkDeviceWaitIdle(vkbDevice);
|
||||
}
|
||||
|
||||
auto oldSwapchain = m_swapchain;
|
||||
@@ -103,23 +117,24 @@ void Swapchain::recreateSwapchain(
|
||||
{
|
||||
ZoneScopedN("vkb::SwapchainBuilder::rebuild");
|
||||
|
||||
auto res = vkb::SwapchainBuilder{gfxDevice.getVkbDevice()}
|
||||
auto res = vkb::SwapchainBuilder{vkbDevice, surface}
|
||||
.set_old_swapchain(oldSwapchain)
|
||||
.set_desired_format(VkSurfaceFormatKHR{
|
||||
.format = format,
|
||||
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||
})
|
||||
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.add_image_usage_flags(
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
.set_desired_present_mode(
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
vSync ? VK_PRESENT_MODE_FIFO_KHR
|
||||
: VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
.set_desired_extent(width, height)
|
||||
.build();
|
||||
|
||||
if (!res.has_value()) {
|
||||
// throw std::runtime_error(std::format(
|
||||
// "failed to create swapchain: error = {}, vk result = {}",
|
||||
// res.full_error().type.message(),
|
||||
// string_VkResult(res.full_error().vk_result)));
|
||||
throw std::runtime_error(
|
||||
"Failed to recreate swapchain: " + res.error().message());
|
||||
}
|
||||
|
||||
m_swapchain = res.value();
|
||||
@@ -129,7 +144,7 @@ void Swapchain::recreateSwapchain(
|
||||
ZoneScopedN("Destroy Old Image Render Semaphores");
|
||||
|
||||
for (auto sem : imageRenderSemaphores) {
|
||||
vkDestroySemaphore(device, sem, nullptr);
|
||||
vkDestroySemaphore(vkbDevice, sem, nullptr);
|
||||
}
|
||||
imageRenderSemaphores.clear();
|
||||
}
|
||||
@@ -138,7 +153,7 @@ void Swapchain::recreateSwapchain(
|
||||
ZoneScopedN("Destroy Old Image Views");
|
||||
|
||||
for (auto imageView : imageViews) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
vkDestroyImageView(vkbDevice, imageView, nullptr);
|
||||
}
|
||||
imageViews.clear();
|
||||
}
|
||||
@@ -151,8 +166,13 @@ void Swapchain::recreateSwapchain(
|
||||
{
|
||||
ZoneScopedN("Get New Swapchain Images / Views");
|
||||
|
||||
images = m_swapchain.get_images().value();
|
||||
imageViews = m_swapchain.get_image_views().value();
|
||||
const auto imageResult = m_swapchain.get_images();
|
||||
const auto viewResult = m_swapchain.get_image_views();
|
||||
if (!imageResult.has_value() || !viewResult.has_value()) {
|
||||
throw std::runtime_error("Failed to retrieve recreated swapchain images or views");
|
||||
}
|
||||
images = imageResult.value();
|
||||
imageViews = viewResult.value();
|
||||
}
|
||||
|
||||
VkSemaphoreCreateInfo sci{
|
||||
@@ -164,57 +184,68 @@ void Swapchain::recreateSwapchain(
|
||||
for (auto& sem : imageRenderSemaphores) {
|
||||
ZoneScopedN("Create New Image Render Semaphore");
|
||||
|
||||
VK_CHECK(vkCreateSemaphore(device, &sci, nullptr, &sem));
|
||||
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
||||
}
|
||||
|
||||
extent = m_swapchain.extent;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Swapchain::cleanup() {
|
||||
for (auto& frame: frames) {
|
||||
vkDestroyFence(m_gfxDevice->getDevice(), frame.renderFence, nullptr);
|
||||
vkDestroySemaphore(m_gfxDevice->getDevice(), frame.swapchainSemaphore, nullptr);
|
||||
} {
|
||||
// destroy swapchain and its views
|
||||
for (auto imageView: imageViews) {
|
||||
vkDestroyImageView(m_gfxDevice->getDevice(), imageView, nullptr);
|
||||
void Swapchain::cleanup(VkDevice device) {
|
||||
for (auto& frame : frames) {
|
||||
if (frame.renderFence != VK_NULL_HANDLE) {
|
||||
vkDestroyFence(device, frame.renderFence, nullptr);
|
||||
frame.renderFence = VK_NULL_HANDLE;
|
||||
}
|
||||
if (frame.swapchainSemaphore != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(device, frame.swapchainSemaphore, nullptr);
|
||||
frame.swapchainSemaphore = VK_NULL_HANDLE;
|
||||
}
|
||||
imageViews.clear();
|
||||
|
||||
vkb::destroy_swapchain(m_swapchain);
|
||||
}
|
||||
|
||||
for (auto& semaphore : imageRenderSemaphores) {
|
||||
if (semaphore != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(device, semaphore, nullptr);
|
||||
semaphore = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
imageRenderSemaphores.clear();
|
||||
|
||||
for (auto imageView : imageViews) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
}
|
||||
imageViews.clear();
|
||||
|
||||
vkb::destroy_swapchain(m_swapchain);
|
||||
m_swapchain = {};
|
||||
images.clear();
|
||||
extent = {};
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Swapchain::beginFrame(int index) const {
|
||||
void Swapchain::beginFrame(VkDevice device, int index) const {
|
||||
ZoneScopedN("Swapchain::beginFrame");
|
||||
|
||||
auto& frame = frames[index];
|
||||
|
||||
{
|
||||
ZoneScopedN("vkWaitForFences");
|
||||
VK_CHECK(vkWaitForFences(m_gfxDevice->getDevice(), 1, &frame.renderFence, true, std::numeric_limits<std::uint64_t>::max()));
|
||||
VK_CHECK(vkWaitForFences(device, 1, &frame.renderFence, true, std::numeric_limits<std::uint64_t>::max()));
|
||||
}
|
||||
}
|
||||
|
||||
void Swapchain::resetFences(int index) const {
|
||||
void Swapchain::resetFences(VkDevice device, int index) const {
|
||||
ZoneScopedN("Swapchain::resetFences");
|
||||
|
||||
auto& frame = frames[index];
|
||||
|
||||
{
|
||||
ZoneScopedN("vkResetFences");
|
||||
VK_CHECK(vkResetFences(m_gfxDevice->getDevice(), 1, &frame.renderFence));
|
||||
VK_CHECK(vkResetFences(device, 1, &frame.renderFence));
|
||||
}
|
||||
}
|
||||
|
||||
struct SwapchainAcquireResult {
|
||||
VkResult result = VK_SUCCESS;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
uint32_t imageIndex = 0;
|
||||
};
|
||||
|
||||
std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||
Swapchain::AcquireResult Swapchain::acquireNextImage(VkDevice device, std::uint32_t index) {
|
||||
ZoneScopedN("Swapchain::acquireNextImage");
|
||||
|
||||
std::uint32_t swapchainImageIndex{};
|
||||
@@ -225,7 +256,7 @@ std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||
ZoneScopedN("vkAcquireNextImageKHR");
|
||||
|
||||
result = vkAcquireNextImageKHR(
|
||||
m_gfxDevice->getDevice(),
|
||||
device,
|
||||
m_swapchain,
|
||||
std::numeric_limits<std::uint64_t>::max(),
|
||||
frames[index].swapchainSemaphore,
|
||||
@@ -233,29 +264,42 @@ std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||
&swapchainImageIndex);
|
||||
}
|
||||
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||
dirty = true;
|
||||
return {images[swapchainImageIndex], swapchainImageIndex};
|
||||
} else if (result != VK_SUCCESS) {
|
||||
return {.result = result};
|
||||
}
|
||||
if (result == VK_SUBOPTIMAL_KHR) {
|
||||
dirty = true;
|
||||
return {
|
||||
.result = result,
|
||||
.image = images.at(swapchainImageIndex),
|
||||
.imageIndex = swapchainImageIndex,
|
||||
};
|
||||
}
|
||||
if (result != VK_SUCCESS) {
|
||||
throw std::runtime_error("failed to acquire swap chain image!");
|
||||
}
|
||||
|
||||
return {images[swapchainImageIndex], swapchainImageIndex};
|
||||
return {
|
||||
.result = result,
|
||||
.image = images.at(swapchainImageIndex),
|
||||
.imageIndex = swapchainImageIndex,
|
||||
};
|
||||
}
|
||||
|
||||
void Swapchain::submitAndPresent(
|
||||
VkDevice device,
|
||||
VkCommandBuffer cmd,
|
||||
VkQueue graphicsQueue,
|
||||
uint32_t imageIndex, // from vkAcquireNextImageKHR
|
||||
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
|
||||
VkQueue presentQueue,
|
||||
std::uint32_t imageIndex,
|
||||
std::uint32_t frameIndex)
|
||||
{
|
||||
ZoneScopedN("Swapchain::submitAndPresent");
|
||||
|
||||
auto& frame = frames[frameIndex]; // ✅ per-frame
|
||||
auto& frame = frames[frameIndex];
|
||||
VkSemaphore renderFinished = imageRenderSemaphores[imageIndex];
|
||||
|
||||
VkSemaphore renderFinished = imageRenderSemaphores[imageIndex]; // ✅ per-image
|
||||
|
||||
// submit
|
||||
VkCommandBufferSubmitInfo cmdInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
|
||||
.commandBuffer = cmd,
|
||||
@@ -263,38 +307,51 @@ void Swapchain::submitAndPresent(
|
||||
|
||||
VkSemaphoreSubmitInfo waitInfo =
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
||||
frame.swapchainSemaphore); // ✅ acquire semaphore (per-frame)
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
frame.swapchainSemaphore);
|
||||
|
||||
VkSemaphoreSubmitInfo signalInfo =
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
|
||||
renderFinished); // ✅ signal semaphore (per-image)
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
renderFinished);
|
||||
|
||||
VkSubmitInfo2 submit = vkinit::submitInfo(&cmdInfo, &waitInfo, &signalInfo);
|
||||
|
||||
VK_CHECK(vkResetFences(device, 1, &frame.renderFence));
|
||||
|
||||
{
|
||||
ZoneScopedN("vkQueueSubmit2");
|
||||
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence)); // ✅ fence (per-frame)
|
||||
const VkResult submitResult = vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence);
|
||||
if (submitResult != VK_SUCCESS) {
|
||||
vkDestroyFence(device, frame.renderFence, nullptr);
|
||||
constexpr VkFenceCreateInfo fenceInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
VK_CHECK(vkCreateFence(device, &fenceInfo, nullptr, &frame.renderFence));
|
||||
checkVkResult(submitResult, "vkQueueSubmit2", __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// present
|
||||
VkPresentInfoKHR presentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &renderFinished,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &m_swapchain.swapchain,
|
||||
.pImageIndices = &imageIndex, // ✅ imageIndex, NOT frameIndex
|
||||
.pImageIndices = &imageIndex,
|
||||
};
|
||||
|
||||
VkResult res = VK_SUCCESS;
|
||||
|
||||
{
|
||||
ZoneScopedN("vkQueuePresentKHR");
|
||||
res = vkQueuePresentKHR(graphicsQueue, &presentInfo);
|
||||
res = vkQueuePresentKHR(presentQueue, &presentInfo);
|
||||
}
|
||||
|
||||
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) dirty = true;
|
||||
else if (res != VK_SUCCESS) dirty = true;
|
||||
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) {
|
||||
dirty = true;
|
||||
} else if (res != VK_SUCCESS) {
|
||||
throw std::runtime_error("failed to present swap chain image");
|
||||
}
|
||||
}
|
||||
|
||||
+120
-81
@@ -6,23 +6,87 @@
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
void vkutil::transitionImage(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout) {
|
||||
VkImageAspectFlags aspectMask =
|
||||
(currentLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL)
|
||||
? VK_IMAGE_ASPECT_DEPTH_BIT
|
||||
: VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
namespace {
|
||||
VkImageAspectFlags AspectForLayout(VkImageLayout currentLayout, VkImageLayout newLayout)
|
||||
{
|
||||
return (currentLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||
newLayout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL)
|
||||
? VK_IMAGE_ASPECT_DEPTH_BIT
|
||||
: VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
|
||||
struct LayoutSync {
|
||||
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
|
||||
VkAccessFlags2 access{VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
};
|
||||
|
||||
LayoutSync SyncForLayout(VkImageLayout layout, bool source)
|
||||
{
|
||||
switch (layout) {
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
return {VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, 0};
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
return {VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT};
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
return {VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT};
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
return {
|
||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT};
|
||||
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
|
||||
return {
|
||||
VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT,
|
||||
VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT};
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL:
|
||||
return {VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_READ_BIT};
|
||||
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
||||
return {VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_READ_BIT};
|
||||
default:
|
||||
return source
|
||||
? LayoutSync{}
|
||||
: LayoutSync{VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vkutil::transitionImage(
|
||||
VkCommandBuffer cmd,
|
||||
VkImage image,
|
||||
VkImageLayout currentLayout,
|
||||
VkImageLayout newLayout)
|
||||
{
|
||||
transitionImage(
|
||||
cmd,
|
||||
image,
|
||||
currentLayout,
|
||||
newLayout,
|
||||
vkinit::imageSubresourceRange(AspectForLayout(currentLayout, newLayout)));
|
||||
}
|
||||
|
||||
void vkutil::transitionImage(
|
||||
VkCommandBuffer cmd,
|
||||
VkImage image,
|
||||
VkImageLayout currentLayout,
|
||||
VkImageLayout newLayout,
|
||||
const VkImageSubresourceRange& subresourceRange)
|
||||
{
|
||||
const LayoutSync sourceSync = SyncForLayout(currentLayout, true);
|
||||
const LayoutSync destinationSync = SyncForLayout(newLayout, false);
|
||||
VkImageMemoryBarrier2 imageBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT,
|
||||
.srcStageMask = sourceSync.stage,
|
||||
.srcAccessMask = sourceSync.access,
|
||||
.dstStageMask = destinationSync.stage,
|
||||
.dstAccessMask = destinationSync.access,
|
||||
.oldLayout = currentLayout,
|
||||
.newLayout = newLayout,
|
||||
.image = image,
|
||||
.subresourceRange = vkinit::imageSubresourceRange(aspectMask),
|
||||
.subresourceRange = subresourceRange,
|
||||
};
|
||||
|
||||
VkDependencyInfo depInfo{
|
||||
@@ -101,6 +165,28 @@ void vkutil::bufferHostWriteToShaderReadBarrier(
|
||||
vkCmdPipelineBarrier2(cmd, &dep);
|
||||
}
|
||||
|
||||
void vkutil::bufferHostWriteToTransferReadBarrier(
|
||||
VkCommandBuffer cmd,
|
||||
VkBuffer buffer,
|
||||
VkDeviceSize offset,
|
||||
VkDeviceSize size)
|
||||
{
|
||||
VkBufferMemoryBarrier2 barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2};
|
||||
barrier.srcStageMask = VK_PIPELINE_STAGE_2_HOST_BIT;
|
||||
barrier.srcAccessMask = VK_ACCESS_2_HOST_WRITE_BIT;
|
||||
barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
|
||||
barrier.buffer = buffer;
|
||||
barrier.offset = offset;
|
||||
barrier.size = size;
|
||||
|
||||
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
|
||||
dep.bufferMemoryBarrierCount = 1;
|
||||
dep.pBufferMemoryBarriers = &barrier;
|
||||
|
||||
vkCmdPipelineBarrier2(cmd, &dep);
|
||||
}
|
||||
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
||||
@@ -109,7 +195,7 @@ void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
||||
.objectHandle = (std::uint64_t)image,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
|
||||
@@ -119,7 +205,7 @@ void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const c
|
||||
.objectHandle = (std::uint64_t)shaderModule,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||
@@ -129,7 +215,7 @@ void vkutil::addDebugLabel(VkDevice device, VkPipeline pipeline, const char* lab
|
||||
.objectHandle = (std::uint64_t)pipeline,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||
@@ -139,7 +225,7 @@ void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label)
|
||||
.objectHandle = (std::uint64_t)buffer,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
||||
@@ -149,7 +235,7 @@ void vkutil::addDebugLabel(VkDevice device, VkSampler sampler, const char* label
|
||||
.objectHandle = (std::uint64_t)sampler,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params) {
|
||||
@@ -207,15 +293,19 @@ vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params
|
||||
VkShaderModule vkutil::loadShaderModule(const std::filesystem::path& path, VkDevice device) {
|
||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
spdlog::error("failed to open shader");
|
||||
std::exit(1);
|
||||
throw std::runtime_error("Failed to open shader: " + path.string());
|
||||
}
|
||||
|
||||
const auto fileSize = file.tellg();
|
||||
if (fileSize < 0 || fileSize % static_cast<std::streamoff>(sizeof(std::uint32_t)) != 0) {
|
||||
throw std::runtime_error("Shader file has an invalid size: " + path.string());
|
||||
}
|
||||
std::vector<std::uint32_t> buffer(fileSize / sizeof(std::uint32_t));
|
||||
|
||||
file.seekg(0);
|
||||
file.read((char*)buffer.data(), fileSize);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), fileSize)) {
|
||||
throw std::runtime_error("Failed to read shader: " + path.string());
|
||||
}
|
||||
file.close();
|
||||
|
||||
auto info = VkShaderModuleCreateInfo{
|
||||
@@ -224,11 +314,8 @@ VkShaderModule vkutil::loadShaderModule(const std::filesystem::path& path, VkDev
|
||||
.pCode = buffer.data(),
|
||||
};
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
if (vkCreateShaderModule(device, &info, nullptr, &shaderModule) != VK_SUCCESS) {
|
||||
spdlog::error("Failed to load");
|
||||
std::exit(1);
|
||||
}
|
||||
VkShaderModule shaderModule{VK_NULL_HANDLE};
|
||||
VK_CHECK(vkCreateShaderModule(device, &info, nullptr, &shaderModule));
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
@@ -239,7 +326,7 @@ void addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
||||
.objectHandle = (std::uint64_t)image,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label) {
|
||||
@@ -249,7 +336,7 @@ void addDebugLabel(VkDevice device, VkImageView imageView, const char* label) {
|
||||
.objectHandle = (std::uint64_t)imageView,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
|
||||
@@ -259,7 +346,7 @@ void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* lab
|
||||
.objectHandle = (std::uint64_t)shaderModule,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||
@@ -269,7 +356,7 @@ void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||
.objectHandle = (std::uint64_t)pipeline,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label) {
|
||||
@@ -279,7 +366,7 @@ void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label)
|
||||
.objectHandle = (std::uint64_t)layout,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||
@@ -289,7 +376,7 @@ void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||
.objectHandle = (std::uint64_t)buffer,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
||||
@@ -299,57 +386,9 @@ void addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
||||
.objectHandle = (std::uint64_t)sampler,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params) {
|
||||
assert(
|
||||
(params.colorImageView || params.depthImageView != nullptr) &&
|
||||
"Either draw or depth image should be present");
|
||||
assert(
|
||||
params.renderExtent.width != 0.f && params.renderExtent.height != 0.f &&
|
||||
"renderExtent not specified");
|
||||
|
||||
vkutil::RenderInfo ri;
|
||||
if (params.colorImageView) {
|
||||
ri.colorAttachment = VkRenderingAttachmentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
|
||||
.imageView = params.colorImageView,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
.loadOp = params.colorImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
};
|
||||
if (params.colorImageClearValue) {
|
||||
const auto col = params.colorImageClearValue.value();
|
||||
ri.colorAttachment.clearValue.color = {col[0], col[1], col[2], col[3]};
|
||||
}
|
||||
}
|
||||
|
||||
if (params.depthImageView) {
|
||||
ri.depthAttachment = VkRenderingAttachmentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
|
||||
.imageView = params.depthImageView,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
|
||||
.loadOp = params.depthImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
};
|
||||
if (params.depthImageClearValue) {
|
||||
ri.depthAttachment.clearValue.depthStencil.depth = params.depthImageClearValue.value();
|
||||
}
|
||||
}
|
||||
|
||||
ri.renderingInfo = VkRenderingInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
|
||||
.renderArea =
|
||||
VkRect2D{
|
||||
.offset = {},
|
||||
.extent = params.renderExtent,
|
||||
},
|
||||
.layerCount = 1,
|
||||
.colorAttachmentCount = params.colorImageView ? 1u : 0u,
|
||||
.pColorAttachments = params.colorImageView ? &ri.colorAttachment : nullptr,
|
||||
.pDepthAttachment = params.depthImageView ? &ri.depthAttachment : nullptr,
|
||||
};
|
||||
|
||||
return ri;
|
||||
return vkutil::createRenderingInfo(params);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <stdexcept>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
|
||||
Component::Component(GameObject& pParent, const std::string& name)
|
||||
: Object(name),
|
||||
@@ -14,6 +16,7 @@ Transform& Component::GetTransform() const {
|
||||
|
||||
void Component::Destroy() {
|
||||
Object::Destroy();
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
void Component::Start() {
|
||||
@@ -21,12 +24,30 @@ void Component::Start() {
|
||||
|
||||
void Component::SetEnabled(bool enabled) {
|
||||
m_IsEnabled = enabled;
|
||||
NotifyPhysicsChanged();
|
||||
}
|
||||
|
||||
void Component::LateUpdate() {
|
||||
void Component::NotifyPhysicsChanged() {
|
||||
if (m_ParentGameObjectPtr != nullptr) {
|
||||
if (auto* rigidbody = m_ParentGameObjectPtr->GetComponent<Rigidbody>();
|
||||
rigidbody != nullptr && rigidbody->GetPhysicsWorld() != nullptr) {
|
||||
rigidbody->GetPhysicsWorld()->RefreshRigidbody(*rigidbody);
|
||||
} else {
|
||||
m_ParentGameObjectPtr->RefreshPhysics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Component::FixedUpdate() {
|
||||
void Component::Update(float dt) {
|
||||
m_LastUpdateDeltaTime = dt;
|
||||
}
|
||||
|
||||
void Component::LateUpdate(float dt) {
|
||||
m_LastUpdateDeltaTime = dt;
|
||||
}
|
||||
|
||||
void Component::FixedUpdate(float fixedDt) {
|
||||
m_LastFixedDeltaTime = fixedDt;
|
||||
}
|
||||
|
||||
void Component::ImGuiInspector() {
|
||||
@@ -35,5 +56,5 @@ void Component::ImGuiInspector() {
|
||||
void Component::ImGuiRender() {
|
||||
}
|
||||
|
||||
void Component::Render(const RenderContext& ctx) {
|
||||
}
|
||||
void Component::Render(const RenderContext&) {
|
||||
}
|
||||
|
||||
@@ -1,32 +1,72 @@
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] std::vector<Component*> SnapshotComponents(const GameObject& object) {
|
||||
std::vector<Component*> components;
|
||||
components.reserve(object.GetComponents().size());
|
||||
for (const auto& component : object.GetComponents()) {
|
||||
if (component != nullptr) {
|
||||
components.push_back(component.get());
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
bool EnsureComponentStarted(Component& component) {
|
||||
if (!component.HasStarted) {
|
||||
component.Start();
|
||||
component.HasStarted = true;
|
||||
}
|
||||
return !component.IsBeingDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
GameObject::~GameObject() {
|
||||
// spdlog::debug("GameObject destroyed: {}", GetName());
|
||||
if (!IsBeingDestroyed()) {
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
GameObject::GameObject(const std::string& name)
|
||||
: Object(name),
|
||||
m_Id(s_NextId++) {
|
||||
m_Id(AllocateId()) {
|
||||
}
|
||||
|
||||
void GameObject::SetIdForDeserialization(ObjectId id) {
|
||||
if (id == InvalidObjectId || id >= std::numeric_limits<ObjectId>::max() - 1) {
|
||||
throw std::out_of_range("Object ID is reserved or cannot be incremented");
|
||||
}
|
||||
|
||||
m_Id = id;
|
||||
|
||||
// Prevent newly created objects from reusing loaded IDs.
|
||||
if (id >= s_NextId) {
|
||||
if (id != std::numeric_limits<ObjectId>::max() && id >= s_NextId) {
|
||||
s_NextId = id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectId GameObject::AllocateId() {
|
||||
if (s_NextId == InvalidObjectId ||
|
||||
s_NextId >= std::numeric_limits<ObjectId>::max() - 1) {
|
||||
throw std::overflow_error("Object ID range exhausted");
|
||||
}
|
||||
return s_NextId++;
|
||||
}
|
||||
|
||||
void GameObject::SetActiveDirty() {
|
||||
m_ActiveDirty = true;
|
||||
|
||||
for (const Transform* child : m_TransformPtr.GetChildren()) {
|
||||
child->GetOwner()->SetActiveDirty();
|
||||
if (child != nullptr && child->GetOwner() != nullptr) {
|
||||
child->GetOwner()->SetActiveDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +75,10 @@ void GameObject::UpdateActiveState() {
|
||||
|
||||
if (parentPtr == nullptr) {
|
||||
m_ActiveInHierarchy = m_Active;
|
||||
} else {
|
||||
} else if (parentPtr->GetOwner() != nullptr) {
|
||||
m_ActiveInHierarchy = m_Active && parentPtr->GetOwner()->IsActiveInHierarchy();
|
||||
} else {
|
||||
m_ActiveInHierarchy = m_Active;
|
||||
}
|
||||
|
||||
m_ActiveDirty = false;
|
||||
@@ -50,61 +92,112 @@ bool GameObject::IsActiveInHierarchy() {
|
||||
return m_ActiveInHierarchy;
|
||||
}
|
||||
|
||||
void GameObject::Update() {
|
||||
for (const auto& component : m_Components) {
|
||||
void GameObject::Update(float dt) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component->HasStarted) {
|
||||
component->Start();
|
||||
component->HasStarted = true;
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
component->Update();
|
||||
component->Update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::LateUpdate() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (component->isEnabled()) {
|
||||
component->LateUpdate();
|
||||
void GameObject::LateUpdate(float dt) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->isEnabled() && !component->IsBeingDestroyed()) {
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
component->LateUpdate(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::FixedUpdate() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (component->isEnabled()) {
|
||||
component->FixedUpdate();
|
||||
void GameObject::FixedUpdate(float fixedDt) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->isEnabled() && !component->IsBeingDestroyed()) {
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
component->FixedUpdate(fixedDt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::Render(const RenderContext& ctx) const {
|
||||
for (const auto& component : m_Components) {
|
||||
if (component->isEnabled()) {
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component->isEnabled() && !component->IsBeingDestroyed()) {
|
||||
if (!EnsureComponentStarted(*component)) {
|
||||
continue;
|
||||
}
|
||||
component->Render(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::Destroy() {
|
||||
if (IsBeingDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object::Destroy();
|
||||
|
||||
if (m_Scene != nullptr) {
|
||||
m_Scene->GetPhysics().UnregisterGameObject(*this);
|
||||
}
|
||||
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (auto* rigidbody = dynamic_cast<Rigidbody*>(component);
|
||||
rigidbody != nullptr && rigidbody->GetPhysicsWorld() != nullptr) {
|
||||
rigidbody->GetPhysicsWorld()->UnregisterRigidbody(*rigidbody);
|
||||
}
|
||||
}
|
||||
|
||||
m_TransformPtr.SetParent(nullptr);
|
||||
|
||||
for (const auto& component : m_Components) {
|
||||
component->Destroy();
|
||||
for (Component* component : SnapshotComponents(*this)) {
|
||||
if (component != nullptr) {
|
||||
component->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto child : m_TransformPtr.GetChildren()) {
|
||||
child->GetOwner()->Destroy();
|
||||
// Destroying a child detaches it from this transform, so iterate over a
|
||||
// snapshot rather than the live child vector.
|
||||
const std::vector<Transform*> children = m_TransformPtr.GetChildren();
|
||||
for (Transform* child : children) {
|
||||
if (child != nullptr && child->GetOwner() != nullptr) {
|
||||
child->GetOwner()->Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::CleanupComponents() {
|
||||
for (const auto& component : m_Components) {
|
||||
if (!component || !component->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* rigidbody = dynamic_cast<Rigidbody*>(component.get()); rigidbody != nullptr &&
|
||||
rigidbody->GetPhysicsWorld() != nullptr) {
|
||||
rigidbody->GetPhysicsWorld()->UnregisterRigidbody(*rigidbody);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(m_Components, [](const std::unique_ptr<Component>& component) {
|
||||
return component->IsBeingDestroyed();
|
||||
return component == nullptr || component->IsBeingDestroyed();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::RefreshPhysics() {
|
||||
if (m_Scene != nullptr) {
|
||||
m_Scene->RefreshPhysics();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
#include <destrum/ObjectModel/Object.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
Object::~Object() {
|
||||
if (!m_BeingDestroyed) {
|
||||
assert(false && "Objects destructor called before destroy");
|
||||
}
|
||||
// spdlog::debug("Object Destroyed: {}", m_Name);
|
||||
}
|
||||
|
||||
void Object::Destroy() {
|
||||
// spdlog::debug("Object marked for destruction: {}", m_Name);
|
||||
if (m_BeingDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_BeingDestroyed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,119 @@
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
namespace {
|
||||
bool IsChildRecursive(const Transform* parent,
|
||||
const Transform* target,
|
||||
std::unordered_set<const Transform*>& visited) {
|
||||
if (parent == nullptr || !visited.insert(parent).second) {
|
||||
return false;
|
||||
}
|
||||
for (const Transform* candidate : parent->GetChildren()) {
|
||||
if (candidate == target || IsChildRecursive(candidate, target, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] glm::mat4 ComposeMatrix(const glm::vec3& position,
|
||||
const glm::quat& rotation,
|
||||
const glm::vec3& scale) {
|
||||
return glm::translate(glm::mat4{1.0f}, position)
|
||||
* glm::mat4_cast(rotation)
|
||||
* glm::scale(glm::mat4{1.0f}, scale);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DecomposeMatrix(const glm::mat4& matrix,
|
||||
glm::vec3& position,
|
||||
glm::quat& rotation,
|
||||
glm::vec3& scale) {
|
||||
glm::vec3 skew{};
|
||||
glm::vec4 perspective{};
|
||||
if (!glm::decompose(matrix, scale, rotation, position, skew, perspective)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (glm::length(skew) > 0.0001f ||
|
||||
glm::length(perspective - glm::vec4{0.0f, 0.0f, 0.0f, 1.0f}) > 0.0001f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float rotationLength = glm::length(rotation);
|
||||
if (rotationLength <= 0.000001f || !std::isfinite(rotationLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rotation = glm::normalize(rotation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Transform::Transform(GameObject* owner): m_Owner(owner) {
|
||||
}
|
||||
|
||||
Transform::~Transform() {
|
||||
SetParent(nullptr);
|
||||
if (m_Parent != nullptr) {
|
||||
Transform* parent = m_Parent;
|
||||
m_Parent = nullptr;
|
||||
parent->RemoveChild(this);
|
||||
}
|
||||
|
||||
for (auto it = m_Children.begin(); it != m_Children.end();) {
|
||||
Transform* child = *it;
|
||||
it = m_Children.erase(it);
|
||||
child->SetParent(nullptr);
|
||||
// Do not call SetParent while the owning object is being torn down. The
|
||||
// parent may already be in its destructor, so update the links directly.
|
||||
const std::vector<Transform*> children = std::move(m_Children);
|
||||
m_Children.clear();
|
||||
for (Transform* child : children) {
|
||||
if (child == nullptr || child->m_Parent != this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
child->m_Parent = nullptr;
|
||||
child->SetPositionDirty();
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3& Transform::GetWorldPosition() {
|
||||
if (m_PositionDirty) {
|
||||
UpdateWorldPosition();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldPosition;
|
||||
}
|
||||
|
||||
const glm::quat& Transform::GetWorldRotation() {
|
||||
if (m_RotationDirty) {
|
||||
UpdateWorldRotation();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldRotation;
|
||||
}
|
||||
|
||||
const glm::vec3& Transform::GetWorldScale() {
|
||||
if (m_ScaleDirty) {
|
||||
UpdateWorldScale();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldScale;
|
||||
}
|
||||
|
||||
const glm::mat4& Transform::GetWorldMatrix() {
|
||||
if (m_MatrixDirty) {
|
||||
UpdateWorldMatrix();
|
||||
if (m_PositionDirty || m_RotationDirty || m_ScaleDirty || m_MatrixDirty) {
|
||||
UpdateWorldCache();
|
||||
}
|
||||
return m_WorldMatrix;
|
||||
}
|
||||
|
||||
void Transform::SetWorldPosition(const glm::vec3& position) {
|
||||
if (m_Parent == nullptr) {
|
||||
SetLocalPosition(position);
|
||||
} else {
|
||||
SetLocalPosition(position - m_Parent->GetWorldPosition());
|
||||
}
|
||||
glm::mat4 worldMatrix = GetWorldMatrix();
|
||||
worldMatrix[3] = glm::vec4(position, 1.0f);
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
}
|
||||
|
||||
void Transform::SetWorldPosition(float x, float y, float z) {
|
||||
@@ -55,11 +121,10 @@ void Transform::SetWorldPosition(float x, float y, float z) {
|
||||
}
|
||||
|
||||
void Transform::SetWorldRotation(const glm::quat& rotation) {
|
||||
if(m_Parent == nullptr) {
|
||||
SetLocalRotation(rotation);
|
||||
} else {
|
||||
SetLocalRotation(glm::inverse(m_Parent->GetWorldRotation()) * rotation);
|
||||
}
|
||||
const glm::vec3 worldPosition = GetWorldPosition();
|
||||
const glm::vec3 worldScale = GetWorldScale();
|
||||
const glm::mat4 worldMatrix = ComposeMatrix(worldPosition, rotation, worldScale);
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
}
|
||||
|
||||
void Transform::SetWorldRotation(const glm::vec3& rotation) {
|
||||
@@ -75,11 +140,10 @@ void Transform::SetWorldScale(double x, double y, double z) {
|
||||
}
|
||||
|
||||
void Transform::SetWorldScale(const glm::vec3& scale) {
|
||||
if (m_Parent == nullptr) {
|
||||
SetLocalScale(scale);
|
||||
} else {
|
||||
SetLocalScale(scale - m_Parent->GetWorldScale());
|
||||
}
|
||||
const glm::vec3 worldPosition = GetWorldPosition();
|
||||
const glm::quat worldRotation = GetWorldRotation();
|
||||
const glm::mat4 worldMatrix = ComposeMatrix(worldPosition, worldRotation, scale);
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
}
|
||||
|
||||
void Transform::Move(const glm::vec3& move) {
|
||||
@@ -87,7 +151,7 @@ void Transform::Move(const glm::vec3& move) {
|
||||
}
|
||||
|
||||
void Transform::Move(double x, double y, double z) {
|
||||
this->Move(glm::vec3(x, y, z));
|
||||
Move(glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
void Transform::SetLocalPosition(const glm::vec3& position) {
|
||||
@@ -108,7 +172,10 @@ void Transform::SetLocalRotation(const glm::vec3& rotation) {
|
||||
}
|
||||
|
||||
void Transform::SetLocalRotation(const glm::quat& rotation) {
|
||||
m_LocalRotation = rotation;
|
||||
const float length = glm::length(rotation);
|
||||
m_LocalRotation = length > 0.000001f
|
||||
? glm::normalize(rotation)
|
||||
: glm::quat{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
SetRotationDirty();
|
||||
}
|
||||
|
||||
@@ -122,132 +189,196 @@ void Transform::SetLocalScale(const glm::vec3& scale) {
|
||||
}
|
||||
|
||||
void Transform::RemoveChild(Transform* transform) {
|
||||
std::erase(m_Children, transform);
|
||||
const auto it = std::find(m_Children.begin(), m_Children.end(), transform);
|
||||
if (it != m_Children.end()) {
|
||||
m_Children.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::AddChild(Transform* transform) {
|
||||
// if (transform == this or transform == nullptr) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (transform->m_Parent) {
|
||||
// transform->m_Parent->RemoveChild(transform);
|
||||
// }
|
||||
//
|
||||
// transform->SetParent(this);
|
||||
m_Children.push_back(transform);
|
||||
//
|
||||
// transform->SetPositionDirty();
|
||||
}
|
||||
|
||||
void Transform::SetParent(Transform* parent, bool useWorldPosition) {
|
||||
if (parent == m_Parent or parent == this or IsChild(parent)) {
|
||||
if (transform == nullptr || transform == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent == nullptr) {
|
||||
SetLocalPosition(GetWorldPosition());
|
||||
} else {
|
||||
if (std::find(m_Children.begin(), m_Children.end(), transform) != m_Children.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Children.push_back(transform);
|
||||
}
|
||||
|
||||
void Transform::SetParent(Transform* parent, bool useWorldPosition) {
|
||||
if (parent == m_Parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A transform cannot be parented to itself or to one of its descendants.
|
||||
if (parent == this || IsChild(parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent != nullptr) {
|
||||
GameObject* parentOwner = parent->GetOwner();
|
||||
if (parentOwner == nullptr || parentOwner->IsBeingDestroyed() ||
|
||||
(m_Owner != nullptr && m_Owner->GetScene() != parentOwner->GetScene())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 worldMatrix{1.0f};
|
||||
if (useWorldPosition) {
|
||||
worldMatrix = GetWorldMatrix();
|
||||
}
|
||||
|
||||
Transform* previousParent = m_Parent;
|
||||
if (previousParent != nullptr) {
|
||||
previousParent->RemoveChild(this);
|
||||
}
|
||||
|
||||
m_Parent = parent;
|
||||
if (m_Parent != nullptr) {
|
||||
m_Parent->AddChild(this);
|
||||
}
|
||||
|
||||
if (m_Owner != nullptr) {
|
||||
m_Owner->SetActiveDirty();
|
||||
m_Owner->RefreshPhysics();
|
||||
}
|
||||
|
||||
try {
|
||||
if (useWorldPosition) {
|
||||
SetLocalPosition(GetWorldPosition() - parent->GetWorldPosition());
|
||||
SetLocalFromWorldMatrix(worldMatrix);
|
||||
} else {
|
||||
SetPositionDirty();
|
||||
}
|
||||
} catch (...) {
|
||||
if (m_Parent != nullptr) {
|
||||
m_Parent->RemoveChild(this);
|
||||
}
|
||||
m_Parent = previousParent;
|
||||
if (m_Parent != nullptr) {
|
||||
m_Parent->AddChild(this);
|
||||
}
|
||||
SetPositionDirty();
|
||||
}
|
||||
if (m_Parent) {
|
||||
m_Parent->RemoveChild(this);
|
||||
}
|
||||
m_Parent = parent;
|
||||
if (m_Parent) {
|
||||
m_Parent->AddChild(this);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Transform::IsChild(Transform* child) const {
|
||||
return std::ranges::find(m_Children, child) != m_Children.end();
|
||||
if (child == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::unordered_set<const Transform*> visited;
|
||||
return IsChildRecursive(this, child, visited);
|
||||
}
|
||||
|
||||
const std::vector<Transform*>& Transform::GetChildren() const {
|
||||
// std::vector<Transform*> validChildren;
|
||||
// for (auto* child : m_Children) {
|
||||
// if (child && !child->GetOwner()->IsBeingDestroyed()) {
|
||||
// validChildren.push_back(child);
|
||||
// }
|
||||
// }
|
||||
// return validChildren;
|
||||
return m_Children;
|
||||
}
|
||||
|
||||
GameObject *Transform::GetOwner() const {
|
||||
GameObject* Transform::GetOwner() const {
|
||||
return m_Owner;
|
||||
}
|
||||
|
||||
void Transform::SetPositionDirty() {
|
||||
m_PositionDirty = true;
|
||||
m_RotationDirty = true;
|
||||
m_ScaleDirty = true;
|
||||
m_MatrixDirty = true;
|
||||
for (const auto child: m_Children) {
|
||||
child->SetPositionDirty();
|
||||
|
||||
for (Transform* child : m_Children) {
|
||||
if (child != nullptr) {
|
||||
child->SetPositionDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::SetRotationDirty() {
|
||||
m_PositionDirty = true;
|
||||
m_RotationDirty = true;
|
||||
m_ScaleDirty = true;
|
||||
m_MatrixDirty = true;
|
||||
|
||||
for(Transform* childPtr : m_Children) {
|
||||
if(not childPtr->m_RotationDirty) {
|
||||
childPtr->SetRotationDirty();
|
||||
for (Transform* child : m_Children) {
|
||||
if (child != nullptr) {
|
||||
child->SetRotationDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::SetScaleDirty() {
|
||||
m_PositionDirty = true;
|
||||
m_RotationDirty = true;
|
||||
m_ScaleDirty = true;
|
||||
m_MatrixDirty = true;
|
||||
|
||||
for(Transform* childPtr : m_Children) {
|
||||
if(not childPtr->m_ScaleDirty) {
|
||||
childPtr->SetScaleDirty();
|
||||
for (Transform* child : m_Children) {
|
||||
if (child != nullptr) {
|
||||
child->SetScaleDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldPosition() {
|
||||
if (m_Parent) {
|
||||
m_WorldPosition = m_Parent->GetWorldPosition() + m_LocalPosition;
|
||||
} else {
|
||||
m_WorldPosition = m_LocalPosition;
|
||||
}
|
||||
m_PositionDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldRotation() {
|
||||
if (m_Parent == nullptr) {
|
||||
m_WorldRotation = m_LocalRotation;
|
||||
} else {
|
||||
m_WorldRotation = m_LocalRotation * m_Parent->GetWorldRotation();
|
||||
}
|
||||
|
||||
m_RotationDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldScale() {
|
||||
if(m_Parent == nullptr) {
|
||||
m_WorldScale = m_LocalScale;
|
||||
} else {
|
||||
m_WorldScale = m_LocalScale * m_Parent->GetWorldScale();
|
||||
}
|
||||
|
||||
m_ScaleDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldMatrix() {
|
||||
const glm::mat4 trans = glm::translate(glm::mat4(1.0f), GetWorldPosition());
|
||||
const glm::mat4 rot = glm::mat4_cast(GetWorldRotation());
|
||||
const glm::mat4 scale = glm::scale(glm::mat4(1.0f), GetWorldScale());
|
||||
|
||||
m_WorldMatrix = trans * rot * scale;
|
||||
m_MatrixDirty = false;
|
||||
UpdateWorldCache();
|
||||
}
|
||||
|
||||
void Transform::SetLocalFromWorldMatrix(const glm::mat4& worldMatrix) {
|
||||
glm::mat4 localMatrix = worldMatrix;
|
||||
if (m_Parent != nullptr) {
|
||||
const glm::mat4 parentMatrix = m_Parent->GetWorldMatrix();
|
||||
if (std::abs(glm::determinant(parentMatrix)) <= 0.000001f) {
|
||||
throw std::runtime_error("Cannot set a world transform under a singular parent");
|
||||
}
|
||||
localMatrix = glm::inverse(parentMatrix) * worldMatrix;
|
||||
}
|
||||
|
||||
glm::vec3 position{};
|
||||
glm::quat rotation{};
|
||||
glm::vec3 scale{};
|
||||
if (DecomposeMatrix(localMatrix, position, rotation, scale)) {
|
||||
m_LocalPosition = position;
|
||||
m_LocalRotation = rotation;
|
||||
m_LocalScale = scale;
|
||||
} else {
|
||||
throw std::runtime_error("World transform cannot be represented as position/rotation/scale");
|
||||
}
|
||||
|
||||
SetPositionDirty();
|
||||
}
|
||||
|
||||
void Transform::UpdateWorldCache() {
|
||||
if (!m_PositionDirty && !m_RotationDirty && !m_ScaleDirty && !m_MatrixDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const glm::mat4 localMatrix = ComposeMatrix(m_LocalPosition, m_LocalRotation, m_LocalScale);
|
||||
m_WorldMatrix = m_Parent != nullptr
|
||||
? m_Parent->GetWorldMatrix() * localMatrix
|
||||
: localMatrix;
|
||||
|
||||
if (!DecomposeMatrix(m_WorldMatrix, m_WorldPosition, m_WorldRotation, m_WorldScale)) {
|
||||
m_WorldPosition = glm::vec3(m_WorldMatrix[3]);
|
||||
m_WorldRotation = m_Parent != nullptr
|
||||
? glm::normalize(m_Parent->GetWorldRotation() * m_LocalRotation)
|
||||
: m_LocalRotation;
|
||||
m_WorldScale = m_LocalScale;
|
||||
}
|
||||
|
||||
m_PositionDirty = false;
|
||||
m_RotationDirty = false;
|
||||
m_ScaleDirty = false;
|
||||
m_MatrixDirty = false;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <Jolt/Physics/Collision/RayCast.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/PhysicsSystem.h>
|
||||
#include <Jolt/RegisterTypes.h>
|
||||
@@ -333,8 +334,24 @@ public:
|
||||
{
|
||||
ShapeRefC shape = CreateJoltShape(desc.shape);
|
||||
|
||||
// This first backend ignores centerOffset for now.
|
||||
// Later, use JPH::OffsetCenterOfMassShapeSettings for offset colliders.
|
||||
// Jolt's offset shape stores the offset from the shape to the center
|
||||
// of mass, so negate the component's center offset. This keeps the
|
||||
// body transform aligned with the owning GameObject.
|
||||
if (glm::length2(desc.shape.centerOffset) > 0.0000001f)
|
||||
{
|
||||
OffsetCenterOfMassShapeSettings offsetSettings(
|
||||
ToJoltVec3(-desc.shape.centerOffset),
|
||||
shape.GetPtr());
|
||||
ShapeSettings::ShapeResult result = offsetSettings.Create();
|
||||
|
||||
if (!result.IsValid())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string("Failed to create Jolt offset shape: ") + std::string(result.GetError()));
|
||||
}
|
||||
|
||||
shape = result.Get();
|
||||
}
|
||||
|
||||
BodyCreationSettings settings(
|
||||
shape,
|
||||
@@ -391,6 +408,7 @@ public:
|
||||
bodyInterface.RemoveBody(it->second.bodyID);
|
||||
bodyInterface.DestroyBody(it->second.bodyID);
|
||||
|
||||
it->second.desc.owner = nullptr;
|
||||
m_Bodies.erase(it);
|
||||
}
|
||||
|
||||
@@ -416,7 +434,8 @@ public:
|
||||
|
||||
for (auto& [handleId, record] : m_Bodies)
|
||||
{
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Kinematic || !record.desc.owner)
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Kinematic || !record.desc.owner ||
|
||||
record.desc.owner->IsBeingDestroyed() || !record.desc.owner->IsActiveInHierarchy())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -437,7 +456,8 @@ public:
|
||||
|
||||
for (auto& [handleId, record] : m_Bodies)
|
||||
{
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Dynamic || !record.desc.owner)
|
||||
if (!record.alive || record.desc.type != RigidbodyType::Dynamic || !record.desc.owner ||
|
||||
record.desc.owner->IsBeingDestroyed() || !record.desc.owner->IsActiveInHierarchy())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ PhysicsSceneBridge::PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world)
|
||||
|
||||
void PhysicsSceneBridge::RegisterGameObject(GameObject& object) {
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
if (!rb->HasPhysicsBody()) {
|
||||
if (!rb->HasPhysicsBody() || rb->GetPhysicsWorld() != &GetWorld()) {
|
||||
m_World->RegisterRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,18 @@ void PhysicsSceneBridge::RegisterGameObject(GameObject& object) {
|
||||
|
||||
void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) {
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
if (rb->HasPhysicsBody()) {
|
||||
if (rb->GetPhysicsWorld() == m_World.get()) {
|
||||
m_World->UnregisterRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::RefreshGameObject(GameObject& object) {
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
m_World->RefreshRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::FixedUpdate(float fixedDt) {
|
||||
m_World->SyncKinematicBodiesToPhysics();
|
||||
m_World->Step(fixedDt);
|
||||
|
||||
@@ -1,20 +1,59 @@
|
||||
#include <destrum/Physics/PhysicsWorld.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
|
||||
PhysicsWorld::~PhysicsWorld() {
|
||||
// Derived backends have already released their body storage by the time
|
||||
// the base destructor runs. Clear component-side handles without calling
|
||||
// a virtual backend method from here.
|
||||
for (Rigidbody* rigidbody : m_RegisteredRigidbodies) {
|
||||
if (rigidbody != nullptr && rigidbody->GetPhysicsWorld() == this) {
|
||||
rigidbody->DetachPhysicsBody();
|
||||
}
|
||||
}
|
||||
m_RegisteredRigidbodies.clear();
|
||||
}
|
||||
|
||||
void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
|
||||
if (rigidbody.IsBeingDestroyed() || !rigidbody.isEnabled()) {
|
||||
if (rigidbody.GetPhysicsWorld() == this) {
|
||||
UnregisterRigidbody(rigidbody);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (rigidbody.GetPhysicsWorld() != nullptr) {
|
||||
if (rigidbody.GetPhysicsWorld() == this && rigidbody.HasPhysicsBody()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicsWorld* previousWorld = rigidbody.GetPhysicsWorld();
|
||||
if (previousWorld != this) {
|
||||
previousWorld->UnregisterRigidbody(rigidbody);
|
||||
} else {
|
||||
rigidbody.DetachPhysicsBody();
|
||||
}
|
||||
}
|
||||
|
||||
GameObject* owner = rigidbody.GetGameObject();
|
||||
if (owner == nullptr || owner->IsBeingDestroyed() || !owner->IsActiveInHierarchy()) {
|
||||
if (rigidbody.GetPhysicsWorld() == this) {
|
||||
UnregisterRigidbody(rigidbody);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Transform& transform = owner->GetTransform();
|
||||
|
||||
auto* collider = owner->GetComponent<Collider>();
|
||||
if (!collider) {
|
||||
throw std::runtime_error("Rigidbody requires a Collider on the same GameObject for now.");
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicsBodyDesc desc{};
|
||||
@@ -23,6 +62,10 @@ void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
|
||||
desc.transform.rotation = transform.GetWorldRotation();
|
||||
desc.shape = collider->BuildPhysicsShape();
|
||||
|
||||
// Collider dimensions are expressed in world units. Render meshes may
|
||||
// use a transform scale to normalize imported asset units, so applying
|
||||
// that scale here would shrink the physics shape a second time.
|
||||
|
||||
desc.type = rigidbody.GetType();
|
||||
desc.mass = rigidbody.GetMass();
|
||||
desc.useGravity = rigidbody.UsesGravity();
|
||||
@@ -31,32 +74,52 @@ void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
|
||||
desc.material.restitution = rigidbody.GetRestitution();
|
||||
|
||||
PhysicsBodyHandle handle = CreateBody(desc);
|
||||
if (!handle.IsValid()) {
|
||||
rigidbody.DetachPhysicsBody();
|
||||
return;
|
||||
}
|
||||
|
||||
rigidbody.AttachPhysicsBody(this, handle);
|
||||
m_RegisteredRigidbodies.insert(&rigidbody);
|
||||
}
|
||||
|
||||
void PhysicsWorld::RefreshRigidbody(Rigidbody& rigidbody) {
|
||||
const bool hadBody = rigidbody.GetPhysicsWorld() == this && rigidbody.HasPhysicsBody();
|
||||
const glm::vec3 previousVelocity = hadBody
|
||||
? GetLinearVelocity(rigidbody.GetBody())
|
||||
: glm::vec3{0.0f};
|
||||
|
||||
if (rigidbody.GetPhysicsWorld() == this) {
|
||||
UnregisterRigidbody(rigidbody);
|
||||
}
|
||||
RegisterRigidbody(rigidbody);
|
||||
|
||||
if (hadBody && rigidbody.HasPhysicsBody()) {
|
||||
SetLinearVelocity(rigidbody.GetBody(), previousVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsWorld::UnregisterRigidbody(Rigidbody& rigidbody) {
|
||||
PhysicsBodyHandle handle = rigidbody.GetBody();
|
||||
if (rigidbody.GetPhysicsWorld() != this) {
|
||||
m_RegisteredRigidbodies.erase(&rigidbody);
|
||||
return;
|
||||
}
|
||||
|
||||
const PhysicsBodyHandle handle = rigidbody.GetBody();
|
||||
if (handle.IsValid()) {
|
||||
DestroyBody(handle);
|
||||
}
|
||||
|
||||
m_RegisteredRigidbodies.erase(&rigidbody);
|
||||
rigidbody.DetachPhysicsBody();
|
||||
}
|
||||
|
||||
void PhysicsWorld::SyncKinematicBodiesToPhysics() {
|
||||
// Backend-independent sync is intentionally not possible here because
|
||||
// PhysicsWorld does not own the list of active rigidbodies.
|
||||
//
|
||||
// Use SimplePhysicsWorld as a reference implementation.
|
||||
//
|
||||
// If you use Jolt/PhysX/Bullet later, keep a body table in the backend:
|
||||
// handle -> { GameObject*, Rigidbody* }.
|
||||
}
|
||||
|
||||
void PhysicsWorld::SyncDynamicBodiesToTransforms() {
|
||||
// Backend-independent sync is intentionally not possible here because
|
||||
// PhysicsWorld does not own the list of active rigidbodies.
|
||||
//
|
||||
// Use SimplePhysicsWorld as a reference implementation.
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ void SimplePhysicsWorld::DestroyBody(PhysicsBodyHandle body) {
|
||||
if (BodyRecord* record = FindBody(body)) {
|
||||
record->alive = false;
|
||||
record->rigidbody = nullptr;
|
||||
record->desc.owner = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +98,9 @@ void SimplePhysicsWorld::Step(float fixedDt) {
|
||||
}
|
||||
|
||||
for (BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive) {
|
||||
if (!body.alive || !body.desc.owner ||
|
||||
body.desc.owner->IsBeingDestroyed() ||
|
||||
!body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -133,7 +136,8 @@ void SimplePhysicsWorld::Step(float fixedDt) {
|
||||
|
||||
void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() {
|
||||
for (BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner ||
|
||||
body.desc.owner->IsBeingDestroyed() || !body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -147,7 +151,8 @@ void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() {
|
||||
|
||||
void SimplePhysicsWorld::SyncDynamicBodiesToTransforms() {
|
||||
for (BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Dynamic || !body.desc.owner) {
|
||||
if (!body.alive || body.desc.type != RigidbodyType::Dynamic || !body.desc.owner ||
|
||||
body.desc.owner->IsBeingDestroyed() || !body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -176,13 +181,16 @@ bool SimplePhysicsWorld::Raycast(const glm::vec3& origin,
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (const BodyRecord& body : m_Bodies) {
|
||||
if (!body.alive || body.desc.shape.type == PhysicsShapeType::None) {
|
||||
if (!body.alive || body.desc.shape.type == PhysicsShapeType::None ||
|
||||
!body.desc.owner || body.desc.owner->IsBeingDestroyed() ||
|
||||
!body.desc.owner->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = 0.0f;
|
||||
const float radius = GetApproxBoundingRadius(body);
|
||||
const glm::vec3 center = body.currentTransform.position + body.desc.shape.centerOffset;
|
||||
const glm::vec3 center = body.currentTransform.position
|
||||
+ body.currentTransform.rotation * body.desc.shape.centerOffset;
|
||||
|
||||
if (RaySphere(origin, dir, center, radius, maxDistance, distance)) {
|
||||
if (distance < bestDistance) {
|
||||
|
||||
+186
-86
@@ -1,163 +1,263 @@
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <SDL_scancode.h>
|
||||
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
// #include "ServiceLocator.h"
|
||||
// #include "Input/InputManager.h"
|
||||
// #include "Managers/Renderer.h"
|
||||
|
||||
|
||||
unsigned int Scene::m_idCounter = 0;
|
||||
|
||||
Scene::Scene(const std::string& name) : m_name(name) {
|
||||
namespace {
|
||||
class IterationGuard {
|
||||
public:
|
||||
explicit IterationGuard(Scene& scene) : m_Scene(scene) {
|
||||
m_Scene.BeginIteration();
|
||||
}
|
||||
|
||||
~IterationGuard() {
|
||||
m_Scene.EndIteration();
|
||||
}
|
||||
|
||||
private:
|
||||
Scene& m_Scene;
|
||||
};
|
||||
}
|
||||
|
||||
Scene::~Scene() = default;
|
||||
Scene::Scene(const std::string& name)
|
||||
: m_name(name),
|
||||
m_id(++m_idCounter) {
|
||||
}
|
||||
|
||||
// void Scene::Add(std::shared_ptr<GameObject> object) {
|
||||
// // m_objects.emplace_back(std::move(object));
|
||||
// m_pendingAdditions.emplace_back(std::move(object));
|
||||
// }
|
||||
Scene::~Scene() {
|
||||
Unload();
|
||||
RemoveAll();
|
||||
}
|
||||
|
||||
GameObject* Scene::CreateGameObject(std::string name)
|
||||
{
|
||||
auto obj = std::make_shared<GameObject>(std::move(name));
|
||||
|
||||
GameObject* rawPtr = obj.get();
|
||||
|
||||
obj->SetScene(this);
|
||||
m_pendingAdditions.emplace_back(std::move(obj));
|
||||
GameObject* Scene::CreateGameObject(std::string name) {
|
||||
auto object = std::make_shared<GameObject>(std::move(name));
|
||||
GameObject* rawPtr = object.get();
|
||||
|
||||
object->SetScene(this);
|
||||
m_pendingAdditions.emplace_back(std::move(object));
|
||||
return rawPtr;
|
||||
}
|
||||
|
||||
void Scene::Remove(GameObject* object) {
|
||||
std::erase_if(m_objects, [object](const std::shared_ptr<GameObject>& obj) {
|
||||
return obj.get() == object;
|
||||
});
|
||||
if (object == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::erase_if(m_pendingAdditions, [object](const std::shared_ptr<GameObject>& obj) {
|
||||
return obj.get() == object;
|
||||
});
|
||||
const auto containsObject = [object](const auto& objects) {
|
||||
return std::any_of(objects.begin(), objects.end(), [object](const auto& candidate) {
|
||||
return candidate != nullptr && candidate.get() == object;
|
||||
});
|
||||
};
|
||||
|
||||
if (!containsObject(m_objects) && !containsObject(m_pendingAdditions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
object->Destroy();
|
||||
|
||||
if (IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto eraseDestroyed = [](auto& objects) {
|
||||
for (const auto& candidate : objects) {
|
||||
if (candidate != nullptr && candidate->IsBeingDestroyed()) {
|
||||
candidate->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(objects, [](const auto& candidate) {
|
||||
return candidate == nullptr || candidate->IsBeingDestroyed();
|
||||
});
|
||||
};
|
||||
|
||||
// Destroying an object also marks its descendants. Remove all of those
|
||||
// marked entries together, after destruction has finished.
|
||||
eraseDestroyed(m_objects);
|
||||
eraseDestroyed(m_pendingAdditions);
|
||||
}
|
||||
|
||||
void Scene::RemoveAll() {
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
if (IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
object->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
object->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
m_objects.clear();
|
||||
m_pendingAdditions.clear();
|
||||
}
|
||||
|
||||
void Scene::Load() {
|
||||
OnSceneLoaded.Invoke();
|
||||
if (m_registerBindings) {
|
||||
m_registerBindings();
|
||||
}
|
||||
LoadBindings();
|
||||
}
|
||||
|
||||
void Scene::Update() {
|
||||
void Scene::Update(float dt) {
|
||||
CommitPendingAdditions();
|
||||
IterationGuard iteration(*this);
|
||||
|
||||
for (const auto& object : m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
object->Update();
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->Update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Scene::FixedUpdate(float dt) {
|
||||
for (const auto& object: m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
object->FixedUpdate();
|
||||
CommitPendingAdditions();
|
||||
IterationGuard iteration(*this);
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->FixedUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
m_Physics.FixedUpdate(dt);
|
||||
}
|
||||
|
||||
void Scene::LateUpdate() {
|
||||
for (const auto& object: m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
object->LateUpdate();
|
||||
void Scene::LateUpdate(float dt) {
|
||||
IterationGuard iteration(*this);
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->LateUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Scene::Render(const RenderContext& ctx) const {
|
||||
for (const auto& object: m_objects) {
|
||||
if (object->IsActiveInHierarchy()) {
|
||||
void Scene::Render(const RenderContext& ctx) {
|
||||
IterationGuard iteration(*this);
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed() && object->IsActiveInHierarchy()) {
|
||||
object->Render(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Scene::RenderImgui() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Scene::CleanupDestroyedGameObjects() {
|
||||
if (m_BeingUnloaded) {
|
||||
//Scene is gone anyways, kill everything
|
||||
m_objects.clear();
|
||||
if (IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& gameObject: m_objects) {
|
||||
//First check if a gameobjects components needs to be destroyed
|
||||
gameObject->CleanupComponents();
|
||||
const auto cleanup = [](auto& objects) {
|
||||
for (const auto& object : objects) {
|
||||
if (object != nullptr && object->IsBeingDestroyed()) {
|
||||
object->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& object : objects) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed()) {
|
||||
object->CleanupComponents();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& object : objects) {
|
||||
if (object != nullptr && object->IsBeingDestroyed()) {
|
||||
object->SetScene(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(objects, [](const auto& object) {
|
||||
return object == nullptr || object->IsBeingDestroyed();
|
||||
});
|
||||
};
|
||||
|
||||
cleanup(m_objects);
|
||||
cleanup(m_pendingAdditions);
|
||||
}
|
||||
|
||||
void Scene::BeginIteration() {
|
||||
++m_IterationDepth;
|
||||
}
|
||||
|
||||
void Scene::EndIteration() {
|
||||
if (m_IterationDepth == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// //Strange for loop since im deleting during looping over it
|
||||
// for (auto it = m_objects.begin(); it != m_objects.end();) {
|
||||
// if ((*it)->IsBeingDestroyed()) {
|
||||
// it = m_objects.erase(it);
|
||||
// } else {
|
||||
// ++it;
|
||||
// }
|
||||
// }
|
||||
|
||||
std::erase_if(m_objects, [] (const std::shared_ptr<GameObject>& gameObject) {
|
||||
return gameObject->IsBeingDestroyed();
|
||||
});
|
||||
--m_IterationDepth;
|
||||
if (m_IterationDepth == 0) {
|
||||
CleanupDestroyedGameObjects();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::Unload() {
|
||||
if (m_unregisterBindings) {
|
||||
m_unregisterBindings();
|
||||
if (m_BeingUnloaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
UnloadBindings();
|
||||
m_BeingUnloaded = true;
|
||||
}
|
||||
|
||||
void Scene::DestroyGameObjects() {
|
||||
if (m_BeingUnloaded) {
|
||||
|
||||
for (auto& obj : m_pendingAdditions) {
|
||||
m_objects.emplace_back(std::move(obj));
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
m_pendingAdditions.clear();
|
||||
|
||||
//Scene is gone anyways, kill everything
|
||||
for (const auto& gameObject: m_objects) {
|
||||
gameObject->Destroy();
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
object->Destroy();
|
||||
}
|
||||
} else {
|
||||
assert(m_BeingUnloaded && "Scene is being cleared but not unloaded? Weird");
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::CommitPendingAdditions() {
|
||||
if (m_pendingAdditions.empty()) {
|
||||
if (m_pendingAdditions.empty() || IsIterating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& obj : m_pendingAdditions) {
|
||||
m_objects.emplace_back(std::move(obj));
|
||||
for (auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr && !object->IsBeingDestroyed()) {
|
||||
m_Physics.RegisterGameObject(*object);
|
||||
m_objects.emplace_back(std::move(object));
|
||||
}
|
||||
}
|
||||
|
||||
m_pendingAdditions.clear();
|
||||
}
|
||||
std::erase_if(m_pendingAdditions, [](const auto& object) {
|
||||
return object == nullptr || object->IsBeingDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
void Scene::RefreshPhysics() {
|
||||
for (const auto& object : m_objects) {
|
||||
if (object != nullptr) {
|
||||
m_Physics.RefreshGameObject(*object);
|
||||
}
|
||||
}
|
||||
for (const auto& object : m_pendingAdditions) {
|
||||
if (object != nullptr) {
|
||||
m_Physics.RefreshGameObject(*object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,57 @@
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
void SceneManager::Update() {
|
||||
m_scenes[m_ActiveSceneIndex]->Update();
|
||||
Scene& SceneManager::GetCurrentScene() const {
|
||||
if (m_scenes.empty()) {
|
||||
throw std::out_of_range("No scenes are available");
|
||||
}
|
||||
|
||||
if (m_ActiveSceneIndex < 0 || m_ActiveSceneIndex >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Active scene index is invalid");
|
||||
}
|
||||
|
||||
return *m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)];
|
||||
}
|
||||
|
||||
void SceneManager::Update(float dt) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->Update(dt);
|
||||
}
|
||||
|
||||
void SceneManager::FixedUpdate(float dt) {
|
||||
m_scenes[m_ActiveSceneIndex]->FixedUpdate(dt);
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->FixedUpdate(dt);
|
||||
}
|
||||
|
||||
void SceneManager::LateUpdate() {
|
||||
m_scenes[m_ActiveSceneIndex]->LateUpdate();
|
||||
void SceneManager::LateUpdate(float dt) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->LateUpdate(dt);
|
||||
}
|
||||
|
||||
void SceneManager::Render(const RenderContext& ctx) {
|
||||
m_scenes[m_ActiveSceneIndex]->Render(ctx);
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->Render(ctx);
|
||||
}
|
||||
|
||||
void SceneManager::RenderImgui() {
|
||||
m_scenes[m_ActiveSceneIndex]->RenderImgui();
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
scene->RenderImgui();
|
||||
}
|
||||
|
||||
void SceneManager::HandleGameObjectDestroy() {
|
||||
@@ -43,6 +73,12 @@ void SceneManager::UnloadAllScenes() {
|
||||
}
|
||||
|
||||
void SceneManager::HandleSceneDestroy() {
|
||||
const std::shared_ptr<Scene> activeScene =
|
||||
m_ActiveSceneIndex >= 0 &&
|
||||
m_ActiveSceneIndex < static_cast<int>(m_scenes.size())
|
||||
? m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]
|
||||
: nullptr;
|
||||
|
||||
for (auto it = m_scenes.begin(); it != m_scenes.end();) {
|
||||
if ((*it)->IsBeingUnloaded()) {
|
||||
it = m_scenes.erase(it);
|
||||
@@ -50,15 +86,37 @@ void SceneManager::HandleSceneDestroy() {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_scenes.empty()) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeScene != nullptr) {
|
||||
const auto activeIt = std::find(m_scenes.begin(), m_scenes.end(), activeScene);
|
||||
if (activeIt != m_scenes.end()) {
|
||||
m_ActiveSceneIndex = static_cast<int>(std::distance(m_scenes.begin(), activeIt));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_ActiveSceneIndex = std::clamp(
|
||||
m_ActiveSceneIndex,
|
||||
0,
|
||||
static_cast<int>(m_scenes.size()) - 1);
|
||||
}
|
||||
|
||||
void SceneManager::HandleScene() {
|
||||
DestroyGameObjects();
|
||||
HandleGameObjectDestroy();
|
||||
HandleSceneDestroy();
|
||||
}
|
||||
|
||||
void SceneManager::Destroy() {
|
||||
if (m_scenes.empty()) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
UnloadAllScenes();
|
||||
DestroyGameObjects();
|
||||
HandleGameObjectDestroy();
|
||||
@@ -71,13 +129,20 @@ void SceneManager::SwitchScene(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Scene index out of range");
|
||||
}
|
||||
m_scenes[m_ActiveSceneIndex]->UnloadBindings();
|
||||
if (index == m_ActiveSceneIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]->UnloadBindings();
|
||||
m_ActiveSceneIndex = index;
|
||||
m_scenes[m_ActiveSceneIndex]->LoadBindings();
|
||||
m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]->LoadBindings();
|
||||
}
|
||||
|
||||
Scene &SceneManager::CreateScene(const std::string &name) {
|
||||
const auto &scene = std::shared_ptr<Scene>(new Scene(name));
|
||||
const auto scene = std::shared_ptr<Scene>(new Scene(name));
|
||||
m_scenes.push_back(scene);
|
||||
if (m_scenes.size() == 1) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
}
|
||||
return *scene;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
#include <destrum/Components/Physics/CapsuleCollider.h>
|
||||
#include <destrum/Components/Physics/SphereCollider.h>
|
||||
|
||||
void RegisterEngineComponents()
|
||||
@@ -22,21 +23,26 @@ void RegisterEngineComponents()
|
||||
|
||||
registered = true;
|
||||
|
||||
ComponentFactory::Register("MeshRendererComponent", [](GameObject& owner) {
|
||||
return owner.AddComponent<MeshRendererComponent>();
|
||||
});
|
||||
// Keep the old serialized spelling readable while writing the canonical
|
||||
// component name returned by MeshRendererComponent.
|
||||
ComponentFactory::Register("MeshRenderer", [](GameObject& owner) {
|
||||
return owner.AddComponent<MeshRendererComponent>();
|
||||
});
|
||||
|
||||
// ComponentFactory::Register("Rotator", [](GameObject& owner) {
|
||||
// return owner.AddComponent<Rotator>();
|
||||
// });
|
||||
ComponentFactory::Register("Rotator", [](GameObject& owner) {
|
||||
return owner.AddComponent<Rotator>();
|
||||
});
|
||||
|
||||
// ComponentFactory::Register("Spinner", [](GameObject& owner) {
|
||||
// return owner.AddComponent<Spinner>();
|
||||
// });
|
||||
ComponentFactory::Register("Spinner", [](GameObject& owner) {
|
||||
return owner.AddComponent<Spinner>();
|
||||
});
|
||||
|
||||
// ComponentFactory::Register("OrbitAndSpin", [](GameObject& owner) {
|
||||
// return owner.AddComponent<OrbitAndSpin>();
|
||||
// });
|
||||
ComponentFactory::Register("OrbitAndSpin", [](GameObject& owner) {
|
||||
return owner.AddComponent<OrbitAndSpin>();
|
||||
});
|
||||
|
||||
ComponentFactory::Register("Animator", [](GameObject& owner) {
|
||||
return owner.AddComponent<Animator>();
|
||||
@@ -45,6 +51,9 @@ void RegisterEngineComponents()
|
||||
ComponentFactory::Register("Rigidbody", [](GameObject& owner) {
|
||||
return owner.AddComponent<Rigidbody>();
|
||||
});
|
||||
ComponentFactory::Register("RigidBody", [](GameObject& owner) {
|
||||
return owner.AddComponent<Rigidbody>();
|
||||
});
|
||||
|
||||
ComponentFactory::Register("BoxCollider", [](GameObject& owner) {
|
||||
return owner.AddComponent<BoxCollider>();
|
||||
@@ -53,4 +62,8 @@ void RegisterEngineComponents()
|
||||
ComponentFactory::Register("SphereCollider", [](GameObject& owner) {
|
||||
return owner.AddComponent<SphereCollider>();
|
||||
});
|
||||
}
|
||||
|
||||
ComponentFactory::Register("CapsuleCollider", [](GameObject& owner) {
|
||||
return owner.AddComponent<CapsuleCollider>();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,75 +1,278 @@
|
||||
#include <destrum/Serialization/SceneSerializer.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/Component.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/ObjectId.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Serialization/ComponentFactory.h>
|
||||
#include <destrum/Serialization/ComponentRegistry.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] bool IsFiniteNumber(const json& value) {
|
||||
if (!value.is_number()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::isfinite(value.get<double>());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsFiniteArray(const json& value, std::size_t size) {
|
||||
if (!value.is_array() || value.size() != size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& element : value) {
|
||||
if (!IsFiniteNumber(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsFiniteJson(const json& value) {
|
||||
if (value.is_number()) {
|
||||
return IsFiniteNumber(value);
|
||||
}
|
||||
if (value.is_array()) {
|
||||
for (const auto& element : value) {
|
||||
if (!IsFiniteJson(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (value.is_object()) {
|
||||
for (auto it = value.begin(); it != value.end(); ++it) {
|
||||
if (!IsFiniteJson(it.value())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ValidateTransform(const json& objectJson, int version) {
|
||||
if (!objectJson.contains("transform")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& transform = objectJson.at("transform");
|
||||
if (!transform.is_object()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Version 1 wrote an empty placeholder transform object.
|
||||
if (version <= 1 && transform.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!transform.contains("position") || !transform.contains("rotation") ||
|
||||
!transform.contains("scale")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsFiniteArray(transform.at("position"), 3) ||
|
||||
!IsFiniteArray(transform.at("scale"), 3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& rotation = transform.at("rotation");
|
||||
return IsFiniteArray(rotation, 3) || IsFiniteArray(rotation, 4);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ValidateSceneJson(const json& root) {
|
||||
if (!root.is_object() || !root.contains("objects") || !root.at("objects").is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root.contains("name") && !root.at("name").is_string()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int version = 1;
|
||||
if (root.contains("version")) {
|
||||
if (!root.at("version").is_number_integer()) {
|
||||
return false;
|
||||
}
|
||||
version = root.at("version").get<int>();
|
||||
if (version < 1 || version > 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<ObjectId> ids;
|
||||
std::unordered_map<ObjectId, ObjectId> parents;
|
||||
|
||||
for (const auto& objectJson : root.at("objects")) {
|
||||
if (!objectJson.is_object() || !objectJson.contains("id")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ObjectId id = objectJson.at("id").get<ObjectId>();
|
||||
if (id == InvalidObjectId ||
|
||||
id >= std::numeric_limits<ObjectId>::max() - 1 ||
|
||||
!ids.insert(id).second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objectJson.contains("name") && !objectJson.at("name").is_string()) {
|
||||
return false;
|
||||
}
|
||||
if (objectJson.contains("active") && !objectJson.at("active").is_boolean()) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateTransform(objectJson, version)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ObjectId parent = objectJson.value("parent", InvalidObjectId);
|
||||
if (parent == id) {
|
||||
return false;
|
||||
}
|
||||
parents.emplace(id, parent);
|
||||
|
||||
if (objectJson.contains("components")) {
|
||||
const auto& components = objectJson.at("components");
|
||||
if (!components.is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& componentJson : components) {
|
||||
if (!componentJson.is_object() || !componentJson.contains("type") ||
|
||||
!componentJson.at("type").is_string()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (componentJson.contains("enabled") &&
|
||||
!componentJson.at("enabled").is_boolean()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (componentJson.contains("data") &&
|
||||
(!componentJson.at("data").is_object() ||
|
||||
!IsFiniteJson(componentJson.at("data")))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComponentFactory::IsRegistered(componentJson.at("type").get<std::string>())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [id, parent] : parents) {
|
||||
if (parent != InvalidObjectId && !ids.contains(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_set<ObjectId> visited;
|
||||
ObjectId current = id;
|
||||
while (current != InvalidObjectId) {
|
||||
if (!visited.insert(current).second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto parentIt = parents.find(current);
|
||||
if (parentIt == parents.end()) {
|
||||
return false;
|
||||
}
|
||||
current = parentIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
|
||||
scene.CommitPendingAdditions();
|
||||
if (scene.IsIterating()) {
|
||||
std::cerr << "Cannot save a scene during an update or render phase: "
|
||||
<< scene.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterEngineComponents();
|
||||
try {
|
||||
scene.CommitPendingAdditions();
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to commit scene additions before save: "
|
||||
<< exception.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
json root;
|
||||
|
||||
root["version"] = 1;
|
||||
root["version"] = 2;
|
||||
root["name"] = scene.GetName();
|
||||
root["objects"] = json::array();
|
||||
|
||||
for (const auto& objectPtr : scene.GetObjects()) {
|
||||
if (!objectPtr) {
|
||||
if (!objectPtr || objectPtr->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GameObject& object = *objectPtr;
|
||||
if (object.GetComponent<Rigidbody>() != nullptr &&
|
||||
object.GetComponent<Collider>() == nullptr) {
|
||||
std::cerr << "Cannot save object with a Rigidbody but no Collider: "
|
||||
<< object.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
const Transform& transform = object.GetTransform();
|
||||
const glm::vec3& position = transform.GetLocalPosition();
|
||||
const glm::quat& rotation = transform.GetLocalRotation();
|
||||
const glm::vec3& scale = transform.GetLocalScale();
|
||||
|
||||
json objectJson;
|
||||
objectJson["id"] = object.GetId();
|
||||
objectJson["name"] = object.GetName();
|
||||
objectJson["active"] = object.IsActive();
|
||||
|
||||
// Transform should be saved separately because in your engine it is not a component.
|
||||
// Replace this with your actual Transform getters.
|
||||
objectJson["transform"] = {
|
||||
// Example:
|
||||
// { "position", { object.GetTransform().GetLocalPosition().x,
|
||||
// object.GetTransform().GetLocalPosition().y,
|
||||
// object.GetTransform().GetLocalPosition().z } },
|
||||
// { "rotation", { ... } },
|
||||
// { "scale", { ... } }
|
||||
{"position", {position.x, position.y, position.z}},
|
||||
{"rotation", {rotation.x, rotation.y, rotation.z, rotation.w}},
|
||||
{"scale", {scale.x, scale.y, scale.z}}
|
||||
};
|
||||
|
||||
const Transform* parent = object.GetTransform().GetParent();
|
||||
objectJson["parent"] = parent
|
||||
const Transform* parent = transform.GetParent();
|
||||
objectJson["parent"] = parent != nullptr && parent->GetOwner() != nullptr &&
|
||||
parent->GetOwner()->GetScene() == &scene &&
|
||||
!parent->GetOwner()->IsBeingDestroyed()
|
||||
? parent->GetOwner()->GetId()
|
||||
: InvalidObjectId;
|
||||
|
||||
objectJson["components"] = json::array();
|
||||
|
||||
for (const auto& componentPtr : object.GetComponents()) {
|
||||
if (!componentPtr) {
|
||||
if (!componentPtr || componentPtr->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Component& component = *componentPtr;
|
||||
|
||||
json componentJson;
|
||||
componentJson["type"] = component.GetTypeName();
|
||||
componentJson["enabled"] = component.isEnabled();
|
||||
componentJson["data"] = component.Serialize();
|
||||
|
||||
objectJson["components"].push_back(componentJson);
|
||||
objectJson["components"].push_back({
|
||||
{"type", component.GetTypeName()},
|
||||
{"enabled", component.isEnabled()},
|
||||
{"data", component.Serialize()}
|
||||
});
|
||||
}
|
||||
|
||||
root["objects"].push_back(objectJson);
|
||||
root["objects"].push_back(std::move(objectJson));
|
||||
}
|
||||
|
||||
std::ofstream file(path);
|
||||
@@ -79,10 +282,23 @@ bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
file << root.dump(4);
|
||||
if (!file.good()) {
|
||||
std::cerr << "Failed to write scene file: " << path << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
if (scene.IsIterating()) {
|
||||
std::cerr << "Cannot load a scene during an update or render phase: "
|
||||
<< scene.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterEngineComponents();
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open scene file for reading: " << path << '\n';
|
||||
@@ -90,134 +306,166 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
json root;
|
||||
|
||||
try {
|
||||
file >> root;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Failed to parse scene file: " << e.what() << '\n';
|
||||
if (!ValidateSceneJson(root)) {
|
||||
std::cerr << "Invalid scene data: " << path << '\n';
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to parse scene file: " << exception.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
scene.RemoveAll();
|
||||
if (scene.IsBeingUnloaded()) {
|
||||
std::cerr << "Cannot load a scene that is being unloaded: " << scene.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build the replacement separately. The current scene is not touched
|
||||
// until all objects, transforms, and components have loaded successfully.
|
||||
Scene stagingScene(scene.GetName());
|
||||
std::unordered_map<ObjectId, GameObject*> idMap;
|
||||
std::vector<const json*> objectJsonList;
|
||||
std::vector<GameObject*> registeredObjects;
|
||||
|
||||
try {
|
||||
const auto& objectsJson = root.at("objects");
|
||||
|
||||
// Pass 1:
|
||||
// Create all GameObjects first.
|
||||
for (const auto& objectJson : objectsJson) {
|
||||
const auto id = objectJson.at("id").get<ObjectId>();
|
||||
for (const auto& objectJson : root.at("objects")) {
|
||||
const ObjectId id = objectJson.at("id").get<ObjectId>();
|
||||
const std::string name = objectJson.value("name", "GameObject");
|
||||
const bool active = objectJson.value("active", true);
|
||||
|
||||
GameObject* object = scene.CreateGameObject(name);
|
||||
GameObject* object = stagingScene.CreateGameObject(name);
|
||||
object->SetIdForDeserialization(id);
|
||||
object->SetActive(active);
|
||||
|
||||
idMap[id] = object;
|
||||
idMap.emplace(id, object);
|
||||
objectJsonList.push_back(&objectJson);
|
||||
}
|
||||
|
||||
scene.CommitPendingAdditions();
|
||||
stagingScene.CommitPendingAdditions();
|
||||
|
||||
// Pass 2:
|
||||
// Restore transforms and parent-child hierarchy.
|
||||
for (const json* objectJson : objectJsonList) {
|
||||
const auto id = objectJson->at("id").get<ObjectId>();
|
||||
|
||||
const ObjectId id = objectJson->at("id").get<ObjectId>();
|
||||
GameObject* object = idMap.at(id);
|
||||
|
||||
// Replace this with your actual Transform deserialization.
|
||||
if (objectJson->contains("transform")) {
|
||||
const json& transformJson = objectJson->at("transform");
|
||||
if (objectJson->contains("transform") &&
|
||||
!objectJson->at("transform").empty()) {
|
||||
const auto& transformJson = objectJson->at("transform");
|
||||
const auto& position = transformJson.at("position");
|
||||
const auto& rotation = transformJson.at("rotation");
|
||||
const auto& scale = transformJson.at("scale");
|
||||
|
||||
auto position = transformJson.at("position");
|
||||
object->GetTransform().SetLocalPosition({
|
||||
position[0].get<float>(),
|
||||
position[1].get<float>(),
|
||||
position[2].get<float>()
|
||||
position.at(0).get<float>(),
|
||||
position.at(1).get<float>(),
|
||||
position.at(2).get<float>()
|
||||
});
|
||||
|
||||
auto rotation = transformJson.at("rotation");
|
||||
object->GetTransform().SetLocalRotation({
|
||||
rotation[0].get<float>(),
|
||||
rotation[1].get<float>(),
|
||||
rotation[2].get<float>()
|
||||
});
|
||||
if (rotation.size() == 4) {
|
||||
object->GetTransform().SetLocalRotation({
|
||||
rotation.at(3).get<float>(),
|
||||
rotation.at(0).get<float>(),
|
||||
rotation.at(1).get<float>(),
|
||||
rotation.at(2).get<float>()
|
||||
});
|
||||
} else {
|
||||
// Version 1 scene files used three Euler angles in
|
||||
// degrees. Continue reading that format safely.
|
||||
object->GetTransform().SetLocalRotation({
|
||||
rotation.at(0).get<float>(),
|
||||
rotation.at(1).get<float>(),
|
||||
rotation.at(2).get<float>()
|
||||
});
|
||||
}
|
||||
|
||||
auto scale = transformJson.at("scale");
|
||||
object->GetTransform().SetLocalScale({
|
||||
scale[0].get<float>(),
|
||||
scale[1].get<float>(),
|
||||
scale[2].get<float>()
|
||||
scale.at(0).get<float>(),
|
||||
scale.at(1).get<float>(),
|
||||
scale.at(2).get<float>()
|
||||
});
|
||||
}
|
||||
|
||||
const ObjectId parentId = objectJson->value("parent", InvalidObjectId);
|
||||
|
||||
if (parentId != InvalidObjectId) {
|
||||
auto parentIt = idMap.find(parentId);
|
||||
|
||||
if (parentIt != idMap.end()) {
|
||||
object->GetTransform().SetParent(&parentIt->second->GetTransform());
|
||||
} else {
|
||||
std::cerr << "Parent not found while loading object " << id << '\n';
|
||||
}
|
||||
object->GetTransform().SetParent(&idMap.at(parentId)->GetTransform(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3:
|
||||
// Create and deserialize components.
|
||||
for (const json* objectJson : objectJsonList) {
|
||||
const auto id = objectJson->at("id").get<ObjectId>();
|
||||
GameObject* object = idMap.at(id);
|
||||
|
||||
GameObject* object = idMap.at(objectJson->at("id").get<ObjectId>());
|
||||
if (!objectJson->contains("components")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& componentJson : objectJson->at("components")) {
|
||||
const std::string type = componentJson.at("type").get<std::string>();
|
||||
|
||||
Component* component = ComponentFactory::Create(type, *object);
|
||||
|
||||
if (!component) {
|
||||
std::cerr << "Unknown component type while loading scene: " << type << '\n';
|
||||
continue;
|
||||
if (component == nullptr) {
|
||||
throw std::runtime_error("Unknown component type: " + type);
|
||||
}
|
||||
|
||||
component->SetEnabled(componentJson.value("enabled", true));
|
||||
|
||||
if (componentJson.contains("data")) {
|
||||
component->Deserialize(componentJson.at("data"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 4:
|
||||
// Resolve object references now that all objects and components exist.
|
||||
for (const auto& objectPtr : scene.GetObjects()) {
|
||||
for (const auto& objectPtr : stagingScene.GetObjects()) {
|
||||
if (!objectPtr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& componentPtr : objectPtr->GetComponents()) {
|
||||
if (!componentPtr) {
|
||||
continue;
|
||||
if (componentPtr) {
|
||||
componentPtr->ResolveReferences(idMap);
|
||||
}
|
||||
|
||||
componentPtr->ResolveReferences(idMap);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Failed to load scene: " << e.what() << '\n';
|
||||
scene.RemoveAll();
|
||||
// Register the replacement bodies in the live physics world before
|
||||
// touching the current scene. If any body fails, the old scene and
|
||||
// its physics state can remain intact.
|
||||
for (const auto& objectPtr : stagingScene.GetObjects()) {
|
||||
if (objectPtr) {
|
||||
if (objectPtr->GetComponent<Rigidbody>() != nullptr &&
|
||||
objectPtr->GetComponent<Collider>() == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"Rigidbody requires a collider on object " + objectPtr->GetName());
|
||||
}
|
||||
scene.GetPhysics().RegisterGameObject(*objectPtr);
|
||||
registeredObjects.push_back(objectPtr.get());
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to load scene: " << exception.what() << '\n';
|
||||
for (GameObject* object : registeredObjects) {
|
||||
if (object != nullptr) {
|
||||
scene.GetPhysics().UnregisterGameObject(*object);
|
||||
}
|
||||
}
|
||||
stagingScene.RemoveAll();
|
||||
return false;
|
||||
}
|
||||
|
||||
scene.RemoveAll();
|
||||
scene.m_objects = std::move(stagingScene.m_objects);
|
||||
scene.m_pendingAdditions = std::move(stagingScene.m_pendingAdditions);
|
||||
if (root.contains("name")) {
|
||||
scene.m_name = root.at("name").get<std::string>();
|
||||
}
|
||||
|
||||
for (const auto& object : scene.m_objects) {
|
||||
if (object) {
|
||||
object->SetScene(&scene);
|
||||
}
|
||||
}
|
||||
for (const auto& object : scene.m_pendingAdditions) {
|
||||
if (object) {
|
||||
object->SetScene(&scene);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user