feat: add tracy / various fixes

This commit is contained in:
2026-06-24 03:00:53 +02:00
parent 067caf5fe6
commit 9d3d14a54d
33 changed files with 1492 additions and 336 deletions
+3 -1
View File
@@ -56,7 +56,7 @@ set(SRC_FILES
"src/Physics/PhysicsWorld.cpp"
"src/Physics/SimplePhysicsWorld.cpp"
"src/Physics/PhysicsSceneBridge.cpp"
src/Components/Physics/BoxCollider.cpp
"src/Physics/JoltPhysicsWorld.cpp"
)
add_library(destrum ${SRC_FILES})
@@ -89,6 +89,7 @@ target_link_libraries(destrum
assimp
imgui
Jolt
Tracy::TracyClient
PRIVATE
freetype::freetype
@@ -99,6 +100,7 @@ target_compile_definitions(destrum
PUBLIC
VK_NO_PROTOTYPES
VMA_VULKAN_VERSION=1003000
TRACY_VK_USE_SYMBOL_TABLE
# VOLK_DEFAULT_VISIBILITY # FIXME: doesn't work for some reason
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.
+8
View File
@@ -60,6 +60,14 @@ protected:
bool resizePending = false;
std::chrono::steady_clock::time_point lastResizeTime{};
bool m_PhysicsPaused = false;
bool m_PhysicsStepOnce = false;
float m_PhysicsTimeScale = 1.0f;
int m_PhysicsStepsLastFrame = 0;
void drawPhysicsPanel(float dt, float fixedDt, float accumulator);
};
@@ -29,6 +29,13 @@
class MeshCache;
class ImguiPass;
#if defined(TRACY_ENABLE)
namespace tracy { class VkCtx; }
using DestrumTracyVkCtx = tracy::VkCtx*;
#else
using DestrumTracyVkCtx = void*;
#endif
namespace {
using ImmediateExecuteFunction = std::function<void(VkCommandBuffer)>;
@@ -126,12 +133,16 @@ public:
return swapchain.getImageView(imageIndex);
}
DestrumTracyVkCtx getTracyVkCtx() const { return tracyVkCtx; }
private:
vkb::Instance instance;
vkb::PhysicalDevice physicalDevice;
vkb::Device device;
VmaAllocator allocator;
DestrumTracyVkCtx tracyVkCtx = nullptr;
std::uint32_t graphicsQueueFamily;
VkQueue graphicsQueue;
@@ -149,6 +160,8 @@ private:
ImageCache imageCache;
bool m_vSync = false;
static uint32_t BytesPerTexel(VkFormat fmt) {
switch (fmt) {
case VK_FORMAT_R8_UNORM: return 1;
@@ -0,0 +1,61 @@
#pragma once
#include <cstdint>
#include <memory>
#include <glm/glm.hpp>
#include <destrum/Physics/PhysicsWorld.h>
class JoltPhysicsWorld final : public PhysicsWorld {
public:
struct Settings {
std::uint32_t maxBodies{65536};
std::uint32_t numBodyMutexes{0};
std::uint32_t maxBodyPairs{65536};
std::uint32_t maxContactConstraints{10240};
// Jolt recommends a temp allocator for physics update allocations.
// 10 MB is the value used in their HelloWorld sample.
std::uint32_t tempAllocatorSizeBytes{10u * 1024u * 1024u};
// 0 means "hardware_concurrency - 1".
std::uint32_t workerThreadCount{0};
glm::vec3 gravity{0.0f, -9.81f, 0.0f};
};
explicit JoltPhysicsWorld(const Settings& settings = Settings{});
~JoltPhysicsWorld() override;
JoltPhysicsWorld(const JoltPhysicsWorld&) = delete;
JoltPhysicsWorld(JoltPhysicsWorld&&) noexcept = delete;
JoltPhysicsWorld& operator=(const JoltPhysicsWorld&) = delete;
JoltPhysicsWorld& operator=(JoltPhysicsWorld&&) noexcept = delete;
void Step(float fixedDt) override;
void SyncKinematicBodiesToPhysics() override;
void SyncDynamicBodiesToTransforms() override;
PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) override;
void DestroyBody(PhysicsBodyHandle body) override;
void SetBodyTransform(PhysicsBodyHandle body, const PhysicsTransform& transform) override;
PhysicsTransform GetBodyTransform(PhysicsBodyHandle body) const override;
void SetLinearVelocity(PhysicsBodyHandle body, const glm::vec3& velocity) override;
glm::vec3 GetLinearVelocity(PhysicsBodyHandle body) const override;
void AddForce(PhysicsBodyHandle body, const glm::vec3& force) override;
void AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse) override;
bool Raycast(const glm::vec3& origin,
const glm::vec3& direction,
float maxDistance,
PhysicsRaycastHit& hit) const override;
private:
class Impl;
std::unique_ptr<Impl> m_Impl;
};
@@ -25,8 +25,8 @@ public:
// - Kinematic: Transform -> physics body before Step()
// - Dynamic: physics body -> Transform after Step()
void SyncKinematicBodiesToPhysics();
void SyncDynamicBodiesToTransforms();
virtual void SyncKinematicBodiesToPhysics();
virtual void SyncDynamicBodiesToTransforms();
virtual PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) = 0;
virtual void DestroyBody(PhysicsBodyHandle body) = 0;
+2 -1
View File
@@ -6,6 +6,7 @@
#include <destrum/Event.h>
#include <destrum/Scene/SceneManager.h>
#include "destrum/Physics/JoltPhysicsWorld.h"
#include "destrum/Physics/PhysicsSceneBridge.h"
class GameObject;
@@ -68,7 +69,7 @@ public:
private:
explicit Scene(const std::string& name);
PhysicsSceneBridge m_Physics{std::make_unique<SimplePhysicsWorld>()};
PhysicsSceneBridge m_Physics{std::make_unique<JoltPhysicsWorld>()};
std::string m_name;
+216 -78
View File
@@ -10,7 +10,16 @@
#include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h"
#include <Jolt/Jolt.h>
#include <tracy/Tracy.hpp>
#include <common/TracySystem.hpp>
struct TracyFrameScope
{
~TracyFrameScope()
{
FrameMark;
}
};
App::App()
{
@@ -19,7 +28,9 @@ App::App()
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");
@@ -53,16 +64,21 @@ void App::init(const AppParams& params)
void App::run()
{
Time::GetInstance().Update(); // initialize delta timing
ZoneScopedN("App::run");
Time::GetInstance().Update();
const float fixedDt = static_cast<float>(Time::GetInstance().FixedDeltaTime());
const int maxSteps = 5; // prevent spiral of death
const int maxSteps = 5;
float accumulator = 0.0f;
isRunning = true;
while (isRunning)
{
// ---- Update timing ---
TracyFrameScope tracyFrame;
ZoneScopedN("App::Frame");
Time::GetInstance().Update();
float dt = static_cast<float>(Time::GetInstance().DeltaTime());
@@ -72,117 +88,193 @@ void App::run()
{
float newFPS = 1.0f / dt;
avgFPS = std::lerp(avgFPS, newFPS, 0.1f);
TracyPlot("FPS", avgFPS);
TracyPlot("Delta Time ms", dt * 1000.0f);
}
accumulator += dt;
InputManager::GetInstance().BeginFrame();
camera.Update(dt);
SDL_Event event;
while (SDL_PollEvent(&event))
{
imguiPass.handleEvent(event);
if (event.type == SDL_QUIT)
ZoneScopedN("Input BeginFrame + Camera");
InputManager::GetInstance().BeginFrame();
camera.Update(dt);
}
{
ZoneScopedN("SDL Events");
SDL_Event event;
while (SDL_PollEvent(&event))
{
isRunning = false;
break;
}
if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
ZoneScopedN("SDL Event");
imguiPass.handleEvent(event);
if (event.type == SDL_QUIT)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
isRunning = false;
break;
}
if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
resizePending = true;
lastResizeTime = std::chrono::steady_clock::now();
break;
}
}
}
const bool mouseEvent =
event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_MOUSEBUTTONUP ||
event.type == SDL_MOUSEMOTION ||
event.type == SDL_MOUSEWHEEL;
const bool keyboardEvent =
event.type == SDL_KEYDOWN ||
event.type == SDL_KEYUP ||
event.type == SDL_TEXTINPUT;
const bool mouseEvent =
event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_MOUSEBUTTONUP ||
event.type == SDL_MOUSEMOTION ||
event.type == SDL_MOUSEWHEEL;
const bool capturedByImgui =
(mouseEvent && imguiPass.wantsMouse()) ||
(keyboardEvent && imguiPass.wantsKeyboard());
const bool keyboardEvent =
event.type == SDL_KEYDOWN ||
event.type == SDL_KEYUP ||
event.type == SDL_TEXTINPUT;
if (!capturedByImgui)
{
if (InputManager::GetInstance().ProcessEvent(event))
const bool capturedByImgui =
(mouseEvent && imguiPass.wantsMouse()) ||
(keyboardEvent && imguiPass.wantsKeyboard());
if (!capturedByImgui)
{
isRunning = false;
ZoneScopedN("Input ProcessEvent");
if (InputManager::GetInstance().ProcessEvent(event))
{
isRunning = false;
}
}
}
}
if (!isRunning) break;
imguiPass.beginFrame();
customUpdate(dt);
ImGui::Begin("Debug");
ImGui::Text("FPS: %.2f", avgFPS);
ImGui::End();
imguiPass.endFrame();
int steps = 0;
while (accumulator >= fixedDt && steps < maxSteps)
{
// physics.Update(fixedDt);
customFixedUpdate(fixedDt);
// physics.Step(fixedDt);
// physics.SyncTransforms();
ZoneScopedN("ImGui BeginFrame");
imguiPass.beginFrame();
}
accumulator -= fixedDt;
steps++;
{
ZoneScopedN("customUpdate");
customUpdate(dt);
}
{
ZoneScopedN("Debug ImGui");
ImGui::Begin("Debug");
ImGui::Text("FPS: %.2f", avgFPS);
ImGui::End();
drawPhysicsPanel(dt, fixedDt, accumulator);
TracyPlot("FPS", avgFPS);
TracyPlot("Frame Time ms", dt * 1000.0f);
TracyPlot("Accumulator ms", accumulator * 1000.0f);
}
{
ZoneScopedN("ImGui EndFrame");
imguiPass.endFrame();
}
{
ZoneScopedN("FixedUpdate");
int steps = 0;
if (m_PhysicsPaused)
{
// Important: prevent physics from building up a huge backlog while paused.
accumulator = 0.0f;
if (m_PhysicsStepOnce)
{
ZoneScopedN("Single Physics Step");
customFixedUpdate(fixedDt * m_PhysicsTimeScale);
steps = 1;
m_PhysicsStepOnce = false;
}
}
else
{
while (accumulator >= fixedDt && steps < maxSteps)
{
ZoneScopedN("Fixed Step");
customFixedUpdate(fixedDt * m_PhysicsTimeScale);
accumulator -= fixedDt;
steps++;
}
if (steps == maxSteps)
{
accumulator = 0.0f;
}
}
m_PhysicsStepsLastFrame = steps;
TracyPlot("Fixed Steps", static_cast<int64_t>(steps));
}
if (steps == maxSteps) accumulator = 0.0f;
const float alpha = accumulator / fixedDt;
(void)alpha;
if (gfxDevice.needsSwapchainRecreate() || resizePending)
{
auto now = std::chrono::steady_clock::now();
ZoneScopedN("Swapchain Resize Check");
if (resizePending &&
now - lastResizeTime < std::chrono::milliseconds(100))
if (gfxDevice.needsSwapchainRecreate() || resizePending)
{
auto now = std::chrono::steady_clock::now();
if (resizePending &&
now - lastResizeTime < std::chrono::milliseconds(100))
{
continue;
}
int w = 0;
int h = 0;
SDL_Vulkan_GetDrawableSize(window, &w, &h);
if (w == 0 || h == 0)
{
continue;
}
spdlog::info("Recreating swapchain to size: {}x{}", w, h);
{
ZoneScopedN("Recreate Swapchain");
gfxDevice.recreateSwapchain(w, h);
onWindowResize(w, h);
}
resizePending = false;
continue;
}
int w = 0;
int h = 0;
SDL_Vulkan_GetDrawableSize(window, &w, &h);
if (w == 0 || h == 0)
{
continue;
}
spdlog::info("Recreating swapchain to size: {}x{}", w, h);
gfxDevice.recreateSwapchain(w, h);
onWindowResize(w, h);
resizePending = false;
continue;
}
customDraw();
{
ZoneScopedN("customDraw");
customDraw();
}
if (frameLimit)
{
ZoneScopedN("Frame Limit Sleep");
auto sleepTime = Time::GetInstance().SleepDuration();
if (sleepTime.count() > 0)
{
@@ -199,3 +291,49 @@ void App::cleanup()
spdlog::info("Cleaning up");
customCleanup();
}
void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
{
ImGui::Begin("Physics");
ImGui::Text("Frame dt: %.3f ms", dt * 1000.0f);
ImGui::Text("Fixed dt: %.3f ms", fixedDt * 1000.0f);
ImGui::Text("Accumulator: %.3f ms", accumulator * 1000.0f);
ImGui::Text("Steps last frame: %d", m_PhysicsStepsLastFrame);
ImGui::Separator();
if (ImGui::Button(m_PhysicsPaused ? "Resume Physics" : "Pause Physics"))
{
m_PhysicsPaused = !m_PhysicsPaused;
}
ImGui::SameLine();
if (!m_PhysicsPaused)
{
ImGui::BeginDisabled();
ImGui::Button("Step Physics");
ImGui::EndDisabled();
}
else
{
if (ImGui::Button("Step Physics"))
{
m_PhysicsStepOnce = true;
}
}
ImGui::Checkbox("Paused", &m_PhysicsPaused);
ImGui::Separator();
ImGui::SliderFloat("Physics Time Scale", &m_PhysicsTimeScale, 0.0f, 2.0f, "%.2fx");
if (ImGui::Button("Reset Time Scale"))
{
m_PhysicsTimeScale = 1.0f;
}
ImGui::End();
}
+94 -11
View File
@@ -19,13 +19,17 @@
#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"
GfxDevice::GfxDevice(): imageCache(*this) {
}
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)
@@ -131,6 +135,34 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
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(
@@ -166,19 +198,30 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
void GfxDevice::recreateSwapchain(int width, int height) {
assert(width != 0 && height != 0);
waitIdle();
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, true);
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, m_vSync);
}
VkCommandBuffer GfxDevice::beginFrame() {
swapchain.beginFrame(getCurrentFrameIndex());
VkCommandBuffer GfxDevice::beginFrame()
{
ZoneScopedN("GfxDevice::beginFrame");
{
ZoneScopedN("Swapchain BeginFrame");
swapchain.beginFrame(getCurrentFrameIndex());
}
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,
};
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
{
ZoneScopedN("vkBeginCommandBuffer");
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
}
return cmd;
}
@@ -188,17 +231,34 @@ VulkanImmediateExecutor& GfxDevice::GetImmediateExecuter() {
}
void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const EndFrameProps& props) {
ZoneScopedN("GfxDevice::endFrame");
// get swapchain image
const auto [swapchainImage, swapchainImageIndex] = swapchain.acquireNextImage(getCurrentFrameIndex());
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 (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
swapchain.resetFences(getCurrentFrameIndex());
{
ZoneScopedN("Swapchain ResetFences");
swapchain.resetFences(getCurrentFrameIndex());
}
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED; {
ZoneScopedN("Clear Swapchain Image");
const VkImageSubresourceRange clearRange = vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL);
swapchainLayout = VK_IMAGE_LAYOUT_GENERAL;
@@ -208,6 +268,8 @@ 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,
@@ -248,6 +310,8 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
// swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
if (props.imguiPass) {
ZoneScopedN("ImGui Render");
props.imguiPass->render(
cmd,
swapchainImage,
@@ -258,18 +322,37 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
}
// prepare for present
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
{
ZoneScopedN("Transition Swapchain To Present");
VK_CHECK(vkEndCommandBuffer(cmd));
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
}
#if defined(TRACY_ENABLE)
TracyVkCollect(tracyVkCtx, cmd);
#endif
{
ZoneScopedN("vkEndCommandBuffer");
VK_CHECK(vkEndCommandBuffer(cmd));
}
// swapchain.submitAndPresent(cmd, graphicsQueue, getCurrentFrameIndex(), swapchainImageIndex);
swapchain.submitAndPresent(cmd, graphicsQueue, swapchainImageIndex, getCurrentFrameIndex());
{
ZoneScopedN("Swapchain SubmitAndPresent");
swapchain.submitAndPresent(cmd, graphicsQueue, swapchainImageIndex, getCurrentFrameIndex());
}
frameNumber++;
FrameMark;
}
void GfxDevice::cleanup() {
#if defined(TRACY_ENABLE)
if (tracyVkCtx) {
TracyVkDestroy(tracyVkCtx);
tracyVkCtx = nullptr;
}
#endif
}
void GfxDevice::waitIdle() {
@@ -98,9 +98,9 @@ void MeshPipeline::draw(VkCommandBuffer cmd,
for (const auto& dcIdx : drawCommands) {
const auto& dc = dcIdx;
// if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) {
// continue;
// }
if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) {
continue;
}
ActualDrawCalls++;
+75 -29
View File
@@ -5,6 +5,8 @@
#include "destrum/Util/GameState.h"
#include "spdlog/spdlog.h"
#include "tracy/TracyVulkan.hpp"
GameRenderer::GameRenderer(MeshCache& meshCache, MaterialCache& matCache): meshCache{meshCache}, materialCache{matCache} {
}
@@ -41,13 +43,20 @@ void GameRenderer::endDrawing() {
}
void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& camera, const SceneData& sceneData) {
ZoneScopedN("GameRenderer::draw");
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "GameRenderer::draw");
for (const auto& dc : meshDrawCommands) {
if (!dc.skinnedMesh) {
continue;
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Skinning");
for (const auto& dc : meshDrawCommands) {
if (!dc.skinnedMesh) {
continue;
}
skinningPipeline->doSkinning(cmd, gfxDevice.getCurrentFrameIndex(), meshCache, dc);
}
skinningPipeline->doSkinning(cmd, gfxDevice.getCurrentFrameIndex(), meshCache, dc);
}
const auto gpuSceneData = GPUSceneData{
.view = sceneData.camera.GetViewMatrix(),
.proj = sceneData.camera.GetProjectionMatrix(),
@@ -59,40 +68,71 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
.fogDensity = sceneData.fogDensity,
.materialsBuffer = materialCache.getMaterialDataBufferAddress(),
};
vkutil::bufferHostWriteToShaderReadBarrier(
cmd,
materialCache.getMaterialDataBuffer().buffer,
0,
VK_WHOLE_SIZE
);
sceneDataBuffer.uploadNewData(cmd, gfxDevice.getCurrentFrameIndex(), (void*)&gpuSceneData, sizeof(GPUSceneData));
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Material Buffer Barrier");
vkutil::bufferHostWriteToShaderReadBarrier(
cmd,
materialCache.getMaterialDataBuffer().buffer,
0,
VK_WHOLE_SIZE
);
}
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Upload Scene Data");
sceneDataBuffer.uploadNewData(
cmd,
gfxDevice.getCurrentFrameIndex(),
(void*)&gpuSceneData,
sizeof(GPUSceneData)
);
}
const auto& drawImage = gfxDevice.getImage(drawImageId);
const auto& depthImage = gfxDevice.getImage(depthImageId);
// vkutil::cmdBeginLabel(cmd, "Geometry");
vkutil::transitionImage(
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Transition Draw Image");
vkutil::transitionImage(
cmd,
drawImage.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}
vkutil::transitionImage(
cmd,
depthImage.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Transition Depth Image");
vkutil::transitionImage(
cmd,
depthImage.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
}
const 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,
});
.renderExtent = drawImage.getExtent2D(),
.colorImageView = drawImage.imageView,
.colorImageClearValue = glm::vec4{0.f, 0.f, 0.f, 1.f},
.depthImageView = depthImage.imageView,
.depthImageClearValue = 1.f,
});
vkCmdBeginRendering(cmd, &renderInfo.renderingInfo);
meshPipeline->draw(
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "vkCmdBeginRendering");
vkCmdBeginRendering(cmd, &renderInfo.renderingInfo);
}
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "MeshPipeline::draw");
meshPipeline->draw(
cmd,
drawImage.getExtent2D(),
gfxDevice,
@@ -102,14 +142,20 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
sceneDataBuffer.getBuffer(),
meshDrawCommands,
sortedMeshDrawCommands);
}
skyboxPipeline->draw(cmd, gfxDevice, camera);
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "SkyboxPipeline::draw");
skyboxPipeline->draw(cmd, gfxDevice, camera);
}
vkCmdEndRendering(cmd);
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "vkCmdEndRendering");
vkCmdEndRendering(cmd);
}
// vkutil::cmdEndLabel(cmd);
}
void GameRenderer::cleanup(GfxDevice& gfxDevice) {
+130 -57
View File
@@ -6,6 +6,7 @@
#include <destrum/Graphics/Init.h>
#include "volk.h"
#include "tracy/Tracy.hpp"
void Swapchain::initSync(VkDevice device) {
@@ -23,30 +24,40 @@ void Swapchain::initSync(VkDevice device) {
}
void Swapchain::createSwapchain(GfxDevice* gfxDevice, 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;
// vSync = true;
auto res = vkb::SwapchainBuilder{gfxDevice->getDevice()}
.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)
.set_desired_present_mode(
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)));
{
ZoneScopedN("vkb::SwapchainBuilder::build");
auto res = vkb::SwapchainBuilder{gfxDevice->getDevice()}
.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)
.set_desired_present_mode(
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)));
}
m_swapchain = res.value();
}
m_swapchain = res.value();
images = m_swapchain.get_images().value();
imageViews = m_swapchain.get_image_views().value();
{
ZoneScopedN("Get Swapchain Images / Views");
images = m_swapchain.get_images().value();
imageViews = m_swapchain.get_image_views().value();
}
imageRenderSemaphores.resize(images.size());
@@ -55,6 +66,8 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
};
for (auto& sem: imageRenderSemaphores) {
ZoneScopedN("Create Image Render Semaphore");
VK_CHECK(vkCreateSemaphore(m_gfxDevice->getDevice(), &sci, nullptr, &sem));
}
@@ -71,6 +84,8 @@ void Swapchain::recreateSwapchain(
std::uint32_t height,
bool vSync)
{
ZoneScopedN("Swapchain::recreateSwapchain");
if (width == 0 || height == 0) {
dirty = true;
return;
@@ -78,45 +93,67 @@ void Swapchain::recreateSwapchain(
VkDevice device = gfxDevice.getDevice();
vkDeviceWaitIdle(device);
{
ZoneScopedN("vkDeviceWaitIdle");
vkDeviceWaitIdle(device);
}
auto oldSwapchain = m_swapchain;
auto res = vkb::SwapchainBuilder{gfxDevice.getVkbDevice()}
.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)
.set_desired_present_mode(
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
.set_desired_extent(width, height)
.build();
{
ZoneScopedN("vkb::SwapchainBuilder::rebuild");
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)));
auto res = vkb::SwapchainBuilder{gfxDevice.getVkbDevice()}
.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)
.set_desired_present_mode(
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)));
}
m_swapchain = res.value();
}
for (auto sem : imageRenderSemaphores) {
vkDestroySemaphore(device, sem, nullptr);
{
ZoneScopedN("Destroy Old Image Render Semaphores");
for (auto sem : imageRenderSemaphores) {
vkDestroySemaphore(device, sem, nullptr);
}
imageRenderSemaphores.clear();
}
imageRenderSemaphores.clear();
for (auto imageView : imageViews) {
vkDestroyImageView(device, imageView, nullptr);
{
ZoneScopedN("Destroy Old Image Views");
for (auto imageView : imageViews) {
vkDestroyImageView(device, imageView, nullptr);
}
imageViews.clear();
}
imageViews.clear();
vkb::destroy_swapchain(oldSwapchain);
{
ZoneScopedN("Destroy Old Swapchain");
vkb::destroy_swapchain(oldSwapchain);
}
m_swapchain = res.value();
{
ZoneScopedN("Get New Swapchain Images / Views");
images = m_swapchain.get_images().value();
imageViews = m_swapchain.get_image_views().value();
images = m_swapchain.get_images().value();
imageViews = m_swapchain.get_image_views().value();
}
VkSemaphoreCreateInfo sci{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
@@ -125,6 +162,8 @@ void Swapchain::recreateSwapchain(
imageRenderSemaphores.resize(images.size());
for (auto& sem : imageRenderSemaphores) {
ZoneScopedN("Create New Image Render Semaphore");
VK_CHECK(vkCreateSemaphore(device, &sci, nullptr, &sem));
}
@@ -148,13 +187,25 @@ void Swapchain::cleanup() {
}
void Swapchain::beginFrame(int index) const {
ZoneScopedN("Swapchain::beginFrame");
auto& frame = frames[index];
VK_CHECK(vkWaitForFences(m_gfxDevice->getDevice(), 1, &frame.renderFence, true, std::numeric_limits<std::uint64_t>::max()));
{
ZoneScopedN("vkWaitForFences");
VK_CHECK(vkWaitForFences(m_gfxDevice->getDevice(), 1, &frame.renderFence, true, std::numeric_limits<std::uint64_t>::max()));
}
}
void Swapchain::resetFences(int index) const {
ZoneScopedN("Swapchain::resetFences");
auto& frame = frames[index];
VK_CHECK(vkResetFences(m_gfxDevice->getDevice(), 1, &frame.renderFence));
{
ZoneScopedN("vkResetFences");
VK_CHECK(vkResetFences(m_gfxDevice->getDevice(), 1, &frame.renderFence));
}
}
struct SwapchainAcquireResult {
@@ -164,14 +215,24 @@ struct SwapchainAcquireResult {
};
std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
ZoneScopedN("Swapchain::acquireNextImage");
std::uint32_t swapchainImageIndex{};
const auto result = vkAcquireNextImageKHR(
m_gfxDevice->getDevice(),
m_swapchain,
std::numeric_limits<std::uint64_t>::max(),
frames[index].swapchainSemaphore,
VK_NULL_HANDLE,
&swapchainImageIndex);
VkResult result = VK_SUCCESS;
{
ZoneScopedN("vkAcquireNextImageKHR");
result = vkAcquireNextImageKHR(
m_gfxDevice->getDevice(),
m_swapchain,
std::numeric_limits<std::uint64_t>::max(),
frames[index].swapchainSemaphore,
VK_NULL_HANDLE,
&swapchainImageIndex);
}
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
dirty = true;
return {images[swapchainImageIndex], swapchainImageIndex};
@@ -188,6 +249,8 @@ void Swapchain::submitAndPresent(
uint32_t imageIndex, // from vkAcquireNextImageKHR
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
{
ZoneScopedN("Swapchain::submitAndPresent");
auto& frame = frames[frameIndex]; // ✅ per-frame
VkSemaphore renderFinished = imageRenderSemaphores[imageIndex]; // ✅ per-image
@@ -209,7 +272,11 @@ void Swapchain::submitAndPresent(
renderFinished); // ✅ signal semaphore (per-image)
VkSubmitInfo2 submit = vkinit::submitInfo(&cmdInfo, &waitInfo, &signalInfo);
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence)); // ✅ fence (per-frame)
{
ZoneScopedN("vkQueueSubmit2");
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence)); // ✅ fence (per-frame)
}
// present
VkPresentInfoKHR presentInfo{
@@ -221,7 +288,13 @@ void Swapchain::submitAndPresent(
.pImageIndices = &imageIndex, // ✅ imageIndex, NOT frameIndex
};
VkResult res = vkQueuePresentKHR(graphicsQueue, &presentInfo);
VkResult res = VK_SUCCESS;
{
ZoneScopedN("vkQueuePresentKHR");
res = vkQueuePresentKHR(graphicsQueue, &presentInfo);
}
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) dirty = true;
else if (res != VK_SUCCESS) dirty = true;
}
+692
View File
@@ -0,0 +1,692 @@
#include <destrum/Physics/JoltPhysicsWorld.h>
// Jolt/Jolt.h must be included before other Jolt headers.
#include <Jolt/Jolt.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/Core/JobSystemThreadPool.h>
#include <Jolt/Core/TempAllocator.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyInterface.h>
#include <Jolt/Physics/Body/MotionType.h>
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#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/SphereShape.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/RegisterTypes.h>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <memory>
#include <stdexcept>
#include <thread>
#include <unordered_map>
#include <glm/gtx/norm.hpp>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h>
namespace
{
using namespace JPH;
static std::atomic_uint32_t g_JoltInstanceCount{0};
static void TraceImpl(const char* inFMT, ...)
{
va_list list;
va_start(list, inFMT);
std::vprintf(inFMT, list);
std::printf("\n");
va_end(list);
}
#ifdef JPH_ENABLE_ASSERTS
static bool AssertFailedImpl(const char* inExpression,
const char* inMessage,
const char* inFile,
JPH::uint inLine)
{
std::printf("%s:%u: (%s) %s\n",
inFile,
inLine,
inExpression,
inMessage != nullptr ? inMessage : "");
return true;
}
#endif
void InitJoltGlobals()
{
if (g_JoltInstanceCount.fetch_add(1) == 0)
{
RegisterDefaultAllocator();
Trace = TraceImpl;
JPH_IF_ENABLE_ASSERTS(AssertFailed = AssertFailedImpl;)
Factory::sInstance = new Factory();
RegisterTypes();
}
}
void ShutdownJoltGlobals()
{
if (g_JoltInstanceCount.fetch_sub(1) == 1)
{
UnregisterTypes();
delete Factory::sInstance;
Factory::sInstance = nullptr;
}
}
namespace Layers
{
static constexpr ObjectLayer NON_MOVING = 0;
static constexpr ObjectLayer MOVING = 1;
static constexpr ObjectLayer NUM_LAYERS = 2;
}
namespace BroadPhaseLayers
{
static constexpr BroadPhaseLayer NON_MOVING(0);
static constexpr BroadPhaseLayer MOVING(1);
static constexpr JPH::uint NUM_LAYERS(2);
}
class ObjectLayerPairFilterImpl final : public ObjectLayerPairFilter
{
public:
bool ShouldCollide(ObjectLayer inObject1, ObjectLayer inObject2) const override
{
switch (inObject1)
{
case Layers::NON_MOVING:
return inObject2 == Layers::MOVING;
case Layers::MOVING:
return true;
default:
JPH_ASSERT(false);
return false;
}
}
};
class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
{
public:
BPLayerInterfaceImpl()
{
m_ObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
m_ObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
}
JPH::uint GetNumBroadPhaseLayers() const override
{
return BroadPhaseLayers::NUM_LAYERS;
}
BroadPhaseLayer GetBroadPhaseLayer(ObjectLayer inLayer) const override
{
JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
return m_ObjectToBroadPhase[inLayer];
}
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
const char* GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override
{
switch ((BroadPhaseLayer::Type)inLayer)
{
case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:
return "NON_MOVING";
case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:
return "MOVING";
default:
JPH_ASSERT(false);
return "INVALID";
}
}
#endif
private:
BroadPhaseLayer m_ObjectToBroadPhase[Layers::NUM_LAYERS];
};
class ObjectVsBroadPhaseLayerFilterImpl final : public ObjectVsBroadPhaseLayerFilter
{
public:
bool ShouldCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2) const override
{
switch (inLayer1)
{
case Layers::NON_MOVING:
return inLayer2 == BroadPhaseLayers::MOVING;
case Layers::MOVING:
return true;
default:
JPH_ASSERT(false);
return false;
}
}
};
[[nodiscard]] Vec3 ToJoltVec3(const glm::vec3& v)
{
return Vec3(v.x, v.y, v.z);
}
[[nodiscard]] RVec3 ToJoltRVec3(const glm::vec3& v)
{
return RVec3(v.x, v.y, v.z);
}
[[nodiscard]] Quat ToJoltQuat(const glm::quat& q)
{
return Quat(q.x, q.y, q.z, q.w);
}
[[nodiscard]] glm::vec3 FromJoltVec3(Vec3Arg v)
{
return glm::vec3(v.GetX(), v.GetY(), v.GetZ());
}
[[nodiscard]] glm::vec3 FromJoltRVec3(RVec3Arg v)
{
return glm::vec3(static_cast<float>(v.GetX()),
static_cast<float>(v.GetY()),
static_cast<float>(v.GetZ()));
}
[[nodiscard]] glm::quat FromJoltQuat(QuatArg q)
{
return glm::quat(q.GetW(), q.GetX(), q.GetY(), q.GetZ());
}
[[nodiscard]] EMotionType ToJoltMotionType(RigidbodyType type)
{
switch (type)
{
case RigidbodyType::Static:
return EMotionType::Static;
case RigidbodyType::Dynamic:
return EMotionType::Dynamic;
case RigidbodyType::Kinematic:
return EMotionType::Kinematic;
default:
return EMotionType::Static;
}
}
[[nodiscard]] ObjectLayer ToJoltObjectLayer(RigidbodyType type)
{
return type == RigidbodyType::Static ? Layers::NON_MOVING : Layers::MOVING;
}
[[nodiscard]] ShapeRefC CreateJoltShape(const PhysicsShapeDesc& desc)
{
switch (desc.type)
{
case PhysicsShapeType::Box:
{
BoxShapeSettings settings(ToJoltVec3(desc.halfExtents));
ShapeSettings::ShapeResult result = settings.Create();
if (!result.IsValid())
{
throw std::runtime_error(
std::string("Failed to create Jolt box shape: ") + std::string(result.GetError()));
}
return result.Get();
}
case PhysicsShapeType::Sphere:
return new SphereShape(desc.radius);
case PhysicsShapeType::Capsule:
{
// Jolt expects half-height of the cylindrical section, not full capsule height.
const float halfCylinderHeight = std::max(0.0f, (desc.height * 0.5f) - desc.radius);
return new CapsuleShape(halfCylinderHeight, desc.radius);
}
case PhysicsShapeType::None:
default:
throw std::runtime_error("Cannot create Jolt body without a valid shape.");
}
}
} // namespace
class JoltPhysicsWorld::Impl
{
public:
explicit Impl(const Settings& settings)
: m_Settings(settings)
{
InitJoltGlobals();
const JPH::uint hardwareThreads = std::thread::hardware_concurrency();
const JPH::uint workerThreads =
settings.workerThreadCount != 0
? settings.workerThreadCount
: std::max<JPH::uint>(1, hardwareThreads > 0 ? hardwareThreads - 1 : 1);
m_TempAllocator = std::make_unique<TempAllocatorImpl>(settings.tempAllocatorSizeBytes);
m_JobSystem = std::make_unique<JobSystemThreadPool>(
cMaxPhysicsJobs,
cMaxPhysicsBarriers,
static_cast<int>(workerThreads));
m_PhysicsSystem.Init(
settings.maxBodies,
settings.numBodyMutexes,
settings.maxBodyPairs,
settings.maxContactConstraints,
m_BPLayerInterface,
m_ObjectVsBroadPhaseLayerFilter,
m_ObjectLayerPairFilter);
m_PhysicsSystem.SetGravity(ToJoltVec3(settings.gravity));
}
~Impl()
{
BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface();
for (auto& [handleId, record] : m_Bodies)
{
if (!record.alive)
{
continue;
}
bodyInterface.RemoveBody(record.bodyID);
bodyInterface.DestroyBody(record.bodyID);
}
m_Bodies.clear();
ShutdownJoltGlobals();
}
PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc)
{
ShapeRefC shape = CreateJoltShape(desc.shape);
// This first backend ignores centerOffset for now.
// Later, use JPH::OffsetCenterOfMassShapeSettings for offset colliders.
BodyCreationSettings settings(
shape,
ToJoltRVec3(desc.transform.position),
ToJoltQuat(desc.transform.rotation),
ToJoltMotionType(desc.type),
ToJoltObjectLayer(desc.type));
settings.mFriction = desc.material.friction;
settings.mRestitution = desc.material.restitution;
settings.mIsSensor = desc.shape.isTrigger;
settings.mAllowSleeping = desc.allowSleep;
settings.mGravityFactor = desc.useGravity ? 1.0f : 0.0f;
settings.mUserData = reinterpret_cast<JPH::uint64>(desc.owner);
if (desc.type == RigidbodyType::Dynamic)
{
settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
settings.mMassPropertiesOverride.mMass = std::max(0.0001f, desc.mass);
}
const EActivation activation =
desc.type == RigidbodyType::Dynamic ? EActivation::Activate : EActivation::DontActivate;
BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface();
BodyID bodyID = bodyInterface.CreateAndAddBody(settings, activation);
if (bodyID.IsInvalid())
{
return {};
}
PhysicsBodyHandle handle{m_NextHandle++};
BodyRecord record{};
record.alive = true;
record.bodyID = bodyID;
record.desc = desc;
m_Bodies.emplace(handle.id, record);
return handle;
}
void DestroyBody(PhysicsBodyHandle body)
{
auto it = m_Bodies.find(body.id);
if (it == m_Bodies.end() || !it->second.alive)
{
return;
}
BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface();
bodyInterface.RemoveBody(it->second.bodyID);
bodyInterface.DestroyBody(it->second.bodyID);
m_Bodies.erase(it);
}
void Step(float fixedDt)
{
if (fixedDt <= 0.0f)
{
return;
}
const int collisionSteps = std::max(1, static_cast<int>(std::ceil(fixedDt / (1.0f / 60.0f))));
m_PhysicsSystem.Update(
fixedDt,
collisionSteps,
m_TempAllocator.get(),
m_JobSystem.get());
}
void SyncKinematicBodiesToPhysics()
{
BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface();
for (auto& [handleId, record] : m_Bodies)
{
if (!record.alive || record.desc.type != RigidbodyType::Kinematic || !record.desc.owner)
{
continue;
}
Transform& transform = record.desc.owner->GetTransform();
bodyInterface.SetPositionAndRotation(
record.bodyID,
ToJoltRVec3(transform.GetWorldPosition()),
ToJoltQuat(transform.GetWorldRotation()),
EActivation::Activate);
}
}
void SyncDynamicBodiesToTransforms()
{
BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface();
for (auto& [handleId, record] : m_Bodies)
{
if (!record.alive || record.desc.type != RigidbodyType::Dynamic || !record.desc.owner)
{
continue;
}
RVec3 position{};
Quat rotation{};
bodyInterface.GetPositionAndRotation(record.bodyID, position, rotation);
Transform& transform = record.desc.owner->GetTransform();
transform.SetWorldPosition(FromJoltRVec3(position));
transform.SetWorldRotation(FromJoltQuat(rotation));
}
}
void SetBodyTransform(PhysicsBodyHandle body, const PhysicsTransform& transform)
{
if (BodyRecord* record = FindBody(body))
{
m_PhysicsSystem.GetBodyInterface().SetPositionAndRotation(
record->bodyID,
ToJoltRVec3(transform.position),
ToJoltQuat(transform.rotation),
EActivation::Activate);
}
}
PhysicsTransform GetBodyTransform(PhysicsBodyHandle body) const
{
const BodyRecord* record = FindBody(body);
if (!record)
{
return {};
}
RVec3 position{};
Quat rotation{};
m_PhysicsSystem.GetBodyInterface().GetPositionAndRotation(record->bodyID, position, rotation);
return PhysicsTransform{
.position = FromJoltRVec3(position),
.rotation = FromJoltQuat(rotation)
};
}
void SetLinearVelocity(PhysicsBodyHandle body, const glm::vec3& velocity)
{
if (BodyRecord* record = FindBody(body))
{
m_PhysicsSystem.GetBodyInterface().SetLinearVelocity(record->bodyID, ToJoltVec3(velocity));
}
}
glm::vec3 GetLinearVelocity(PhysicsBodyHandle body) const
{
const BodyRecord* record = FindBody(body);
if (!record)
{
return glm::vec3{0.0f};
}
return FromJoltVec3(m_PhysicsSystem.GetBodyInterface().GetLinearVelocity(record->bodyID));
}
void AddForce(PhysicsBodyHandle body, const glm::vec3& force)
{
if (BodyRecord* record = FindBody(body))
{
m_PhysicsSystem.GetBodyInterface().AddForce(record->bodyID, ToJoltVec3(force));
}
}
void AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse)
{
if (BodyRecord* record = FindBody(body))
{
m_PhysicsSystem.GetBodyInterface().AddImpulse(record->bodyID, ToJoltVec3(impulse));
}
}
bool Raycast(const glm::vec3& origin,
const glm::vec3& direction,
float maxDistance,
PhysicsRaycastHit& hit) const
{
if (maxDistance <= 0.0f || glm::length2(direction) <= 0.0000001f)
{
return false;
}
const glm::vec3 directionNormalized = glm::normalize(direction);
RRayCast ray{
ToJoltRVec3(origin),
ToJoltVec3(directionNormalized * maxDistance)
};
RayCastResult result{};
if (!m_PhysicsSystem.GetNarrowPhaseQuery().CastRay(ray, result))
{
return false;
}
hit.body = FindHandle(result.mBodyID);
hit.distance = result.mFraction * maxDistance;
hit.point = origin + directionNormalized * hit.distance;
hit.object = nullptr;
hit.normal = glm::vec3{0.0f, 1.0f, 0.0f};
BodyLockRead lock(m_PhysicsSystem.GetBodyLockInterface(), result.mBodyID);
if (lock.Succeeded())
{
const Body& body = lock.GetBody();
hit.object = reinterpret_cast<GameObject*>(body.GetUserData());
hit.normal = FromJoltVec3(
body.GetWorldSpaceSurfaceNormal(
result.mSubShapeID2,
ray.GetPointOnRay(result.mFraction)
)
);
}
return true;
}
private:
struct BodyRecord
{
bool alive{false};
BodyID bodyID{};
PhysicsBodyDesc desc{};
};
BodyRecord* FindBody(PhysicsBodyHandle body)
{
auto it = m_Bodies.find(body.id);
if (it == m_Bodies.end())
{
return nullptr;
}
return &it->second;
}
const BodyRecord* FindBody(PhysicsBodyHandle body) const
{
auto it = m_Bodies.find(body.id);
if (it == m_Bodies.end())
{
return nullptr;
}
return &it->second;
}
PhysicsBodyHandle FindHandle(BodyID bodyID) const
{
for (const auto& [handleId, record] : m_Bodies)
{
if (record.alive && record.bodyID == bodyID)
{
return PhysicsBodyHandle{handleId};
}
}
return {};
}
Settings m_Settings{};
BPLayerInterfaceImpl m_BPLayerInterface{};
ObjectVsBroadPhaseLayerFilterImpl m_ObjectVsBroadPhaseLayerFilter{};
ObjectLayerPairFilterImpl m_ObjectLayerPairFilter{};
PhysicsSystem m_PhysicsSystem{};
std::unique_ptr<TempAllocatorImpl> m_TempAllocator;
std::unique_ptr<JobSystemThreadPool> m_JobSystem;
std::unordered_map<std::uint32_t, BodyRecord> m_Bodies;
std::uint32_t m_NextHandle{0};
};
JoltPhysicsWorld::JoltPhysicsWorld(const Settings& settings)
: m_Impl(std::make_unique<Impl>(settings))
{
}
JoltPhysicsWorld::~JoltPhysicsWorld() = default;
void JoltPhysicsWorld::Step(float fixedDt)
{
m_Impl->Step(fixedDt);
}
void JoltPhysicsWorld::SyncKinematicBodiesToPhysics()
{
m_Impl->SyncKinematicBodiesToPhysics();
}
void JoltPhysicsWorld::SyncDynamicBodiesToTransforms()
{
m_Impl->SyncDynamicBodiesToTransforms();
}
PhysicsBodyHandle JoltPhysicsWorld::CreateBody(const PhysicsBodyDesc& desc)
{
return m_Impl->CreateBody(desc);
}
void JoltPhysicsWorld::DestroyBody(PhysicsBodyHandle body)
{
m_Impl->DestroyBody(body);
}
void JoltPhysicsWorld::SetBodyTransform(PhysicsBodyHandle body, const PhysicsTransform& transform)
{
m_Impl->SetBodyTransform(body, transform);
}
PhysicsTransform JoltPhysicsWorld::GetBodyTransform(PhysicsBodyHandle body) const
{
return m_Impl->GetBodyTransform(body);
}
void JoltPhysicsWorld::SetLinearVelocity(PhysicsBodyHandle body, const glm::vec3& velocity)
{
m_Impl->SetLinearVelocity(body, velocity);
}
glm::vec3 JoltPhysicsWorld::GetLinearVelocity(PhysicsBodyHandle body) const
{
return m_Impl->GetLinearVelocity(body);
}
void JoltPhysicsWorld::AddForce(PhysicsBodyHandle body, const glm::vec3& force)
{
m_Impl->AddForce(body, force);
}
void JoltPhysicsWorld::AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse)
{
m_Impl->AddImpulse(body, impulse);
}
bool JoltPhysicsWorld::Raycast(const glm::vec3& origin,
const glm::vec3& direction,
float maxDistance,
PhysicsRaycastHit& hit) const
{
return m_Impl->Raycast(origin, direction, maxDistance, hit);
}
+2 -7
View File
@@ -24,12 +24,7 @@ void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) {
}
void PhysicsSceneBridge::FixedUpdate(float fixedDt) {
if (auto* simple = dynamic_cast<SimplePhysicsWorld*>(m_World.get())) {
simple->SyncKinematicBodiesToPhysics();
simple->Step(fixedDt);
simple->SyncDynamicBodiesToTransforms();
return;
}
m_World->SyncKinematicBodiesToPhysics();
m_World->Step(fixedDt);
m_World->SyncDynamicBodiesToTransforms();
}
+7
View File
@@ -122,3 +122,10 @@ set(USE_F16C ON CACHE BOOL "" FORCE)
set(USE_FMADD ON CACHE BOOL "" FORCE)
add_subdirectory(jolt/Build)
# 3rdparty/tracy is the Tracy repo
option(TRACY_ENABLE "" ON)
option(TRACY_ON_DEMAND "" ON)
add_subdirectory(tracy)
Vendored Submodule
+1