Browse Source

Fix jitter when rendering the vehicle and character samples at > 60 fps (#911)

Fixes #905
Jorrit Rouwe 1 year ago
parent
commit
edd89fe992

+ 1 - 1
JoltViewer/JoltViewer.cpp

@@ -79,7 +79,7 @@ JoltViewer::JoltViewer()
 	mDebugUI->ShowMenu(main_menu);
 }
 
-bool JoltViewer::RenderFrame(float inDeltaTime)
+bool JoltViewer::UpdateFrame(float inDeltaTime)
 {
 	// If no frames were read, abort
 	if (mRendererPlayback.GetNumFrames() == 0)

+ 2 - 2
JoltViewer/JoltViewer.h

@@ -26,8 +26,8 @@ public:
 	// Constructor / destructor
 							JoltViewer();
 
-	// Render the frame
-	virtual bool			RenderFrame(float inDeltaTime) override;
+	// Update the application
+	virtual bool			UpdateFrame(float inDeltaTime) override;
 
 private:
 	enum class EPlaybackMode

+ 1 - 1
Samples/SamplesApp.cpp

@@ -1945,7 +1945,7 @@ void SamplesApp::UpdateDebug(float inDeltaTime)
 	}
 }
 
-bool SamplesApp::RenderFrame(float inDeltaTime)
+bool SamplesApp::UpdateFrame(float inDeltaTime)
 {
 	// Reinitialize the job system if the concurrency setting changed
 	if (mMaxConcurrentJobs != mJobSystem->GetMaxConcurrency())

+ 2 - 2
Samples/SamplesApp.h

@@ -28,8 +28,8 @@ public:
 							SamplesApp();
 	virtual					~SamplesApp() override;
 
-	// Render the frame.
-	virtual bool			RenderFrame(float inDeltaTime) override;
+	// Update the application
+	virtual bool			UpdateFrame(float inDeltaTime) override;
 
 	// Override to specify the initial camera state (local to GetCameraPivot)
 	virtual void			GetInitialCamera(CameraState &ioState) const override;

+ 4 - 1
Samples/Tests/Character/CharacterBaseTest.cpp

@@ -564,6 +564,9 @@ void CharacterBaseTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	// Update scene time
 	mTime += inParams.mDeltaTime;
 
+	// Update camera pivot
+	mCameraPivot = GetCharacterPosition();
+
 	// Animate bodies
 	if (!mRotatingBody.IsInvalid())
 		mBodyInterface->MoveKinematic(mRotatingBody, cRotatingPosition, Quat::sRotation(Vec3::sAxisY(), JPH_PI * Sin(mTime)), inParams.mDeltaTime);
@@ -641,7 +644,7 @@ RMat44 CharacterBaseTest::GetCameraPivot(float inCameraHeading, float inCameraPi
 {
 	// Pivot is center of character + distance behind based on the heading and pitch of the camera
 	Vec3 fwd = Vec3(Cos(inCameraPitch) * Cos(inCameraHeading), Sin(inCameraPitch), Cos(inCameraPitch) * Sin(inCameraHeading));
-	return RMat44::sTranslation(GetCharacterPosition() + Vec3(0, cCharacterHeightStanding + cCharacterRadiusStanding, 0) - 5.0f * fwd);
+	return RMat44::sTranslation(mCameraPivot + Vec3(0, cCharacterHeightStanding + cCharacterRadiusStanding, 0) - 5.0f * fwd);
 }
 
 void CharacterBaseTest::SaveState(StateRecorder &inStream) const

+ 3 - 0
Samples/Tests/Character/CharacterBaseTest.h

@@ -105,6 +105,9 @@ private:
 	// Scene time (for moving bodies)
 	float					mTime = 0.0f;
 
+	// The camera pivot, recorded before the physics update to align with the drawn world
+	RVec3					mCameraPivot = RVec3::sZero();
+
 	// Moving bodies
 	BodyID					mRotatingBody;
 	BodyID					mRotatingWallBody;

+ 6 - 2
Samples/Tests/Vehicle/MotorcycleTest.cpp

@@ -125,6 +125,8 @@ void MotorcycleTest::Initialize()
 	mVehicleConstraint->SetVehicleCollisionTester(new VehicleCollisionTesterCastCylinder(Layers::MOVING, 1.0f)); // Use half wheel width as convex radius so we get a rounded cyclinder
 	mPhysicsSystem->AddConstraint(mVehicleConstraint);
 	mPhysicsSystem->AddStepListener(mVehicleConstraint);
+
+	UpdateCameraPivot();
 }
 
 void MotorcycleTest::ProcessInput(const ProcessInputParams &inParams)
@@ -185,6 +187,8 @@ void MotorcycleTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 {
 	VehicleTest::PrePhysicsUpdate(inParams);
 
+	UpdateCameraPivot();
+
 	// On user input, assure that the motorcycle is active
 	if (mRight != 0.0f || mForward != 0.0f || mBrake != 0.0f)
 		mBodyInterface->ActivateBody(mMotorcycleBody->GetID());
@@ -228,7 +232,7 @@ void MotorcycleTest::GetInitialCamera(CameraState &ioState) const
 	ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
 }
 
-RMat44 MotorcycleTest::GetCameraPivot(float inCameraHeading, float inCameraPitch) const
+void MotorcycleTest::UpdateCameraPivot()
 {
 	// Pivot is center of motorcycle and rotates with motorcycle around Y axis only
 	Vec3 fwd = mMotorcycleBody->GetRotation().RotateAxisZ();
@@ -240,7 +244,7 @@ RMat44 MotorcycleTest::GetCameraPivot(float inCameraHeading, float inCameraPitch
 		fwd = Vec3::sAxisZ();
 	Vec3 up = Vec3::sAxisY();
 	Vec3 right = up.Cross(fwd);
-	return RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mMotorcycleBody->GetPosition());
+	mCameraPivot = RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mMotorcycleBody->GetPosition());
 }
 
 void MotorcycleTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)

+ 4 - 1
Samples/Tests/Vehicle/MotorcycleTest.h

@@ -25,17 +25,20 @@ public:
 	virtual void				RestoreInputState(StateRecorder &inStream) override;
 
 	virtual void				GetInitialCamera(CameraState &ioState) const override;
-	virtual RMat44				GetCameraPivot(float inCameraHeading, float inCameraPitch) const override;
+	virtual RMat44				GetCameraPivot(float inCameraHeading, float inCameraPitch) const override { return mCameraPivot; }
 
 	virtual void				CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
 
 private:
+	void						UpdateCameraPivot();
+
 	static inline bool			sOverrideFrontSuspensionForcePoint = false;	///< If true, the front suspension force point is overridden
 	static inline bool			sOverrideRearSuspensionForcePoint = false;	///< If true, the rear suspension force point is overridden
 	static inline bool			sEnableLeanController = true;				///< If true, the lean controller is enabled
 
 	Body *						mMotorcycleBody;							///< The vehicle
 	Ref<VehicleConstraint>		mVehicleConstraint;							///< The vehicle constraint
+	RMat44						mCameraPivot = RMat44::sIdentity();			///< The camera pivot, recorded before the physics update to align with the drawn world
 
 	// Player input
 	float						mForward = 0.0f;

+ 7 - 1
Samples/Tests/Vehicle/TankTest.cpp

@@ -160,6 +160,9 @@ void TankTest::Initialize()
 	mBarrelHinge = static_cast<HingeConstraint *>(barrel_hinge.Create(*mTurretBody, *mBarrelBody));
 	mBarrelHinge->SetMotorState(EMotorState::Position);
 	mPhysicsSystem->AddConstraint(mBarrelHinge);
+
+	// Update camera pivot
+	mCameraPivot = mTankBody->GetPosition();
 }
 
 void TankTest::ProcessInput(const ProcessInputParams &inParams)
@@ -257,6 +260,9 @@ void TankTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	const float bullet_mass = 40.0f; // Normal projectile weight is around 7 kg, use an increased value so the momentum is more realistic (with the lower exit velocity)
 	const float bullet_reload_time = 2.0f;
 
+	// Update camera pivot
+	mCameraPivot = mTankBody->GetPosition();
+
 	// Assure the tank stays active as we're controlling the turret with the mouse
 	mBodyInterface->ActivateBody(mTankBody->GetID());
 
@@ -347,5 +353,5 @@ RMat44 TankTest::GetCameraPivot(float inCameraHeading, float inCameraPitch) cons
 {
 	// Pivot is center of tank + a distance away from the tank based on the heading and pitch of the camera
 	Vec3 fwd = Vec3(Cos(inCameraPitch) * Cos(inCameraHeading), Sin(inCameraPitch), Cos(inCameraPitch) * Sin(inCameraHeading));
-	return RMat44::sTranslation(mTankBody->GetPosition() - 10.0f * fwd);
+	return RMat44::sTranslation(mCameraPivot - 10.0f * fwd);
 }

+ 1 - 0
Samples/Tests/Vehicle/TankTest.h

@@ -37,6 +37,7 @@ private:
 	Ref<HingeConstraint>		mTurretHinge;								///< Hinge connecting tank body and turret
 	Ref<HingeConstraint>		mBarrelHinge;								///< Hinge connecting tank turret and barrel
 	float						mReloadTime = 0.0f;							///< How long it still takes to reload the main gun
+	RVec3						mCameraPivot = RVec3::sZero();				///< The camera pivot, recorded before the physics update to align with the drawn world
 
 	// Player input
 	float						mForward = 0.0f;

+ 6 - 2
Samples/Tests/Vehicle/VehicleConstraintTest.cpp

@@ -165,6 +165,8 @@ void VehicleConstraintTest::Initialize()
 
 	mPhysicsSystem->AddConstraint(mVehicleConstraint);
 	mPhysicsSystem->AddStepListener(mVehicleConstraint);
+
+	UpdateCameraPivot();
 }
 
 void VehicleConstraintTest::ProcessInput(const ProcessInputParams &inParams)
@@ -215,6 +217,8 @@ void VehicleConstraintTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 {
 	VehicleTest::PrePhysicsUpdate(inParams);
 
+	UpdateCameraPivot();
+
 	// On user input, assure that the car is active
 	if (mRight != 0.0f || mForward != 0.0f || mBrake != 0.0f || mHandBrake != 0.0f)
 		mBodyInterface->ActivateBody(mCarBody->GetID());
@@ -272,7 +276,7 @@ void VehicleConstraintTest::GetInitialCamera(CameraState &ioState) const
 	ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
 }
 
-RMat44 VehicleConstraintTest::GetCameraPivot(float inCameraHeading, float inCameraPitch) const
+void VehicleConstraintTest::UpdateCameraPivot()
 {
 	// Pivot is center of car and rotates with car around Y axis only
 	Vec3 fwd = mCarBody->GetRotation().RotateAxisZ();
@@ -284,7 +288,7 @@ RMat44 VehicleConstraintTest::GetCameraPivot(float inCameraHeading, float inCame
 		fwd = Vec3::sAxisZ();
 	Vec3 up = Vec3::sAxisY();
 	Vec3 right = up.Cross(fwd);
-	return RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mCarBody->GetPosition());
+	mCameraPivot = RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mCarBody->GetPosition());
 }
 
 void VehicleConstraintTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)

+ 4 - 1
Samples/Tests/Vehicle/VehicleConstraintTest.h

@@ -24,11 +24,13 @@ public:
 	virtual void				RestoreInputState(StateRecorder &inStream) override;
 
 	virtual void				GetInitialCamera(CameraState &ioState) const override;
-	virtual RMat44				GetCameraPivot(float inCameraHeading, float inCameraPitch) const override;
+	virtual RMat44				GetCameraPivot(float inCameraHeading, float inCameraPitch) const override { return mCameraPivot; }
 
 	virtual void				CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
 
 private:
+	void						UpdateCameraPivot();
+
 	static inline float			sInitialRollAngle = 0;
 	static inline float			sMaxRollAngle = DegreesToRadians(60.0f);
 	static inline float			sMaxSteeringAngle = DegreesToRadians(30.0f);
@@ -62,6 +64,7 @@ private:
 	Body *						mCarBody;									///< The vehicle
 	Ref<VehicleConstraint>		mVehicleConstraint;							///< The vehicle constraint
 	Ref<VehicleCollisionTester>	mTesters[3];								///< Collision testers for the wheel
+	RMat44						mCameraPivot = RMat44::sIdentity();			///< The camera pivot, recorded before the physics update to align with the drawn world
 
 	// Player input
 	float						mForward = 0.0f;

+ 6 - 2
Samples/Tests/Vehicle/VehicleSixDOFTest.cpp

@@ -116,6 +116,8 @@ void VehicleSixDOFTest::Initialize()
 			wheel_constraint->SetMotorState(EAxis::RotationZ, EMotorState::Position);
 		}
 	}
+
+	UpdateCameraPivot();
 }
 
 void VehicleSixDOFTest::ProcessInput(const ProcessInputParams &inParams)
@@ -135,6 +137,8 @@ void VehicleSixDOFTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 {
 	VehicleTest::PrePhysicsUpdate(inParams);
 
+	UpdateCameraPivot();
+
 	// On user input, assure that the car is active
 	if (mSteeringAngle != 0.0f || mSpeed != 0.0f)
 		mBodyInterface->ActivateBody(mCarBody->GetID());
@@ -205,7 +209,7 @@ void VehicleSixDOFTest::GetInitialCamera(CameraState &ioState) const
 	ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
 }
 
-RMat44 VehicleSixDOFTest::GetCameraPivot(float inCameraHeading, float inCameraPitch) const
+void VehicleSixDOFTest::UpdateCameraPivot()
 {
 	// Pivot is center of car and rotates with car around Y axis only
 	Vec3 fwd = mCarBody->GetRotation().RotateAxisZ();
@@ -217,7 +221,7 @@ RMat44 VehicleSixDOFTest::GetCameraPivot(float inCameraHeading, float inCameraPi
 		fwd = Vec3::sAxisZ();
 	Vec3 up = Vec3::sAxisY();
 	Vec3 right = up.Cross(fwd);
-	return RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mCarBody->GetPosition());
+	mCameraPivot = RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mCarBody->GetPosition());
 }
 
 void VehicleSixDOFTest::SaveInputState(StateRecorder &inStream) const

+ 4 - 1
Samples/Tests/Vehicle/VehicleSixDOFTest.h

@@ -21,13 +21,15 @@ public:
 	virtual void			RestoreInputState(StateRecorder &inStream) override;
 
 	virtual void			GetInitialCamera(CameraState &ioState) const override;
-	virtual RMat44			GetCameraPivot(float inCameraHeading, float inCameraPitch) const override;
+	virtual RMat44			GetCameraPivot(float inCameraHeading, float inCameraPitch) const override { return mCameraPivot; }
 
 private:
 	static constexpr float	cMaxSteeringAngle = DegreesToRadians(30);
 
 	using EAxis = SixDOFConstraintSettings::EAxis;
 
+	void					UpdateCameraPivot();
+
 	enum class EWheel : int
 	{
 		LeftFront,
@@ -42,6 +44,7 @@ private:
 
 	Body *					mCarBody;
 	Ref<SixDOFConstraint>	mWheels[int(EWheel::Num)];
+	RMat44					mCameraPivot = RMat44::sIdentity();	///< The camera pivot, recorded before the physics update to align with the drawn world
 
 	// Player input
 	float					mSteeringAngle = 0.0f;

+ 9 - 9
TestFramework/Application/Application.cpp

@@ -206,16 +206,9 @@ void Application::Run()
 			if (world_delta_time > 0.0f)
 				ClearDebugRenderer();
 
-			// Update the camera position
-			if (!mUI->IsVisible())
-				UpdateCamera(clock_delta_time);
-
-			// Start rendering
-			mRenderer->BeginFrame(mWorldCamera, GetWorldScale());
-
 			{
-				JPH_PROFILE("RenderFrame");
-				if (!RenderFrame(world_delta_time))
+				JPH_PROFILE("UpdateFrame");
+				if (!UpdateFrame(world_delta_time))
 					break;
 			}
 
@@ -226,6 +219,13 @@ void Application::Run()
 			// For next frame: mark that we haven't cleared debug stuff
 			mDebugRendererCleared = false;
 
+			// Update the camera position
+			if (!mUI->IsVisible())
+				UpdateCamera(clock_delta_time);
+
+			// Start rendering
+			mRenderer->BeginFrame(mWorldCamera, GetWorldScale());
+
 			// Draw debug information
 			static_cast<DebugRendererImp *>(mDebugRenderer)->Draw();
 

+ 2 - 2
TestFramework/Application/Application.h

@@ -56,8 +56,8 @@ public:
 	void						Run();
 
 protected:
-	/// Callback to render a frame
-	virtual bool				RenderFrame(float inDeltaTime)					{ return false; }
+	/// Update the application
+	virtual bool				UpdateFrame(float inDeltaTime)					{ return false; }
 
 	/// Pause / unpause the simulation
 	void						Pause(bool inPaused)							{ mIsPaused = inPaused; }

+ 16 - 0
TestFramework/Renderer/Renderer.cpp

@@ -404,6 +404,8 @@ void Renderer::Initialize()
 
 void Renderer::OnWindowResize()
 {
+	JPH_ASSERT(!mInFrame);
+
 	// Wait for the previous frame to be rendered
 	WaitForGpu();
 
@@ -442,6 +444,10 @@ void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale)
 {
 	JPH_PROFILE_FUNCTION();
 
+	// Mark that we're in the frame
+	JPH_ASSERT(!mInFrame);
+	mInFrame = true;
+
 	// Store state
 	mCameraState = inCamera;
 
@@ -543,6 +549,10 @@ void Renderer::EndFrame()
 {
 	JPH_PROFILE_FUNCTION();
 
+	// Mark that we're no longer in the frame
+	JPH_ASSERT(mInFrame);
+	mInFrame = false;
+
 	// Indicate that the back buffer will now be used to present.
 	D3D12_RESOURCE_BARRIER barrier;
 	barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
@@ -591,11 +601,15 @@ void Renderer::EndFrame()
 
 void Renderer::SetProjectionMode()
 {
+	JPH_ASSERT(mInFrame);
+
 	mVertexShaderConstantBufferProjection[mFrameIndex]->Bind(0);
 }
 
 void Renderer::SetOrthoMode()
 {
+	JPH_ASSERT(mInFrame);
+
 	mVertexShaderConstantBufferOrtho[mFrameIndex]->Bind(0);
 }
 
@@ -611,6 +625,8 @@ Ref<Texture> Renderer::CreateRenderTarget(int inWidth, int inHeight)
 
 void Renderer::SetRenderTarget(Texture *inRenderTarget)
 {
+	JPH_ASSERT(mInFrame);
+
 	// Unset the previous render target
 	if (mRenderTargetTexture != nullptr)
 		mRenderTargetTexture->SetAsRenderTarget(false);

+ 6 - 5
TestFramework/Renderer/Renderer.h

@@ -51,7 +51,7 @@ public:
 	/// Access to the most important DirectX structures
 	ID3D12Device *					GetDevice()							{ return mDevice.Get(); }
 	ID3D12RootSignature *			GetRootSignature()					{ return mRootSignature.Get(); }
-	ID3D12GraphicsCommandList *		GetCommandList()					{ return mCommandList.Get(); }
+	ID3D12GraphicsCommandList *		GetCommandList()					{ JPH_ASSERT(mInFrame); return mCommandList.Get(); }
 	CommandQueue &					GetUploadQueue()					{ return mUploadQueue; }
 	DescriptorHeap &				GetDSVHeap()						{ return mDSVHeap; }
 	DescriptorHeap &				GetSRVHeap()						{ return mSRVHeap; }
@@ -86,21 +86,21 @@ public:
 	unique_ptr<PipelineState>		CreatePipelineState(ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode);
 
 	/// Get the camera state / frustum (only valid between BeginFrame() / EndFrame())
-	const CameraState &				GetCameraState() const				{ return mCameraState; }
-	const Frustum &					GetCameraFrustum() const			{ return mCameraFrustum; }
+	const CameraState &				GetCameraState() const				{ JPH_ASSERT(mInFrame); return mCameraState; }
+	const Frustum &					GetCameraFrustum() const			{ JPH_ASSERT(mInFrame); return mCameraFrustum; }
 
 	/// Offset relative to which the world is rendered, helps avoiding rendering artifacts at big distances
 	RVec3							GetBaseOffset() const				{ return mBaseOffset; }
 	void							SetBaseOffset(RVec3 inOffset)		{ mBaseOffset = inOffset; }
 
 	/// Get the light frustum (only valid between BeginFrame() / EndFrame())
-	const Frustum &					GetLightFrustum() const				{ return mLightFrustum; }
+	const Frustum &					GetLightFrustum() const				{ JPH_ASSERT(mInFrame); return mLightFrustum; }
 
 	/// How many frames our pipeline is
 	static const uint				cFrameCount = 2;
 
 	/// Which frame is currently rendering (to keep track of which buffers are free to overwrite)
-	uint							GetCurrentFrameIndex() const		{ return mFrameIndex; }
+	uint							GetCurrentFrameIndex() const		{ JPH_ASSERT(mInFrame); return mFrameIndex; }
 
 	/// Create a buffer on the default heap (usable for permanent buffers)
 	ComPtr<ID3D12Resource>			CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize);
@@ -139,6 +139,7 @@ private:
 	unique_ptr<ConstantBuffer>		mVertexShaderConstantBufferProjection[cFrameCount];
 	unique_ptr<ConstantBuffer>		mVertexShaderConstantBufferOrtho[cFrameCount];
 	unique_ptr<ConstantBuffer>		mPixelShaderConstantBuffer[cFrameCount];
+	bool							mInFrame = false;					///< If we're within a BeginFrame() / EndFrame() pair
 	CameraState						mCameraState;
 	RVec3							mBaseOffset { RVec3::sZero() };		///< Offset to subtract from the camera position to deal with large worlds
 	Frustum							mCameraFrustum;