Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer

This commit is contained in:
Tom Ray
2026-03-22 00:28:03 +01:00
parent 6695d46bcd
commit 48348936a8
1147 changed files with 214331 additions and 353 deletions

View File

@@ -0,0 +1,433 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Application/Application.h>
#include <UI/UIManager.h>
#include <Application/DebugUI.h>
#include <Utils/Log.h>
#include <Utils/CustomMemoryHook.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/RegisterTypes.h>
#include <Renderer/DebugRendererImp.h>
#ifdef JPH_PLATFORM_WINDOWS
#include <crtdbg.h>
#include <Input/Win/KeyboardWin.h>
#include <Input/Win/MouseWin.h>
#include <Window/ApplicationWindowWin.h>
#elif defined(JPH_PLATFORM_LINUX)
#include <Input/Linux/KeyboardLinux.h>
#include <Input/Linux/MouseLinux.h>
#include <Window/ApplicationWindowLinux.h>
#elif defined(JPH_PLATFORM_MACOS)
#include <Input/MacOS/KeyboardMacOS.h>
#include <Input/MacOS/MouseMacOS.h>
#include <Window/ApplicationWindowMacOS.h>
#endif
JPH_GCC_SUPPRESS_WARNING("-Wswitch")
// Constructor
Application::Application(const char *inApplicationName, [[maybe_unused]] const String &inCommandLine) :
mDebugRenderer(nullptr),
mRenderer(nullptr),
mKeyboard(nullptr),
mMouse(nullptr),
mUI(nullptr),
mDebugUI(nullptr)
{
#if defined(JPH_PLATFORM_WINDOWS) && defined(_DEBUG)
// Enable leak detection
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
// Register trace implementation
Trace = TraceImpl;
#ifdef JPH_ENABLE_ASSERTS
// Register assert failed handler
AssertFailed = [](const char *inExpression, const char *inMessage, const char *inFile, uint inLine)
{
Trace("%s (%d): Assert Failed: %s", inFile, inLine, inMessage != nullptr? inMessage : inExpression);
return true;
};
#endif // JPH_ENABLE_ASSERTS
// Create factory
Factory::sInstance = new Factory;
// Register physics types with the factory
RegisterTypes();
{
// Disable allocation checking
DisableCustomMemoryHook dcmh;
// Create window
#ifdef JPH_PLATFORM_WINDOWS
mWindow = new ApplicationWindowWin;
#elif defined(JPH_PLATFORM_LINUX)
mWindow = new ApplicationWindowLinux;
#elif defined(JPH_PLATFORM_MACOS)
mWindow = new ApplicationWindowMacOS;
#else
#error No window defined
#endif
mWindow->Initialize(inApplicationName);
// Create renderer
mRenderer = Renderer::sCreate();
mRenderer->Initialize(mWindow);
// Create font
Font *font = new Font(mRenderer);
font->Create("Roboto-Regular", 24);
mFont = font;
// Init debug renderer
mDebugRenderer = new DebugRendererImp(mRenderer, mFont);
// Init keyboard
#ifdef JPH_PLATFORM_WINDOWS
mKeyboard = new KeyboardWin;
#elif defined(JPH_PLATFORM_LINUX)
mKeyboard = new KeyboardLinux;
#elif defined(JPH_PLATFORM_MACOS)
mKeyboard = new KeyboardMacOS;
#else
#error No keyboard defined
#endif
mKeyboard->Initialize(mWindow);
// Init mouse
#ifdef JPH_PLATFORM_WINDOWS
mMouse = new MouseWin;
#elif defined(JPH_PLATFORM_LINUX)
mMouse = new MouseLinux;
#elif defined(JPH_PLATFORM_MACOS)
mMouse = new MouseMacOS;
#else
#error No mouse defined
#endif
mMouse->Initialize(mWindow);
// Init UI
mUI = new UIManager(mRenderer);
mUI->SetVisible(false);
// Init debug UI
mDebugUI = new DebugUI(mUI, mFont);
}
// Get initial time
mLastUpdateTime = chrono::high_resolution_clock::now();
}
// Destructor
Application::~Application()
{
{
// Disable allocation checking
DisableCustomMemoryHook dcmh;
delete mDebugUI;
delete mUI;
delete mMouse;
delete mKeyboard;
delete mDebugRenderer;
mFont = nullptr;
delete mRenderer;
delete mWindow;
}
// Unregisters all types with the factory and cleans up the default material
UnregisterTypes();
delete Factory::sInstance;
Factory::sInstance = nullptr;
}
String Application::sCreateCommandLine(int inArgC, char **inArgV)
{
String command_line;
for (int i = 0; i < inArgC; ++i)
{
if (i > 0)
command_line += " ";
command_line += inArgV[i];
}
return command_line;
}
// Clear debug lines / triangles / texts that have been accumulated
void Application::ClearDebugRenderer()
{
JPH_PROFILE_FUNCTION();
static_cast<DebugRendererImp *>(mDebugRenderer)->Clear();
mDebugRendererCleared = true;
}
// Main loop
void Application::Run()
{
// Set initial camera position
ResetCamera();
// Enter the main loop
mWindow->MainLoop([this]() { return RenderFrame(); });
}
bool Application::RenderFrame()
{
// Get new input
mKeyboard->Poll();
mMouse->Poll();
// Handle keyboard input
for (EKey key = mKeyboard->GetFirstKey(); key != EKey::Invalid; key = mKeyboard->GetNextKey())
switch (key)
{
case EKey::P:
mIsPaused = !mIsPaused;
break;
case EKey::O:
mSingleStep = true;
break;
case EKey::T:
// Dump timing info to file
JPH_PROFILE_DUMP();
break;
case EKey::Escape:
mDebugUI->ToggleVisibility();
break;
}
// Calculate delta time
chrono::high_resolution_clock::time_point time = chrono::high_resolution_clock::now();
chrono::microseconds delta = chrono::duration_cast<chrono::microseconds>(time - mLastUpdateTime);
mLastUpdateTime = time;
float clock_delta_time = 1.0e-6f * delta.count();
float world_delta_time = 0.0f;
if (mRequestedDeltaTime <= 0.0f)
{
// If no fixed frequency update is requested, update with variable time step
world_delta_time = !mIsPaused || mSingleStep? clock_delta_time : 0.0f;
mResidualDeltaTime = 0.0f;
}
else
{
// Else use fixed time steps
if (mSingleStep)
{
// Single step
world_delta_time = mRequestedDeltaTime;
}
else if (!mIsPaused)
{
// Calculate how much time has passed since the last render
world_delta_time = clock_delta_time + mResidualDeltaTime;
if (world_delta_time < mRequestedDeltaTime)
{
// Too soon, set the residual time and don't update
mResidualDeltaTime = world_delta_time;
world_delta_time = 0.0f;
}
else
{
// Update and clamp the residual time to a full update to avoid spiral of death
mResidualDeltaTime = min(mRequestedDeltaTime, world_delta_time - mRequestedDeltaTime);
world_delta_time = mRequestedDeltaTime;
}
}
}
mSingleStep = false;
// Clear debug lines if we're going to step
if (world_delta_time > 0.0f)
ClearDebugRenderer();
{
JPH_PROFILE("UpdateFrame");
if (!UpdateFrame(world_delta_time))
return false;
}
// Draw coordinate axis
if (mDebugRendererCleared)
mDebugRenderer->DrawCoordinateSystem(RMat44::sIdentity());
// 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
if (!mRenderer->BeginFrame(mWorldCamera, GetWorldScale()))
return true;
// Draw from light
static_cast<DebugRendererImp *>(mDebugRenderer)->DrawShadowPass();
// Start drawing normally
mRenderer->EndShadowPass();
// Draw debug information
static_cast<DebugRendererImp *>(mDebugRenderer)->Draw();
// Draw the frame rate counter
DrawFPS(clock_delta_time);
if (mUI->IsVisible())
{
// Send mouse input to UI
bool left_pressed = mMouse->IsLeftPressed();
if (left_pressed && !mLeftMousePressed)
mUI->MouseDown(mMouse->GetX(), mMouse->GetY());
else if (!left_pressed && mLeftMousePressed)
mUI->MouseUp(mMouse->GetX(), mMouse->GetY());
mLeftMousePressed = left_pressed;
mUI->MouseMove(mMouse->GetX(), mMouse->GetY());
{
// Disable allocation checking
DisableCustomMemoryHook dcmh;
// Update and draw the menu
mUI->Update(clock_delta_time);
mUI->Draw();
}
}
else
{
// Menu not visible, cancel any mouse operations
mUI->MouseCancel();
}
// Show the frame
mRenderer->EndFrame();
// Notify of next frame
JPH_PROFILE_NEXTFRAME();
return true;
}
void Application::GetCameraLocalHeadingAndPitch(float &outHeading, float &outPitch)
{
outHeading = ATan2(mLocalCamera.mForward.GetZ(), mLocalCamera.mForward.GetX());
outPitch = ATan2(mLocalCamera.mForward.GetY(), Vec3(mLocalCamera.mForward.GetX(), 0, mLocalCamera.mForward.GetZ()).Length());
}
void Application::ConvertCameraLocalToWorld(float inCameraHeading, float inCameraPitch)
{
// Convert local to world space using the camera pivot
RMat44 pivot = GetCameraPivot(inCameraHeading, inCameraPitch);
mWorldCamera = mLocalCamera;
mWorldCamera.mPos = pivot * mLocalCamera.mPos;
mWorldCamera.mForward = pivot.Multiply3x3(mLocalCamera.mForward);
mWorldCamera.mUp = pivot.Multiply3x3(mLocalCamera.mUp);
}
void Application::ResetCamera()
{
// Get local space camera state
mLocalCamera = CameraState();
GetInitialCamera(mLocalCamera);
// Convert to world space
float heading, pitch;
GetCameraLocalHeadingAndPitch(heading, pitch);
ConvertCameraLocalToWorld(heading, pitch);
}
// Update camera position
void Application::UpdateCamera(float inDeltaTime)
{
JPH_PROFILE_FUNCTION();
// Determine speed
float speed = 20.0f * GetWorldScale() * inDeltaTime;
bool shift = mKeyboard->IsKeyPressed(EKey::LShift) || mKeyboard->IsKeyPressed(EKey::RShift);
bool control = mKeyboard->IsKeyPressed(EKey::LControl) || mKeyboard->IsKeyPressed(EKey::RControl);
bool alt = mKeyboard->IsKeyPressed(EKey::LAlt) || mKeyboard->IsKeyPressed(EKey::RAlt);
if (shift) speed *= 10.0f;
else if (control) speed /= 25.0f;
else if (alt) speed = 0.0f;
// Position
Vec3 right = mLocalCamera.mForward.Cross(mLocalCamera.mUp);
if (mKeyboard->IsKeyPressed(EKey::A)) mLocalCamera.mPos -= speed * right;
if (mKeyboard->IsKeyPressed(EKey::D)) mLocalCamera.mPos += speed * right;
if (mKeyboard->IsKeyPressed(EKey::W)) mLocalCamera.mPos += speed * mLocalCamera.mForward;
if (mKeyboard->IsKeyPressed(EKey::S)) mLocalCamera.mPos -= speed * mLocalCamera.mForward;
// Forward
float heading, pitch;
GetCameraLocalHeadingAndPitch(heading, pitch);
heading += DegreesToRadians(mMouse->GetDX() * 0.5f);
pitch = Clamp(pitch - DegreesToRadians(mMouse->GetDY() * 0.5f), -0.49f * JPH_PI, 0.49f * JPH_PI);
mLocalCamera.mForward = Vec3(Cos(pitch) * Cos(heading), Sin(pitch), Cos(pitch) * Sin(heading));
// Convert to world space
ConvertCameraLocalToWorld(heading, pitch);
}
void Application::DrawFPS(float inDeltaTime)
{
JPH_PROFILE_FUNCTION();
// Don't divide by zero
if (inDeltaTime <= 0.0f)
return;
// Switch tho ortho mode
mRenderer->SetOrthoMode();
// Update stats
mTotalDeltaTime += inDeltaTime;
mNumFrames++;
if (mNumFrames > 10)
{
mFPS = mNumFrames / mTotalDeltaTime;
mNumFrames = 0;
mTotalDeltaTime = 0.0f;
}
// Create string
String fps = StringFormat("%.1f", (double)mFPS);
// Get size of text on screen
Float2 text_size = mFont->MeasureText(fps);
int text_w = int(text_size.x * mFont->GetCharHeight());
int text_h = int(text_size.y * mFont->GetCharHeight());
// Draw FPS counter
int x = (mWindow->GetWindowWidth() - text_w) / 2 - 20;
int y = 10;
mUI->DrawQuad(x - 5, y - 3, text_w + 10, text_h + 6, UITexturedQuad(), Color(0, 0, 0, 128));
mUI->DrawText(x, y, fps, mFont);
// Draw status string
if (!mStatusString.empty())
mUI->DrawText(5, 5, mStatusString, mFont);
// Draw paused string if the app is paused
if (mIsPaused)
{
string_view paused_str = "P: Unpause, ESC: Menu";
Float2 pause_size = mFont->MeasureText(paused_str);
mUI->DrawText(mWindow->GetWindowWidth() - 5 - int(pause_size.x * mFont->GetCharHeight()), 5, paused_str, mFont);
}
// Restore state
mRenderer->SetProjectionMode();
}

View File

@@ -0,0 +1,121 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Renderer/Renderer.h>
#include <Renderer/Font.h>
#include <Input/Keyboard.h>
#include <Input/Mouse.h>
#include <Jolt/Core/Reference.h>
// STL includes
JPH_SUPPRESS_WARNINGS_STD_BEGIN
#include <chrono>
JPH_SUPPRESS_WARNINGS_STD_END
class UIManager;
class DebugUI;
namespace JPH {
class DebugRenderer;
}
class Application
{
private:
/// Camera state
CameraState mLocalCamera;
CameraState mWorldCamera;
protected:
/// Debug renderer module
DebugRenderer * mDebugRenderer;
/// Main window
ApplicationWindow * mWindow;
/// Render module
Renderer * mRenderer;
/// Default font
RefConst<Font> mFont;
/// Input
Keyboard * mKeyboard;
Mouse * mMouse;
/// Menu
UIManager * mUI;
DebugUI * mDebugUI;
/// A string that is shown on screen to indicate the status of the application
String mStatusString;
public:
/// Constructor
Application(const char *inApplicationName, const String &inCommandLine);
virtual ~Application();
/// Create a single string command line
static String sCreateCommandLine(int inArgC, char **inArgV);
/// Enter the main loop
void Run();
protected:
/// Update the application
virtual bool UpdateFrame(float inDeltaTime) { return false; }
/// Pause / unpause the simulation
void Pause(bool inPaused) { mIsPaused = inPaused; }
/// Programmatically single step the simulation
void SingleStep() { mIsPaused = true; mSingleStep = true; }
/// Set the frequency at which we want to render frames
void SetRenderFrequency(float inFrequency) { mRequestedDeltaTime = 1.0f / inFrequency; }
/// Will restore camera position to that returned by GetInitialCamera
void ResetCamera();
/// Override to specify the initial camera state (local to GetCameraPivot)
virtual void GetInitialCamera(CameraState &ioState) const { }
/// Override to specify a camera pivot point and orientation (world space)
virtual RMat44 GetCameraPivot(float inCameraHeading, float inCameraPitch) const { return RMat44::sIdentity(); }
/// Get scale factor for this world, used to boost camera speed and to scale detail of the shadows
virtual float GetWorldScale() const { return 1.0f; }
/// Get current state of the camera (world space)
const CameraState & GetCamera() const { return mWorldCamera; }
/// Clear debug lines / triangles / texts that have been accumulated
void ClearDebugRenderer();
private:
/// Render a frame
bool RenderFrame();
/// Extract heading and pitch from the local space (relative to the camera pivot) camera forward
void GetCameraLocalHeadingAndPitch(float &outHeading, float &outPitch);
/// Convert local space camera to world space camera
void ConvertCameraLocalToWorld(float inCameraHeading, float inCameraPitch);
/// Update the local and world space camera transform
void UpdateCamera(float inDeltaTime);
/// Draw the frame rate counter
void DrawFPS(float inDeltaTime);
chrono::high_resolution_clock::time_point mLastUpdateTime;
bool mIsPaused = false;
bool mSingleStep = false;
bool mDebugRendererCleared = true;
bool mLeftMousePressed = false;
float mFPS = 0.0f;
float mRequestedDeltaTime = 0.0f;
float mResidualDeltaTime = 0.0f;
float mTotalDeltaTime = 0.0f;
int mNumFrames = 0;
};

View File

@@ -0,0 +1,215 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Application/DebugUI.h>
#include <Renderer/Texture.h>
#include <UI/UIManager.h>
#include <UI/UIImage.h>
#include <UI/UIStaticText.h>
#include <UI/UICheckBox.h>
#include <UI/UIHorizontalStack.h>
#include <UI/UIVerticalStack.h>
#include <UI/UITextButton.h>
#include <Image/LoadTGA.h>
#include <Utils/Log.h>
#include <Utils/AssetStream.h>
JPH_SUPPRESS_WARNINGS_STD_BEGIN
#include <fstream>
JPH_SUPPRESS_WARNINGS_STD_END
DebugUI::DebugUI(UIManager *inUIManager, const Font *inFont) :
mUI(inUIManager),
mFont(inFont)
{
// Load UI texture with commonly used UI elements
AssetStream texture_stream("UI.tga", std::ios::in | std::ios::binary);
Ref<Surface> texture_surface = LoadTGA(texture_stream.Get());
if (texture_surface == nullptr)
FatalError("Failed to load UI.tga");
mUITexture = mUI->GetRenderer()->CreateTexture(texture_surface);
// Install callback that pops a layer when the deactivate animation finishes
mUI->SetDeactivatedAction([this]() {
mUI->PopLayer();
});
// Don't want to draw any layers that are not active
mUI->SetDrawInactiveLayers(false);
}
UIElement *DebugUI::CreateMenu()
{
mUI->PushLayer();
UIImage *background = new UIImage();
background->SetRelativeX(10);
background->SetRelativeY(10);
background->SetImage(UITexturedQuad(mUITexture, 0, 0, 33, 30, 4, 4, 24, 21));
mUI->Add(background);
UIVerticalStack *stack = new UIVerticalStack();
stack->SetRelativeX(10);
stack->SetRelativeY(10);
stack->SetPaddingRight(10);
stack->SetPaddingBottom(10);
background->Add(stack);
return stack;
}
UIStaticText *DebugUI::CreateStaticText(UIElement *inMenu, const string_view &inText)
{
UIStaticText *text = new UIStaticText();
text->SetText(inText);
text->SetFont(mFont);
inMenu->Add(text);
return text;
}
UITextButton *DebugUI::CreateTextButton(UIElement *inMenu, const string_view &inName, UITextButton::ClickAction inAction)
{
UITextButton *button = new UITextButton();
button->SetText(inName);
button->SetFont(mFont);
button->SetClickAction(inAction);
button->SetTextPadding(0, 24, 0, 0);
button->SetPaddingRight(24);
inMenu->Add(button);
return button;
}
UICheckBox *DebugUI::CreateCheckBox(UIElement *inMenu, const string_view &inName, bool inInitiallyChecked, UICheckBox::ClickAction inAction)
{
UICheckBox *check_box = new UICheckBox();
check_box->SetUncheckedStateQuad(UITexturedQuad(mUITexture, 48, 0, 16, 16));
check_box->SetCheckedStateQuad(UITexturedQuad(mUITexture, 65, 0, 16, 16));
check_box->SetFont(mFont);
check_box->SetText(inName);
check_box->SetClickAction(inAction);
check_box->SetState(inInitiallyChecked? UICheckBox::STATE_CHECKED : UICheckBox::STATE_UNCHECKED);
check_box->SetPaddingRight(24);
inMenu->Add(check_box);
return check_box;
}
UISlider *DebugUI::CreateSlider(UIElement *inMenu, const string_view &inName, float inInitialValue, float inMinValue, float inMaxValue, float inStepValue, UISlider::ValueChangedAction inAction)
{
UIHorizontalStack *horiz = new UIHorizontalStack();
horiz->SetPaddingRight(24);
inMenu->Add(horiz);
UIStaticText *text = new UIStaticText();
text->SetFont(mFont);
text->SetTextPadding(0, 24, 0, 0);
text->SetText(inName);
text->SetPaddingRight(20);
horiz->Add(text);
UISlider *slider = new UISlider();
slider->SetHeight(24);
slider->SetWidth(250);
slider->SetPaddingRight(20);
slider->SetValue(inInitialValue);
slider->SetRange(inMinValue, inMaxValue, inStepValue);
slider->SetValueChangedAction(inAction);
slider->SetSlider(UITexturedQuad(mUITexture, 44, 37, 1, 9));
slider->SetThumb(UITexturedQuad(mUITexture, 31, 32, 11, 19));
horiz->Add(slider);
UIButton *decr_button = new UIButton();
decr_button->SetRepeat(0.5f, 0.2f);
decr_button->SetButtonQuad(UITexturedQuad(mUITexture, 0, 31, 17, 21));
slider->Add(decr_button);
slider->SetDecreaseButton(decr_button);
UIButton *incr_button = new UIButton();
incr_button->SetRepeat(0.5f, 0.2f);
incr_button->SetButtonQuad(UITexturedQuad(mUITexture, 13, 31, 17, 21));
slider->Add(incr_button);
slider->SetIncreaseButton(incr_button);
UIImage *image = new UIImage();
image->SetImage(UITexturedQuad(mUITexture, 34, 0, 13, 24, 36, 2, 9, 20));
horiz->Add(image);
UIStaticText *value = new UIStaticText();
value->SetWidth(75);
value->SetTextPadding(0, 5, 0, 5);
value->SetWrap(true);
value->SetTextAlignment(UIElement::RIGHT);
value->SetFont(mFont);
image->Add(value);
slider->SetStaticText(value);
return slider;
}
UIComboBox *DebugUI::CreateComboBox(UIElement *inMenu, const string_view &inName, const Array<String> &inItems, int inInitialItem, UIComboBox::ItemChangedAction inAction)
{
UIHorizontalStack *horiz = new UIHorizontalStack();
horiz->SetPaddingRight(24);
inMenu->Add(horiz);
UIStaticText *text = new UIStaticText();
text->SetFont(mFont);
text->SetTextPadding(0, 24, 0, 0);
text->SetText(inName);
text->SetPaddingRight(20);
horiz->Add(text);
UIComboBox *combo = new UIComboBox();
combo->SetHeight(24);
combo->SetWidth(250);
combo->SetPaddingRight(20);
combo->SetItems(inItems);
combo->SetCurrentItem(inInitialItem);
combo->SetItemChangedAction(inAction);
horiz->Add(combo);
UIButton *prev_button = new UIButton();
prev_button->SetRepeat(0.5f, 0.2f);
prev_button->SetButtonQuad(UITexturedQuad(mUITexture, 0, 31, 17, 21));
combo->Add(prev_button);
combo->SetPreviousButton(prev_button);
UIButton *next_button = new UIButton();
next_button->SetRepeat(0.5f, 0.2f);
next_button->SetButtonQuad(UITexturedQuad(mUITexture, 13, 31, 17, 21));
combo->Add(next_button);
combo->SetNextButton(next_button);
UIStaticText *value = new UIStaticText();
value->SetTextPadding(0, 5, 0, 5);
value->SetWrap(false);
value->SetTextAlignment(UIElement::CENTER);
value->SetFont(mFont);
combo->Add(value);
combo->SetStaticText(value);
return combo;
}
void DebugUI::ShowMenu(UIElement *inMenu)
{
UIHorizontalStack::sUniformChildWidth(inMenu);
mUI->AutoLayout();
mUI->SwitchToState(UIManager::STATE_ACTIVATING);
}
void DebugUI::BackToMain()
{
while (mUI->GetNumLayers() > 2)
mUI->PopLayer();
}
void DebugUI::ToggleVisibility()
{
if (mUI->GetNumLayers() > 2)
mUI->SwitchToState(UIManager::STATE_DEACTIVATING);
else
mUI->SetVisible(!mUI->IsVisible());
}

View File

@@ -0,0 +1,48 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
#include <UI/UICheckBox.h>
#include <UI/UITextButton.h>
#include <UI/UISlider.h>
#include <UI/UIComboBox.h>
#include <UI/UIStaticText.h>
class UIManager;
class Texture;
class Font;
class Keyboard;
class DebugUI
{
public:
/// Constructor
DebugUI(UIManager *inUIManager, const Font *inFont);
/// Create a new (sub) menu
UIElement * CreateMenu();
/// Add items to the menu
UIStaticText * CreateStaticText(UIElement *inMenu, const string_view &inText);
UITextButton * CreateTextButton(UIElement *inMenu, const string_view &inName, UITextButton::ClickAction inAction);
UICheckBox * CreateCheckBox(UIElement *inMenu, const string_view &inName, bool inInitiallyChecked, UICheckBox::ClickAction inAction);
UISlider * CreateSlider(UIElement *inMenu, const string_view &inName, float inInitialValue, float inMinValue, float inMaxValue, float inStepValue, UISlider::ValueChangedAction inAction);
UIComboBox * CreateComboBox(UIElement *inMenu, const string_view &inName, const Array<String> &inItems, int inInitialItem, UIComboBox::ItemChangedAction inAction);
/// Show it
void ShowMenu(UIElement *inMenu);
/// Go back to the main menu
void BackToMain();
/// Show or hide the entire menu
void ToggleVisibility();
private:
UIManager * mUI;
RefConst<Font> mFont;
RefConst<Texture> mUITexture;
};

View File

@@ -0,0 +1,74 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/FPException.h>
#if defined(JPH_PLATFORM_WINDOWS)
#define ENTRY_POINT(AppName, RegisterAllocator) \
\
int WINAPI wWinMain(_In_ HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) \
{ \
RegisterAllocator(); \
\
JPH_PROFILE_START("Main"); \
\
FPExceptionsEnable enable_exceptions; \
JPH_UNUSED(enable_exceptions); \
\
{ \
AppName app(GetCommandLineA()); \
app.Run(); \
} \
\
JPH_PROFILE_END(); \
\
return 0; \
} \
\
int __cdecl main(int inArgC, char **inArgV) \
{ \
RegisterAllocator(); \
\
JPH_PROFILE_START("Main"); \
\
FPExceptionsEnable enable_exceptions; \
JPH_UNUSED(enable_exceptions); \
\
{ \
AppName app(Application::sCreateCommandLine(inArgC, inArgV)); \
app.Run(); \
} \
\
JPH_PROFILE_END(); \
\
return 0; \
}
#else
#define ENTRY_POINT(AppName, RegisterAllocator) \
\
int main(int inArgC, char **inArgV) \
{ \
RegisterAllocator(); \
\
JPH_PROFILE_START("Main"); \
\
FPExceptionsEnable enable_exceptions; \
JPH_UNUSED(enable_exceptions); \
\
{ \
AppName app(Application::sCreateCommandLine(inArgC, inArgV)); \
app.Run(); \
} \
\
JPH_PROFILE_END(); \
\
return 0; \
}
#endif

View File

@@ -0,0 +1,240 @@
#include <TestFramework.h>
#include <External/Perlin.h>
// not same permutation table as Perlin's reference to avoid copyright issues;
// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/
// @OPTIMIZE: should this be unsigned char instead of int for cache?
static unsigned char stb_perlin_randtab[512] =
{
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
// and a second copy so we don't need an extra mask or static initializer
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
};
static float stb_perlin_lerp(float a, float b, float t)
{
return a + (b-a) * t;
}
static int stb_perlin_fastfloor(float a)
{
int ai = (int) a;
return (a < ai) ? ai-1 : ai;
}
// different grad function from Perlin's, but easy to modify to match reference
static float stb_perlin_grad(int hash, float x, float y, float z)
{
static float basis[12][4] =
{
{ 1, 1, 0 },
{ -1, 1, 0 },
{ 1,-1, 0 },
{ -1,-1, 0 },
{ 1, 0, 1 },
{ -1, 0, 1 },
{ 1, 0,-1 },
{ -1, 0,-1 },
{ 0, 1, 1 },
{ 0,-1, 1 },
{ 0, 1,-1 },
{ 0,-1,-1 },
};
// perlin's gradient has 12 cases so some get used 1/16th of the time
// and some 2/16ths. We reduce bias by changing those fractions
// to 5/64ths and 6/64ths, and the same 4 cases get the extra weight.
static unsigned char indices[64] =
{
0,1,2,3,4,5,6,7,8,9,10,11,
0,9,1,11,
0,1,2,3,4,5,6,7,8,9,10,11,
0,1,2,3,4,5,6,7,8,9,10,11,
0,1,2,3,4,5,6,7,8,9,10,11,
0,1,2,3,4,5,6,7,8,9,10,11,
};
// if you use reference permutation table, change 63 below to 15 to match reference
// (this is why the ordering of the table above is funky)
float *grad = basis[indices[hash & 63]];
return grad[0]*x + grad[1]*y + grad[2]*z;
}
float PerlinNoise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap)
{
float u,v,w;
float n000,n001,n010,n011,n100,n101,n110,n111;
float n00,n01,n10,n11;
float n0,n1;
int x_mask = (x_wrap-1) & 255;
int y_mask = (y_wrap-1) & 255;
int z_mask = (z_wrap-1) & 255;
int px = stb_perlin_fastfloor(x);
int py = stb_perlin_fastfloor(y);
int pz = stb_perlin_fastfloor(z);
int x0 = px & x_mask, x1 = (px+1) & x_mask;
int y0 = py & y_mask, y1 = (py+1) & y_mask;
int z0 = pz & z_mask, z1 = (pz+1) & z_mask;
int r0,r1, r00,r01,r10,r11;
#define stb_perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
x -= px; u = stb_perlin_ease(x);
y -= py; v = stb_perlin_ease(y);
z -= pz; w = stb_perlin_ease(z);
r0 = stb_perlin_randtab[x0];
r1 = stb_perlin_randtab[x1];
r00 = stb_perlin_randtab[r0+y0];
r01 = stb_perlin_randtab[r0+y1];
r10 = stb_perlin_randtab[r1+y0];
r11 = stb_perlin_randtab[r1+y1];
n000 = stb_perlin_grad(stb_perlin_randtab[r00+z0], x , y , z );
n001 = stb_perlin_grad(stb_perlin_randtab[r00+z1], x , y , z-1 );
n010 = stb_perlin_grad(stb_perlin_randtab[r01+z0], x , y-1, z );
n011 = stb_perlin_grad(stb_perlin_randtab[r01+z1], x , y-1, z-1 );
n100 = stb_perlin_grad(stb_perlin_randtab[r10+z0], x-1, y , z );
n101 = stb_perlin_grad(stb_perlin_randtab[r10+z1], x-1, y , z-1 );
n110 = stb_perlin_grad(stb_perlin_randtab[r11+z0], x-1, y-1, z );
n111 = stb_perlin_grad(stb_perlin_randtab[r11+z1], x-1, y-1, z-1 );
n00 = stb_perlin_lerp(n000,n001,w);
n01 = stb_perlin_lerp(n010,n011,w);
n10 = stb_perlin_lerp(n100,n101,w);
n11 = stb_perlin_lerp(n110,n111,w);
n0 = stb_perlin_lerp(n00,n01,v);
n1 = stb_perlin_lerp(n10,n11,v);
return stb_perlin_lerp(n0,n1,u);
}
float PerlinRidgeNoise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves, int x_wrap, int y_wrap, int z_wrap)
{
int i;
float frequency = 1.0f;
float prev = 1.0f;
float amplitude = 0.5f;
float sum = 0.0f;
for (i = 0; i < octaves; i++) {
float r = (float)(PerlinNoise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap));
r = r<0 ? -r : r; // abs()
r = offset - r;
r = r*r;
sum += r*amplitude*prev;
prev = r;
frequency *= lacunarity;
amplitude *= gain;
}
return sum;
}
float PerlinFBMNoise3(float x, float y, float z, float lacunarity, float gain, int octaves, int x_wrap, int y_wrap, int z_wrap)
{
int i;
float frequency = 1.0f;
float amplitude = 1.0f;
float sum = 0.0f;
for (i = 0; i < octaves; i++) {
sum += PerlinNoise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)*amplitude;
frequency *= lacunarity;
amplitude *= gain;
}
return sum;
}
float PerlinTurbulenceNoise3(float x, float y, float z, float lacunarity, float gain, int octaves, int x_wrap, int y_wrap, int z_wrap)
{
int i;
float frequency = 1.0f;
float amplitude = 1.0f;
float sum = 0.0f;
for (i = 0; i < octaves; i++) {
float r = PerlinNoise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)*amplitude;
r = r<0 ? -r : r; // abs()
sum += r;
frequency *= lacunarity;
amplitude *= gain;
}
return sum;
}
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/

View File

@@ -0,0 +1,62 @@
#pragma once
// Taken from: https://github.com/nothings/stb/blob/master/stb_perlin.h
//
// stb_perlin.h - v0.3 - perlin noise
// public domain single-file C implementation by Sean Barrett
//
// LICENSE
//
// See cpp file.
//
// Contributors:
// Jack Mott - additional noise functions
//
// float PerlinNoise3(float x,
// float y,
// float z,
// int x_wrap = 0,
// int y_wrap = 0,
// int z_wrap = 0)
//
// This function computes a random value at the coordinate (x,y,z).
// Adjacent random values are continuous but the noise fluctuates
// its randomness with period 1, i.e. takes on wholly unrelated values
// at integer points. Specifically, this implements Ken Perlin's
// revised noise function from 2002.
//
// The "wrap" parameters can be used to create wraparound noise that
// wraps at powers of two. The numbers MUST be powers of two. Specify
// 0 to mean "don't care". (The noise always wraps every 256 due
// details of the implementation, even if you ask for larger or no
// wrapping.)
//
// Fractal Noise:
//
// Three common fractal noise functions are included, which produce
// a wide variety of nice effects depending on the parameters
// provided. Note that each function will call PerlinNoise3
// 'octaves' times, so this parameter will affect runtime.
//
// float PerlinRidgeNoise3(float x, float y, float z,
// float lacunarity, float gain, float offset, int octaves,
// int x_wrap, int y_wrap, int z_wrap);
//
// float PerlinFBMNoise3(float x, float y, float z,
// float lacunarity, float gain, int octaves,
// int x_wrap, int y_wrap, int z_wrap);
//
// float PerlinTurbulenceNoise3(float x, float y, float z,
// float lacunarity, float gain,int octaves,
// int x_wrap, int y_wrap, int z_wrap);
//
// Typical values to start playing with:
// octaves = 6 -- number of "octaves" of noise3() to sum
// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output)
// gain = 0.5 -- relative weighting applied to each successive octave
// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure
//
float PerlinNoise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap);
float PerlinRidgeNoise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves, int x_wrap, int y_wrap, int z_wrap);
float PerlinFBMNoise3(float x, float y, float z, float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap);
float PerlinTurbulenceNoise3(float x, float y, float z, float lacunarity, float gain, int octaves, int x_wrap, int y_wrap, int z_wrap);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,356 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/BlitSurface.h>
#include <Image/Surface.h>
#include <Jolt/Core/Color.h>
#include <Jolt/Core/Profiler.h>
//////////////////////////////////////////////////////////////////////////////////////////
// BlitSettings
//////////////////////////////////////////////////////////////////////////////////////////
const BlitSettings BlitSettings::sDefault;
BlitSettings::BlitSettings() :
mConvertRGBToAlpha(false),
mConvertAlphaToRGB(false),
mConvertToGrayScale(false),
mInvertAlpha(false),
mColorKeyAlpha(false),
mColorKeyStart(240, 0, 240),
mColorKeyEnd(255, 15, 255)
{
}
bool BlitSettings::operator == (const BlitSettings &inRHS) const
{
return mConvertRGBToAlpha == inRHS.mConvertRGBToAlpha
&& mConvertAlphaToRGB == inRHS.mConvertAlphaToRGB
&& mConvertToGrayScale == inRHS.mConvertToGrayScale
&& mInvertAlpha == inRHS.mInvertAlpha
&& mColorKeyAlpha == inRHS.mColorKeyAlpha
&& mColorKeyStart == inRHS.mColorKeyStart
&& mColorKeyEnd == inRHS.mColorKeyEnd
&& mZoomSettings == inRHS.mZoomSettings;
}
//////////////////////////////////////////////////////////////////////////////////////////
// Converting from one format to another
//////////////////////////////////////////////////////////////////////////////////////////
// The macro COL(s) converts color s to another color given the mapping tables
#define CMP(s, c) map[256 * c + ((s & src_mask[c]) >> src_shift[c])]
#define COL(s) (CMP(s, 0) + CMP(s, 1) + CMP(s, 2) + CMP(s, 3))
static void sComputeTranslationTable(const FormatDescription & inSrcDesc, const FormatDescription & inDstDesc, uint32 *outMask, uint32 *outShift, uint32 *outMap)
{
JPH_PROFILE("sComputeTranslationTable");
// Compute translation tables for each color component
uint32 written_mask = 0;
for (int c = 0; c < 4; ++c)
{
outMask[c] = inSrcDesc.GetComponentMask(c);
outShift[c] = CountTrailingZeros(outMask[c]);
uint32 src_shifted_mask = outMask[c] >> outShift[c];
uint32 dst_mask = inDstDesc.GetComponentMask(c);
uint32 dst_shift = CountTrailingZeros(dst_mask);
uint32 dst_shifted_mask = dst_mask >> dst_shift;
if ((written_mask & dst_mask) != 0)
{
dst_mask = 0;
dst_shift = 0;
dst_shifted_mask = 0;
}
else
written_mask |= dst_mask;
float scale = src_shifted_mask > 0? float(dst_shifted_mask) / src_shifted_mask : 0.0f;
uint32 entry = 0;
if (src_shifted_mask != 0)
for (; entry <= src_shifted_mask; ++entry)
outMap[256 * c + entry] = uint32(round(scale * entry)) << dst_shift;
for (; entry < 256; ++entry)
outMap[256 * c + entry] = dst_mask;
}
}
static bool sConvertImageDifferentTypes(RefConst<Surface> inSrc, Ref<Surface> ioDst)
{
JPH_PROFILE("sConvertImageDifferentTypes");
// Get image properties
int sbpp = inSrc->GetBytesPerPixel();
int dbpp = ioDst->GetBytesPerPixel();
int width = inSrc->GetWidth();
int height = inSrc->GetHeight();
JPH_ASSERT(width == ioDst->GetWidth());
JPH_ASSERT(height == ioDst->GetHeight());
// Compute conversion map
uint32 src_mask[4];
uint32 src_shift[4];
uint32 map[4 * 256];
sComputeTranslationTable(inSrc->GetFormatDescription(), ioDst->GetFormatDescription(), src_mask, src_shift, map);
inSrc->Lock(ESurfaceLockMode::Read);
ioDst->Lock(ESurfaceLockMode::Write);
// Convert the image
for (int y = 0; y < height; ++y)
{
const uint8 *s = inSrc->GetScanLine(y);
const uint8 *s_end = inSrc->GetScanLine(y) + width * sbpp;
uint8 *d = ioDst->GetScanLine(y);
while (s < s_end)
{
uint32 src = 0;
memcpy(&src, s, sbpp);
uint32 dst = COL(src);
memcpy(d, &dst, dbpp);
s += sbpp;
d += dbpp;
}
}
inSrc->UnLock();
ioDst->UnLock();
return true;
}
static bool sConvertImageSameTypes(RefConst<Surface> inSrc, Ref<Surface> ioDst)
{
JPH_PROFILE("sConvertImageSameTypes");
// Get image properties
int dbpp = ioDst->GetBytesPerPixel();
int width = inSrc->GetWidth();
int height = inSrc->GetHeight();
JPH_ASSERT(inSrc->GetFormat() == ioDst->GetFormat());
JPH_ASSERT(dbpp == inSrc->GetBytesPerPixel());
JPH_ASSERT(width == ioDst->GetWidth());
JPH_ASSERT(height == ioDst->GetHeight());
inSrc->Lock(ESurfaceLockMode::Read);
ioDst->Lock(ESurfaceLockMode::Write);
// Copy the image line by line to compensate for stride
for (int y = 0; y < height; ++y)
memcpy(ioDst->GetScanLine(y), inSrc->GetScanLine(y), width * dbpp);
inSrc->UnLock();
ioDst->UnLock();
return true;
}
static bool sConvertImage(RefConst<Surface> inSrc, Ref<Surface> ioDst)
{
JPH_PROFILE("sConvertImage");
if (inSrc->GetFormat() == ioDst->GetFormat())
return sConvertImageSameTypes(inSrc, ioDst);
else
return sConvertImageDifferentTypes(inSrc, ioDst);
}
//////////////////////////////////////////////////////////////////////////////////////////
// Special color conversions
//////////////////////////////////////////////////////////////////////////////////////////
static void sConvertRGBToAlpha(Ref<Surface> ioSurface)
{
JPH_PROFILE("sConvertRGBToAlpha");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Convert RGB values to alpha values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
c->a = c->GetIntensity();
++c;
}
}
}
static void sConvertAlphaToRGB(Ref<Surface> ioSurface)
{
JPH_PROFILE("sConvertAlphaToRGB");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Convert alpha values to RGB values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
c->r = c->g = c->b = c->a;
++c;
}
}
}
static void sConvertToGrayScale(Ref<Surface> ioSurface)
{
JPH_PROFILE("sConvertToGrayScale");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Convert RGB values to grayscale values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
uint8 intensity = c->GetIntensity();
c->r = intensity;
c->g = intensity;
c->b = intensity;
++c;
}
}
}
static void sInvertAlpha(Ref<Surface> ioSurface)
{
JPH_PROFILE("sInvertAlpha");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Invert all alpha values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
c->a = uint8(255 - c->a);
++c;
}
}
}
static void sColorKeyAlpha(Ref<Surface> ioSurface, ColorArg inStart, ColorArg inEnd)
{
JPH_PROFILE("sColorKeyAlpha");
// Check surface format
JPH_ASSERT(ioSurface->GetFormat() == ESurfaceFormat::A8R8G8B8);
// Get dimensions of image
int width = ioSurface->GetWidth();
int height = ioSurface->GetHeight();
// Set alpha values
for (int y = 0; y < height; ++y)
{
Color *c = (Color *)ioSurface->GetScanLine(y);
Color *c_end = (Color *)(ioSurface->GetScanLine(y) + width * sizeof(Color));
while (c < c_end)
{
if (c->r >= inStart.r && c->r <= inEnd.r && c->g >= inStart.g && c->g <= inEnd.g && c->b >= inStart.b && c->b <= inEnd.b)
c->a = 0;
else
c->a = 255;
++c;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// BlitSurface
//////////////////////////////////////////////////////////////////////////////////////////
bool BlitSurface(RefConst<Surface> inSrc, Ref<Surface> ioDst, const BlitSettings &inBlitSettings)
{
JPH_PROFILE("BlitSurface");
// Do extra conversion options
RefConst<Surface> src = inSrc;
if (inBlitSettings.mConvertRGBToAlpha || inBlitSettings.mConvertAlphaToRGB || inBlitSettings.mConvertToGrayScale || inBlitSettings.mInvertAlpha || inBlitSettings.mColorKeyAlpha)
{
// Do them on A8R8G8B8 format so the conversion routines are simple
Ref<Surface> tmp = new SoftwareSurface(inSrc->GetWidth(), inSrc->GetHeight(), ESurfaceFormat::A8R8G8B8);
sConvertImage(inSrc, tmp);
src = tmp;
// Perform all optional conversions
tmp->Lock(ESurfaceLockMode::ReadWrite);
if (inBlitSettings.mConvertRGBToAlpha)
sConvertRGBToAlpha(tmp);
if (inBlitSettings.mConvertAlphaToRGB)
sConvertAlphaToRGB(tmp);
if (inBlitSettings.mConvertToGrayScale)
sConvertToGrayScale(tmp);
if (inBlitSettings.mInvertAlpha)
sInvertAlpha(tmp);
if (inBlitSettings.mColorKeyAlpha)
sColorKeyAlpha(tmp, inBlitSettings.mColorKeyStart, inBlitSettings.mColorKeyEnd);
tmp->UnLock();
}
if (src->GetWidth() != ioDst->GetWidth() || src->GetHeight() != ioDst->GetHeight())
{
// Zoom the image if the destination size is not equal to the source size
if (!ZoomImage(src, ioDst, inBlitSettings.mZoomSettings))
return false;
}
else
{
// Convert the image if the sizes are equal
if (!sConvertImage(src, ioDst))
return false;
}
return true;
}

View File

@@ -0,0 +1,37 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Image/ZoomImage.h>
#include <Jolt/Core/Color.h>
/// Settings for blitting one surface to another with possibly different formats and dimensions. The blit
/// routine can use filtering or blurring on the fly. Also it can perform some other
/// basic operations like converting an image to grayscale or alpha only surfaces.
class BlitSettings
{
public:
/// Constructor
BlitSettings();
/// Comparison operators
bool operator == (const BlitSettings &inRHS) const;
/// Default settings
static const BlitSettings sDefault;
/// Special operations that can be applied during the blit
bool mConvertRGBToAlpha; ///< Convert RGB values to alpha values (RGB values remain untouched)
bool mConvertAlphaToRGB; ///< Convert alpha values to grayscale RGB values (Alpha values remain untouched)
bool mConvertToGrayScale; ///< Convert RGB values to grayscale values (Alpha values remain untouched)
bool mInvertAlpha; ///< Invert alpha values
bool mColorKeyAlpha; ///< If true, colors in the range mColorKeyStart..mColorKeyEnd will get an alpha of 0, other colors will get an alpha of 255
Color mColorKeyStart;
Color mColorKeyEnd;
ZoomSettings mZoomSettings; ///< Settings for resizing the image
};
/// Copies an image from inSrc to inDst, converting it on the fly as defined by inBlitSettings
bool BlitSurface(RefConst<Surface> inSrc, Ref<Surface> ioDst, const BlitSettings &inBlitSettings = BlitSettings::sDefault);

View File

@@ -0,0 +1,199 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/LoadBMP.h>
#include <Image/BlitSurface.h>
#include <Image/Surface.h>
#pragma pack (1)
struct BitmapFileHeader
{
char mTypeB;
char mTypeM;
uint32 mSize;
uint16 mReserved1;
uint16 mReserved2;
uint32 mOffBits;
};
struct BitmapInfoHeader
{
uint32 mSize;
uint32 mWidth;
uint32 mHeight;
uint16 mPlanes;
uint16 mBitCount;
uint32 mCompression;
uint32 mSizeImage;
uint32 mXPelsPerMeter;
uint32 mYPelsPerMeter;
uint32 mClrUsed;
uint32 mClrImportant;
};
#pragma pack ()
Ref<Surface> LoadBMP(istream &inStream)
{
bool loaded = true;
// Read bitmap info
BitmapFileHeader bfh;
BitmapInfoHeader bih;
inStream.read((char *)&bfh, sizeof(bfh));
if (inStream.fail())
return nullptr;
inStream.read((char *)&bih, sizeof(bih));
if (inStream.fail())
return nullptr;
// Get properties
int bpp = (bih.mBitCount + 7) >> 3;
int scan_width = (bih.mWidth * bpp + 3) & (~3);
// Check if it is a bitmap
if (bfh.mTypeB != 'B' || bfh.mTypeM != 'M')
{
Trace("Not a BMP");
return nullptr;
}
// Check if bitmap is bottom-up
if (bih.mHeight <= 0)
{
Trace("Not bottom-up");
return nullptr;
}
// Check if it is not compressed
if (bih.mCompression != 0)
{
Trace("Is compressed");
return nullptr;
}
Ref<Surface> surface;
if (bih.mBitCount == 8)
{
// Load palette
uint32 *palette = new uint32 [256];
int pal_bytes = 4 * (bih.mClrUsed != 0? bih.mClrUsed : 256);
inStream.read((char *)palette, pal_bytes);
loaded = loaded && !inStream.fail();
// Seek to image data
inStream.seekg(bfh.mOffBits);
// Convert pixel data to a surface
surface = new SoftwareSurface(bih.mWidth, bih.mHeight, ESurfaceFormat::X8R8G8B8);
surface->Lock(ESurfaceLockMode::Write);
uint8 *scan_line = new uint8 [scan_width];
for (int y = bih.mHeight - 1; y >= 0; --y)
{
// Load one scan line
inStream.read((char *)scan_line, scan_width);
loaded = loaded && !inStream.fail();
// Copy one scan line
uint8 *in_pixel = scan_line;
uint32 *out_pixel = (uint32 *)surface->GetScanLine(y);
for (uint x = 0; x < bih.mWidth; ++x, ++in_pixel, ++out_pixel)
*out_pixel = palette[*in_pixel];
}
surface->UnLock();
// Release temporaries
delete [] palette;
delete [] scan_line;
}
else
{
// Determine pixel format
ESurfaceFormat format;
switch (bih.mBitCount)
{
case 16: format = ESurfaceFormat::X1R5G5B5; break;
case 24: format = ESurfaceFormat::R8G8B8; break;
default: Trace("Has invalid format"); return nullptr;
}
// Seek to image data
inStream.seekg(bfh.mOffBits);
// Convert pixel data to a surface
surface = new SoftwareSurface(bih.mWidth, bih.mHeight, format, scan_width);
surface->Lock(ESurfaceLockMode::Write);
for (int y = bih.mHeight - 1; y >= 0; --y)
{
inStream.read((char *)surface->GetScanLine(y), scan_width);
loaded = loaded && !inStream.fail();
}
surface->UnLock();
}
return loaded? surface : Ref<Surface>(nullptr);
}
bool SaveBMP(RefConst<Surface> inSurface, ostream &inStream)
{
bool stored = true;
// Convert surface if required
const Surface *src = inSurface;
Ref<Surface> tmp_src;
if (inSurface->GetFormat() != ESurfaceFormat::R8G8B8)
{
tmp_src = new SoftwareSurface(inSurface->GetWidth(), inSurface->GetHeight(), ESurfaceFormat::R8G8B8);
BlitSurface(inSurface, tmp_src);
src = tmp_src.GetPtr();
}
// Lock the surface
src->Lock(ESurfaceLockMode::Read);
JPH_ASSERT(src->GetStride() % 4 == 0);
BitmapFileHeader bfh;
BitmapInfoHeader bih;
// Fill in headers
bfh.mTypeB = 'B';
bfh.mTypeM = 'M';
bfh.mSize = sizeof(bfh) + sizeof(bih) + src->GetHeight() * src->GetStride();
bfh.mReserved1 = 0;
bfh.mReserved2 = 0;
bfh.mOffBits = sizeof(bfh) + sizeof(bih);
bih.mSize = sizeof(bih);
bih.mWidth = src->GetWidth();
bih.mHeight = src->GetHeight();
bih.mPlanes = 1;
bih.mBitCount = 24;
bih.mCompression = 0;
bih.mSizeImage = src->GetHeight() * src->GetStride();
bih.mXPelsPerMeter = 300;
bih.mYPelsPerMeter = 300;
bih.mClrUsed = 0;
bih.mClrImportant = 0;
// Write headers
inStream.write((char *)&bfh, sizeof(bfh));
stored = stored && !inStream.fail();
inStream.write((char *)&bih, sizeof(bih));
stored = stored && !inStream.fail();
// Write image data
for (int y = src->GetHeight() - 1; y >= 0; --y)
{
inStream.write((const char *)src->GetScanLine(y), src->GetStride());
stored = stored && !inStream.fail();
}
src->UnLock();
return stored;
}

View File

@@ -0,0 +1,15 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
class Surface;
/// Load a windows BMP file
Ref<Surface> LoadBMP(istream &inStream);
/// Write a windows BMP file
bool SaveBMP(RefConst<Surface> inSurface, ostream &inStream);

View File

@@ -0,0 +1,130 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/LoadTGA.h>
#include <Image/Surface.h>
#pragma pack (1)
struct TGAHeader
{
uint8 mIDLength;
uint8 mColorMapType;
uint8 mImageType;
uint16 mColorMapFirstEntryIndex;
uint16 mColorMapLength;
uint8 mColorMapEntrySize;
uint16 mXOrigin;
uint16 mYOrigin;
uint16 mWidth;
uint16 mHeight;
uint8 mPixelDepth;
uint8 mImageDescriptor;
};
#pragma pack ()
Ref<Surface> LoadTGA(istream &inStream)
{
bool loaded = true;
// Read header
TGAHeader header;
inStream.read((char *)&header, sizeof(header));
if (inStream.fail())
return nullptr;
// Get properties
int bytes_per_pixel = (header.mPixelDepth + 7) >> 3;
int scan_width = bytes_per_pixel * header.mWidth;
// Check type
if (header.mImageType < 1 || header.mImageType > 2)
{
Trace("Not a readable TGA");
return nullptr;
}
// Check compression
if ((header.mImageType == 1 && header.mColorMapType != 1) || (header.mImageType == 2 && header.mColorMapType != 0))
{
Trace("Not an uncompressed TGA");
return nullptr;
}
Ref<Surface> surface;
if (header.mPixelDepth == 8)
{
// Determine pixel format
ESurfaceFormat format;
int pixel_size;
switch (header.mColorMapEntrySize)
{
case 15: format = ESurfaceFormat::X1R5G5B5; pixel_size = 2; break;
case 16: format = ESurfaceFormat::X1R5G5B5; pixel_size = 2; break;
case 24: format = ESurfaceFormat::R8G8B8; pixel_size = 3; break;
case 32: format = ESurfaceFormat::A8R8G8B8; pixel_size = 4; break;
default: Trace("Has invalid format"); return nullptr;
}
// Seek to beginning of palette
inStream.seekg(sizeof(TGAHeader) + header.mIDLength);
// Load palette
int pal_bytes = pixel_size * header.mColorMapLength;
uint8 *palette = new uint8 [pal_bytes];
inStream.read((char *)palette, pal_bytes);
loaded = loaded && !inStream.fail();
// Convert pixel data to a surface
surface = new SoftwareSurface(header.mWidth, header.mHeight, format);
surface->Lock(ESurfaceLockMode::Write);
uint8 *scan_line = new uint8 [scan_width];
for (int y = header.mHeight - 1; y >= 0; --y)
{
// Load one scan line
inStream.read((char *)scan_line, scan_width);
loaded = loaded && !inStream.fail();
// Copy one scan line
uint8 *in_pixel = scan_line;
uint8 *out_pixel = (uint8 *)surface->GetScanLine(y);
for (int x = 0; x < header.mWidth; ++x, ++in_pixel, out_pixel += pixel_size)
memcpy(out_pixel, palette + (*in_pixel - header.mColorMapFirstEntryIndex) * pixel_size, pixel_size);
}
surface->UnLock();
// Release temporaries
delete [] palette;
delete [] scan_line;
}
else
{
// Determine pixel format
ESurfaceFormat format;
switch (header.mPixelDepth)
{
case 15: format = ESurfaceFormat::X1R5G5B5; break;
case 16: format = ESurfaceFormat::X1R5G5B5; break;
case 24: format = ESurfaceFormat::R8G8B8; break;
case 32: format = ESurfaceFormat::A8R8G8B8; break;
default: Trace("Invalid format"); return nullptr;
}
// Convert pixel data to a surface
surface = new SoftwareSurface(header.mWidth, header.mHeight, format, scan_width);
surface->Lock(ESurfaceLockMode::Write);
for (int y = header.mHeight - 1; y >= 0; --y)
{
inStream.read((char *)surface->GetScanLine(y), scan_width);
loaded = loaded && !inStream.fail();
}
surface->UnLock();
}
return loaded? surface : Ref<Surface>(nullptr);
}

View File

@@ -0,0 +1,12 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
class Surface;
/// Image routines, loads a Targa (TGA) file.
Ref<Surface> LoadTGA(istream &inStream);

View File

@@ -0,0 +1,220 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/Surface.h>
//////////////////////////////////////////////////////////////////////////////////////////
// FormatDescription
//
// Description of a surface format
//////////////////////////////////////////////////////////////////////////////////////////
// Format descriptions
static FormatDescription sFormats[] =
{
// Description BPP #CMP Closest 8 Bit Closest Alpha Red Mask Green Mask Blue Mask Alpha Mask
FormatDescription("A4L4", 8, 2, ESurfaceFormat::A8L8, ESurfaceFormat::A4L4, 0x0000000f, 0x0000000f, 0x0000000f, 0x000000f0),
FormatDescription("L8", 8, 1, ESurfaceFormat::L8, ESurfaceFormat::A8L8, 0x000000ff, 0x000000ff, 0x000000ff, 0x00000000),
FormatDescription("A8", 8, 1, ESurfaceFormat::A8, ESurfaceFormat::A8, 0x00000000, 0x00000000, 0x00000000, 0x000000ff),
FormatDescription("A8L8", 16, 2, ESurfaceFormat::A8L8, ESurfaceFormat::A8L8, 0x000000ff, 0x000000ff, 0x000000ff, 0x0000ff00),
FormatDescription("R5G6B5", 16, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A1R5G5B5, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000),
FormatDescription("X1R5G5B5", 16, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A1R5G5B5, 0x00007c00, 0x000003e0, 0x0000001f, 0x00000000),
FormatDescription("X4R4G4B4", 16, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A4R4G4B4, 0x00000f00, 0x000000f0, 0x0000000f, 0x00000000),
FormatDescription("A1R5G5B5", 16, 4, ESurfaceFormat::A8R8G8B8, ESurfaceFormat::A1R5G5B5, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000),
FormatDescription("A4R4G4B4", 16, 4, ESurfaceFormat::A8R8G8B8, ESurfaceFormat::A4R4G4B4, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000),
FormatDescription("R8G8B8", 24, 3, ESurfaceFormat::R8G8B8, ESurfaceFormat::A8R8G8B8, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000),
FormatDescription("B8G8R8", 24, 3, ESurfaceFormat::B8G8R8, ESurfaceFormat::A8B8G8R8, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000),
FormatDescription("X8R8G8B8", 32, 3, ESurfaceFormat::X8R8G8B8, ESurfaceFormat::A8R8G8B8, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000),
FormatDescription("X8B8G8R8", 32, 3, ESurfaceFormat::X8B8G8R8, ESurfaceFormat::A8B8G8R8, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000),
FormatDescription("A8R8G8B8", 32, 4, ESurfaceFormat::A8R8G8B8, ESurfaceFormat::A8R8G8B8, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000),
FormatDescription("A8B8G8R8", 32, 4, ESurfaceFormat::A8B8G8R8, ESurfaceFormat::A8B8G8R8, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000),
FormatDescription("Invalid", 0, 0, ESurfaceFormat::Invalid, ESurfaceFormat::Invalid, 0x00000000, 0x00000000, 0x00000000, 0x00000000),
};
FormatDescription::FormatDescription(const char *inFormatName, int inBitsPerPixel, int inNumberOfComponents, ESurfaceFormat inClosest8BitFormat, ESurfaceFormat inClosestAlphaFormat, uint32 inRedMask, uint32 inGreenMask, uint32 inBlueMask, uint32 inAlphaMask) :
mFormatName(inFormatName),
mBitsPerPixel(inBitsPerPixel),
mNumberOfComponents(inNumberOfComponents),
mClosest8BitFormat(inClosest8BitFormat),
mClosestAlphaFormat(inClosestAlphaFormat),
mRedMask(inRedMask),
mGreenMask(inGreenMask),
mBlueMask(inBlueMask),
mAlphaMask(inAlphaMask)
{
}
uint32 FormatDescription::Encode(ColorArg inColor) const
{
uint32 col = 0;
uint32 written_mask = 0;
// Loop through all components
for (int c = 0; c < 4; ++c)
{
// Check that we have not yet written this part of the color yet
uint32 mask = GetComponentMask(c);
if ((written_mask & mask) != 0) continue;
written_mask |= mask;
// Or in this component
col |= int(round((1.0f / 255.0f) * mask * inColor(c))) & mask;
}
return col;
}
const Color FormatDescription::Decode(uint32 inColor) const
{
Color col(0, 0, 0, 0);
// Loop through all components
for (int c = 0; c < 4; ++c)
{
uint32 mask = GetComponentMask(c);
if (mask != 0)
{
uint32 shift = CountTrailingZeros(mask);
uint32 shifted_color = (inColor & mask) >> shift;
uint32 shifted_mask = mask >> shift;
col(c) = uint8((255 * shifted_color + 127) / shifted_mask);
}
else
col(c) = 255;
}
return col;
}
const FormatDescription &GetFormatDescription(ESurfaceFormat inFormat)
{
if (inFormat <= ESurfaceFormat::Invalid)
return sFormats[uint(inFormat)];
return sFormats[uint(ESurfaceFormat::Invalid)];
}
//////////////////////////////////////////////////////////////////////////////////////////
// Surface
//
// Class that contains an image in arbitrary format
//////////////////////////////////////////////////////////////////////////////////////////
Surface::Surface(int inWidth, int inHeight, ESurfaceFormat inFormat) :
mFormat(inFormat),
mWidth(inWidth),
mHeight(inHeight),
mLength(0),
mLockMode(ESurfaceLockMode::None),
mStride(0),
mData(nullptr)
{
}
Surface::~Surface()
{
JPH_ASSERT(!IsLocked());
JPH_ASSERT(mData == nullptr);
JPH_ASSERT(mStride == 0);
JPH_ASSERT(mLength == 0);
}
void Surface::Lock(ESurfaceLockMode inMode) const
{
// Check if this resource can be locked
JPH_ASSERT(!IsLocked());
JPH_ASSERT((uint(inMode) & uint(ESurfaceLockMode::ReadWrite)) != 0);
// Store mode
mLockMode = inMode;
// Lock the buffer
HardwareLock();
// Check that data and stride were filled in
JPH_ASSERT(mData != nullptr);
JPH_ASSERT(mStride > 0);
JPH_ASSERT(mLength > 0);
}
void Surface::UnLock() const
{
// Check if this resource was locked
JPH_ASSERT(IsLocked());
// Unlock the hardware resource
HardwareUnLock();
// Reset members, so we are sure they will be set next time
mLockMode = ESurfaceLockMode::None;
mStride = 0;
mLength = 0;
mData = nullptr;
}
void Surface::Clear(ColorArg inColor)
{
Lock(ESurfaceLockMode::Write);
// Get image properties
int bpp = GetBytesPerPixel();
int width = GetWidth();
int height = GetHeight();
// Determine clear color
uint32 col = GetFormatDescription().Encode(inColor);
// Clear the image
for (int y = 0; y < height; ++y)
{
uint8 *d = GetScanLine(y);
uint8 *d_end = GetScanLine(y) + width * bpp;
while (d < d_end)
{
memcpy(d, &col, bpp);
d += bpp;
}
}
UnLock();
}
//////////////////////////////////////////////////////////////////////////////////////////
// SoftwareSurface
//
// Class that contains an image in arbitrary format
//////////////////////////////////////////////////////////////////////////////////////////
SoftwareSurface::SoftwareSurface(int inWidth, int inHeight, ESurfaceFormat inFormat, int inStride) :
Surface(inWidth, inHeight, inFormat)
{
// Determine stride and length
mPixelStride = inStride == 0? ((mWidth * GetBytesPerPixel() + 3) & ~3) : inStride;
mPixelLength = mPixelStride * inHeight;
// Allocate pixel data
JPH_ASSERT(mPixelLength > 0);
mPixelData = new uint8 [mPixelLength];
}
SoftwareSurface::~SoftwareSurface()
{
delete [] mPixelData;
}
void SoftwareSurface::HardwareLock() const
{
// Get pointer to data
mData = mPixelData;
mStride = mPixelStride;
mLength = mPixelLength;
}
void SoftwareSurface::HardwareUnLock() const
{
}

View File

@@ -0,0 +1,181 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
#include <Jolt/Core/Color.h>
#include <Jolt/Core/StringTools.h>
/// Possible lock modes of a Surface
enum class ESurfaceLockMode : uint
{
None = 0 << 0, ///< Not locked, cannot be used as a parameter
Read = 1 << 0,
Write = 2 << 0,
ReadWrite = Read | Write,
};
/// Possible surface formats, most significant bit (MSB) first
enum class ESurfaceFormat : uint
{
A4L4, ///< 4 bit alpha, 4 bit luminance (grayscale)
L8, ///< 8 bit luminance (grayscale)
A8, ///< 8 bit alpha
A8L8, ///< 8 bit luminance and 8 bit alpha
R5G6B5, ///< 16 bit RGB
X1R5G5B5, ///< 16 bit RGB
X4R4G4B4, ///< 16 bit RGB
A1R5G5B5, ///< 16 bit RGBA
A4R4G4B4, ///< 16 bit RGBA
R8G8B8, ///< 24 bit RGB
B8G8R8, ///< 24 bit BGR
X8R8G8B8, ///< 32 bit RGB
X8B8G8R8, ///< 32 bit RGB
A8R8G8B8, ///< 32 bit RGBA
A8B8G8R8, ///< 32 bit BGRA
Invalid, ///< Invalid value
Count = Invalid, ///< Number of pixel formats
};
/// Description of a surface format
class FormatDescription
{
public:
/// Constructor
FormatDescription(const char *inFormatName, int inBitsPerPixel, int inNumberOfComponents, ESurfaceFormat inClosest8BitFormat, ESurfaceFormat inClosestAlphaFormat, uint32 inRedMask, uint32 inGreenMask, uint32 inBlueMask, uint32 inAlphaMask);
/// General properties
const string_view & GetFormatName() const { return mFormatName; }
int GetBytesPerPixel() const { return (mBitsPerPixel + 7) >> 3; }
int GetNumberOfComponents() const { return mNumberOfComponents; }
ESurfaceFormat GetClosest8BitFormat() const { return mClosest8BitFormat; }
ESurfaceFormat GetClosestAlphaFormat() const { return mClosestAlphaFormat; }
/// Bitcounts for the various components of the image
int GetBitsPerPixel() const { return mBitsPerPixel; }
int GetRedBitsPerPixel() const { return CountBits(mRedMask); }
int GetGreenBitsPerPixel() const { return CountBits(mGreenMask); }
int GetBlueBitsPerPixel() const { return CountBits(mBlueMask); }
int GetAlphaBitsPerPixel() const { return CountBits(mAlphaMask); }
int GetComponentBitCount(int inComponent) const { return CountBits(GetComponentMask(inComponent)); }
/// Bitmasks indicating the various components of the image
uint32 GetRedMask() const { return mRedMask; }
uint32 GetGreenMask() const { return mGreenMask; }
uint32 GetBlueMask() const { return mBlueMask; }
uint32 GetAlphaMask() const { return mAlphaMask; }
uint32 GetComponentMask(int inComponent) const { return *(&mRedMask + inComponent); }
/// Convert a single color
uint32 Encode(ColorArg inColor) const;
const Color Decode(uint32 inColor) const;
private:
string_view mFormatName; ///< User displayable String describing the format
int mBitsPerPixel; ///< Number of bits per pixel
int mNumberOfComponents; ///< Number of color components per pixel
ESurfaceFormat mClosest8BitFormat; ///< Closest matching format that has 8 bit color components
ESurfaceFormat mClosestAlphaFormat; ///< Closest matching format that has an alpha channel
uint32 mRedMask; ///< Bitmasks indicating which bits are used by which color components
uint32 mGreenMask;
uint32 mBlueMask;
uint32 mAlphaMask;
};
/// Get the description for a specific surface format
const FormatDescription & GetFormatDescription(ESurfaceFormat inFormat);
/// Class that contains an image in arbitrary format
class Surface : public RefTarget<Surface>
{
public:
/// Constructor
Surface(int inWidth, int inHeight, ESurfaceFormat inFormat);
virtual ~Surface();
/// Type of the image data
const FormatDescription & GetFormatDescription() const { return ::GetFormatDescription(mFormat); }
const string_view & GetFormatName() const { return GetFormatDescription().GetFormatName(); }
int GetBytesPerPixel() const { return GetFormatDescription().GetBytesPerPixel(); }
int GetNumberOfComponents() const { return GetFormatDescription().GetNumberOfComponents(); }
ESurfaceFormat GetClosest8BitFormat() const { return GetFormatDescription().GetClosest8BitFormat(); }
int GetBitsPerPixel() const { return GetFormatDescription().GetBitsPerPixel(); }
int GetRedBitsPerPixel() const { return GetFormatDescription().GetRedBitsPerPixel(); }
int GetGreenBitsPerPixel() const { return GetFormatDescription().GetGreenBitsPerPixel(); }
int GetBlueBitsPerPixel() const { return GetFormatDescription().GetBlueBitsPerPixel(); }
int GetAlphaBitsPerPixel() const { return GetFormatDescription().GetAlphaBitsPerPixel(); }
int GetComponentBitCount(int inComponent) const { return GetFormatDescription().GetComponentBitCount(inComponent); }
uint32 GetRedMask() const { return GetFormatDescription().GetRedMask(); }
uint32 GetGreenMask() const { return GetFormatDescription().GetGreenMask(); }
uint32 GetBlueMask() const { return GetFormatDescription().GetBlueMask(); }
uint32 GetAlphaMask() const { return GetFormatDescription().GetAlphaMask(); }
uint32 GetComponentMask(int inComponent) const { return GetFormatDescription().GetComponentMask(inComponent); }
/// Get properties of this surface
inline ESurfaceFormat GetFormat() const { return mFormat; }
inline int GetWidth() const { return mWidth; }
inline int GetHeight() const { return mHeight; }
/// Sets the image to a specific color
void Clear(ColorArg inColor = Color::sBlack);
/// Locking functions
void Lock(ESurfaceLockMode inMode) const;
void UnLock() const;
/// Current lock state
inline ESurfaceLockMode GetLockMode() const { return mLockMode; }
inline bool IsLocked() const { return mLockMode != ESurfaceLockMode::None; }
inline bool IsLockedForRead() const { return (uint(mLockMode) & uint(ESurfaceLockMode::Read)) != 0; }
inline bool IsLockedForWrite() const { return (uint(mLockMode) & uint(ESurfaceLockMode::Write)) != 0; }
inline bool IsLockedForReadWrite() const { return IsLockedForRead() && IsLockedForWrite(); }
/// Access to the image data
inline const uint8 * GetData() const { JPH_ASSERT(IsLockedForRead()); return mData; }
inline uint8 * GetData() { JPH_ASSERT(IsLockedForWrite()); return mData; }
inline int GetStride() const { JPH_ASSERT(IsLocked()); return mStride; }
inline int GetLength() const { JPH_ASSERT(IsLocked()); return mLength; }
/// Get start of a specific scanline
inline const uint8 * GetScanLine(int inScanLine) const { JPH_ASSERT(inScanLine >= 0 && inScanLine < GetHeight()); return GetData() + inScanLine * GetStride(); }
inline uint8 * GetScanLine(int inScanLine) { JPH_ASSERT(inScanLine >= 0 && inScanLine < GetHeight()); return GetData() + inScanLine * GetStride(); }
protected:
/// These functions must be overridden by the hardware buffer
virtual void HardwareLock() const = 0;
virtual void HardwareUnLock() const = 0;
/// Data
ESurfaceFormat mFormat; ///< Pixel format of the surface
int mWidth; ///< Width of the image
int mHeight; ///< Height of the image
mutable int mLength; ///< Length in bytes of the image
mutable ESurfaceLockMode mLockMode;
mutable int mStride; ///< Width of one scanline in bytes
mutable uint8 * mData; ///< Pointer to image data, starting at top-left of locked rectangle
};
/// Class that contains an image in arbitrary format, backed by normal memory (not device specific)
class SoftwareSurface : public Surface
{
public:
/// Constructor
SoftwareSurface(int inWidth, int inHeight, ESurfaceFormat inFormat, int inStride = 0);
virtual ~SoftwareSurface() override;
protected:
/// These functions must be overridden by the hardware buffer
virtual void HardwareLock() const override;
virtual void HardwareUnLock() const override;
uint8 * mPixelData;
int mPixelStride;
int mPixelLength;
};

View File

@@ -0,0 +1,526 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Image/ZoomImage.h>
#include <Image/Surface.h>
#include <Image/BlitSurface.h>
#include <Jolt/Core/Profiler.h>
//////////////////////////////////////////////////////////////////////////////////////////
// ImageFilter
//
// Abstract class and some implementations of a filter, essentially an 1D weighting function
// which is not zero for t e [-GetSupport(), GetSupport()] and zero for all other t
// The integrand is usually 1 although it is not required for this implementation,
// since the filter is renormalized when it is sampled.
//////////////////////////////////////////////////////////////////////////////////////////
class ImageFilter
{
public:
// Destructor
virtual ~ImageFilter() = default;
// Get support of this filter (+/- the range the filter function is not zero)
virtual float GetSupport() const = 0;
// Sample filter function at a certain point
virtual float GetValue(float t) const = 0;
};
class ImageFilterBox : public ImageFilter
{
virtual float GetSupport() const override
{
return 0.5f;
}
virtual float GetValue(float t) const override
{
if (abs(t) <= 0.5f)
return 1.0f;
else
return 0.0f;
}
};
class ImageFilterTriangle : public ImageFilter
{
virtual float GetSupport() const override
{
return 1.0f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 1.0f)
return 1.0f - t;
else
return 0.0f;
}
};
class ImageFilterBell : public ImageFilter
{
virtual float GetSupport() const override
{
return 1.5f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 0.5f)
return 0.75f - t * t;
else if (t < 1.5f)
{
t = t - 1.5f;
return 0.5f * t * t;
}
else
return 0.0f;
}
};
class ImageFilterBSpline : public ImageFilter
{
virtual float GetSupport() const override
{
return 2.0f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 1.0f)
{
float tt = t * t;
return (0.5f * tt * t) - tt + (2.0f / 3.0f);
}
else if (t < 2.0f)
{
t = 2.0f - t;
return (1.0f / 6.0f) * (t * t * t);
}
else
return 0.0f;
}
};
class ImageFilterLanczos3 : public ImageFilter
{
virtual float GetSupport() const override
{
return 3.0f;
}
virtual float GetValue(float t) const override
{
t = abs(t);
if (t < 3.0f)
return Sinc(t) * Sinc(t / 3.0f);
else
return 0.0f;
}
private:
static float Sinc(float x)
{
x *= JPH_PI;
if (abs(x) < 1.0e-5f)
return 1.0f;
return Sin(x) / x;
}
};
class ImageFilterMitchell : public ImageFilter
{
virtual float GetSupport() const override
{
return 2.0f;
}
virtual float GetValue(float t) const override
{
float tt = t * t;
t = abs(t);
if (t < 1.0f)
return (7.0f * (t * tt) - 12.0f * tt + (16.0f / 3.0f)) / 6.0f;
else if (t < 2.0f)
return ((-7.0f / 3.0f) * (t * tt) + 12.0f * tt + -20.0f * t + (32.0f / 3.0f)) / 6.0f;
else
return 0.0f;
}
};
static const ImageFilter &GetFilter(EFilter inFilter)
{
static ImageFilterBox box;
static ImageFilterTriangle triangle;
static ImageFilterBell bell;
static ImageFilterBSpline bspline;
static ImageFilterLanczos3 lanczos3;
static ImageFilterMitchell mitchell;
switch (inFilter)
{
case FilterBox: return box;
case FilterTriangle: return triangle;
case FilterBell: return bell;
case FilterBSpline: return bspline;
case FilterLanczos3: return lanczos3;
case FilterMitchell: return mitchell;
default: JPH_ASSERT(false); return mitchell;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// ZoomSettings
//////////////////////////////////////////////////////////////////////////////////////////
const ZoomSettings ZoomSettings::sDefault;
ZoomSettings::ZoomSettings() :
mFilter(FilterMitchell),
mWrapFilter(true),
mBlur(1.0f)
{
}
bool ZoomSettings::operator == (const ZoomSettings &inRHS) const
{
return mFilter == inRHS.mFilter
&& mWrapFilter == inRHS.mWrapFilter
&& mBlur == inRHS.mBlur;
}
//////////////////////////////////////////////////////////////////////////////////////////
// Resizing a surface
//////////////////////////////////////////////////////////////////////////////////////////
// Structure used for zooming
struct Contrib
{
int mOffset; // Offset of this pixel (relative to start of scanline)
int mWeight; // Weight of this pixel in 0.12 fixed point format
};
static void sPrecalculateFilter(const ZoomSettings &inZoomSettings, int inOldLength, int inNewLength, int inOffsetFactor, Array<Array<Contrib>> &outContrib)
{
JPH_PROFILE("PrecalculateFilter");
// Get filter
const ImageFilter &filter = GetFilter(inZoomSettings.mFilter);
// Get scale
float scale = float(inNewLength) / inOldLength;
float fwidth, fscale;
if (scale < 1.0f)
{
// Minify, broaden filter
fwidth = filter.GetSupport() / scale;
fscale = scale;
}
else
{
// Enlarge, filter is always used as is
fwidth = filter.GetSupport();
fscale = 1.0f;
}
// Adjust filter for blur
fwidth *= inZoomSettings.mBlur;
fscale /= inZoomSettings.mBlur;
float min_fwidth = 1.0f;
if (fwidth < min_fwidth)
{
fwidth = min_fwidth;
fscale = filter.GetSupport() / min_fwidth;
}
// Make room for a whole scanline
outContrib.resize(inNewLength);
// Loop over the whole scanline
for (int i = 0; i < inNewLength; ++i)
{
// Compute center and left- and rightmost pixels affected
float center = float(i) / scale;
int left = int(floor(center - fwidth));
int right = int(ceil(center + fwidth));
// Reserve required elements
Array<Contrib> &a = outContrib[i];
a.reserve(right - left + 1);
// Total sum of all weights, for renormalization of the filter
int filter_sum = 0;
// Compute the contributions for each
for (int source = left; source <= right; ++source)
{
Contrib c;
// Initialize the offset
c.mOffset = source;
// Compute weight at this position in 0.12 fixed point
c.mWeight = int(4096.0f * filter.GetValue(fscale * (center - source)));
if (c.mWeight == 0) continue;
// Add weight to filter total
filter_sum += c.mWeight;
// Reflect the filter at the edges if the filter is not to be wrapped (clamp)
if (!inZoomSettings.mWrapFilter && (c.mOffset < 0 || c.mOffset >= inOldLength))
c.mOffset = -c.mOffset - 1;
// Wrap the offset so that it falls within the image
c.mOffset = (c.mOffset % inOldLength + inOldLength) % inOldLength;
// Check that the offset falls within the image
JPH_ASSERT(c.mOffset >= 0 && c.mOffset < inOldLength);
// Multiply the offset with the specified factor
c.mOffset *= inOffsetFactor;
// Add the filter element
a.push_back(c);
}
// Normalize the filter to 0.12 fixed point
if (filter_sum != 0)
for (uint n = 0; n < a.size(); ++n)
a[n].mWeight = (a[n].mWeight * 4096) / filter_sum;
}
}
static void sZoomHorizontal(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings)
{
JPH_PROFILE("ZoomHorizontal");
// Check zoom parameters
JPH_ASSERT(inSrc->GetHeight() == ioDst->GetHeight());
JPH_ASSERT(inSrc->GetFormat() == ioDst->GetFormat());
const int width = ioDst->GetWidth();
const int height = ioDst->GetHeight();
const int components = ioDst->GetNumberOfComponents();
const int delta_s = -components;
const int delta_d = ioDst->GetBytesPerPixel() - components;
// Pre-calculate filter contributions for a row
Array<Array<Contrib>> contrib;
sPrecalculateFilter(inZoomSettings, inSrc->GetWidth(), ioDst->GetWidth(), inSrc->GetBytesPerPixel(), contrib);
// Do the zoom
for (int y = 0; y < height; ++y)
{
const uint8 *s = inSrc->GetScanLine(y);
uint8 *d = ioDst->GetScanLine(y);
for (int x = 0; x < width; ++x)
{
const Array<Contrib> &line = contrib[x];
const size_t line_size_min_one = line.size() - 1;
int c = components;
do
{
int pixel = 0;
// Apply the filter for one color component
size_t j = line_size_min_one;
do
{
const Contrib &cmp = line[j];
pixel += cmp.mWeight * s[cmp.mOffset];
}
while (j--);
// Clamp the pixel value
if (pixel <= 0)
*d = 0;
else if (pixel >= (255 << 12))
*d = 255;
else
*d = uint8(pixel >> 12);
++s;
++d;
}
while (--c);
// Skip unused components if there are any
s += delta_s;
d += delta_d;
}
}
}
static void sZoomVertical(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings)
{
JPH_PROFILE("ZoomVertical");
// Check zoom parameters
JPH_ASSERT(inSrc->GetWidth() == ioDst->GetWidth());
JPH_ASSERT(inSrc->GetFormat() == ioDst->GetFormat());
const int width = ioDst->GetWidth();
const int height = ioDst->GetHeight();
const int components = ioDst->GetNumberOfComponents();
const int delta_s = inSrc->GetBytesPerPixel() - components;
const int delta_d = ioDst->GetBytesPerPixel() - components;
// Pre-calculate filter contributions for a row
Array<Array<Contrib>> contrib;
sPrecalculateFilter(inZoomSettings, inSrc->GetHeight(), ioDst->GetHeight(), inSrc->GetStride(), contrib);
// Do the zoom
for (int y = 0; y < height; ++y)
{
const uint8 *s = inSrc->GetScanLine(0);
uint8 *d = ioDst->GetScanLine(y);
const Array<Contrib> &line = contrib[y];
const size_t line_size_min_one = line.size() - 1;
for (int x = 0; x < width; ++x)
{
int c = components;
do
{
int pixel = 0;
// Apply the filter for one color component
size_t j = line_size_min_one;
do
{
const Contrib &cmp = line[j];
pixel += cmp.mWeight * s[cmp.mOffset];
}
while (j--);
// Clamp the pixel value
if (pixel <= 0)
*d = 0;
else if (pixel >= (255 << 12))
*d = 255;
else
*d = uint8(pixel >> 12);
++s;
++d;
}
while (--c);
// Skip unused components if there are any
s += delta_s;
d += delta_d;
}
}
}
bool ZoomImage(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings)
{
JPH_PROFILE("ZoomImage");
// Get filter
const ImageFilter &filter = GetFilter(inZoomSettings.mFilter);
// Determine the temporary format that will require the least amount of components to be zoomed and the least amount of bytes pushed around
ESurfaceFormat tmp_format;
ESurfaceFormat src_format = inSrc->GetClosest8BitFormat();
ESurfaceFormat dst_format = ioDst->GetClosest8BitFormat();
const FormatDescription &src_desc = GetFormatDescription(src_format);
const FormatDescription &dst_desc = GetFormatDescription(dst_format);
if (src_desc.GetNumberOfComponents() < dst_desc.GetNumberOfComponents())
tmp_format = src_format;
else if (src_desc.GetNumberOfComponents() > dst_desc.GetNumberOfComponents())
tmp_format = dst_format;
else if (src_desc.GetBytesPerPixel() < dst_desc.GetBytesPerPixel())
tmp_format = src_format;
else
tmp_format = dst_format;
// Create temporary source buffer if necessary
RefConst<Surface> src = inSrc;
if (inSrc->GetFormat() != tmp_format)
{
Ref<Surface> tmp = new SoftwareSurface(inSrc->GetWidth(), inSrc->GetHeight(), tmp_format);
if (!BlitSurface(inSrc, tmp))
return false;
src = tmp;
}
// Create temporary destination buffer if necessary
Ref<Surface> dst = ioDst;
if (ioDst->GetFormat() != tmp_format)
dst = new SoftwareSurface(ioDst->GetWidth(), ioDst->GetHeight(), tmp_format);
src->Lock(ESurfaceLockMode::Read);
dst->Lock(ESurfaceLockMode::Write);
if (src->GetWidth() == dst->GetWidth())
{
// Only vertical zoom required
sZoomVertical(src, dst, inZoomSettings);
}
else if (src->GetHeight() == dst->GetHeight())
{
// Only horizontal zoom required
sZoomHorizontal(src, dst, inZoomSettings);
}
else
{
// Determine most optimal order
float operations_vh = float(dst->GetWidth()) * (filter.GetSupport() * src->GetHeight() + filter.GetSupport() * dst->GetHeight());
float operations_hv = float(dst->GetHeight()) * (filter.GetSupport() * src->GetWidth() + filter.GetSupport() * dst->GetWidth());
if (operations_vh < operations_hv)
{
// Create temporary buffer to hold the vertical scale
Ref<Surface> tmp = new SoftwareSurface(src->GetWidth(), dst->GetHeight(), tmp_format);
tmp->Lock(ESurfaceLockMode::ReadWrite);
// First scale vertically then horizontally
sZoomVertical(src, tmp, inZoomSettings);
sZoomHorizontal(tmp, dst, inZoomSettings);
tmp->UnLock();
}
else
{
// Create temporary buffer to hold the horizontal scale
Ref<Surface> tmp = new SoftwareSurface(dst->GetWidth(), src->GetHeight(), tmp_format);
tmp->Lock(ESurfaceLockMode::ReadWrite);
// First scale horizontally then vertically
sZoomHorizontal(src, tmp, inZoomSettings);
sZoomVertical(tmp, dst, inZoomSettings);
tmp->UnLock();
}
}
src->UnLock();
dst->UnLock();
// Convert to destination if required
if (dst != ioDst)
if (!BlitSurface(dst, ioDst))
return false;
return true;
}

View File

@@ -0,0 +1,41 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
class Surface;
/// Filter function used to rescale the image
enum EFilter
{
FilterBox,
FilterTriangle,
FilterBell,
FilterBSpline,
FilterLanczos3,
FilterMitchell,
};
/// Zoom settings for ZoomImage
class ZoomSettings
{
public:
/// Constructor
ZoomSettings();
/// Comparison operators
bool operator == (const ZoomSettings &inRHS) const;
/// Default settings
static const ZoomSettings sDefault;
EFilter mFilter; ///< Filter function for image scaling
bool mWrapFilter; ///< If true, the filter will be applied wrapping around the image, this provides better results for repeating textures
float mBlur; ///< If > 1 then the image will be blurred, if < 1 the image will be sharpened
};
/// Function to resize an image
bool ZoomImage(RefConst<Surface> inSrc, Ref<Surface> ioDst, const ZoomSettings &inZoomSettings = ZoomSettings::sDefault);

View File

@@ -0,0 +1,96 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
class ApplicationWindow;
enum class EKey
{
Invalid,
Unknown,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Num0,
Num1,
Num2,
Num3,
Num4,
Num5,
Num6,
Num7,
Num8,
Num9,
Space,
Comma,
Period,
Escape,
LShift,
RShift,
LControl,
RControl,
LAlt,
RAlt,
Left,
Right,
Up,
Down,
Return,
NumKeys,
};
/// Keyboard interface class which keeps track on the status of all keys and keeps track of the list of keys pressed.
class Keyboard
{
public:
/// Constructor
Keyboard() = default;
virtual ~Keyboard() = default;
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) = 0;
virtual void Shutdown() = 0;
/// Update the keyboard state
virtual void Poll() = 0;
/// Checks if a key is pressed or not
virtual bool IsKeyPressed(EKey inKey) const = 0;
/// Checks if a key is pressed and was not pressed the last time this function was called (state is stored in ioPrevState)
bool IsKeyPressedAndTriggered(EKey inKey, bool &ioPrevState) const
{
bool prev_state = ioPrevState;
ioPrevState = IsKeyPressed(inKey);
return ioPrevState && !prev_state;
}
/// Buffered keyboard input, returns EKey::Invalid for none
virtual EKey GetFirstKey() = 0;
virtual EKey GetNextKey() = 0;
};

View File

@@ -0,0 +1,153 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Input/Linux/KeyboardLinux.h>
#include <Window/ApplicationWindowLinux.h>
KeyboardLinux::~KeyboardLinux()
{
Shutdown();
}
bool KeyboardLinux::Initialize(ApplicationWindow *inWindow)
{
mWindow = static_cast<ApplicationWindowLinux *>(inWindow);
mWindow->SetEventListener([this](const XEvent &inEvent) { HandleEvent(inEvent); });
return true;
}
void KeyboardLinux::Shutdown()
{
if (mWindow != nullptr)
{
mWindow->SetEventListener({});
mWindow = nullptr;
}
}
void KeyboardLinux::Poll()
{
// Reset the keys pressed
memset(mKeysPressed, 0, sizeof(mKeysPressed));
Display *display = mWindow->GetDisplay();
// Get pressed keys
char keymap[32];
XQueryKeymap(display, keymap);
for (int i = 0; i < 32; ++i)
{
// Iterate 8 bits at a time
uint keycode = i << 3;
uint32 value = uint8(keymap[i]);
while (value != 0)
{
// Get the next bit
uint lz = CountTrailingZeros(value);
keycode += lz;
// Convert to key
KeySym keysym = XkbKeycodeToKeysym(display, keycode, 0, 0);
EKey key = ToKey(keysym);
if (key != EKey::Unknown)
mKeysPressed[(int)key] = true;
// Skip this bit
keycode++;
value >>= lz + 1;
}
}
// Make the pending buffer the active buffer
mKeyBuffer = mPendingKeyBuffer;
mPendingKeyBuffer.clear();
}
EKey KeyboardLinux::GetFirstKey()
{
mCurrentKey = 0;
return GetNextKey();
}
EKey KeyboardLinux::GetNextKey()
{
if (mCurrentKey < mKeyBuffer.size())
return mKeyBuffer[mCurrentKey++];
return EKey::Invalid;
}
void KeyboardLinux::HandleEvent(const XEvent &inEvent)
{
// If this is a key press event and the buffer is not yet full
if (inEvent.type == KeyPress && mPendingKeyBuffer.size() < mPendingKeyBuffer.capacity())
{
// Convert to key
KeySym keysym = XkbKeycodeToKeysym(mWindow->GetDisplay(), inEvent.xkey.keycode, 0, 0);
EKey key = ToKey(keysym);
if (key != EKey::Unknown)
mPendingKeyBuffer.push_back(key);
}
}
EKey KeyboardLinux::ToKey(int inValue) const
{
switch (inValue)
{
case XK_a: return EKey::A;
case XK_b: return EKey::B;
case XK_c: return EKey::C;
case XK_d: return EKey::D;
case XK_e: return EKey::E;
case XK_f: return EKey::F;
case XK_g: return EKey::G;
case XK_h: return EKey::H;
case XK_i: return EKey::I;
case XK_j: return EKey::J;
case XK_k: return EKey::K;
case XK_l: return EKey::L;
case XK_m: return EKey::M;
case XK_n: return EKey::N;
case XK_o: return EKey::O;
case XK_p: return EKey::P;
case XK_q: return EKey::Q;
case XK_r: return EKey::R;
case XK_s: return EKey::S;
case XK_t: return EKey::T;
case XK_u: return EKey::U;
case XK_v: return EKey::V;
case XK_w: return EKey::W;
case XK_x: return EKey::X;
case XK_y: return EKey::Y;
case XK_z: return EKey::Z;
case XK_0: return EKey::Num0;
case XK_1: return EKey::Num1;
case XK_2: return EKey::Num2;
case XK_3: return EKey::Num3;
case XK_4: return EKey::Num4;
case XK_5: return EKey::Num5;
case XK_6: return EKey::Num6;
case XK_7: return EKey::Num7;
case XK_8: return EKey::Num8;
case XK_9: return EKey::Num9;
case XK_space: return EKey::Space;
case XK_comma: return EKey::Comma;
case XK_period: return EKey::Period;
case XK_Escape: return EKey::Escape;
case XK_Shift_L: return EKey::LShift;
case XK_Shift_R: return EKey::RShift;
case XK_Control_L: return EKey::LControl;
case XK_Control_R: return EKey::RControl;
case XK_Alt_L: return EKey::LAlt;
case XK_Alt_R: return EKey::RAlt;
case XK_Left: return EKey::Left;
case XK_Right: return EKey::Right;
case XK_Up: return EKey::Up;
case XK_Down: return EKey::Down;
case XK_Return: return EKey::Return;
default: return EKey::Unknown;
}
}

View File

@@ -0,0 +1,42 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Input/Keyboard.h>
#include <Jolt/Core/StaticArray.h>
class ApplicationWindowLinux;
/// Keyboard interface class which keeps track on the status of all keys and keeps track of the list of keys pressed.
class KeyboardLinux : public Keyboard
{
public:
/// Destructor
virtual ~KeyboardLinux() override;
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) override;
virtual void Shutdown() override;
/// Update the keyboard state
virtual void Poll() override;
/// Checks if a key is pressed or not
virtual bool IsKeyPressed(EKey inKey) const override { return mKeysPressed[(int)inKey]; }
/// Buffered keyboard input, returns EKey::Invalid for none
virtual EKey GetFirstKey() override;
virtual EKey GetNextKey() override;
private:
void HandleEvent(const XEvent &inEvent);
EKey ToKey(int inKey) const;
ApplicationWindowLinux * mWindow = nullptr;
bool mKeysPressed[(int)EKey::NumKeys] = { };
StaticArray<EKey, 128> mPendingKeyBuffer;
StaticArray<EKey, 128> mKeyBuffer;
uint mCurrentKey = 0;
};

View File

@@ -0,0 +1,76 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Input/Linux/MouseLinux.h>
#include <Window/ApplicationWindowLinux.h>
MouseLinux::MouseLinux()
{
Reset();
}
MouseLinux::~MouseLinux()
{
Shutdown();
}
bool MouseLinux::Initialize(ApplicationWindow *inWindow)
{
ApplicationWindowLinux *window = static_cast<ApplicationWindowLinux *>(inWindow);
mDisplay = window->GetDisplay();
mWindow = window->GetWindow();
// Poll once and reset the deltas
Poll();
mDX = 0;
mDY = 0;
return true;
}
void MouseLinux::Shutdown()
{
mWindow = 0;
mDisplay = nullptr;
}
void MouseLinux::Reset()
{
mX = 0;
mY = 0;
mDX = 0;
mDY = 0;
mLeftPressed = false;
mRightPressed = false;
mMiddlePressed = false;
}
void MouseLinux::Poll()
{
Window root_return, child_return;
int root_x, root_y, win_x, win_y;
unsigned int mask;
if (XQueryPointer(mDisplay, mWindow, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask))
{
mDX = win_x - mX;
mDY = win_y - mY;
mX = win_x;
mY = win_y;
mLeftPressed = mask & Button1Mask;
mRightPressed = mask & Button3Mask;
mMiddlePressed = mask & Button2Mask;
}
else
Reset();
}
void MouseLinux::HideCursor()
{
}
void MouseLinux::ShowCursor()
{
}

View File

@@ -0,0 +1,49 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Input/Mouse.h>
/// Mouse interface class, keeps track of the mouse button state and of the absolute and relative movements of the mouse.
class MouseLinux : public Mouse
{
public:
/// Constructor
MouseLinux();
virtual ~MouseLinux() override;
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) override;
virtual void Shutdown() override;
/// Update the mouse state
virtual void Poll() override;
virtual int GetX() const override { return mX; }
virtual int GetY() const override { return mY; }
virtual int GetDX() const override { return mDX; }
virtual int GetDY() const override { return mDY; }
virtual bool IsLeftPressed() const override { return mLeftPressed; }
virtual bool IsRightPressed() const override { return mRightPressed; }
virtual bool IsMiddlePressed() const override { return mMiddlePressed; }
virtual void HideCursor() override;
virtual void ShowCursor() override;
private:
void Reset();
Display * mDisplay;
Window mWindow;
int mX;
int mY;
int mDX;
int mDY;
bool mLeftPressed;
bool mRightPressed;
bool mMiddlePressed;
};

View File

@@ -0,0 +1,35 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Input/Keyboard.h>
/// Keyboard interface class which keeps track on the status of all keys and keeps track of the list of keys pressed.
class KeyboardMacOS : public Keyboard
{
public:
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) override;
virtual void Shutdown() override { }
/// Update the keyboard state
virtual void Poll() override;
/// Checks if a key is pressed or not
virtual bool IsKeyPressed(EKey inKey) const override { return mKeyPressed[(int)inKey]; }
/// Buffered keyboard input, returns EKey::Invalid for none
virtual EKey GetFirstKey() override;
virtual EKey GetNextKey() override;
/// Handle a key press event
void OnKeyPressed(EKey inKey, bool inPressed);
private:
bool mKeyPressed[(int)EKey::NumKeys] = { };
StaticArray<EKey, 128> mPendingKeyBuffer;
StaticArray<EKey, 128> mKeyBuffer;
uint mCurrentKey = 0;
};

View File

@@ -0,0 +1,143 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Input/MacOS/KeyboardMacOS.h>
#import <GameController/GameController.h>
static EKey sToKey(GCKeyCode inValue)
{
if (inValue == GCKeyCodeKeyA) return EKey::A;
if (inValue == GCKeyCodeKeyB) return EKey::B;
if (inValue == GCKeyCodeKeyC) return EKey::C;
if (inValue == GCKeyCodeKeyD) return EKey::D;
if (inValue == GCKeyCodeKeyE) return EKey::E;
if (inValue == GCKeyCodeKeyF) return EKey::F;
if (inValue == GCKeyCodeKeyG) return EKey::G;
if (inValue == GCKeyCodeKeyH) return EKey::H;
if (inValue == GCKeyCodeKeyI) return EKey::I;
if (inValue == GCKeyCodeKeyJ) return EKey::J;
if (inValue == GCKeyCodeKeyK) return EKey::K;
if (inValue == GCKeyCodeKeyL) return EKey::L;
if (inValue == GCKeyCodeKeyM) return EKey::M;
if (inValue == GCKeyCodeKeyN) return EKey::N;
if (inValue == GCKeyCodeKeyO) return EKey::O;
if (inValue == GCKeyCodeKeyP) return EKey::P;
if (inValue == GCKeyCodeKeyQ) return EKey::Q;
if (inValue == GCKeyCodeKeyR) return EKey::R;
if (inValue == GCKeyCodeKeyS) return EKey::S;
if (inValue == GCKeyCodeKeyT) return EKey::T;
if (inValue == GCKeyCodeKeyU) return EKey::U;
if (inValue == GCKeyCodeKeyV) return EKey::V;
if (inValue == GCKeyCodeKeyW) return EKey::W;
if (inValue == GCKeyCodeKeyX) return EKey::X;
if (inValue == GCKeyCodeKeyY) return EKey::Y;
if (inValue == GCKeyCodeKeyZ) return EKey::Z;
if (inValue == GCKeyCodeZero) return EKey::Num0;
if (inValue == GCKeyCodeOne) return EKey::Num1;
if (inValue == GCKeyCodeTwo) return EKey::Num2;
if (inValue == GCKeyCodeThree) return EKey::Num3;
if (inValue == GCKeyCodeFour) return EKey::Num4;
if (inValue == GCKeyCodeFive) return EKey::Num5;
if (inValue == GCKeyCodeSix) return EKey::Num6;
if (inValue == GCKeyCodeSeven) return EKey::Num7;
if (inValue == GCKeyCodeEight) return EKey::Num8;
if (inValue == GCKeyCodeNine) return EKey::Num9;
if (inValue == GCKeyCodeSpacebar) return EKey::Space;
if (inValue == GCKeyCodeComma) return EKey::Comma;
if (inValue == GCKeyCodePeriod) return EKey::Period;
if (inValue == GCKeyCodeEscape) return EKey::Escape;
if (inValue == GCKeyCodeLeftShift) return EKey::LShift;
if (inValue == GCKeyCodeRightShift) return EKey::RShift;
if (inValue == GCKeyCodeLeftControl) return EKey::LControl;
if (inValue == GCKeyCodeRightControl) return EKey::RControl;
if (inValue == GCKeyCodeLeftAlt) return EKey::LAlt;
if (inValue == GCKeyCodeRightAlt) return EKey::RAlt;
if (inValue == GCKeyCodeLeftArrow) return EKey::Left;
if (inValue == GCKeyCodeRightArrow) return EKey::Right;
if (inValue == GCKeyCodeUpArrow) return EKey::Up;
if (inValue == GCKeyCodeDownArrow) return EKey::Down;
if (inValue == GCKeyCodeReturnOrEnter) return EKey::Return;
return EKey::Unknown;
}
// This class receives keyboard connect callbacks
@interface KeyboardDelegate : NSObject
@end
@implementation KeyboardDelegate
{
KeyboardMacOS *mKeyboard;
}
- (KeyboardDelegate *)init:(KeyboardMacOS *)Keyboard
{
mKeyboard = Keyboard;
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^NSEvent *(NSEvent *event) {
// Ignore all keystrokes except Command-Q (Quit).
if ((event.modifierFlags & NSEventModifierFlagCommand) && [event.charactersIgnoringModifiers isEqual:@"q"])
return event;
else
return nil;
}];
return self;
}
- (void)keyboardDidConnect:(NSNotification *)notification
{
GCKeyboard *keyboard = (GCKeyboard *)notification.object;
if (!keyboard)
return;
__block KeyboardDelegate *weakSelf = self;
keyboard.keyboardInput.keyChangedHandler = ^(GCKeyboardInput *keyboard, GCControllerButtonInput *key, GCKeyCode keyCode, BOOL pressed) {
KeyboardDelegate *strongSelf = weakSelf;
if (strongSelf == nil)
return;
EKey ekey = sToKey(keyCode);
if (ekey != EKey::Invalid)
strongSelf->mKeyboard->OnKeyPressed(ekey, pressed);
};
}
@end
bool KeyboardMacOS::Initialize(ApplicationWindow *inWindow)
{
KeyboardDelegate *delegate = [[KeyboardDelegate alloc] init: this];
[NSNotificationCenter.defaultCenter addObserver: delegate selector: @selector(keyboardDidConnect:) name: GCKeyboardDidConnectNotification object: nil];
return true;
}
void KeyboardMacOS::Poll()
{
// Make the pending buffer the active buffer
mKeyBuffer = mPendingKeyBuffer;
mPendingKeyBuffer.clear();
}
EKey KeyboardMacOS::GetFirstKey()
{
mCurrentKey = 0;
return GetNextKey();
}
EKey KeyboardMacOS::GetNextKey()
{
if (mCurrentKey < mKeyBuffer.size())
return mKeyBuffer[mCurrentKey++];
return EKey::Invalid;
}
void KeyboardMacOS::OnKeyPressed(EKey inKey, bool inPressed)
{
if (inPressed && mPendingKeyBuffer.size() < mPendingKeyBuffer.capacity())
mPendingKeyBuffer.push_back(inKey);
mKeyPressed[(int)inKey] = inPressed;
}

View File

@@ -0,0 +1,54 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Input/Mouse.h>
class ApplicationWindowMacOS;
/// Mouse interface class, keeps track of the mouse button state and of the absolute and relative movements of the mouse.
class MouseMacOS : public Mouse
{
public:
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) override;
virtual void Shutdown() override;
/// Update the mouse state
virtual void Poll() override;
virtual int GetX() const override { return mX; }
virtual int GetY() const override { return mY; }
virtual int GetDX() const override { return mDeltaX; }
virtual int GetDY() const override { return mDeltaY; }
virtual bool IsLeftPressed() const override { return mLeftPressed; }
virtual bool IsRightPressed() const override { return mRightPressed; }
virtual bool IsMiddlePressed() const override { return mMiddlePressed; }
virtual void HideCursor() override { }
virtual void ShowCursor() override { }
/// Internal callbacks
void OnMouseMoved(int inX, int inY) { mX = inX; mY = inY; }
void OnMouseDelta(int inDX, int inDY) { mDeltaXAcc += inDX; mDeltaYAcc += inDY; }
void SetLeftPressed(bool inPressed) { mLeftPressed = inPressed; }
void SetRightPressed(bool inPressed) { mRightPressed = inPressed; }
void SetMiddlePressed(bool inPressed) { mMiddlePressed = inPressed; }
private:
ApplicationWindowMacOS * mWindow = nullptr;
int mX = 0;
int mY = 0;
int mDeltaX = 0;
int mDeltaY = 0;
int mDeltaXAcc = 0;
int mDeltaYAcc = 0;
bool mLeftPressed = false;
bool mRightPressed = false;
bool mMiddlePressed = false;
};

View File

@@ -0,0 +1,103 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Input/MacOS/MouseMacOS.h>
#include <Window/ApplicationWindowMacOS.h>
#import <Cocoa/Cocoa.h>
#import <GameController/GameController.h>
// This class receives mouse connect callbacks
@interface MouseDelegate : NSObject
@end
@implementation MouseDelegate
{
MouseMacOS *mMouse;
}
- (MouseDelegate *)init:(MouseMacOS *)mouse
{
mMouse = mouse;
return self;
}
- (void)mouseDidConnect:(NSNotification *)notification
{
GCMouse *mouse = (GCMouse *)notification.object;
if (mouse == nil)
return;
GCMouseInput *mouseInput = mouse.mouseInput;
if (mouseInput == nil)
return;
__block MouseDelegate *weakSelf = self;
mouseInput.mouseMovedHandler = ^(GCMouseInput *mouse, float deltaX, float deltaY) {
MouseDelegate *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->mMouse->OnMouseDelta(deltaX, -deltaY);
};
mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
MouseDelegate *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->mMouse->SetLeftPressed(pressed);
};
mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
MouseDelegate *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->mMouse->SetRightPressed(pressed);
};
mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
MouseDelegate *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->mMouse->SetMiddlePressed(pressed);
};
}
@end
bool MouseMacOS::Initialize(ApplicationWindow *inWindow)
{
mWindow = static_cast<ApplicationWindowMacOS *>(inWindow);
// Install listener for mouse move callbacks
mWindow->SetMouseMovedCallback([this](int inX, int inY) { OnMouseMoved(inX, inY); });
// Install listener for mouse delta callbacks (will work also when mouse is outside the window or at the edge of the screen)
MouseDelegate *delegate = [[MouseDelegate alloc] init: this];
[NSNotificationCenter.defaultCenter addObserver: delegate selector: @selector(mouseDidConnect:) name: GCMouseDidConnectNotification object:nil];
return true;
}
void MouseMacOS::Shutdown()
{
if (mWindow != nullptr)
{
mWindow->SetMouseMovedCallback({});
mWindow = nullptr;
}
}
void MouseMacOS::Poll()
{
mDeltaX = mDeltaXAcc;
mDeltaY = mDeltaYAcc;
mDeltaXAcc = 0;
mDeltaYAcc = 0;
}

View File

@@ -0,0 +1,35 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
class ApplicationWindow;
/// Mouse interface class, keeps track of the mouse button state and of the absolute and relative movements of the mouse.
class Mouse
{
public:
/// Constructor
Mouse() = default;
virtual ~Mouse() = default;
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) = 0;
virtual void Shutdown() = 0;
/// Update the mouse state
virtual void Poll() = 0;
virtual int GetX() const = 0;
virtual int GetY() const = 0;
virtual int GetDX() const = 0;
virtual int GetDY() const = 0;
virtual bool IsLeftPressed() const = 0;
virtual bool IsRightPressed() const = 0;
virtual bool IsMiddlePressed() const = 0;
virtual void HideCursor() = 0;
virtual void ShowCursor() = 0;
};

View File

@@ -0,0 +1,281 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Input/Win/KeyboardWin.h>
#include <Renderer/Renderer.h>
#include <Window/ApplicationWindowWin.h>
#include <Jolt/Core/Profiler.h>
KeyboardWin::KeyboardWin()
{
Reset();
}
KeyboardWin::~KeyboardWin()
{
Shutdown();
}
void KeyboardWin::Reset()
{
mDI = nullptr;
mKeyboard = nullptr;
ResetKeyboard();
}
void KeyboardWin::ResetKeyboard()
{
memset(&mKeyPressed, 0, sizeof(mKeyPressed));
memset(&mDOD, 0, sizeof(mDOD));
mDODLength = 0;
mCurrentPosition = 0;
}
bool KeyboardWin::Initialize(ApplicationWindow *inWindow)
#ifdef JPH_COMPILER_CLANG
// DIPROP_BUFFERSIZE is a pointer to 1 which causes UBSan: runtime error: reference binding to misaligned address 0x000000000001
__attribute__((no_sanitize("alignment")))
#endif
{
// Create direct input interface
if (FAILED(CoCreateInstance(CLSID_DirectInput8, nullptr, CLSCTX_INPROC_SERVER, IID_IDirectInput8W, (void **)&mDI)))
{
Trace("Unable to create DirectInput interface, DirectX 8.0 is required");
return false;
}
// Initialize direct input interface
if (FAILED(mDI->Initialize((HINSTANCE)GetModuleHandle(nullptr), DIRECTINPUT_VERSION)))
{
Trace("Unable to initialize DirectInput interface, DirectX 8.0 is required");
return false;
}
// Create keyboard device
if (FAILED(mDI->CreateDevice(GUID_SysKeyboard, &mKeyboard, nullptr)))
{
Trace("Unable to get DirectInputDevice interface, DirectX 8.0 is required");
return false;
}
// Set cooperative level for keyboard
if (FAILED(mKeyboard->SetCooperativeLevel(static_cast<ApplicationWindowWin *>(inWindow)->GetWindowHandle(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND)))
{
Trace("Unable to set cooperative level for keyboard");
return false;
}
// Set data format
if (FAILED(mKeyboard->SetDataFormat(&c_dfDIKeyboard)))
{
Trace("Unable to set data format to keyboard");
return false;
}
// Create a keyboard buffer
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = BUFFERSIZE;
if (FAILED(mKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
{
Trace("Unable to set keyboard buffer size");
return false;
}
return true;
}
void KeyboardWin::Shutdown()
{
if (mKeyboard)
{
mKeyboard->Unacquire();
mKeyboard = nullptr;
}
mDI = nullptr;
Reset();
}
void KeyboardWin::Poll()
{
JPH_PROFILE_FUNCTION();
// Get the state of the keyboard
if (FAILED(mKeyboard->GetDeviceState(sizeof(mKeyPressed), mKeyPressed)))
{
mKeyboard->Acquire();
if (FAILED(mKeyboard->GetDeviceState(sizeof(mKeyPressed), mKeyPressed)))
{
ResetKeyboard();
return;
}
}
// Get the state in a buffer
mDODLength = BUFFERSIZE;
mCurrentPosition = 0;
if (FAILED(mKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), mDOD, &mDODLength, 0)))
{
mKeyboard->Acquire();
if (FAILED(mKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), mDOD, &mDODLength, 0)))
{
ResetKeyboard();
return;
}
}
}
EKey KeyboardWin::GetFirstKey()
{
mCurrentPosition = 0;
return GetNextKey();
}
EKey KeyboardWin::GetNextKey()
{
while (mCurrentPosition < mDODLength)
{
// Get next key
const DIDEVICEOBJECTDATA &current = mDOD[mCurrentPosition];
mCurrentPosition++;
// Return it
if (current.dwData & 0x80)
return ToKey(current.dwOfs);
}
return EKey::Invalid;
}
EKey KeyboardWin::ToKey(int inValue) const
{
switch (inValue)
{
case DIK_A: return EKey::A;
case DIK_B: return EKey::B;
case DIK_C: return EKey::C;
case DIK_D: return EKey::D;
case DIK_E: return EKey::E;
case DIK_F: return EKey::F;
case DIK_G: return EKey::G;
case DIK_H: return EKey::H;
case DIK_I: return EKey::I;
case DIK_J: return EKey::J;
case DIK_K: return EKey::K;
case DIK_L: return EKey::L;
case DIK_M: return EKey::M;
case DIK_N: return EKey::N;
case DIK_O: return EKey::O;
case DIK_P: return EKey::P;
case DIK_Q: return EKey::Q;
case DIK_R: return EKey::R;
case DIK_S: return EKey::S;
case DIK_T: return EKey::T;
case DIK_U: return EKey::U;
case DIK_V: return EKey::V;
case DIK_W: return EKey::W;
case DIK_X: return EKey::X;
case DIK_Y: return EKey::Y;
case DIK_Z: return EKey::Z;
case DIK_0: return EKey::Num0;
case DIK_1: return EKey::Num1;
case DIK_2: return EKey::Num2;
case DIK_3: return EKey::Num3;
case DIK_4: return EKey::Num4;
case DIK_5: return EKey::Num5;
case DIK_6: return EKey::Num6;
case DIK_7: return EKey::Num7;
case DIK_8: return EKey::Num8;
case DIK_9: return EKey::Num9;
case DIK_SPACE: return EKey::Space;
case DIK_COMMA: return EKey::Comma;
case DIK_PERIOD: return EKey::Period;
case DIK_ESCAPE: return EKey::Escape;
case DIK_LSHIFT: return EKey::LShift;
case DIK_RSHIFT: return EKey::RShift;
case DIK_LCONTROL: return EKey::LControl;
case DIK_RCONTROL: return EKey::RControl;
case DIK_LALT: return EKey::LAlt;
case DIK_RALT: return EKey::RAlt;
case DIK_LEFT: return EKey::Left;
case DIK_RIGHT: return EKey::Right;
case DIK_UP: return EKey::Up;
case DIK_DOWN: return EKey::Down;
case DIK_RETURN: return EKey::Return;
default: return EKey::Unknown;
}
}
int KeyboardWin::FromKey(EKey inKey) const
{
switch (inKey)
{
case EKey::A: return DIK_A;
case EKey::B: return DIK_B;
case EKey::C: return DIK_C;
case EKey::D: return DIK_D;
case EKey::E: return DIK_E;
case EKey::F: return DIK_F;
case EKey::G: return DIK_G;
case EKey::H: return DIK_H;
case EKey::I: return DIK_I;
case EKey::J: return DIK_J;
case EKey::K: return DIK_K;
case EKey::L: return DIK_L;
case EKey::M: return DIK_M;
case EKey::N: return DIK_N;
case EKey::O: return DIK_O;
case EKey::P: return DIK_P;
case EKey::Q: return DIK_Q;
case EKey::R: return DIK_R;
case EKey::S: return DIK_S;
case EKey::T: return DIK_T;
case EKey::U: return DIK_U;
case EKey::V: return DIK_V;
case EKey::W: return DIK_W;
case EKey::X: return DIK_X;
case EKey::Y: return DIK_Y;
case EKey::Z: return DIK_Z;
case EKey::Num0: return DIK_0;
case EKey::Num1: return DIK_1;
case EKey::Num2: return DIK_2;
case EKey::Num3: return DIK_3;
case EKey::Num4: return DIK_4;
case EKey::Num5: return DIK_5;
case EKey::Num6: return DIK_6;
case EKey::Num7: return DIK_7;
case EKey::Num8: return DIK_8;
case EKey::Num9: return DIK_9;
case EKey::Space: return DIK_SPACE;
case EKey::Comma: return DIK_COMMA;
case EKey::Period: return DIK_PERIOD;
case EKey::Escape: return DIK_ESCAPE;
case EKey::LShift: return DIK_LSHIFT;
case EKey::RShift: return DIK_RSHIFT;
case EKey::LControl: return DIK_LCONTROL;
case EKey::RControl: return DIK_RCONTROL;
case EKey::LAlt: return DIK_LALT;
case EKey::RAlt: return DIK_RALT;
case EKey::Left: return DIK_LEFT;
case EKey::Right: return DIK_RIGHT;
case EKey::Up: return DIK_UP;
case EKey::Down: return DIK_DOWN;
case EKey::Return: return DIK_RETURN;
case EKey::Invalid:
case EKey::Unknown:
default:
return 0;
}
}

View File

@@ -0,0 +1,53 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Input/Keyboard.h>
// We're using DX8's DirectInput API
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
/// Keyboard interface class which keeps track on the status of all keys and keeps track of the list of keys pressed.
class KeyboardWin : public Keyboard
{
public:
/// Constructor
KeyboardWin();
virtual ~KeyboardWin() override;
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) override;
virtual void Shutdown() override;
/// Update the keyboard state
virtual void Poll() override;
/// Checks if a key is pressed or not
virtual bool IsKeyPressed(EKey inKey) const override { return mKeyPressed[FromKey(inKey)] != 0; }
/// Buffered keyboard input, returns EKey::Invalid for none
virtual EKey GetFirstKey() override;
virtual EKey GetNextKey() override;
private:
void Reset();
void ResetKeyboard();
EKey ToKey(int inKey) const;
int FromKey(EKey inKey) const;
enum
{
BUFFERSIZE = 64, ///< Number of keys cached
};
// DirectInput part
ComPtr<IDirectInput8> mDI;
ComPtr<IDirectInputDevice8> mKeyboard;
char mKeyPressed[256];
DIDEVICEOBJECTDATA mDOD[BUFFERSIZE];
DWORD mDODLength;
DWORD mCurrentPosition;
};

View File

@@ -0,0 +1,189 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Input/Win/MouseWin.h>
#include <Window/ApplicationWindowWin.h>
#include <Jolt/Core/Profiler.h>
MouseWin::MouseWin()
{
Reset();
}
MouseWin::~MouseWin()
{
Shutdown();
}
void MouseWin::Reset()
{
mDI = nullptr;
mMouse = nullptr;
mMousePos.x = 0;
mMousePos.y = 0;
ResetMouse();
}
void MouseWin::ResetMouse()
{
memset(&mMouseState, 0, sizeof(mMouseState));
mMousePosInitialized = false;
}
void MouseWin::DetectParsecRunning()
{
mIsParsecRunning = false;
if (SC_HANDLE manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT))
{
if (SC_HANDLE service = OpenServiceA(manager, "Parsec", SERVICE_QUERY_STATUS))
{
SERVICE_STATUS status;
if (QueryServiceStatus(service, &status))
{
mIsParsecRunning = status.dwCurrentState == SERVICE_RUNNING;
}
CloseServiceHandle(service);
}
CloseServiceHandle(manager);
}
}
bool MouseWin::Initialize(ApplicationWindow *inWindow)
#ifdef JPH_COMPILER_CLANG
// DIPROP_BUFFERSIZE is a pointer to 1 which causes UBSan: runtime error: reference binding to misaligned address 0x000000000001
__attribute__((no_sanitize("alignment")))
#endif
{
// Store window
mWindow = static_cast<ApplicationWindowWin *>(inWindow);
// Create direct input interface
if (FAILED(CoCreateInstance(CLSID_DirectInput8, nullptr, CLSCTX_INPROC_SERVER, IID_IDirectInput8W, (void **)&mDI)))
{
Trace("Unable to create DirectInput interface, DirectX 8.0 is required");
return false;
}
// Initialize direct input interface
if (FAILED(mDI->Initialize((HINSTANCE)GetModuleHandle(nullptr), DIRECTINPUT_VERSION)))
{
Trace("Unable to initialize DirectInput interface, DirectX 8.0 is required");
return false;
}
// Create Mouse device
if (FAILED(mDI->CreateDevice(GUID_SysMouse, &mMouse, nullptr)))
{
Trace("Unable to get DirectInputDevice interface, DirectX 8.0 is required");
return false;
}
// Set cooperative level for Mouse
if (FAILED(mMouse->SetCooperativeLevel(mWindow->GetWindowHandle(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND)))
Trace("Failed to set cooperative level for mouse");
// Set data format
if (FAILED(mMouse->SetDataFormat(&c_dfDIMouse)))
{
Trace("Unable to set data format to mouse");
return false;
}
// Create a mouse buffer
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = BUFFERSIZE;
if (FAILED(mMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
{
Trace("Unable to set mouse buffer size");
return false;
}
// Check if the parsec service is running
DetectParsecRunning();
return true;
}
void MouseWin::Shutdown()
{
if (mMouse)
{
mMouse->Unacquire();
mMouse = nullptr;
}
mDI = nullptr;
Reset();
}
void MouseWin::Poll()
{
JPH_PROFILE_FUNCTION();
// Remember last position
POINT old_mouse_pos = mMousePos;
// Get mouse position using the standard window call
if (!GetCursorPos(&mMousePos))
{
ResetMouse();
return;
}
// If we lost mouse before, we need to reset the old mouse pos to the current one
if (!mMousePosInitialized)
{
old_mouse_pos = mMousePos;
mMousePosInitialized = true;
}
// Convert to window space
if (!ScreenToClient(mWindow->GetWindowHandle(), &mMousePos))
{
ResetMouse();
return;
}
// Get relative movement
if (FAILED(mMouse->GetDeviceState(sizeof(mMouseState), &mMouseState)))
{
// Mouse input was lost, reacquire
mMouse->Acquire();
if (FAILED(mMouse->GetDeviceState(sizeof(mMouseState), &mMouseState)))
{
ResetMouse();
return;
}
}
// If we're connected through remote desktop or Parsec then GetDeviceState returns faulty data for lX and lY so we need to use a fallback
if (GetSystemMetrics(SM_REMOTESESSION) || mIsParsecRunning)
{
// Just use the delta between the current and last mouse position.
// Note that this has the disadvantage that you can no longer rotate any further if you're at the edge of the screen,
// but unfortunately a RDP session doesn't allow capturing the mouse so there doesn't seem to be a workaround for this.
mMouseState.lX = mMousePos.x - old_mouse_pos.x;
mMouseState.lY = mMousePos.y - old_mouse_pos.y;
}
}
void MouseWin::HideCursor()
{
::ShowCursor(false);
}
void MouseWin::ShowCursor()
{
::ShowCursor(true);
}

View File

@@ -0,0 +1,59 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Input/Mouse.h>
// We're using DX8's DirectInput API
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
class ApplicationWindowWin;
/// Mouse interface class, keeps track of the mouse button state and of the absolute and relative movements of the mouse.
class MouseWin : public Mouse
{
public:
/// Constructor
MouseWin();
virtual ~MouseWin() override;
/// Initialization / shutdown
virtual bool Initialize(ApplicationWindow *inWindow) override;
virtual void Shutdown() override;
/// Update the mouse state
virtual void Poll() override;
virtual int GetX() const override { return mMousePos.x; }
virtual int GetY() const override { return mMousePos.y; }
virtual int GetDX() const override { return mMouseState.lX; }
virtual int GetDY() const override { return mMouseState.lY; }
virtual bool IsLeftPressed() const override { return (mMouseState.rgbButtons[0] & 0x80) != 0; }
virtual bool IsRightPressed() const override { return (mMouseState.rgbButtons[1] & 0x80) != 0; }
virtual bool IsMiddlePressed() const override { return (mMouseState.rgbButtons[2] & 0x80) != 0; }
virtual void HideCursor() override;
virtual void ShowCursor() override;
private:
void DetectParsecRunning();
void Reset();
void ResetMouse();
enum
{
BUFFERSIZE = 64, ///< Number of keys cached
};
ApplicationWindowWin * mWindow;
ComPtr<IDirectInput8> mDI;
ComPtr<IDirectInputDevice8> mMouse;
bool mIsParsecRunning; ///< If the Parsec remote desktop solution is running, if so we can't trust the mouse movement information from DX and it will make the mouse too sensitive
DIMOUSESTATE mMouseState;
bool mMousePosInitialized = false;
POINT mMousePos;
};

View File

@@ -0,0 +1,117 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
/// Holds a number of DirectX operations with logic to wait for completion
class CommandQueueDX12
{
public:
/// Destructor
~CommandQueueDX12()
{
WaitUntilFinished();
if (mFenceEvent != INVALID_HANDLE_VALUE)
CloseHandle(mFenceEvent);
}
/// Initialize the queue
void Initialize(ID3D12Device *inDevice)
{
D3D12_COMMAND_QUEUE_DESC queue_desc = {};
queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
FatalErrorIfFailed(inDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&mCommandQueue)));
FatalErrorIfFailed(inDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocator)));
// Create the command list
FatalErrorIfFailed(inDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocator.Get(), nullptr, IID_PPV_ARGS(&mCommandList)));
// Command lists are created in the recording state, but there is nothing to record yet. The main loop expects it to be closed, so close it now
FatalErrorIfFailed(mCommandList->Close());
// Create synchronization object
FatalErrorIfFailed(inDevice->CreateFence(mFenceValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
// Increment fence value so we don't skip waiting the first time a command list is executed
mFenceValue++;
// Create an event handle to use for frame synchronization
mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (mFenceEvent == nullptr)
FatalErrorIfFailed(HRESULT_FROM_WIN32(GetLastError()));
}
/// Start the command list (requires waiting until the previous one is finished)
ID3D12GraphicsCommandList * Start()
{
// Reset the allocator
FatalErrorIfFailed(mCommandAllocator->Reset());
// Reset the command list
FatalErrorIfFailed(mCommandList->Reset(mCommandAllocator.Get(), nullptr));
return mCommandList.Get();
}
/// Execute accumulated command list
void Execute()
{
JPH_ASSERT(!mIsExecuting);
// Close the command list
FatalErrorIfFailed(mCommandList->Close());
// Execute the command list
ID3D12CommandList* ppCommandLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
// Schedule a Signal command in the queue
FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), mFenceValue));
// Mark that we're executing
mIsExecuting = true;
}
/// After executing, this waits until execution is done
void WaitUntilFinished()
{
// Check if we've been started
if (mIsExecuting)
{
if (mFence->GetCompletedValue() < mFenceValue)
{
// Wait until the fence has been processed
FatalErrorIfFailed(mFence->SetEventOnCompletion(mFenceValue, mFenceEvent));
WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE);
}
// Increment the fence value
mFenceValue++;
// Done executing
mIsExecuting = false;
}
}
/// Execute and wait for the command list to finish
void ExecuteAndWait()
{
Execute();
WaitUntilFinished();
}
private:
ComPtr<ID3D12CommandQueue> mCommandQueue; ///< The command queue that will hold command lists
ComPtr<ID3D12CommandAllocator> mCommandAllocator; ///< Allocator that holds the memory for the commands
ComPtr<ID3D12GraphicsCommandList> mCommandList; ///< The command list that will hold the render commands / state changes
HANDLE mFenceEvent = INVALID_HANDLE_VALUE; ///< Fence event, used to wait for rendering to complete
ComPtr<ID3D12Fence> mFence; ///< Fence object, used to signal the fence event
UINT64 mFenceValue = 0; ///< Current fence value, each time we need to wait we will signal the fence with this value, wait for it and then increase the value
bool mIsExecuting = false; ///< If a commandlist is currently executing on the queue
};

View File

@@ -0,0 +1,40 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/DX12/ConstantBufferDX12.h>
#include <Renderer/DX12/RendererDX12.h>
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
ConstantBufferDX12::ConstantBufferDX12(RendererDX12 *inRenderer, uint64 inBufferSize) :
mRenderer(inRenderer)
{
mBuffer = mRenderer->CreateD3DResourceOnUploadHeap(inBufferSize);
mBufferSize = inBufferSize;
}
ConstantBufferDX12::~ConstantBufferDX12()
{
if (mBuffer != nullptr)
mRenderer->RecycleD3DResourceOnUploadHeap(mBuffer.Get(), mBufferSize);
}
void *ConstantBufferDX12::MapInternal()
{
void *mapped_resource;
D3D12_RANGE range = { 0, 0 }; // We're not going to read
FatalErrorIfFailed(mBuffer->Map(0, &range, &mapped_resource));
return mapped_resource;
}
void ConstantBufferDX12::Unmap()
{
mBuffer->Unmap(0, nullptr);
}
void ConstantBufferDX12::Bind(int inSlot)
{
mRenderer->GetCommandList()->SetGraphicsRootConstantBufferView(inSlot, mBuffer->GetGPUVirtualAddress());
}

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
class RendererDX12;
/// A binary blob that can be used to pass constants to a shader
class ConstantBufferDX12
{
public:
/// Constructor
ConstantBufferDX12(RendererDX12 *inRenderer, uint64 inBufferSize);
~ConstantBufferDX12();
/// Map / unmap buffer (get pointer to data). This will discard all data in the buffer.
template <typename T> T * Map() { return reinterpret_cast<T *>(MapInternal()); }
void Unmap();
// Bind the constant buffer to a slot
void Bind(int inSlot);
private:
friend class RendererDX12;
void * MapInternal();
RendererDX12 * mRenderer;
ComPtr<ID3D12Resource> mBuffer;
uint64 mBufferSize;
};

View File

@@ -0,0 +1,78 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
/// DirectX descriptor heap, used to allocate handles for resources to bind them to shaders
class DescriptorHeapDX12
{
public:
/// Initialize the heap
/// @param inDevice The DirectX device
/// @param inType Type of heap
/// @param inFlags Flags for the heap
/// @param inNumber Number of handles to reserve
void Init(ID3D12Device *inDevice, D3D12_DESCRIPTOR_HEAP_TYPE inType, D3D12_DESCRIPTOR_HEAP_FLAGS inFlags, uint inNumber)
{
// Create the heap
D3D12_DESCRIPTOR_HEAP_DESC heap_desc = {};
heap_desc.NumDescriptors = inNumber;
heap_desc.Type = inType;
heap_desc.Flags = inFlags;
FatalErrorIfFailed(inDevice->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&mHeap)));
// Delta between descriptor elements
mDescriptorSize = inDevice->GetDescriptorHandleIncrementSize(heap_desc.Type);
// Delta between the CPU and GPU heap
if (inFlags & D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)
mGPUOffset = mHeap->GetGPUDescriptorHandleForHeapStart().ptr - mHeap->GetCPUDescriptorHandleForHeapStart().ptr;
// Populate the freelist
mFreeList.reserve(inNumber);
for (uint i = 0; i < inNumber; ++i)
mFreeList.push_back(i);
}
/// Allocate and return a new handle
D3D12_CPU_DESCRIPTOR_HANDLE Allocate()
{
JPH_ASSERT(!mFreeList.empty());
D3D12_CPU_DESCRIPTOR_HANDLE handle = mHeap->GetCPUDescriptorHandleForHeapStart();
uint index = mFreeList.back();
mFreeList.pop_back();
handle.ptr += index * mDescriptorSize;
return handle;
}
/// Free a handle and return it to the freelist
void Free(D3D12_CPU_DESCRIPTOR_HANDLE inHandle)
{
uint index = uint((inHandle.ptr - mHeap->GetCPUDescriptorHandleForHeapStart().ptr) / mDescriptorSize);
mFreeList.push_back(index);
}
/// Convert from a CPU to a GPU handle
D3D12_GPU_DESCRIPTOR_HANDLE ConvertToGPUHandle(D3D12_CPU_DESCRIPTOR_HANDLE inHandle)
{
JPH_ASSERT(mGPUOffset != -1);
return { UINT64(inHandle.ptr) + mGPUOffset };
}
/// Access to the underlying DirectX structure
ID3D12DescriptorHeap * Get()
{
return mHeap.Get();
}
private:
ComPtr<ID3D12DescriptorHeap> mHeap;
uint mDescriptorSize; ///< The size (in bytes) of a single heap descriptor
Array<uint> mFreeList; ///< List of indices in the heap that are still free
INT64 mGPUOffset = -1; ///< Offset between CPU and GPU handles
};

View File

@@ -0,0 +1,20 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <system_error>
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
#include <Jolt/Core/StringTools.h>
#include <Utils/Log.h>
void FatalErrorIfFailed(HRESULT inHResult)
{
if (FAILED(inHResult))
{
string message = system_category().message(inHResult);
FatalError("DirectX error returned: %s (%s)", ConvertToString(inHResult).c_str(), message.c_str());
}
}

View File

@@ -0,0 +1,9 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
/// Convert DirectX error to readable text and alert
void FatalErrorIfFailed(HRESULT inHResult);

View File

@@ -0,0 +1,135 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/DX12/PipelineStateDX12.h>
#include <Renderer/DX12/RendererDX12.h>
#include <Renderer/DX12/VertexShaderDX12.h>
#include <Renderer/DX12/PixelShaderDX12.h>
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
PipelineStateDX12::PipelineStateDX12(RendererDX12 *inRenderer, const VertexShaderDX12 *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderDX12 *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode) :
mRenderer(inRenderer)
{
D3D12_PRIMITIVE_TOPOLOGY_TYPE topology = inTopology == ETopology::Triangle? D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE : D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
Array<D3D12_INPUT_ELEMENT_DESC> input_description;
uint vertex_offset = 0, instance_offset = 0;
for (uint i = 0; i < inInputDescriptionCount; ++i)
switch (inInputDescription[i])
{
case EInputDescription::Position:
input_description.push_back({ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 });
vertex_offset += 3 * sizeof(float);
break;
case EInputDescription::Color:
input_description.push_back({ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 });
vertex_offset += 4 * sizeof(uint8);
break;
case EInputDescription::Normal:
input_description.push_back({ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 });
vertex_offset += 3 * sizeof(float);
break;
case EInputDescription::TexCoord:
input_description.push_back({ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 });
vertex_offset += 2 * sizeof(float);
break;
case EInputDescription::InstanceColor:
input_description.push_back({ "INSTANCE_COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 1, instance_offset, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 });
instance_offset += 4 * sizeof(uint8);
break;
case EInputDescription::InstanceTransform:
{
for (uint j = 0; j < 4; ++j)
{
input_description.push_back({ "INSTANCE_TRANSFORM", j, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, instance_offset, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 });
instance_offset += 4 * sizeof(float);
}
break;
}
case EInputDescription::InstanceInvTransform:
{
for (uint j = 0; j < 4; ++j)
{
input_description.push_back({ "INSTANCE_INV_TRANSFORM", j, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, instance_offset, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 });
instance_offset += 4 * sizeof(float);
}
break;
}
}
D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {};
pso_desc.InputLayout = { input_description.data(), (UINT)input_description.size() };
pso_desc.pRootSignature = mRenderer->GetRootSignature();
pso_desc.VS = { inVertexShader->mShader->GetBufferPointer(), inVertexShader->mShader->GetBufferSize() };
pso_desc.PS = { inPixelShader->mShader->GetBufferPointer(), inPixelShader->mShader->GetBufferSize() };
pso_desc.RasterizerState.FillMode = inFillMode == EFillMode::Solid? D3D12_FILL_MODE_SOLID : D3D12_FILL_MODE_WIREFRAME;
pso_desc.RasterizerState.CullMode = inCullMode == ECullMode::Backface? D3D12_CULL_MODE_FRONT : D3D12_CULL_MODE_BACK; // DX uses left handed system so we reverse the options
pso_desc.RasterizerState.FrontCounterClockwise = FALSE;
pso_desc.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
pso_desc.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
pso_desc.RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
pso_desc.RasterizerState.DepthClipEnable = TRUE;
pso_desc.RasterizerState.MultisampleEnable = FALSE;
pso_desc.RasterizerState.AntialiasedLineEnable = FALSE;
pso_desc.RasterizerState.ForcedSampleCount = 0;
pso_desc.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
pso_desc.BlendState.AlphaToCoverageEnable = FALSE;
pso_desc.BlendState.IndependentBlendEnable = FALSE;
D3D12_RENDER_TARGET_BLEND_DESC &blend_desc = pso_desc.BlendState.RenderTarget[0];
blend_desc.LogicOpEnable = FALSE;
blend_desc.LogicOp = D3D12_LOGIC_OP_NOOP;
blend_desc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
switch (inBlendMode)
{
case EBlendMode::Write:
blend_desc.BlendEnable = FALSE;
break;
case EBlendMode::AlphaBlend:
blend_desc.BlendEnable = TRUE;
blend_desc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
blend_desc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
blend_desc.BlendOp = D3D12_BLEND_OP_ADD;
blend_desc.SrcBlendAlpha = D3D12_BLEND_ZERO;
blend_desc.DestBlendAlpha = D3D12_BLEND_ZERO;
blend_desc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
break;
}
pso_desc.DepthStencilState.DepthEnable = inDepthTest == EDepthTest::On? TRUE : FALSE;
pso_desc.DepthStencilState.DepthWriteMask = inDepthTest == EDepthTest::On? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
pso_desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_GREATER_EQUAL;
pso_desc.DepthStencilState.StencilEnable = FALSE;
pso_desc.SampleMask = UINT_MAX;
pso_desc.PrimitiveTopologyType = topology;
pso_desc.NumRenderTargets = 1;
pso_desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
pso_desc.DSVFormat = DXGI_FORMAT_D32_FLOAT;
pso_desc.SampleDesc.Count = 1;
FatalErrorIfFailed(mRenderer->GetDevice()->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&mPSO)));
}
PipelineStateDX12::~PipelineStateDX12()
{
if (mPSO != nullptr)
mRenderer->RecycleD3DObject(mPSO.Get());
}
void PipelineStateDX12::Activate()
{
mRenderer->GetCommandList()->SetPipelineState(mPSO.Get());
}

View File

@@ -0,0 +1,27 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/PipelineState.h>
class RendererDX12;
class VertexShaderDX12;
class PixelShaderDX12;
/// DirectX 12 pipeline state object
class PipelineStateDX12 : public PipelineState
{
public:
/// Constructor
PipelineStateDX12(RendererDX12 *inRenderer, const VertexShaderDX12 *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderDX12 *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode);
virtual ~PipelineStateDX12() override;
/// Make this pipeline state active (any primitives rendered after this will use this state)
virtual void Activate() override;
private:
RendererDX12 * mRenderer;
ComPtr<ID3D12PipelineState> mPSO;
};

View File

@@ -0,0 +1,17 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/PixelShader.h>
/// Pixel shader handle for DirectX
class PixelShaderDX12 : public PixelShader
{
public:
/// Constructor
PixelShaderDX12(ComPtr<ID3DBlob> inShader) : mShader(inShader) { }
ComPtr<ID3DBlob> mShader; ///< The compiled shader
};

View File

@@ -0,0 +1,97 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/DX12/RenderInstancesDX12.h>
#include <Renderer/DX12/RenderPrimitiveDX12.h>
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
void RenderInstancesDX12::Clear()
{
if (mInstanceBuffer != nullptr)
mRenderer->RecycleD3DResourceOnUploadHeap(mInstanceBuffer.Get(), mInstanceBufferSize);
mInstanceBuffer = nullptr;
mInstanceBufferSize = 0;
mInstanceSize = 0;
}
void RenderInstancesDX12::CreateBuffer(int inNumInstances, int inInstanceSize)
{
uint new_size = uint(inNumInstances) * inInstanceSize;
if (mInstanceBuffer == nullptr || mInstanceBufferSize < new_size)
{
// Delete the old buffer
Clear();
// Calculate size
mInstanceBufferSize = new_size;
// Create buffer
mInstanceBuffer = mRenderer->CreateD3DResourceOnUploadHeap(mInstanceBufferSize);
JPH_IF_DEBUG(mInstanceBuffer->SetName(L"Instance Buffer");)
}
// Update parameters
mInstanceSize = inInstanceSize;
}
void *RenderInstancesDX12::Lock()
{
uint32 *mapped_resource;
D3D12_RANGE range = { 0, 0 };
mInstanceBuffer->Map(0, &range, (void **)&mapped_resource);
return mapped_resource;
}
void RenderInstancesDX12::Unlock()
{
mInstanceBuffer->Unmap(0, nullptr);
}
void RenderInstancesDX12::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const
{
if (inNumInstances <= 0)
return;
RenderPrimitiveDX12 *primitive = static_cast<RenderPrimitiveDX12 *>(inPrimitive);
ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList();
// Set topology
command_list->IASetPrimitiveTopology(primitive->mType == PipelineState::ETopology::Triangle? D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST : D3D_PRIMITIVE_TOPOLOGY_LINELIST);
D3D12_VERTEX_BUFFER_VIEW vb_view[2];
// Vertex buffer
vb_view[0].BufferLocation = primitive->mVtxBuffer->GetGPUVirtualAddress();
vb_view[0].StrideInBytes = primitive->mVtxSize;
vb_view[0].SizeInBytes = primitive->mNumVtxToDraw * primitive->mVtxSize;
// Instances buffer
vb_view[1].BufferLocation = mInstanceBuffer->GetGPUVirtualAddress();
vb_view[1].StrideInBytes = mInstanceSize;
vb_view[1].SizeInBytes = mInstanceBufferSize;
command_list->IASetVertexBuffers(0, 2, &vb_view[0]);
if (primitive->mIdxBuffer == nullptr)
{
// Draw instanced primitive
command_list->DrawInstanced(primitive->mNumVtxToDraw, inNumInstances, 0, inStartInstance);
}
else
{
// Set index buffer
D3D12_INDEX_BUFFER_VIEW ib_view;
ib_view.BufferLocation = primitive->mIdxBuffer->GetGPUVirtualAddress();
ib_view.SizeInBytes = primitive->mNumIdxToDraw * sizeof(uint32);
ib_view.Format = DXGI_FORMAT_R32_UINT;
command_list->IASetIndexBuffer(&ib_view);
// Draw instanced primitive
command_list->DrawIndexedInstanced(primitive->mNumIdxToDraw, inNumInstances, 0, 0, inStartInstance);
}
}

View File

@@ -0,0 +1,37 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/DX12/RendererDX12.h>
#include <Renderer/RenderInstances.h>
class RenderPrimitive;
/// DirectX 12 implementation of a render instances object
class RenderInstancesDX12 : public RenderInstances
{
public:
/// Constructor
RenderInstancesDX12(RendererDX12 *inRenderer) : mRenderer(inRenderer) { }
virtual ~RenderInstancesDX12() override { Clear(); }
/// Erase all instance data
virtual void Clear() override;
/// Instance buffer management functions
virtual void CreateBuffer(int inNumInstances, int inInstanceSize) override;
virtual void * Lock() override;
virtual void Unlock() override;
/// Draw the instances when context has been set by Renderer::BindShader
virtual void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const override;
private:
RendererDX12 * mRenderer;
ComPtr<ID3D12Resource> mInstanceBuffer;
uint mInstanceBufferSize = 0;
int mInstanceSize = 0;
};

View File

@@ -0,0 +1,148 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/DX12/RenderPrimitiveDX12.h>
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
void RenderPrimitiveDX12::ReleaseVertexBuffer()
{
if (mVtxBuffer != nullptr)
{
if (mVtxBufferInUploadHeap)
mRenderer->RecycleD3DResourceOnUploadHeap(mVtxBuffer.Get(), mNumVtx * mVtxSize);
else
mRenderer->RecycleD3DObject(mVtxBuffer.Get());
mVtxBuffer = nullptr;
}
mVtxBufferInUploadHeap = false;
RenderPrimitive::ReleaseVertexBuffer();
}
void RenderPrimitiveDX12::ReleaseIndexBuffer()
{
if (mIdxBuffer != nullptr)
{
if (mIdxBufferInUploadHeap)
mRenderer->RecycleD3DResourceOnUploadHeap(mIdxBuffer.Get(), mNumIdx * sizeof(uint32));
else
mRenderer->RecycleD3DObject(mIdxBuffer.Get());
mIdxBuffer = nullptr;
}
mIdxBufferInUploadHeap = false;
RenderPrimitive::ReleaseIndexBuffer();
}
void RenderPrimitiveDX12::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData)
{
RenderPrimitive::CreateVertexBuffer(inNumVtx, inVtxSize, inData);
uint64 size = uint64(inNumVtx) * inVtxSize;
if (inData != nullptr)
{
// Data provided, assume the buffer is static so allocate it on the GPU
mVtxBuffer = mRenderer->CreateD3DResourceOnDefaultHeap(inData, size);
mVtxBufferInUploadHeap = false;
}
else
{
// No data provided, create a buffer that will be uploaded to the GPU every time it is used
mVtxBuffer = mRenderer->CreateD3DResourceOnUploadHeap(size);
mVtxBufferInUploadHeap = true;
}
JPH_IF_DEBUG(mVtxBuffer->SetName(L"Vertex Buffer");)
}
void *RenderPrimitiveDX12::LockVertexBuffer()
{
void *mapped_resource;
D3D12_RANGE range = { 0, 0 };
FatalErrorIfFailed(mVtxBuffer->Map(0, &range, &mapped_resource));
return mapped_resource;
}
void RenderPrimitiveDX12::UnlockVertexBuffer()
{
mVtxBuffer->Unmap(0, nullptr);
}
void RenderPrimitiveDX12::CreateIndexBuffer(int inNumIdx, const uint32 *inData)
{
RenderPrimitive::CreateIndexBuffer(inNumIdx, inData);
uint64 size = uint64(inNumIdx) * sizeof(uint32);
if (inData != nullptr)
{
// Data provided, assume the buffer is static so allocate it on the GPU
mIdxBuffer = mRenderer->CreateD3DResourceOnDefaultHeap(inData, size);
mIdxBufferInUploadHeap = false;
}
else
{
// No data provided, create a buffer that will be uploaded to the GPU every time it is used
mIdxBuffer = mRenderer->CreateD3DResourceOnUploadHeap(size);
mIdxBufferInUploadHeap = true;
}
JPH_IF_DEBUG(mIdxBuffer->SetName(L"Index Buffer");)
}
uint32 *RenderPrimitiveDX12::LockIndexBuffer()
{
uint32 *mapped_resource;
D3D12_RANGE range = { 0, 0 };
FatalErrorIfFailed(mIdxBuffer->Map(0, &range, (void **)&mapped_resource));
return mapped_resource;
}
void RenderPrimitiveDX12::UnlockIndexBuffer()
{
mIdxBuffer->Unmap(0, nullptr);
}
void RenderPrimitiveDX12::Draw() const
{
ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList();
// Set topology
command_list->IASetPrimitiveTopology(mType == PipelineState::ETopology::Triangle? D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST : D3D_PRIMITIVE_TOPOLOGY_LINELIST);
if (mIdxBuffer == nullptr)
{
// Set vertex buffer
D3D12_VERTEX_BUFFER_VIEW vb_view;
vb_view.BufferLocation = mVtxBuffer->GetGPUVirtualAddress();
vb_view.StrideInBytes = mVtxSize;
vb_view.SizeInBytes = mNumVtxToDraw * mVtxSize;
command_list->IASetVertexBuffers(0, 1, &vb_view);
// Draw the non indexed primitive
command_list->DrawInstanced(mNumVtxToDraw, 1, 0, 0);
}
else
{
// Set vertex buffer
D3D12_VERTEX_BUFFER_VIEW vb_view;
vb_view.BufferLocation = mVtxBuffer->GetGPUVirtualAddress();
vb_view.StrideInBytes = mVtxSize;
vb_view.SizeInBytes = mNumVtx * mVtxSize;
command_list->IASetVertexBuffers(0, 1, &vb_view);
// Set index buffer
D3D12_INDEX_BUFFER_VIEW ib_view;
ib_view.BufferLocation = mIdxBuffer->GetGPUVirtualAddress();
ib_view.SizeInBytes = mNumIdxToDraw * sizeof(uint32);
ib_view.Format = DXGI_FORMAT_R32_UINT;
command_list->IASetIndexBuffer(&ib_view);
// Draw indexed primitive
command_list->DrawIndexedInstanced(mNumIdxToDraw, 1, 0, 0, 0);
}
}

View File

@@ -0,0 +1,47 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/RenderPrimitive.h>
#include <Renderer/DX12/RendererDX12.h>
/// DirectX 12 implementation of a render primitive
class RenderPrimitiveDX12 : public RenderPrimitive
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Constructor
RenderPrimitiveDX12(RendererDX12 *inRenderer, PipelineState::ETopology inType) : mRenderer(inRenderer), mType(inType) { }
virtual ~RenderPrimitiveDX12() override { Clear(); }
/// Vertex buffer management functions
virtual void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr) override;
virtual void ReleaseVertexBuffer() override;
virtual void * LockVertexBuffer() override;
virtual void UnlockVertexBuffer() override;
/// Index buffer management functions
virtual void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr) override;
virtual void ReleaseIndexBuffer() override;
virtual uint32 * LockIndexBuffer() override;
virtual void UnlockIndexBuffer() override;
/// Draw the primitive
virtual void Draw() const override;
private:
friend class RenderInstancesDX12;
RendererDX12 * mRenderer;
PipelineState::ETopology mType;
ComPtr<ID3D12Resource> mVtxBuffer;
bool mVtxBufferInUploadHeap = false;
ComPtr<ID3D12Resource> mIdxBuffer;
bool mIdxBufferInUploadHeap = false;
};

View File

@@ -0,0 +1,719 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/DX12/RendererDX12.h>
#include <Renderer/DX12/RenderPrimitiveDX12.h>
#include <Renderer/DX12/PipelineStateDX12.h>
#include <Renderer/DX12/VertexShaderDX12.h>
#include <Renderer/DX12/PixelShaderDX12.h>
#include <Renderer/DX12/TextureDX12.h>
#include <Renderer/DX12/RenderInstancesDX12.h>
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
#include <Window/ApplicationWindowWin.h>
#include <Jolt/Core/Profiler.h>
#include <Utils/ReadData.h>
#include <Utils/Log.h>
#include <Utils/AssetStream.h>
#include <d3dcompiler.h>
#ifdef JPH_DEBUG
#include <d3d12sdklayers.h>
#endif
RendererDX12::~RendererDX12()
{
// Ensure that the GPU is no longer referencing resources that are about to be cleaned up by the destructor.
WaitForGpu();
// Don't add more stuff to the delay reference list
mIsExiting = true;
CloseHandle(mFenceEvent);
}
void RendererDX12::WaitForGpu()
{
// Schedule a Signal command in the queue
UINT64 current_fence_value = mFenceValues[mFrameIndex];
FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value));
// Wait until the fence has been processed
FatalErrorIfFailed(mFence->SetEventOnCompletion(current_fence_value, mFenceEvent));
WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE);
// Increment the fence value for all frames
for (uint n = 0; n < cFrameCount; ++n)
mFenceValues[n] = current_fence_value + 1;
// Release all used resources
for (Array<ComPtr<ID3D12Object>> &list : mDelayReleased)
list.clear();
// Anything that's not used yet can be removed, delayed objects are now available
mResourceCache.clear();
mDelayCached[mFrameIndex].swap(mResourceCache);
}
void RendererDX12::CreateRenderTargets()
{
// Create render targets and views
for (uint n = 0; n < cFrameCount; ++n)
{
mRenderTargetViews[n] = mRTVHeap.Allocate();
FatalErrorIfFailed(mSwapChain->GetBuffer(n, IID_PPV_ARGS(&mRenderTargets[n])));
mDevice->CreateRenderTargetView(mRenderTargets[n].Get(), nullptr, mRenderTargetViews[n]);
}
}
void RendererDX12::CreateDepthBuffer()
{
// Free any previous depth stencil view
if (mDepthStencilView.ptr != 0)
mDSVHeap.Free(mDepthStencilView);
// Free any previous depth stencil buffer
mDepthStencilBuffer.Reset();
// Allocate depth stencil buffer
D3D12_CLEAR_VALUE clear_value = {};
clear_value.Format = DXGI_FORMAT_D32_FLOAT;
clear_value.DepthStencil.Depth = 0.0f;
clear_value.DepthStencil.Stencil = 0;
D3D12_HEAP_PROPERTIES heap_properties = {};
heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT;
heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap_properties.CreationNodeMask = 1;
heap_properties.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC depth_stencil_desc = {};
depth_stencil_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depth_stencil_desc.Alignment = 0;
depth_stencil_desc.Width = mWindow->GetWindowWidth();
depth_stencil_desc.Height = mWindow->GetWindowHeight();
depth_stencil_desc.DepthOrArraySize = 1;
depth_stencil_desc.MipLevels = 1;
depth_stencil_desc.Format = DXGI_FORMAT_D32_FLOAT;
depth_stencil_desc.SampleDesc.Count = 1;
depth_stencil_desc.SampleDesc.Quality = 0;
depth_stencil_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depth_stencil_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &depth_stencil_desc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear_value, IID_PPV_ARGS(&mDepthStencilBuffer)));
// Allocate depth stencil view
D3D12_DEPTH_STENCIL_VIEW_DESC depth_stencil_view_desc = {};
depth_stencil_view_desc.Format = DXGI_FORMAT_D32_FLOAT;
depth_stencil_view_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
depth_stencil_view_desc.Flags = D3D12_DSV_FLAG_NONE;
mDepthStencilView = mDSVHeap.Allocate();
mDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &depth_stencil_view_desc, mDepthStencilView);
}
void RendererDX12::Initialize(ApplicationWindow *inWindow)
{
Renderer::Initialize(inWindow);
#if defined(JPH_DEBUG)
// Enable the D3D12 debug layer
ComPtr<ID3D12Debug> debug_controller;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_controller))))
debug_controller->EnableDebugLayer();
#endif
// Create DXGI factory
FatalErrorIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mDXGIFactory)));
// Find adapter
ComPtr<IDXGIAdapter1> adapter;
HRESULT result = E_FAIL;
// First check if we have the Windows 1803 IDXGIFactory6 interface
ComPtr<IDXGIFactory6> factory6;
if (SUCCEEDED(mDXGIFactory->QueryInterface(IID_PPV_ARGS(&factory6))))
{
for (UINT index = 0; DXGI_ERROR_NOT_FOUND != factory6->EnumAdapterByGpuPreference(index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)); ++index)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
// We don't want software renderers
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
continue;
// Check to see whether the adapter supports Direct3D 12
result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice));
if (SUCCEEDED(result))
break;
}
}
else
{
// Fall back to the older method that may not get the fastest GPU
for (UINT index = 0; DXGI_ERROR_NOT_FOUND != mDXGIFactory->EnumAdapters1(index, &adapter); ++index)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
// We don't want software renderers
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
continue;
// Check to see whether the adapter supports Direct3D 12
result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice));
if (SUCCEEDED(result))
break;
}
}
// Check if we managed to obtain a device
FatalErrorIfFailed(result);
#ifdef JPH_DEBUG
// Enable breaking on errors
ComPtr<ID3D12InfoQueue> info_queue;
if (SUCCEEDED(mDevice.As(&info_queue)))
{
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
// Disable an error that triggers on Windows 11 with a hybrid graphic system
// See: https://stackoverflow.com/questions/69805245/directx-12-application-is-crashing-in-windows-11
D3D12_MESSAGE_ID hide[] =
{
D3D12_MESSAGE_ID_RESOURCE_BARRIER_MISMATCHING_COMMAND_LIST_TYPE,
};
D3D12_INFO_QUEUE_FILTER filter = { };
filter.DenyList.NumIDs = static_cast<UINT>( std::size( hide ) );
filter.DenyList.pIDList = hide;
info_queue->AddStorageFilterEntries( &filter );
}
#endif // JPH_DEBUG
// Disable full screen transitions
FatalErrorIfFailed(mDXGIFactory->MakeWindowAssociation(static_cast<ApplicationWindowWin *>(mWindow)->GetWindowHandle(), DXGI_MWA_NO_ALT_ENTER));
// Create heaps
mRTVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 2);
mDSVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 4);
mSRVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 128);
// Create a command queue
D3D12_COMMAND_QUEUE_DESC queue_desc = {};
queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
FatalErrorIfFailed(mDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&mCommandQueue)));
// Create a command allocator for each frame
for (uint n = 0; n < cFrameCount; n++)
FatalErrorIfFailed(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocators[n])));
// Describe and create the swap chain
DXGI_SWAP_CHAIN_DESC swap_chain_desc = {};
swap_chain_desc.BufferCount = cFrameCount;
swap_chain_desc.BufferDesc.Width = mWindow->GetWindowWidth();
swap_chain_desc.BufferDesc.Height = mWindow->GetWindowHeight();
swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.OutputWindow = static_cast<ApplicationWindowWin *>(mWindow)->GetWindowHandle();
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.Windowed = TRUE;
ComPtr<IDXGISwapChain> swap_chain;
FatalErrorIfFailed(mDXGIFactory->CreateSwapChain(mCommandQueue.Get(), &swap_chain_desc, &swap_chain));
FatalErrorIfFailed(swap_chain.As(&mSwapChain));
mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();
CreateRenderTargets();
CreateDepthBuffer();
// Create a root signature suitable for all our shaders
D3D12_ROOT_PARAMETER params[3] = {};
// Mapping a constant buffer to slot 0 for the vertex shader
params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
params[0].Descriptor.ShaderRegister = 0;
params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
// Mapping a constant buffer to slot 1 in the pixel shader
params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
params[1].Descriptor.ShaderRegister = 1;
params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
// Mapping a texture to slot 2 in the pixel shader
D3D12_DESCRIPTOR_RANGE range = {};
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
range.BaseShaderRegister = 2;
range.NumDescriptors = 1;
params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
params[2].DescriptorTable.NumDescriptorRanges = 1;
params[2].DescriptorTable.pDescriptorRanges = &range;
params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
D3D12_STATIC_SAMPLER_DESC samplers[3] = {};
// Sampler 0: Non-wrapping linear filtering
samplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplers[0].MipLODBias = 0.0f;
samplers[0].MaxAnisotropy = 1;
samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
samplers[0].BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
samplers[0].MinLOD = 0.0f;
samplers[0].MaxLOD = D3D12_FLOAT32_MAX;
samplers[0].ShaderRegister = 0;
samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
// Sampler 1: Wrapping and linear filtering
samplers[1] = samplers[0];
samplers[1].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplers[1].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplers[1].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplers[1].ShaderRegister = 1;
// Sampler 2: Point filtering, using SampleCmp mode to compare if sampled value >= reference value (for shadows)
samplers[2] = samplers[0];
samplers[2].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT;
samplers[2].ComparisonFunc = D3D12_COMPARISON_FUNC_GREATER_EQUAL;
samplers[2].ShaderRegister = 2;
D3D12_ROOT_SIGNATURE_DESC root_signature_desc = {};
root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
root_signature_desc.NumParameters = ARRAYSIZE(params);
root_signature_desc.pParameters = params;
root_signature_desc.NumStaticSamplers = ARRAYSIZE(samplers);
root_signature_desc.pStaticSamplers = samplers;
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
FatalErrorIfFailed(D3D12SerializeRootSignature(&root_signature_desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
FatalErrorIfFailed(mDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&mRootSignature)));
// Create the command list
FatalErrorIfFailed(mDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocators[mFrameIndex].Get(), nullptr, IID_PPV_ARGS(&mCommandList)));
// Command lists are created in the recording state, but there is nothing to record yet. The main loop expects it to be closed, so close it now
FatalErrorIfFailed(mCommandList->Close());
// Create synchronization object
FatalErrorIfFailed(mDevice->CreateFence(mFenceValues[mFrameIndex], D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
// Increment fence value so we don't skip waiting the first time a command list is executed
mFenceValues[mFrameIndex]++;
// Create an event handle to use for frame synchronization
mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (mFenceEvent == nullptr)
FatalErrorIfFailed(HRESULT_FROM_WIN32(GetLastError()));
// Initialize the queue used to upload resources to the GPU
mUploadQueue.Initialize(mDevice.Get());
// Create constant buffer. One per frame to avoid overwriting the constant buffer while the GPU is still using it.
for (uint n = 0; n < cFrameCount; ++n)
{
mVertexShaderConstantBufferProjection[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer));
mVertexShaderConstantBufferOrtho[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer));
mPixelShaderConstantBuffer[n] = CreateConstantBuffer(sizeof(PixelShaderConstantBuffer));
}
// Create depth only texture (no color buffer, as seen from light)
mShadowMap = new TextureDX12(this, cShadowMapSize, cShadowMapSize);
}
void RendererDX12::OnWindowResize()
{
// Wait for the previous frame to be rendered
WaitForGpu();
// Free the render targets and views to allow resizing the swap chain
for (uint n = 0; n < cFrameCount; ++n)
{
mRTVHeap.Free(mRenderTargetViews[n]);
mRenderTargets[n].Reset();
}
// Resize the swap chain buffers
FatalErrorIfFailed(mSwapChain->ResizeBuffers(cFrameCount, mWindow->GetWindowWidth(), mWindow->GetWindowHeight(), DXGI_FORMAT_R8G8B8A8_UNORM, 0));
// Back buffer index may have changed after the resize (it always seems to go to 0 again)
mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();
// Since we may have switched frame index and we know everything is done, we need to update the fence value for our other frame as completed
for (uint n = 0; n < cFrameCount; ++n)
if (mFrameIndex != n)
mFenceValues[n] = mFence->GetCompletedValue();
// Recreate render targets
CreateRenderTargets();
// Recreate depth buffer
CreateDepthBuffer();
}
bool RendererDX12::BeginFrame(const CameraState &inCamera, float inWorldScale)
{
JPH_PROFILE_FUNCTION();
Renderer::BeginFrame(inCamera, inWorldScale);
// Reset command allocator
FatalErrorIfFailed(mCommandAllocators[mFrameIndex]->Reset());
// Reset command list
FatalErrorIfFailed(mCommandList->Reset(mCommandAllocators[mFrameIndex].Get(), nullptr));
// Set root signature
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
// Set SRV heap
ID3D12DescriptorHeap *heaps[] = { mSRVHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(heaps), heaps);
// Indicate that the back buffer will be used as a render target.
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
mCommandList->ResourceBarrier(1, &barrier);
// Clear the back buffer.
const float blue[] = { 0.098f, 0.098f, 0.439f, 1.000f };
mCommandList->ClearRenderTargetView(mRenderTargetViews[mFrameIndex], blue, 0, nullptr);
mCommandList->ClearDepthStencilView(mDepthStencilView, D3D12_CLEAR_FLAG_DEPTH, 0.0f, 0, 0, nullptr);
// Set constants for vertex shader in projection mode
VertexShaderConstantBuffer *vs = mVertexShaderConstantBufferProjection[mFrameIndex]->Map<VertexShaderConstantBuffer>();
*vs = mVSBuffer;
mVertexShaderConstantBufferProjection[mFrameIndex]->Unmap();
// Set constants for vertex shader in ortho mode
vs = mVertexShaderConstantBufferOrtho[mFrameIndex]->Map<VertexShaderConstantBuffer>();
*vs = mVSBufferOrtho;
mVertexShaderConstantBufferOrtho[mFrameIndex]->Unmap();
// Switch to 3d projection mode
SetProjectionMode();
// Set constants for pixel shader
PixelShaderConstantBuffer *ps = mPixelShaderConstantBuffer[mFrameIndex]->Map<PixelShaderConstantBuffer>();
*ps = mPSBuffer;
mPixelShaderConstantBuffer[mFrameIndex]->Unmap();
// Set the pixel shader constant buffer data.
mPixelShaderConstantBuffer[mFrameIndex]->Bind(1);
// Start drawing the shadow pass
mShadowMap->SetAsRenderTarget(true);
return true;
}
void RendererDX12::EndShadowPass()
{
JPH_PROFILE_FUNCTION();
// Finish drawing the shadow pass
mShadowMap->SetAsRenderTarget(false);
// Set the main back buffer as render target
mCommandList->OMSetRenderTargets(1, &mRenderTargetViews[mFrameIndex], FALSE, &mDepthStencilView);
// Set viewport
D3D12_VIEWPORT viewport = { 0.0f, 0.0f, static_cast<float>(mWindow->GetWindowWidth()), static_cast<float>(mWindow->GetWindowHeight()), 0.0f, 1.0f };
mCommandList->RSSetViewports(1, &viewport);
// Set scissor rect
D3D12_RECT scissor_rect = { 0, 0, static_cast<LONG>(mWindow->GetWindowWidth()), static_cast<LONG>(mWindow->GetWindowHeight()) };
mCommandList->RSSetScissorRects(1, &scissor_rect);
}
void RendererDX12::EndFrame()
{
JPH_PROFILE_FUNCTION();
Renderer::EndFrame();
// Indicate that the back buffer will now be used to present.
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
mCommandList->ResourceBarrier(1, &barrier);
// Close the command list
FatalErrorIfFailed(mCommandList->Close());
// Execute the command list
ID3D12CommandList* command_lists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(command_lists), command_lists);
// Present the frame
FatalErrorIfFailed(mSwapChain->Present(1, 0));
// Schedule a Signal command in the queue
UINT64 current_fence_value = mFenceValues[mFrameIndex];
FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value));
// Update the frame index
mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();
// If the next frame is not ready to be rendered yet, wait until it is ready
UINT64 completed_value = mFence->GetCompletedValue();
if (completed_value < mFenceValues[mFrameIndex])
{
FatalErrorIfFailed(mFence->SetEventOnCompletion(mFenceValues[mFrameIndex], mFenceEvent));
WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE);
}
// Release all used resources
mDelayReleased[mFrameIndex].clear();
// Anything that's not used yet can be removed, delayed objects are now available
mResourceCache.clear();
mDelayCached[mFrameIndex].swap(mResourceCache);
// Set the fence value for the next frame.
mFenceValues[mFrameIndex] = current_fence_value + 1;
}
void RendererDX12::SetProjectionMode()
{
JPH_ASSERT(mInFrame);
mVertexShaderConstantBufferProjection[mFrameIndex]->Bind(0);
}
void RendererDX12::SetOrthoMode()
{
JPH_ASSERT(mInFrame);
mVertexShaderConstantBufferOrtho[mFrameIndex]->Bind(0);
}
Ref<Texture> RendererDX12::CreateTexture(const Surface *inSurface)
{
return new TextureDX12(this, inSurface);
}
Ref<VertexShader> RendererDX12::CreateVertexShader(const char *inName)
{
UINT flags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef JPH_DEBUG
flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
const D3D_SHADER_MACRO defines[] =
{
{ nullptr, nullptr }
};
// Read shader source file
String file_name = String("Shaders/DX/") + inName + ".hlsl";
Array<uint8> data = ReadData(file_name.c_str());
// Compile source
ComPtr<ID3DBlob> shader_blob, error_blob;
HRESULT hr = D3DCompile(&data[0],
(uint)data.size(),
(AssetStream::sGetAssetsBasePath() + file_name).c_str(),
defines,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
"main",
"vs_5_0",
flags,
0,
shader_blob.GetAddressOf(),
error_blob.GetAddressOf());
if (FAILED(hr))
{
// Throw error if compilation failed
if (error_blob)
OutputDebugStringA((const char *)error_blob->GetBufferPointer());
FatalError("Failed to compile vertex shader");
}
return new VertexShaderDX12(shader_blob);
}
Ref<PixelShader> RendererDX12::CreatePixelShader(const char *inName)
{
UINT flags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef JPH_DEBUG
flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
const D3D_SHADER_MACRO defines[] =
{
{ nullptr, nullptr }
};
// Read shader source file
String file_name = String("Shaders/DX/") + inName + ".hlsl";
Array<uint8> data = ReadData(file_name.c_str());
// Compile source
ComPtr<ID3DBlob> shader_blob, error_blob;
HRESULT hr = D3DCompile(&data[0],
(uint)data.size(),
(AssetStream::sGetAssetsBasePath() + file_name).c_str(),
defines,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
"main",
"ps_5_0",
flags,
0,
shader_blob.GetAddressOf(),
error_blob.GetAddressOf());
if (FAILED(hr))
{
// Throw error if compilation failed
if (error_blob)
OutputDebugStringA((const char *)error_blob->GetBufferPointer());
FatalError("Failed to compile pixel shader");
}
return new PixelShaderDX12(shader_blob);
}
unique_ptr<ConstantBufferDX12> RendererDX12::CreateConstantBuffer(uint inBufferSize)
{
return make_unique<ConstantBufferDX12>(this, inBufferSize);
}
unique_ptr<PipelineState> RendererDX12::CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode)
{
return make_unique<PipelineStateDX12>(this, static_cast<const VertexShaderDX12 *>(inVertexShader), inInputDescription, inInputDescriptionCount, static_cast<const PixelShaderDX12 *>(inPixelShader), inDrawPass, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode);
}
RenderPrimitive *RendererDX12::CreateRenderPrimitive(PipelineState::ETopology inType)
{
return new RenderPrimitiveDX12(this, inType);
}
RenderInstances *RendererDX12::CreateRenderInstances()
{
return new RenderInstancesDX12(this);
}
ComPtr<ID3D12Resource> RendererDX12::CreateD3DResource(D3D12_HEAP_TYPE inHeapType, D3D12_RESOURCE_STATES inResourceState, uint64 inSize)
{
// Create a new resource
D3D12_RESOURCE_DESC desc;
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Alignment = 0;
desc.Width = inSize;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = DXGI_FORMAT_UNKNOWN;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
D3D12_HEAP_PROPERTIES heap_properties = {};
heap_properties.Type = inHeapType;
heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap_properties.CreationNodeMask = 1;
heap_properties.VisibleNodeMask = 1;
ComPtr<ID3D12Resource> resource;
FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &desc, inResourceState, nullptr, IID_PPV_ARGS(&resource)));
return resource;
}
void RendererDX12::CopyD3DResource(ID3D12Resource *inDest, const void *inSrc, uint64 inSize)
{
// Copy data to destination buffer
void *data;
D3D12_RANGE range = { 0, 0 }; // We're not going to read
FatalErrorIfFailed(inDest->Map(0, &range, &data));
memcpy(data, inSrc, size_t(inSize));
inDest->Unmap(0, nullptr);
}
void RendererDX12::CopyD3DResource(ID3D12Resource *inDest, ID3D12Resource *inSrc, uint64 inSize)
{
// Start a commandlist for the upload
ID3D12GraphicsCommandList *list = mUploadQueue.Start();
// Copy the data to the GPU
list->CopyBufferRegion(inDest, 0, inSrc, 0, inSize);
// Change the state of the resource to generic read
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = inDest;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
list->ResourceBarrier(1, &barrier);
// Wait for copying to finish
mUploadQueue.ExecuteAndWait();
}
ComPtr<ID3D12Resource> RendererDX12::CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize)
{
ComPtr<ID3D12Resource> upload = CreateD3DResourceOnUploadHeap(inSize);
ComPtr<ID3D12Resource> resource = CreateD3DResource(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, inSize);
CopyD3DResource(upload.Get(), inData, inSize);
CopyD3DResource(resource.Get(), upload.Get(), inSize);
RecycleD3DResourceOnUploadHeap(upload.Get(), inSize);
return resource;
}
ComPtr<ID3D12Resource> RendererDX12::CreateD3DResourceOnUploadHeap(uint64 inSize)
{
// Try cache first
ResourceCache::iterator i = mResourceCache.find(inSize);
if (i != mResourceCache.end() && !i->second.empty())
{
ComPtr<ID3D12Resource> resource = i->second.back();
i->second.pop_back();
return resource;
}
return CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, inSize);
}
void RendererDX12::RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize)
{
if (!mIsExiting)
mDelayCached[mFrameIndex][inSize].push_back(inResource);
}
void RendererDX12::RecycleD3DObject(ID3D12Object *inResource)
{
if (!mIsExiting)
mDelayReleased[mFrameIndex].push_back(inResource);
}
#ifndef JPH_ENABLE_VULKAN
Renderer *Renderer::sCreate()
{
return new RendererDX12;
}
#endif

View File

@@ -0,0 +1,111 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/UnorderedMap.h>
#include <Renderer/Renderer.h>
#include <Renderer/DX12/CommandQueueDX12.h>
#include <Renderer/DX12/DescriptorHeapDX12.h>
#include <Renderer/DX12/ConstantBufferDX12.h>
#include <Renderer/DX12/TextureDX12.h>
/// DirectX 12 renderer
class RendererDX12 : public Renderer
{
public:
/// Destructor
virtual ~RendererDX12() override;
// See: Renderer
virtual void Initialize(ApplicationWindow *inWindow) override;
virtual bool BeginFrame(const CameraState &inCamera, float inWorldScale) override;
virtual void EndShadowPass() override;
virtual void EndFrame() override;
virtual void SetProjectionMode() override;
virtual void SetOrthoMode() override;
virtual Ref<Texture> CreateTexture(const Surface *inSurface) override;
virtual Ref<VertexShader> CreateVertexShader(const char *inName) override;
virtual Ref<PixelShader> CreatePixelShader(const char *inName) override;
virtual unique_ptr<PipelineState> CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) override;
virtual RenderPrimitive * CreateRenderPrimitive(PipelineState::ETopology inType) override;
virtual RenderInstances * CreateRenderInstances() override;
virtual Texture * GetShadowMap() const override { return mShadowMap.GetPtr(); }
virtual void OnWindowResize() override;
/// Create a constant buffer
unique_ptr<ConstantBufferDX12> CreateConstantBuffer(uint inBufferSize);
/// Create a buffer on the default heap (usable for permanent buffers)
ComPtr<ID3D12Resource> CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize);
/// Create buffer on the upload heap (usable for temporary buffers).
ComPtr<ID3D12Resource> CreateD3DResourceOnUploadHeap(uint64 inSize);
/// Recycle a buffer on the upload heap. This puts it back in a cache and will reuse it when it is certain the GPU is no longer referencing it.
void RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize);
/// Keeps a reference to the resource until the current frame has finished
void RecycleD3DObject(ID3D12Object *inResource);
/// Access to the most important DirectX structures
ID3D12Device * GetDevice() { return mDevice.Get(); }
ID3D12RootSignature * GetRootSignature() { return mRootSignature.Get(); }
ID3D12GraphicsCommandList * GetCommandList() { JPH_ASSERT(mInFrame); return mCommandList.Get(); }
CommandQueueDX12 & GetUploadQueue() { return mUploadQueue; }
DescriptorHeapDX12 & GetDSVHeap() { return mDSVHeap; }
DescriptorHeapDX12 & GetSRVHeap() { return mSRVHeap; }
private:
// Wait for pending GPU work to complete
void WaitForGpu();
// Create render targets and their views
void CreateRenderTargets();
// Create a depth buffer for the back buffer
void CreateDepthBuffer();
// Function to create a ID3D12Resource on specified heap with specified state
ComPtr<ID3D12Resource> CreateD3DResource(D3D12_HEAP_TYPE inHeapType, D3D12_RESOURCE_STATES inResourceState, uint64 inSize);
// Copy CPU memory into a ID3D12Resource
void CopyD3DResource(ID3D12Resource *inDest, const void *inSrc, uint64 inSize);
// Copy a CPU resource to a GPU resource
void CopyD3DResource(ID3D12Resource *inDest, ID3D12Resource *inSrc, uint64 inSize);
// DirectX interfaces
ComPtr<IDXGIFactory4> mDXGIFactory;
ComPtr<ID3D12Device> mDevice;
DescriptorHeapDX12 mRTVHeap; ///< Render target view heap
DescriptorHeapDX12 mDSVHeap; ///< Depth stencil view heap
DescriptorHeapDX12 mSRVHeap; ///< Shader resource view heap
ComPtr<IDXGISwapChain3> mSwapChain;
ComPtr<ID3D12Resource> mRenderTargets[cFrameCount]; ///< Two render targets (we're double buffering in order for the CPU to continue while the GPU is rendering)
D3D12_CPU_DESCRIPTOR_HANDLE mRenderTargetViews[cFrameCount]; ///< The two render views corresponding to the render targets
ComPtr<ID3D12Resource> mDepthStencilBuffer; ///< The main depth buffer
D3D12_CPU_DESCRIPTOR_HANDLE mDepthStencilView { 0 }; ///< A view for binding the depth buffer
ComPtr<ID3D12CommandAllocator> mCommandAllocators[cFrameCount]; ///< Two command allocator lists (one per frame)
ComPtr<ID3D12CommandQueue> mCommandQueue; ///< The command queue that will execute commands (there's only 1 since we want to finish rendering 1 frame before moving onto the next)
ComPtr<ID3D12GraphicsCommandList> mCommandList; ///< The command list
ComPtr<ID3D12RootSignature> mRootSignature; ///< The root signature, we have a simple application so we only need 1, which is suitable for all our shaders
Ref<TextureDX12> mShadowMap; ///< Used to render shadow maps
CommandQueueDX12 mUploadQueue; ///< Queue used to upload resources to GPU memory
unique_ptr<ConstantBufferDX12> mVertexShaderConstantBufferProjection[cFrameCount];
unique_ptr<ConstantBufferDX12> mVertexShaderConstantBufferOrtho[cFrameCount];
unique_ptr<ConstantBufferDX12> mPixelShaderConstantBuffer[cFrameCount];
// Synchronization objects used to finish rendering and swapping before reusing a command queue
HANDLE mFenceEvent; ///< Fence event to wait for the previous frame rendering to complete (in order to free 1 of the buffers)
ComPtr<ID3D12Fence> mFence; ///< Fence object, used to signal the end of a frame
UINT64 mFenceValues[cFrameCount] = {}; ///< Values that were used to signal completion of one of the two frames
using ResourceCache = UnorderedMap<uint64, Array<ComPtr<ID3D12Resource>>>;
ResourceCache mResourceCache; ///< Cache items ready to be reused
ResourceCache mDelayCached[cFrameCount]; ///< List of reusable ID3D12Resources that are potentially referenced by the GPU so can be used only when the GPU finishes
Array<ComPtr<ID3D12Object>> mDelayReleased[cFrameCount]; ///< Objects that are potentially referenced by the GPU so can only be freed when the GPU finishes
bool mIsExiting = false; ///< When exiting we don't want to add references too buffers
};

View File

@@ -0,0 +1,236 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/DX12/TextureDX12.h>
#include <Renderer/DX12/RendererDX12.h>
#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
#include <Image/BlitSurface.h>
TextureDX12::TextureDX12(RendererDX12 *inRenderer, const Surface *inSurface) :
Texture(inSurface->GetWidth(), inSurface->GetHeight()),
mRenderer(inRenderer)
{
// Create description
D3D12_RESOURCE_DESC desc = {};
desc.MipLevels = 1;
ESurfaceFormat format = inSurface->GetFormat();
switch (format)
{
case ESurfaceFormat::A4L4: desc.Format = DXGI_FORMAT_R8G8_UNORM; format = ESurfaceFormat::A8L8; break;
case ESurfaceFormat::L8: desc.Format = DXGI_FORMAT_R8_UNORM; break;
case ESurfaceFormat::A8: desc.Format = DXGI_FORMAT_A8_UNORM; break;
case ESurfaceFormat::A8L8: desc.Format = DXGI_FORMAT_R8G8_UNORM; break;
case ESurfaceFormat::R5G6B5: desc.Format = DXGI_FORMAT_B5G6R5_UNORM; break;
case ESurfaceFormat::X1R5G5B5: desc.Format = DXGI_FORMAT_B5G5R5A1_UNORM; format = ESurfaceFormat::A1R5G5B5; break;
case ESurfaceFormat::X4R4G4B4: desc.Format = DXGI_FORMAT_B4G4R4A4_UNORM; format = ESurfaceFormat::A4R4G4B4; break;
case ESurfaceFormat::A1R5G5B5: desc.Format = DXGI_FORMAT_B5G5R5A1_UNORM; break;
case ESurfaceFormat::A4R4G4B4: desc.Format = DXGI_FORMAT_B4G4R4A4_UNORM; break;
case ESurfaceFormat::R8G8B8: desc.Format = DXGI_FORMAT_B8G8R8X8_UNORM; format = ESurfaceFormat::X8R8G8B8; break;
case ESurfaceFormat::B8G8R8: desc.Format = DXGI_FORMAT_B8G8R8X8_UNORM; format = ESurfaceFormat::X8R8G8B8; break;
case ESurfaceFormat::X8R8G8B8: desc.Format = DXGI_FORMAT_B8G8R8X8_UNORM; break;
case ESurfaceFormat::X8B8G8R8: desc.Format = DXGI_FORMAT_B8G8R8X8_UNORM; format = ESurfaceFormat::X8R8G8B8; break;
case ESurfaceFormat::A8R8G8B8: desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; break;
case ESurfaceFormat::A8B8G8R8: desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; format = ESurfaceFormat::A8R8G8B8; break;
case ESurfaceFormat::Invalid:
default: JPH_ASSERT(false); break;
}
desc.Width = mWidth;
desc.Height = mHeight;
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
desc.DepthOrArraySize = 1;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
// Blit the surface to another temporary surface if the format changed
const Surface *surface = inSurface;
Ref<Surface> tmp;
if (format != inSurface->GetFormat())
{
tmp = new SoftwareSurface(mWidth, mHeight, format);
BlitSurface(inSurface, tmp);
surface = tmp;
}
// Create texture in default heap
D3D12_HEAP_PROPERTIES heap_properties = {};
heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT;
heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap_properties.CreationNodeMask = 1;
heap_properties.VisibleNodeMask = 1;
FatalErrorIfFailed(inRenderer->GetDevice()->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&mTexture)));
JPH_IF_DEBUG(mTexture->SetName(L"Texture");)
// Determine required size of data to copy
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint;
UINT64 row_size_in_bytes;
UINT64 required_size = 0;
inRenderer->GetDevice()->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, nullptr, &row_size_in_bytes, &required_size);
// Create the GPU upload buffer
ComPtr<ID3D12Resource> upload_resource = mRenderer->CreateD3DResourceOnUploadHeap(required_size);
JPH_IF_DEBUG(upload_resource->SetName(L"Texture Upload");)
// Copy data to upload texture
surface->Lock(ESurfaceLockMode::Read);
uint8 *upload_data;
D3D12_RANGE range = { 0, 0 }; // We're not going to read
FatalErrorIfFailed(upload_resource->Map(0, &range, (void **)&upload_data));
for (int y = 0; y < mHeight; ++y)
memcpy(upload_data + y * row_size_in_bytes, surface->GetData() + y * surface->GetStride(), surface->GetBytesPerPixel() * mWidth);
upload_resource->Unmap(0, nullptr);
surface->UnLock();
// Start a commandlist for the upload
ID3D12GraphicsCommandList *list = inRenderer->GetUploadQueue().Start();
// Copy the texture from our upload buffer to our final texture
D3D12_TEXTURE_COPY_LOCATION copy_dst;
copy_dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
copy_dst.pResource = mTexture.Get();
copy_dst.SubresourceIndex = 0;
D3D12_TEXTURE_COPY_LOCATION copy_src;
copy_src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
copy_src.pResource = upload_resource.Get();
copy_src.PlacedFootprint = footprint;
list->CopyTextureRegion(&copy_dst, 0, 0, 0, &copy_src, nullptr);
// Indicate that the texture is now ready to be used by a pixel shader
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = mTexture.Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
list->ResourceBarrier(1, &barrier);
// Create a SRV for the texture
mSRV = inRenderer->GetSRVHeap().Allocate();
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srv_desc.Format = desc.Format;
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = 1;
inRenderer->GetDevice()->CreateShaderResourceView(mTexture.Get(), &srv_desc, mSRV);
// Wait for copying to finish so we can destroy the upload texture
inRenderer->GetUploadQueue().ExecuteAndWait();
// Recycle the upload buffer
inRenderer->RecycleD3DResourceOnUploadHeap(upload_resource.Get(), required_size);
}
TextureDX12::TextureDX12(RendererDX12 *inRenderer, int inWidth, int inHeight) :
Texture(inWidth, inHeight),
mRenderer(inRenderer)
{
// Allocate depth stencil buffer
D3D12_CLEAR_VALUE clear_value = {};
clear_value.Format = DXGI_FORMAT_D32_FLOAT;
clear_value.DepthStencil.Depth = 0;
clear_value.DepthStencil.Stencil = 0;
D3D12_HEAP_PROPERTIES heap_properties = {};
heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT;
heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap_properties.CreationNodeMask = 1;
heap_properties.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC depth_stencil_desc = {};
depth_stencil_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depth_stencil_desc.Alignment = 0;
depth_stencil_desc.Width = mWidth;
depth_stencil_desc.Height = mHeight;
depth_stencil_desc.DepthOrArraySize = 1;
depth_stencil_desc.MipLevels = 1;
depth_stencil_desc.Format = DXGI_FORMAT_D32_FLOAT;
depth_stencil_desc.SampleDesc.Count = 1;
depth_stencil_desc.SampleDesc.Quality = 0;
depth_stencil_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depth_stencil_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
FatalErrorIfFailed(inRenderer->GetDevice()->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &depth_stencil_desc, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, &clear_value, IID_PPV_ARGS(&mTexture)));
JPH_IF_DEBUG(mTexture->SetName(L"Render Target Texture");)
// Create DSV for the texture
mDSV = inRenderer->GetDSVHeap().Allocate();
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc = {};
dsv_desc.Format = DXGI_FORMAT_D32_FLOAT;
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsv_desc.Flags = D3D12_DSV_FLAG_NONE;
inRenderer->GetDevice()->CreateDepthStencilView(mTexture.Get(), &dsv_desc, mDSV);
// Create a SRV for the texture
mSRV = inRenderer->GetSRVHeap().Allocate();
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srv_desc.Format = DXGI_FORMAT_R32_FLOAT;
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = 1;
inRenderer->GetDevice()->CreateShaderResourceView(mTexture.Get(), &srv_desc, mSRV);
}
TextureDX12::~TextureDX12()
{
if (mSRV.ptr != 0)
mRenderer->GetSRVHeap().Free(mSRV);
if (mDSV.ptr != 0)
mRenderer->GetDSVHeap().Free(mDSV);
if (mTexture != nullptr)
mRenderer->RecycleD3DObject(mTexture.Get());
}
void TextureDX12::Bind() const
{
mRenderer->GetCommandList()->SetGraphicsRootDescriptorTable(2 /* All shaders use slot 2 to bind their texture */, mRenderer->GetSRVHeap().ConvertToGPUHandle(mSRV));
}
void TextureDX12::SetAsRenderTarget(bool inSet) const
{
if (inSet)
{
// Indicate make the texture ready for rendering to
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = mTexture.Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_DEPTH_WRITE;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
mRenderer->GetCommandList()->ResourceBarrier(1, &barrier);
// Set as render target
mRenderer->GetCommandList()->OMSetRenderTargets(0, nullptr, FALSE, &mDSV);
// Set view port
D3D12_VIEWPORT viewport = { 0.0f, 0.0f, static_cast<float>(mWidth), static_cast<float>(mHeight), 0.0f, 1.0f };
mRenderer->GetCommandList()->RSSetViewports(1, &viewport);
// Set scissor rect
D3D12_RECT scissor_rect = { 0, 0, static_cast<LONG>(mWidth), static_cast<LONG>(mHeight) };
mRenderer->GetCommandList()->RSSetScissorRects(1, &scissor_rect);
// Clear the render target
mRenderer->GetCommandList()->ClearDepthStencilView(mDSV, D3D12_CLEAR_FLAG_DEPTH, 0, 0, 0, nullptr);
}
else
{
// Indicate that the texture is now ready to be used by a pixel shader
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = mTexture.Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_DEPTH_WRITE;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
mRenderer->GetCommandList()->ResourceBarrier(1, &barrier);
}
}

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/Texture.h>
class RendererDX12;
class TextureDX12 : public Texture
{
public:
/// Constructor, called by Renderer::CreateTextureDX12
TextureDX12(RendererDX12 *inRenderer, const Surface *inSurface); // Create a normal TextureDX12
TextureDX12(RendererDX12 *inRenderer, int inWidth, int inHeight); // Create a render target (depth only)
virtual ~TextureDX12() override;
/// Bind texture to the pixel shader
virtual void Bind() const override;
/// Activate this texture as the current render target, used by RendererDX12::BeginFrame, EndShadowPass
void SetAsRenderTarget(bool inSet) const;
private:
RendererDX12 * mRenderer;
ComPtr<ID3D12Resource> mTexture; ///< The texture data
D3D12_CPU_DESCRIPTOR_HANDLE mSRV { 0 }; ///< Shader resource view to bind as texture
D3D12_CPU_DESCRIPTOR_HANDLE mDSV { 0 }; ///< Depth shader view to bind as render target
};

View File

@@ -0,0 +1,17 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/VertexShader.h>
/// Vertex shader handle for DirectX
class VertexShaderDX12 : public VertexShader
{
public:
/// Constructor
VertexShaderDX12(ComPtr<ID3DBlob> inShader) : mShader(inShader) { }
ComPtr<ID3DBlob> mShader; ///< The compiled shader
};

View File

@@ -0,0 +1,511 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/DebugRendererImp.h>
#include <Renderer/Renderer.h>
#include <Renderer/Font.h>
#ifndef JPH_DEBUG_RENDERER
// Hack to still compile DebugRenderer inside the test framework when Jolt is compiled without
#define JPH_DEBUG_RENDERER
#include <Jolt/Renderer/DebugRenderer.cpp>
#undef JPH_DEBUG_RENDERER
#endif // !JPH_DEBUG_RENDERER
DebugRendererImp::DebugRendererImp(Renderer *inRenderer, const Font *inFont) :
mRenderer(inRenderer),
mFont(inFont)
{
// Create input layout for lines
const PipelineState::EInputDescription line_vertex_desc[] =
{
PipelineState::EInputDescription::Position,
PipelineState::EInputDescription::Color
};
// Lines
Ref<VertexShader> vtx_line = mRenderer->CreateVertexShader("LineVertexShader");
Ref<PixelShader> pix_line = mRenderer->CreatePixelShader("LinePixelShader");
mLineState = mRenderer->CreatePipelineState(vtx_line, line_vertex_desc, std::size(line_vertex_desc), pix_line, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Line, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
// Create input layout for triangles
const PipelineState::EInputDescription triangles_vertex_desc[] =
{
PipelineState::EInputDescription::Position,
PipelineState::EInputDescription::Normal,
PipelineState::EInputDescription::TexCoord,
PipelineState::EInputDescription::Color,
PipelineState::EInputDescription::InstanceTransform,
PipelineState::EInputDescription::InstanceInvTransform,
PipelineState::EInputDescription::InstanceColor
};
// Triangles
Ref<VertexShader> vtx_triangle = mRenderer->CreateVertexShader("TriangleVertexShader");
Ref<PixelShader> pix_triangle = mRenderer->CreatePixelShader("TrianglePixelShader");
mTriangleStateBF = mRenderer->CreatePipelineState(vtx_triangle, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_triangle, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
mTriangleStateFF = mRenderer->CreatePipelineState(vtx_triangle, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_triangle, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace);
mTriangleStateWire = mRenderer->CreatePipelineState(vtx_triangle, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_triangle, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Wireframe, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
// Shadow pass
Ref<VertexShader> vtx_shadow = mRenderer->CreateVertexShader("TriangleDepthVertexShader");
Ref<PixelShader> pix_shadow = mRenderer->CreatePixelShader("TriangleDepthPixelShader");
mShadowStateBF = mRenderer->CreatePipelineState(vtx_shadow, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_shadow, PipelineState::EDrawPass::Shadow, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
mShadowStateFF = mRenderer->CreatePipelineState(vtx_shadow, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_shadow, PipelineState::EDrawPass::Shadow, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace);
mShadowStateWire = mRenderer->CreatePipelineState(vtx_shadow, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_shadow, PipelineState::EDrawPass::Shadow, PipelineState::EFillMode::Wireframe, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
// Create instances buffer
for (uint n = 0; n < Renderer::cFrameCount; ++n)
mInstancesBuffer[n] = mRenderer->CreateRenderInstances();
// Create empty batch
Vertex empty_vertex { Float3(0, 0, 0), Float3(1, 0, 0), Float2(0, 0), Color::sWhite };
uint32 empty_indices[] = { 0, 0, 0 };
mEmptyBatch = CreateTriangleBatch(&empty_vertex, 1, empty_indices, 3);
// Initialize base class
DebugRenderer::Initialize();
}
void DebugRendererImp::DrawLine(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor)
{
RVec3 offset = mRenderer->GetBaseOffset();
Line line;
Vec3(inFrom - offset).StoreFloat3(&line.mFrom);
line.mFromColor = inColor;
Vec3(inTo - offset).StoreFloat3(&line.mTo);
line.mToColor = inColor;
lock_guard lock(mLinesLock);
mLines.push_back(line);
}
DebugRenderer::Batch DebugRendererImp::CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount)
{
if (inTriangles == nullptr || inTriangleCount == 0)
return mEmptyBatch;
RenderPrimitive *primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
primitive->CreateVertexBuffer(3 * inTriangleCount, sizeof(Vertex), inTriangles);
return primitive;
}
DebugRenderer::Batch DebugRendererImp::CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount)
{
if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0)
return mEmptyBatch;
RenderPrimitive *primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
primitive->CreateVertexBuffer(inVertexCount, sizeof(Vertex), inVertices);
primitive->CreateIndexBuffer(inIndexCount, inIndices);
return primitive;
}
void DebugRendererImp::DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode)
{
lock_guard lock(mPrimitivesLock);
RVec3 offset = mRenderer->GetBaseOffset();
Mat44 model_matrix = inModelMatrix.PostTranslated(-offset).ToMat44();
AABox world_space_bounds = inWorldSpaceBounds;
world_space_bounds.Translate(Vec3(-offset));
// Our pixel shader uses alpha only to turn on/off shadows
Color color = inCastShadow == ECastShadow::On? Color(inModelColor, 255) : Color(inModelColor, 0);
if (inDrawMode == EDrawMode::Wireframe)
{
mWireframePrimitives[inGeometry].mInstances.push_back({ model_matrix, model_matrix.GetDirectionPreservingMatrix(), color, world_space_bounds, inLODScaleSq });
++mNumInstances;
}
else
{
if (inCullMode != ECullMode::CullFrontFace)
{
mPrimitives[inGeometry].mInstances.push_back({ model_matrix, model_matrix.GetDirectionPreservingMatrix(), color, world_space_bounds, inLODScaleSq });
++mNumInstances;
}
if (inCullMode != ECullMode::CullBackFace)
{
mPrimitivesBackFacing[inGeometry].mInstances.push_back({ model_matrix, model_matrix.GetDirectionPreservingMatrix(), color, world_space_bounds, inLODScaleSq });
++mNumInstances;
}
}
}
void DebugRendererImp::FinalizePrimitive()
{
JPH_PROFILE_FUNCTION();
if (mLockedPrimitive != nullptr)
{
// Unlock the primitive
mLockedPrimitive->UnlockVertexBuffer();
// Set number of indices to draw
mLockedPrimitive->SetNumVtxToDraw(int(mLockedVertices - mLockedVerticesStart));
// Add to draw list
mTempPrimitives[new Geometry(mLockedPrimitive.GetPtr(), mLockedPrimitiveBounds)].mInstances.push_back({ Mat44::sIdentity(), Mat44::sIdentity(), Color::sWhite, mLockedPrimitiveBounds, 1.0f });
++mNumInstances;
// Clear pointers
mLockedPrimitive = nullptr;
mLockedVerticesStart = nullptr;
mLockedVertices = nullptr;
mLockedVerticesEnd = nullptr;
mLockedPrimitiveBounds = AABox();
}
}
void DebugRendererImp::EnsurePrimitiveSpace(int inVtxSize)
{
const int cVertexBufferSize = 10240;
if (mLockedPrimitive == nullptr
|| mLockedVerticesEnd - mLockedVertices < inVtxSize)
{
FinalizePrimitive();
// Create new
mLockedPrimitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
mLockedPrimitive->CreateVertexBuffer(cVertexBufferSize, sizeof(Vertex));
// Lock buffers
mLockedVerticesStart = mLockedVertices = (Vertex *)mLockedPrimitive->LockVertexBuffer();
mLockedVerticesEnd = mLockedVertices + cVertexBufferSize;
}
}
void DebugRendererImp::DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow)
{
RVec3 offset = mRenderer->GetBaseOffset();
Vec3 v1(inV1 - offset);
Vec3 v2(inV2 - offset);
Vec3 v3(inV3 - offset);
lock_guard lock(mPrimitivesLock);
EnsurePrimitiveSpace(3);
// Set alpha to zero if we don't want to cast shadows to notify the pixel shader
Color color(inColor, inCastShadow == ECastShadow::Off? 0 : 0xff);
// Construct triangle in separate buffer and then copy it to the target memory block (may be uncached memory)
Triangle triangle(v1, v2, v3, color);
*(Triangle *)mLockedVertices = triangle;
mLockedVertices += 3;
// Update bounding box
mLockedPrimitiveBounds.Encapsulate(v1);
mLockedPrimitiveBounds.Encapsulate(v2);
mLockedPrimitiveBounds.Encapsulate(v3);
}
void DebugRendererImp::DrawInstances(const Geometry *inGeometry, const Array<int> &inStartIdx)
{
RenderInstances *instances_buffer = mInstancesBuffer[mRenderer->GetCurrentFrameIndex()];
if (!inStartIdx.empty())
{
// Get LODs
const Array<LOD> &geometry_lods = inGeometry->mLODs;
// Write instances for all LODS
int next_start_idx = inStartIdx.front();
for (size_t lod = 0; lod < geometry_lods.size(); ++lod)
{
int start_idx = next_start_idx;
next_start_idx = inStartIdx[lod + 1];
int num_instances = next_start_idx - start_idx;
instances_buffer->Draw(static_cast<RenderPrimitive *>(geometry_lods[lod].mTriangleBatch.GetPtr()), start_idx, num_instances);
}
}
}
void DebugRendererImp::DrawText3D(RVec3Arg inPosition, const string_view &inString, ColorArg inColor, float inHeight)
{
RVec3 offset = mRenderer->GetBaseOffset();
Vec3 pos(inPosition - offset);
lock_guard lock(mTextsLock);
mTexts.emplace_back(pos, inString, inColor, inHeight);
}
void DebugRendererImp::DrawLines()
{
JPH_PROFILE_FUNCTION();
lock_guard lock(mLinesLock);
// Draw the lines
if (!mLines.empty())
{
Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Line);
primitive->CreateVertexBuffer((int)mLines.size() * 2, sizeof(Line) / 2);
void *data = primitive->LockVertexBuffer();
memcpy(data, &mLines[0], mLines.size() * sizeof(Line));
primitive->UnlockVertexBuffer();
mLineState->Activate();
primitive->Draw();
}
}
void DebugRendererImp::DrawShadowPass()
{
JPH_PROFILE_FUNCTION();
lock_guard lock(mPrimitivesLock);
// Finish the last primitive
FinalizePrimitive();
// Get the camera and light frustum for culling
Vec3 camera_pos(mRenderer->GetCameraState().mPos - mRenderer->GetBaseOffset());
const Frustum &camera_frustum = mRenderer->GetCameraFrustum();
const Frustum &light_frustum = mRenderer->GetLightFrustum();
// Resize instances buffer and copy all visible instance data into it
if (mNumInstances > 0)
{
// Create instances buffer
RenderInstances *instances_buffer = mInstancesBuffer[mRenderer->GetCurrentFrameIndex()];
instances_buffer->CreateBuffer(2 * mNumInstances, sizeof(Instance));
Instance *dst_instance = reinterpret_cast<Instance *>(instances_buffer->Lock());
// Next write index
int dst_index = 0;
// This keeps track of which instances use which lod, first array: 0 = light pass, 1 = geometry pass
Array<Array<int>> lod_indices[2];
for (InstanceMap *primitive_map : { &mPrimitives, &mTempPrimitives, &mPrimitivesBackFacing, &mWireframePrimitives })
for (InstanceMap::value_type &v : *primitive_map)
{
// Get LODs
const Array<LOD> &geometry_lods = v.first->mLODs;
size_t num_lods = geometry_lods.size();
JPH_ASSERT(num_lods > 0);
// Ensure that our lod index array is big enough (to avoid reallocating memory too often)
if (lod_indices[0].size() < num_lods)
lod_indices[0].resize(num_lods);
if (lod_indices[1].size() < num_lods)
lod_indices[1].resize(num_lods);
// Iterate over all instances
const Array<InstanceWithLODInfo> &instances = v.second.mInstances;
for (size_t i = 0; i < instances.size(); ++i)
{
const InstanceWithLODInfo &src_instance = instances[i];
// Check if it overlaps with the light or camera frustum
bool light_overlaps = light_frustum.Overlaps(src_instance.mWorldSpaceBounds);
bool camera_overlaps = camera_frustum.Overlaps(src_instance.mWorldSpaceBounds);
if (light_overlaps || camera_overlaps)
{
// Figure out which LOD to use
const LOD &lod = v.first->GetLOD(camera_pos, src_instance.mWorldSpaceBounds, src_instance.mLODScaleSq);
size_t lod_index = &lod - geometry_lods.data();
// Store which index goes in which LOD
if (light_overlaps)
lod_indices[0][lod_index].push_back((int)i);
if (camera_overlaps)
lod_indices[1][lod_index].push_back((int)i);
}
}
// Loop over both passes: 0 = light, 1 = geometry
Array<int> *start_idx[] = { &v.second.mLightStartIdx, &v.second.mGeometryStartIdx };
for (int type = 0; type < 2; ++type)
{
// Reserve space for instance indices
Array<int> &type_start_idx = *start_idx[type];
type_start_idx.resize(num_lods + 1);
// Write out geometry pass instances
for (size_t lod = 0; lod < num_lods; ++lod)
{
// Write start index for this LOD
type_start_idx[lod] = dst_index;
// Copy instances
Array<int> &this_lod_indices = lod_indices[type][lod];
for (int i : this_lod_indices)
{
const Instance &src_instance = instances[i];
dst_instance[dst_index++] = src_instance;
}
// Prepare for next iteration (will preserve memory)
this_lod_indices.clear();
}
// Write out end of last LOD
type_start_idx.back() = dst_index;
}
}
instances_buffer->Unlock();
}
if (!mPrimitives.empty() || !mTempPrimitives.empty())
{
// Front face culling, we want to render the back side of the geometry for casting shadows
mShadowStateFF->Activate();
// Draw all primitives as seen from the light
if (mNumInstances > 0)
for (InstanceMap::value_type &v : mPrimitives)
DrawInstances(v.first, v.second.mLightStartIdx);
for (InstanceMap::value_type &v : mTempPrimitives)
DrawInstances(v.first, v.second.mLightStartIdx);
}
if (!mPrimitivesBackFacing.empty())
{
// Back face culling, we want to render the front side of back facing geometry
mShadowStateBF->Activate();
// Draw all primitives as seen from the light
for (InstanceMap::value_type &v : mPrimitivesBackFacing)
DrawInstances(v.first, v.second.mLightStartIdx);
}
if (!mWireframePrimitives.empty())
{
// Switch to wireframe mode
mShadowStateWire->Activate();
// Draw all wireframe primitives as seen from the light
for (InstanceMap::value_type &v : mWireframePrimitives)
DrawInstances(v.first, v.second.mLightStartIdx);
}
}
void DebugRendererImp::DrawTriangles()
{
// Bind the shadow map texture
mRenderer->GetShadowMap()->Bind();
if (!mPrimitives.empty() || !mTempPrimitives.empty())
{
// Bind the normal shader, back face culling
mTriangleStateBF->Activate();
// Draw all primitives
if (mNumInstances > 0)
for (InstanceMap::value_type &v : mPrimitives)
DrawInstances(v.first, v.second.mGeometryStartIdx);
for (InstanceMap::value_type &v : mTempPrimitives)
DrawInstances(v.first, v.second.mGeometryStartIdx);
}
if (!mPrimitivesBackFacing.empty())
{
// Front face culling, the next batch needs to render inside out
mTriangleStateFF->Activate();
// Draw all back primitives
for (InstanceMap::value_type &v : mPrimitivesBackFacing)
DrawInstances(v.first, v.second.mGeometryStartIdx);
}
if (!mWireframePrimitives.empty())
{
// Wire frame mode
mTriangleStateWire->Activate();
// Draw all wireframe primitives
for (InstanceMap::value_type &v : mWireframePrimitives)
DrawInstances(v.first, v.second.mGeometryStartIdx);
}
}
void DebugRendererImp::DrawTexts()
{
lock_guard lock(mTextsLock);
JPH_PROFILE_FUNCTION();
const CameraState &camera_state = mRenderer->GetCameraState();
for (const Text &t : mTexts)
{
Vec3 forward = camera_state.mForward;
Vec3 right = forward.Cross(camera_state.mUp).Normalized();
Vec3 up = right.Cross(forward).Normalized();
Mat44 transform(Vec4(right, 0), Vec4(up, 0), Vec4(forward, 0), Vec4(t.mPosition, 1));
mFont->DrawText3D(transform * Mat44::sScale(t.mHeight), t.mText, t.mColor);
}
}
void DebugRendererImp::Draw()
{
DrawLines();
DrawTriangles();
DrawTexts();
}
void DebugRendererImp::ClearLines()
{
lock_guard lock(mLinesLock);
mLines.clear();
}
void DebugRendererImp::ClearMap(InstanceMap &ioInstances)
{
Array<GeometryRef> to_delete;
for (InstanceMap::value_type &kv : ioInstances)
{
if (kv.second.mInstances.empty())
to_delete.push_back(kv.first);
else
kv.second.mInstances.clear();
}
for (GeometryRef &b : to_delete)
ioInstances.erase(b);
}
void DebugRendererImp::ClearTriangles()
{
lock_guard lock(mPrimitivesLock);
// Close any primitive that's being built
FinalizePrimitive();
// Move primitives to draw back to the free list
ClearMap(mWireframePrimitives);
ClearMap(mPrimitives);
mTempPrimitives.clear(); // These are created by FinalizePrimitive() and need to be cleared every frame
ClearMap(mPrimitivesBackFacing);
mNumInstances = 0;
}
void DebugRendererImp::ClearTexts()
{
lock_guard lock(mTextsLock);
mTexts.clear();
}
void DebugRendererImp::Clear()
{
ClearLines();
ClearTriangles();
ClearTexts();
NextFrame();
}

View File

@@ -0,0 +1,178 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#ifdef JPH_DEBUG_RENDERER
#include <Jolt/Renderer/DebugRenderer.h>
#else
// Hack to still compile DebugRenderer inside the test framework when Jolt is compiled without
#define JPH_DEBUG_RENDERER
// Make sure the debug renderer symbols don't get imported or exported
#define JPH_DEBUG_RENDERER_EXPORT
#include <Jolt/Renderer/DebugRenderer.h>
#undef JPH_DEBUG_RENDERER
#undef JPH_DEBUG_RENDERER_EXPORT
#endif
#include <Renderer/Renderer.h>
#include <Jolt/Core/Mutex.h>
#include <Jolt/Core/UnorderedMap.h>
class Renderer;
class Font;
/// Implementation of DebugRenderer
class DebugRendererImp final : public DebugRenderer
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Constructor
DebugRendererImp(Renderer *inRenderer, const Font *inFont);
/// Implementation of DebugRenderer interface
virtual void DrawLine(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor) override;
virtual void DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::Off) override;
virtual Batch CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount) override;
virtual Batch CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount) override;
virtual void DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode) override;
virtual void DrawText3D(RVec3Arg inPosition, const string_view &inString, ColorArg inColor, float inHeight) override;
/// Draw all primitives from the light source
void DrawShadowPass();
/// Draw all primitives that were added
void Draw();
/// Clear all primitives (to be called after drawing)
void Clear();
private:
/// Helper functions to draw sub parts
void DrawLines();
void DrawTriangles();
void DrawTexts();
/// Helper functions to clear sub parts
void ClearLines();
void ClearTriangles();
void ClearTexts();
/// Finalize the current locked primitive and add it to the primitives to draw
void FinalizePrimitive();
/// Ensure that the current locked primitive has space for a primitive consisting inVtxSize vertices
void EnsurePrimitiveSpace(int inVtxSize);
Renderer * mRenderer;
/// Shaders for triangles
unique_ptr<PipelineState> mTriangleStateBF;
unique_ptr<PipelineState> mTriangleStateFF;
unique_ptr<PipelineState> mTriangleStateWire;
/// Shaders for shadow pass for triangles
unique_ptr<PipelineState> mShadowStateBF;
unique_ptr<PipelineState> mShadowStateFF;
unique_ptr<PipelineState> mShadowStateWire;
/// Lock that protects the triangle batches from being accessed from multiple threads
Mutex mPrimitivesLock;
Batch mEmptyBatch;
/// Properties for a single rendered instance
struct Instance
{
/// Constructor
Instance(Mat44Arg inModelMatrix, Mat44Arg inModelMatrixInvTrans, ColorArg inModelColor) : mModelMatrix(inModelMatrix), mModelMatrixInvTrans(inModelMatrixInvTrans), mModelColor(inModelColor) { }
Mat44 mModelMatrix;
Mat44 mModelMatrixInvTrans;
Color mModelColor;
};
/// Rendered instance with added information for lodding
struct InstanceWithLODInfo : public Instance
{
/// Constructor
InstanceWithLODInfo(Mat44Arg inModelMatrix, Mat44Arg inModelMatrixInvTrans, ColorArg inModelColor, const AABox &inWorldSpaceBounds, float inLODScaleSq) : Instance(inModelMatrix, inModelMatrixInvTrans, inModelColor), mWorldSpaceBounds(inWorldSpaceBounds), mLODScaleSq(inLODScaleSq) { }
/// Bounding box for culling
AABox mWorldSpaceBounds;
/// Square of scale factor for LODding (1 = original, > 1 = lod out further, < 1 = lod out earlier)
float mLODScaleSq;
};
/// Properties for a batch of instances that have the same primitive
struct Instances
{
Array<InstanceWithLODInfo> mInstances;
/// Start index in mInstancesBuffer for each of the LOD in the geometry pass. Length is one longer than the number of LODs to indicate how many instances the last lod has.
Array<int> mGeometryStartIdx;
/// Start index in mInstancesBuffer for each of the LOD in the light pass. Length is one longer than the number of LODs to indicate how many instances the last lod has.
Array<int> mLightStartIdx;
};
using InstanceMap = UnorderedMap<GeometryRef, Instances>;
/// Clear map of instances and make it ready for the next frame
void ClearMap(InstanceMap &ioInstances);
/// Helper function to draw instances
inline void DrawInstances(const Geometry *inGeometry, const Array<int> &inStartIdx);
/// List of primitives that are finished and ready for drawing
InstanceMap mWireframePrimitives;
InstanceMap mPrimitives;
InstanceMap mTempPrimitives;
InstanceMap mPrimitivesBackFacing;
int mNumInstances = 0;
Ref<RenderInstances> mInstancesBuffer[Renderer::cFrameCount];
/// Primitive that is being built + its properties
Ref<RenderPrimitive> mLockedPrimitive;
Vertex * mLockedVerticesStart = nullptr;
Vertex * mLockedVertices = nullptr;
Vertex * mLockedVerticesEnd = nullptr;
AABox mLockedPrimitiveBounds;
/// A single text string
struct Text
{
Text(Vec3Arg inPosition, const string_view &inText, ColorArg inColor, float inHeight) : mPosition(inPosition), mText(inText), mColor(inColor), mHeight(inHeight) { }
Vec3 mPosition;
String mText;
Color mColor;
float mHeight;
};
/// All text strings that are to be drawn on screen
Array<Text> mTexts;
Mutex mTextsLock;
/// Font with which to draw the texts
RefConst<Font> mFont;
/// A single line segment
struct Line
{
Float3 mFrom;
Color mFromColor;
Float3 mTo;
Color mToColor;
};
/// The list of line segments
Array<Line> mLines;
Mutex mLinesLock;
/// The shaders for the line segments
unique_ptr<PipelineState> mLineState;
};

View File

@@ -0,0 +1,349 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/Font.h>
#include <Renderer/Renderer.h>
#include <Image/Surface.h>
#include <Utils/ReadData.h>
#include <Jolt/Core/Profiler.h>
#include <Jolt/Core/ScopeExit.h>
JPH_SUPPRESS_WARNINGS_STD_BEGIN
JPH_CLANG_SUPPRESS_WARNING("-Wreserved-identifier")
JPH_CLANG_SUPPRESS_WARNING("-Wzero-as-null-pointer-constant")
JPH_CLANG_SUPPRESS_WARNING("-Wcast-qual")
JPH_CLANG_SUPPRESS_WARNING("-Wimplicit-fallthrough")
JPH_CLANG_SUPPRESS_WARNING("-Wcomma")
JPH_CLANG_SUPPRESS_WARNING("-Wdouble-promotion")
#define STB_TRUETYPE_IMPLEMENTATION
#include <External/stb_truetype.h>
JPH_SUPPRESS_WARNINGS_STD_END
Font::Font(Renderer *inRenderer) :
mRenderer(inRenderer)
{
}
bool Font::Create(const char *inFontName, int inCharHeight)
{
JPH_PROFILE("Create");
// Initialize
mFontName = inFontName;
mCharHeight = inCharHeight;
mHorizontalTexels = 64;
mVerticalTexels = 64;
constexpr int cSpacingH = 2; // Number of pixels to put horizontally between characters
constexpr int cSpacingV = 2; // Number of pixels to put vertically between characters
// Read font data
Array<uint8> font_data = ReadData((String("Fonts/") + inFontName + ".ttf").c_str());
// Construct a font info
stbtt_fontinfo font;
if (!stbtt_InitFont(&font, font_data.data(), stbtt_GetFontOffsetForIndex(font_data.data(), 0)))
return false;
// Get the base line for the font
float scale = stbtt_ScaleForPixelHeight(&font, float(mCharHeight));
int ascent;
stbtt_GetFontVMetrics(&font, &ascent, nullptr, nullptr);
int baseline = int(ascent * scale);
// Create surface for characters
Ref<SoftwareSurface> surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
surface->Clear();
surface->Lock(ESurfaceLockMode::Write);
// Draw all printable characters
try_again:;
int x = 0, y = 0;
static_assert(cBeginChar == ' ', "We skip space in the for loop below");
for (int c = cBeginChar + 1; c < cEndChar; ++c)
{
// Get index in the arrays
int idx = c - cBeginChar;
int w, h, xoff, yoff;
unsigned char *bitmap = stbtt_GetCodepointBitmap(&font, 0, scale, c, &w, &h, &xoff, &yoff);
JPH_SCOPE_EXIT([bitmap]{ STBTT_free(bitmap, nullptr); });
yoff = baseline + yoff;
// Check if there is room on this line
if (int(x + xoff + w + cSpacingH) > mHorizontalTexels)
{
// Next line
x = 0;
y += mCharHeight + cSpacingV;
// Check if character fits
if (y + mCharHeight + cSpacingV > mVerticalTexels)
{
// Character doesn't fit, enlarge surface
if (mHorizontalTexels < 2 * mVerticalTexels)
mHorizontalTexels <<= 1;
else
mVerticalTexels <<= 1;
// Create new surface
surface->UnLock();
surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
surface->Clear();
surface->Lock(ESurfaceLockMode::Write);
// Try again with the larger texture
goto try_again;
}
}
// Get location of character in font surface
JPH_ASSERT(x >= 0 && x <= 0xffff);
JPH_ASSERT(y >= 0 && y <= 0xffff);
JPH_ASSERT(w <= 0xff);
mStartU[idx] = uint16(x);
mStartV[idx] = uint16(y);
mWidth[idx] = uint8(w + 1);
// Copy the character data
for (int y2 = 0; y2 < h; ++y2)
{
uint8 *src = bitmap + y2 * w;
uint8 *dst = surface->GetScanLine(y + yoff + y2) + x + xoff;
memcpy(dst, src, w);
}
// Go to the next character
x += w + cSpacingH;
}
// Calculate spacing between characters
for (int idx1 = 0; idx1 < cNumChars; ++idx1)
for (int idx2 = 0; idx2 < cNumChars; ++idx2)
{
int c1 = cBeginChar + idx1;
int c2 = cBeginChar + idx2;
int advance;
stbtt_GetCodepointHMetrics(&font, c1, &advance, nullptr);
int spacing = Clamp(int(scale * (advance + stbtt_GetCodepointKernAdvance(&font, c1, c2))), 0, 0xff);
mSpacing[idx1][idx2] = (uint8)spacing;
}
// Unlock surface
surface->UnLock();
// Create input layout
const PipelineState::EInputDescription vertex_desc[] =
{
PipelineState::EInputDescription::Position,
PipelineState::EInputDescription::TexCoord,
PipelineState::EInputDescription::Color
};
// Load vertex shader
Ref<VertexShader> vtx = mRenderer->CreateVertexShader("FontVertexShader");
// Load pixel shader
Ref<PixelShader> pix = mRenderer->CreatePixelShader("FontPixelShader");
mPipelineState = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
// Create texture
mTexture = mRenderer->CreateTexture(surface);
// Trace success
Trace("Created font \"%s\" with height %d in a %dx%d surface", mFontName.c_str(), mCharHeight, mHorizontalTexels, mVerticalTexels);
return true;
}
Float2 Font::MeasureText(const string_view &inText) const
{
JPH_PROFILE("MeasureText");
Float2 extents(0, 1.0f);
// Current raster position
float x = 0;
// Loop through string
for (uint i = 0; i < inText.size(); ++i)
{
// Get character
int ch = inText[i];
// Create character if it is printable
static_assert(cBeginChar == ' ', "We skip space in the for loop below");
if (ch > cBeginChar && ch < cEndChar)
{
// Update extents
int c1 = ch - cBeginChar;
extents.x = max(extents.x, x + float(mWidth[c1]) / mCharHeight);
}
// Go to next (x, y) location
if (ch == '\n')
{
// Next line
x = 0;
extents.y += 1.0f;
}
else if (i + 1 < inText.size())
{
// Do spacing between the two characters
int c1 = ch - cBeginChar;
int c2 = inText[i + 1] - cBeginChar;
if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
x += float(mSpacing[c1][c2]) / mCharHeight;
}
}
return extents;
}
bool Font::CreateString(Mat44Arg inTransform, const string_view &inText, ColorArg inColor, RenderPrimitive &ioPrimitive) const
{
JPH_PROFILE("CreateString");
// Reset primitive
ioPrimitive.Clear();
// Count the number of printable chars
int printable = 0;
for (uint i = 0; i < inText.size(); ++i)
{
int ch = inText[i];
static_assert(cBeginChar == ' ', "We skip space in the for loop below");
if (ch > cBeginChar && ch < cEndChar) // Space is not printable
printable++;
}
if (printable == 0)
return false;
// Get correction factor for texture size
float texel_to_u = 1.0f / mHorizontalTexels;
float texel_to_v = 1.0f / mVerticalTexels;
int vtx_size = printable * 4;
int idx_size = printable * 6;
ioPrimitive.CreateVertexBuffer(vtx_size, sizeof(FontVertex));
ioPrimitive.CreateIndexBuffer(idx_size);
// Current vertex
uint32 vtx = 0;
// Lock buffers
FontVertex *font_vtx = (FontVertex *)ioPrimitive.LockVertexBuffer();
uint32 *idx_start = ioPrimitive.LockIndexBuffer();
uint32 *idx = idx_start;
// Current raster position
float x = 0, y = -1.0f;
// Loop through string
for (uint i = 0; i < inText.size(); ++i)
{
// Get character
int ch = inText[i];
// Create character if it is printable
static_assert(cBeginChar == ' ', "We skip space in the for loop below");
if (ch > cBeginChar && ch < cEndChar)
{
// Get index for character
int c1 = ch - cBeginChar;
// Create indices
*idx = vtx;
++idx;
*idx = vtx + 3;
++idx;
*idx = vtx + 1;
++idx;
*idx = vtx;
++idx;
*idx = vtx + 2;
++idx;
*idx = vtx + 3;
++idx;
vtx += 4;
// Get properties of this character
Float2 uv_start(texel_to_u * mStartU[c1], texel_to_v * mStartV[c1]);
Float2 uv_end(texel_to_u * (mStartU[c1] + mWidth[c1]), texel_to_v * (mStartV[c1] + mCharHeight));
Float2 xy_end(x + float(mWidth[c1]) / mCharHeight, y + 1.0f);
// Create vertices
(inTransform * Vec3(x, y, 0)).StoreFloat3(&font_vtx->mPosition);
font_vtx->mColor = inColor;
font_vtx->mTexCoord = Float2(uv_start.x, uv_end.y);
++font_vtx;
(inTransform * Vec3(x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
font_vtx->mColor = inColor;
font_vtx->mTexCoord = uv_start;
++font_vtx;
(inTransform * Vec3(xy_end.x, y, 0)).StoreFloat3(&font_vtx->mPosition);
font_vtx->mColor = inColor;
font_vtx->mTexCoord = uv_end;
++font_vtx;
(inTransform * Vec3(xy_end.x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
font_vtx->mColor = inColor;
font_vtx->mTexCoord = Float2(uv_end.x, uv_start.y);
++font_vtx;
}
// Go to next (x, y) location
if (ch == '\n')
{
// Next line
x = 0.0f;
y -= 1.0f;
}
else if (i + 1 < inText.size())
{
// Do spacing between the two characters
int c1 = ch - cBeginChar;
int c2 = inText[i + 1] - cBeginChar;
if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
x += float(mSpacing[c1][c2]) / mCharHeight;
}
}
// Check that we completely filled the output buffer
JPH_ASSERT(vtx == (uint32)vtx_size);
JPH_ASSERT(idx == idx_start + idx_size);
// Unlock buffers
ioPrimitive.UnlockVertexBuffer();
ioPrimitive.UnlockIndexBuffer();
return true;
}
void Font::DrawText3D(Mat44Arg inTransform, const string_view &inText, ColorArg inColor) const
{
JPH_PROFILE("DrawText3D");
// Early out
if (inText.empty())
return;
Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
if (CreateString(inTransform, inText, inColor, *primitive))
{
mTexture->Bind();
mPipelineState->Activate();
primitive->Draw();
}
}

View File

@@ -0,0 +1,70 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
#include <Jolt/Core/Color.h>
#include <Jolt/Math/Float2.h>
#include <Renderer/RenderPrimitive.h>
#include <Renderer/Texture.h>
#include <Renderer/PipelineState.h>
#include <memory>
class Renderer;
class Texture;
/// Font class, used to display text in 3D mode. Does variable width fonts with kerning. Font names are identical to the Windows font names.
class Font : public RefTarget<Font>
{
public:
/// Constants
static const int cBeginChar = ' '; ///< First character that is drawable in the character set
static const int cEndChar = 256; ///< Last character + 1 that is drawable in the character set
static const int cNumChars = cEndChar - cBeginChar; ///< Number of drawable characters in the character set
/// Constructor
Font(Renderer *inRenderer);
/// Create a font
bool Create(const char *inFontName, int inCharHeight);
/// Properties
const String & GetFontName() const { return mFontName; }
int GetCharHeight() const { return mCharHeight; }
/// Get extents of a string, assuming the height of the text is 1 and with the normal aspect ratio of the font
Float2 MeasureText(const string_view &inText) const;
/// Draw a string at a specific location
/// If the string is drawn with the identity matrix, it's top left will start at (0, 0, 0)
/// The text width is in the X direction and the text height is in the Y direction and it will have a height of 1
void DrawText3D(Mat44Arg inTransform, const string_view &inText, ColorArg inColor = Color::sWhite) const;
private:
/// Create a primitive for a string
bool CreateString(Mat44Arg inTransform, const string_view &inText, ColorArg inColor, RenderPrimitive &ioPrimitive) const;
struct FontVertex
{
Float3 mPosition;
Float2 mTexCoord;
Color mColor;
};
/// Properties of the font
String mFontName; ///< Name of the font
int mCharHeight; ///< Height of a character
int mHorizontalTexels; ///< Number of texels horizontally, determines the scale of mStartU, mWidth and mSpacing
int mVerticalTexels; ///< Number of texels vertically, determines the scale of mStartV
uint16 mStartU[cNumChars] = {}; ///< Start U in texels
uint16 mStartV[cNumChars] = {}; ///< Start V in texels
uint8 mWidth[cNumChars] = {}; ///< Width of character in texels
uint8 mSpacing[cNumChars][cNumChars] = {}; ///< Spacing between characters in texels
/// Structures used for drawing
Renderer * mRenderer; ///< Our renderer
Ref<Texture> mTexture; ///< The texture containing all characters
unique_ptr<PipelineState> mPipelineState; ///< The state used to render characters
};

View File

@@ -0,0 +1,57 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Geometry/Plane.h>
#include <Jolt/Geometry/AABox.h>
/// A camera frustum containing of 6 planes (left, right, top, bottom, near, far) pointing inwards
class [[nodiscard]] Frustum
{
public:
/// Empty constructor
Frustum() = default;
/// Construct frustum from position, forward, up, field of view x and y and near plane.
/// Note that inUp does not need to be perpendicular to inForward but cannot be collinear.
inline Frustum(Vec3Arg inPosition, Vec3Arg inForward, Vec3Arg inUp, float inFOVX, float inFOVY, float inNear)
{
Vec3 right = inForward.Cross(inUp).Normalized();
Vec3 up = right.Cross(inForward).Normalized(); // Calculate the real up vector (inUp does not need to be perpendicular to inForward)
// Near plane
mPlanes[0] = Plane::sFromPointAndNormal(inPosition + inNear * inForward, inForward);
// Top and bottom planes
mPlanes[1] = Plane::sFromPointAndNormal(inPosition, Mat44::sRotation(right, 0.5f * inFOVY) * -up);
mPlanes[2] = Plane::sFromPointAndNormal(inPosition, Mat44::sRotation(right, -0.5f * inFOVY) * up);
// Left and right planes
mPlanes[3] = Plane::sFromPointAndNormal(inPosition, Mat44::sRotation(up, 0.5f * inFOVX) * right);
mPlanes[4] = Plane::sFromPointAndNormal(inPosition, Mat44::sRotation(up, -0.5f * inFOVX) * -right);
}
/// Test if frustum overlaps with axis aligned box. Note that this is a conservative estimate and can return true if the
/// frustum doesn't actually overlap with the box. This is because we only test the plane axis as separating axis
/// and skip checking the cross products of the edges of the frustum
inline bool Overlaps(const AABox &inBox) const
{
// Loop over all frustum planes
for (const Plane &p : mPlanes)
{
// Get support point (the maximum extent) in the direction of our normal
Vec3 support = inBox.GetSupport(p.GetNormal());
// If this is behind our plane, the box is not inside the frustum
if (p.SignedDistance(support) < 0.0f)
return false;
}
return true;
}
private:
Plane mPlanes[5]; ///< Planes forming the frustum
};

View File

@@ -0,0 +1,11 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#import <MetalKit/MetalKit.h>
/// Convert Metal error to readable text and alert
void FatalErrorIfFailed(NSError *inResult);

View File

@@ -0,0 +1,14 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/MTL/FatalErrorIfFailedMTL.h>
#include <Utils/Log.h>
void FatalErrorIfFailed(NSError *inResult)
{
if (inResult != nullptr)
FatalError("Metal error returned: %s", [[inResult localizedDescription] cStringUsingEncoding: NSUTF8StringEncoding]);
}

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/PipelineState.h>
#include <Renderer/MTL/VertexShaderMTL.h>
#include <Renderer/MTL/PixelShaderMTL.h>
class RendererMTL;
/// Metal pipeline state object
class PipelineStateMTL : public PipelineState
{
public:
/// Constructor
PipelineStateMTL(RendererMTL *inRenderer, const VertexShaderMTL *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderMTL *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode);
virtual ~PipelineStateMTL() override;
/// Make this pipeline state active (any primitives rendered after this will use this state)
virtual void Activate() override;
private:
RendererMTL * mRenderer;
RefConst<VertexShaderMTL> mVertexShader;
RefConst<PixelShaderMTL> mPixelShader;
id<MTLRenderPipelineState> mPipelineState;
id<MTLDepthStencilState> mDepthState;
MTLCullMode mCullMode;
MTLTriangleFillMode mFillMode;
};

View File

@@ -0,0 +1,163 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/MTL/PipelineStateMTL.h>
#include <Renderer/MTL/RendererMTL.h>
#include <Renderer/MTL/FatalErrorIfFailedMTL.h>
PipelineStateMTL::PipelineStateMTL(RendererMTL *inRenderer, const VertexShaderMTL *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderMTL *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode) :
mRenderer(inRenderer),
mVertexShader(inVertexShader),
mPixelShader(inPixelShader)
{
// Create a vertex descriptor
MTLVertexDescriptor *vertex_descriptor = [[MTLVertexDescriptor alloc] init];
uint vertex_offset = 0;
uint instance_offset = 0, instance_alignment = 4;
uint index = 0;
for (uint i = 0; i < inInputDescriptionCount; ++i)
switch (inInputDescription[i])
{
case EInputDescription::Position:
case EInputDescription::Normal:
vertex_descriptor.attributes[index].format = MTLVertexFormatFloat3;
vertex_descriptor.attributes[index].offset = vertex_offset;
vertex_descriptor.attributes[index].bufferIndex = 0;
vertex_offset += 3 * sizeof(float);
++index;
break;
case EInputDescription::Color:
vertex_descriptor.attributes[index].format = MTLVertexFormatUChar4;
vertex_descriptor.attributes[index].offset = vertex_offset;
vertex_descriptor.attributes[index].bufferIndex = 0;
vertex_offset += 4 * sizeof(uint8);
++index;
break;
case EInputDescription::TexCoord:
vertex_descriptor.attributes[index].format = MTLVertexFormatFloat2;
vertex_descriptor.attributes[index].offset = vertex_offset;
vertex_descriptor.attributes[index].bufferIndex = 0;
vertex_offset += 2 * sizeof(float);
++index;
break;
case EInputDescription::InstanceColor:
vertex_descriptor.attributes[index].format = MTLVertexFormatUChar4;
vertex_descriptor.attributes[index].offset = instance_offset;
vertex_descriptor.attributes[index].bufferIndex = 1;
instance_offset += 4 * sizeof(uint8);
++index;
break;
case EInputDescription::InstanceTransform:
case EInputDescription::InstanceInvTransform:
instance_alignment = max(instance_alignment, 16u);
instance_offset = AlignUp(instance_offset, 16u);
for (int j = 0; j < 4; ++j)
{
vertex_descriptor.attributes[index].format = MTLVertexFormatFloat4;
vertex_descriptor.attributes[index].offset = instance_offset;
vertex_descriptor.attributes[index].bufferIndex = 1;
instance_offset += 4 * sizeof(float);
++index;
}
break;
}
// Configure layouts
vertex_descriptor.layouts[0].stride = vertex_offset;
vertex_descriptor.layouts[0].stepRate = 1;
vertex_descriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
if (instance_offset > 0)
{
vertex_descriptor.layouts[1].stride = AlignUp(instance_offset, instance_alignment);
vertex_descriptor.layouts[1].stepRate = 1;
vertex_descriptor.layouts[1].stepFunction = MTLVertexStepFunctionPerInstance;
}
// Create the pipeline descriptor
MTLRenderPipelineDescriptor *descriptor = [[MTLRenderPipelineDescriptor alloc] init];
descriptor.vertexFunction = inVertexShader->GetFunction();
descriptor.fragmentFunction = inPixelShader->GetFunction();
descriptor.vertexDescriptor = vertex_descriptor;
switch (inDrawPass)
{
case EDrawPass::Shadow:
descriptor.depthAttachmentPixelFormat = static_cast<TextureMTL *>(mRenderer->GetShadowMap())->GetTexture().pixelFormat;
break;
case EDrawPass::Normal:
descriptor.colorAttachments[0].pixelFormat = mRenderer->GetView().colorPixelFormat;
switch (inBlendMode)
{
case EBlendMode::Write:
descriptor.colorAttachments[0].blendingEnabled = NO;
break;
case EBlendMode::AlphaBlend:
descriptor.colorAttachments[0].blendingEnabled = YES;
descriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
descriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorZero;
descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorZero;
descriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
break;
}
descriptor.depthAttachmentPixelFormat = mRenderer->GetView().depthStencilPixelFormat;
}
NSError *error = nullptr;
mPipelineState = [mRenderer->GetDevice() newRenderPipelineStateWithDescriptor: descriptor error: &error];
FatalErrorIfFailed(error);
[descriptor release];
[vertex_descriptor release];
// Create depth descriptor
MTLDepthStencilDescriptor *depth_descriptor = [[MTLDepthStencilDescriptor new] init];
if (inDepthTest == EDepthTest::On)
{
depth_descriptor.depthCompareFunction = MTLCompareFunctionGreaterEqual;
depth_descriptor.depthWriteEnabled = YES;
}
else
{
depth_descriptor.depthCompareFunction = MTLCompareFunctionAlways;
depth_descriptor.depthWriteEnabled = NO;
}
mDepthState = [mRenderer->GetDevice() newDepthStencilStateWithDescriptor: depth_descriptor];
[depth_descriptor release];
// Determine cull mode
if (inCullMode == ECullMode::FrontFace)
mCullMode = MTLCullModeFront;
else
mCullMode = MTLCullModeBack;
// Determine fill mode
if (inFillMode == EFillMode::Solid)
mFillMode = MTLTriangleFillModeFill;
else
mFillMode = MTLTriangleFillModeLines;
}
PipelineStateMTL::~PipelineStateMTL()
{
[mPipelineState release];
[mDepthState release];
}
void PipelineStateMTL::Activate()
{
id<MTLRenderCommandEncoder> encoder = mRenderer->GetRenderEncoder();
[encoder setRenderPipelineState: mPipelineState];
[encoder setDepthStencilState: mDepthState];
[encoder setCullMode: mCullMode];
[encoder setTriangleFillMode: mFillMode];
}

View File

@@ -0,0 +1,24 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/PixelShader.h>
#include <MetalKit/MetalKit.h>
/// Pixel shader handle for Metal
class PixelShaderMTL : public PixelShader
{
public:
/// Constructor
PixelShaderMTL(id<MTLFunction> inFunction) : mFunction(inFunction) { }
virtual ~PixelShaderMTL() override { [mFunction release]; }
/// Access to the function
id<MTLFunction> GetFunction() const { return mFunction; }
private:
id<MTLFunction> mFunction;
};

View File

@@ -0,0 +1,36 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/MTL/RendererMTL.h>
#include <Renderer/RenderInstances.h>
class RenderPrimitive;
/// Metal implementation of a render instances object
class RenderInstancesMTL : public RenderInstances
{
public:
/// Constructor
RenderInstancesMTL(RendererMTL *inRenderer) : mRenderer(inRenderer) { }
virtual ~RenderInstancesMTL() override { Clear(); }
/// Erase all instance data
virtual void Clear() override;
/// Instance buffer management functions
virtual void CreateBuffer(int inNumInstances, int inInstanceSize) override;
virtual void * Lock() override;
virtual void Unlock() override;
/// Draw the instances when context has been set by Renderer::BindShader
virtual void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const override;
private:
RendererMTL * mRenderer;
id<MTLBuffer> mBuffer;
NSUInteger mBufferSize;
NSUInteger mInstanceSize;
};

View File

@@ -0,0 +1,52 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/MTL/RenderInstancesMTL.h>
#include <Renderer/MTL/RenderPrimitiveMTL.h>
void RenderInstancesMTL::Clear()
{
[mBuffer release];
mBuffer = nil;
}
void RenderInstancesMTL::CreateBuffer(int inNumInstances, int inInstanceSize)
{
mInstanceSize = NSUInteger(inInstanceSize);
NSUInteger size = mInstanceSize * inNumInstances;
if (mBuffer == nullptr || mBufferSize < size)
{
Clear();
mBuffer = [mRenderer->GetView().device newBufferWithLength: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
mBufferSize = size;
}
}
void *RenderInstancesMTL::Lock()
{
return mBuffer.contents;
}
void RenderInstancesMTL::Unlock()
{
}
void RenderInstancesMTL::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const
{
if (inNumInstances <= 0)
return;
id<MTLRenderCommandEncoder> encoder = mRenderer->GetRenderEncoder();
RenderPrimitiveMTL *prim = static_cast<RenderPrimitiveMTL *>(inPrimitive);
[encoder setVertexBuffer: prim->mVertexBuffer offset: 0 atIndex: 0];
[encoder setVertexBuffer: mBuffer offset: mInstanceSize * inStartInstance atIndex: 1];
if (prim->mIndexBuffer == nil)
[encoder drawPrimitives: prim->mPrimitiveType vertexStart: 0 vertexCount: prim->mNumVtxToDraw instanceCount: inNumInstances];
else
[encoder drawIndexedPrimitives: prim->mPrimitiveType indexCount: prim->mNumIdxToDraw indexType: MTLIndexTypeUInt32 indexBuffer: prim->mIndexBuffer indexBufferOffset: 0 instanceCount: inNumInstances];
}

View File

@@ -0,0 +1,40 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/RenderPrimitive.h>
#include <Renderer/MTL/RendererMTL.h>
/// Metal implementation of a render primitive
class RenderPrimitiveMTL : public RenderPrimitive
{
public:
/// Constructor
RenderPrimitiveMTL(RendererMTL *inRenderer, MTLPrimitiveType inType) : mRenderer(inRenderer), mPrimitiveType(inType) { }
virtual ~RenderPrimitiveMTL() override { Clear(); }
/// Vertex buffer management functions
virtual void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr) override;
virtual void ReleaseVertexBuffer() override;
virtual void * LockVertexBuffer() override;
virtual void UnlockVertexBuffer() override;
/// Index buffer management functions
virtual void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr) override;
virtual void ReleaseIndexBuffer() override;
virtual uint32 * LockIndexBuffer() override;
virtual void UnlockIndexBuffer() override;
/// Draw the primitive
virtual void Draw() const override;
private:
friend class RenderInstancesMTL;
RendererMTL * mRenderer;
MTLPrimitiveType mPrimitiveType;
id<MTLBuffer> mVertexBuffer;
id<MTLBuffer> mIndexBuffer;
};

View File

@@ -0,0 +1,74 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/MTL/RenderPrimitiveMTL.h>
void RenderPrimitiveMTL::ReleaseVertexBuffer()
{
[mVertexBuffer release];
mVertexBuffer = nil;
RenderPrimitive::ReleaseVertexBuffer();
}
void RenderPrimitiveMTL::ReleaseIndexBuffer()
{
[mIndexBuffer release];
mIndexBuffer = nil;
RenderPrimitive::ReleaseIndexBuffer();
}
void RenderPrimitiveMTL::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData)
{
RenderPrimitive::CreateVertexBuffer(inNumVtx, inVtxSize, inData);
NSUInteger size = NSUInteger(inNumVtx) * inVtxSize;
if (inData != nullptr)
mVertexBuffer = [mRenderer->GetDevice() newBufferWithBytes: inData length: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
else
mVertexBuffer = [mRenderer->GetDevice() newBufferWithLength: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
}
void *RenderPrimitiveMTL::LockVertexBuffer()
{
return mVertexBuffer.contents;
}
void RenderPrimitiveMTL::UnlockVertexBuffer()
{
}
void RenderPrimitiveMTL::CreateIndexBuffer(int inNumIdx, const uint32 *inData)
{
RenderPrimitive::CreateIndexBuffer(inNumIdx, inData);
NSUInteger size = NSUInteger(inNumIdx) * sizeof(uint32);
if (inData != nullptr)
mIndexBuffer = [mRenderer->GetDevice() newBufferWithBytes: inData length: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged | MTLResourceHazardTrackingModeTracked];
else
mIndexBuffer = [mRenderer->GetDevice() newBufferWithLength: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
}
uint32 *RenderPrimitiveMTL::LockIndexBuffer()
{
return (uint32 *)mIndexBuffer.contents;
}
void RenderPrimitiveMTL::UnlockIndexBuffer()
{
}
void RenderPrimitiveMTL::Draw() const
{
id<MTLRenderCommandEncoder> encoder = mRenderer->GetRenderEncoder();
[encoder setVertexBuffer: mVertexBuffer offset: 0 atIndex: 0];
if (mIndexBuffer == nil)
[encoder drawPrimitives: mPrimitiveType vertexStart: 0 vertexCount: mNumVtxToDraw];
else
[encoder drawIndexedPrimitives: mPrimitiveType indexCount: mNumIdxToDraw indexType: MTLIndexTypeUInt32 indexBuffer: mIndexBuffer indexBufferOffset: 0];
}

View File

@@ -0,0 +1,46 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/Renderer.h>
#include <Renderer/MTL/TextureMTL.h>
#include <MetalKit/MetalKit.h>
/// Metal renderer
class RendererMTL : public Renderer
{
public:
virtual ~RendererMTL() override;
// See: Renderer
virtual void Initialize(ApplicationWindow *inWindow) override;
virtual bool BeginFrame(const CameraState &inCamera, float inWorldScale) override;
virtual void EndShadowPass() override;
virtual void EndFrame() override;
virtual void SetProjectionMode() override;
virtual void SetOrthoMode() override;
virtual Ref<Texture> CreateTexture(const Surface *inSurface) override;
virtual Ref<VertexShader> CreateVertexShader(const char *inName) override;
virtual Ref<PixelShader> CreatePixelShader(const char *inName) override;
virtual unique_ptr<PipelineState> CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) override;
virtual RenderPrimitive * CreateRenderPrimitive(PipelineState::ETopology inType) override;
virtual RenderInstances * CreateRenderInstances() override;
virtual Texture * GetShadowMap() const override { return mShadowMap; }
virtual void OnWindowResize() override { }
MTKView * GetView() const { return mView; }
id<MTLDevice> GetDevice() const { return mView.device; }
id<MTLRenderCommandEncoder> GetRenderEncoder() const { return mRenderEncoder; }
private:
MTKView * mView;
MTLRenderPassDescriptor * mShadowRenderPass;
Ref<TextureMTL> mShadowMap;
id<MTLLibrary> mShaderLibrary;
id<MTLCommandQueue> mCommandQueue;
id<MTLCommandBuffer> mCommandBuffer;
id<MTLRenderCommandEncoder> mRenderEncoder;
};

View File

@@ -0,0 +1,184 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/MTL/RendererMTL.h>
#include <Renderer/MTL/RenderPrimitiveMTL.h>
#include <Renderer/MTL/RenderInstancesMTL.h>
#include <Renderer/MTL/PipelineStateMTL.h>
#include <Renderer/MTL/VertexShaderMTL.h>
#include <Renderer/MTL/PixelShaderMTL.h>
#include <Renderer/MTL/TextureMTL.h>
#include <Renderer/MTL/FatalErrorIfFailedMTL.h>
#include <Window/ApplicationWindowMacOS.h>
#include <Utils/Log.h>
#include <Utils/AssetStream.h>
#include <Jolt/Core/Profiler.h>
RendererMTL::~RendererMTL()
{
[mCommandQueue release];
[mShadowRenderPass release];
[mShaderLibrary release];
}
void RendererMTL::Initialize(ApplicationWindow *inWindow)
{
Renderer::Initialize(inWindow);
mView = static_cast<ApplicationWindowMacOS *>(inWindow)->GetMetalView();
id<MTLDevice> device = GetDevice();
// Load the shader library containing all shaders for the test framework
NSError *error = nullptr;
NSURL *url = [NSURL URLWithString: [NSString stringWithCString: (AssetStream::sGetAssetsBasePath() + "Shaders/MTL/Shaders.metallib").c_str() encoding: NSUTF8StringEncoding]];
mShaderLibrary = [device newLibraryWithURL: url error: &error];
FatalErrorIfFailed(error);
// Create depth only texture (no color buffer, as seen from light)
mShadowMap = new TextureMTL(this, cShadowMapSize, cShadowMapSize);
// Create render pass descriptor for shadow pass
mShadowRenderPass = [[MTLRenderPassDescriptor alloc] init];
mShadowRenderPass.depthAttachment.texture = mShadowMap->GetTexture();
mShadowRenderPass.depthAttachment.loadAction = MTLLoadActionClear;
mShadowRenderPass.depthAttachment.storeAction = MTLStoreActionStore;
mShadowRenderPass.depthAttachment.clearDepth = 0.0f;
// Create the command queue
mCommandQueue = [device newCommandQueue];
}
bool RendererMTL::BeginFrame(const CameraState &inCamera, float inWorldScale)
{
JPH_PROFILE_FUNCTION();
Renderer::BeginFrame(inCamera, inWorldScale);
// Update frame index
mFrameIndex = (mFrameIndex + 1) % cFrameCount;
// Create a new command buffer
mCommandBuffer = [mCommandQueue commandBuffer];
// Create shadow render encoder
mRenderEncoder = [mCommandBuffer renderCommandEncoderWithDescriptor: mShadowRenderPass];
// Set viewport to size of shadow map
[mRenderEncoder setViewport: (MTLViewport){ 0.0, 0.0, double(cShadowMapSize), double(cShadowMapSize), 0.0, 1.0 }];
// Set pixel shader constants
[mRenderEncoder setFragmentBytes: &mPSBuffer length: sizeof(mPSBuffer) atIndex: 0];
// Counter clockwise is default winding order
[mRenderEncoder setFrontFacingWinding: MTLWindingCounterClockwise];
// Start with projection mode
SetProjectionMode();
return true;
}
void RendererMTL::EndShadowPass()
{
// Finish the shadow encoder
[mRenderEncoder endEncoding];
mRenderEncoder = nil;
// Get the descriptor for the main window
MTLRenderPassDescriptor *render_pass_descriptor = mView.currentRenderPassDescriptor;
if (render_pass_descriptor == nullptr)
return;
// Create render encoder
mRenderEncoder = [mCommandBuffer renderCommandEncoderWithDescriptor: render_pass_descriptor];
// Set viewport
[mRenderEncoder setViewport: (MTLViewport){ 0.0, 0.0, double(mWindow->GetWindowWidth()), double(mWindow->GetWindowHeight()), 0.0, 1.0 }];
// Set pixel shader constants
[mRenderEncoder setFragmentBytes: &mPSBuffer length: sizeof(mPSBuffer) atIndex: 0];
// Counter clockwise is default winding order
[mRenderEncoder setFrontFacingWinding: MTLWindingCounterClockwise];
// Start with projection mode
SetProjectionMode();
}
void RendererMTL::EndFrame()
{
JPH_PROFILE_FUNCTION();
// Finish the encoder
[mRenderEncoder endEncoding];
mRenderEncoder = nil;
// Schedule a present
[mCommandBuffer presentDrawable: mView.currentDrawable];
// Commit the command buffer
[mCommandBuffer commit];
Renderer::EndFrame();
}
void RendererMTL::SetProjectionMode()
{
JPH_ASSERT(mInFrame);
[mRenderEncoder setVertexBytes: &mVSBuffer length: sizeof(mVSBuffer) atIndex: 2];
}
void RendererMTL::SetOrthoMode()
{
JPH_ASSERT(mInFrame);
[mRenderEncoder setVertexBytes: &mVSBufferOrtho length: sizeof(mVSBufferOrtho) atIndex: 2];
}
Ref<Texture> RendererMTL::CreateTexture(const Surface *inSurface)
{
return new TextureMTL(this, inSurface);
}
Ref<VertexShader> RendererMTL::CreateVertexShader(const char *inName)
{
id<MTLFunction> function = [mShaderLibrary newFunctionWithName: [NSString stringWithCString: inName encoding: NSUTF8StringEncoding]];
if (function == nil)
FatalError("Vertex shader %s not found", inName);
return new VertexShaderMTL(function);
}
Ref<PixelShader> RendererMTL::CreatePixelShader(const char *inName)
{
id<MTLFunction> function = [mShaderLibrary newFunctionWithName: [NSString stringWithCString: inName encoding: NSUTF8StringEncoding]];
if (function == nil)
FatalError("Pixel shader %s not found", inName);
return new PixelShaderMTL(function);
}
unique_ptr<PipelineState> RendererMTL::CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode)
{
return make_unique<PipelineStateMTL>(this, static_cast<const VertexShaderMTL *>(inVertexShader), inInputDescription, inInputDescriptionCount, static_cast<const PixelShaderMTL *>(inPixelShader), inDrawPass, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode);
}
RenderPrimitive *RendererMTL::CreateRenderPrimitive(PipelineState::ETopology inType)
{
return new RenderPrimitiveMTL(this, inType == PipelineState::ETopology::Line? MTLPrimitiveTypeLine : MTLPrimitiveTypeTriangle);
}
RenderInstances *RendererMTL::CreateRenderInstances()
{
return new RenderInstancesMTL(this);
}
#ifndef JPH_ENABLE_VULKAN
Renderer *Renderer::sCreate()
{
return new RendererMTL;
}
#endif

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/Texture.h>
#include <MetalKit/MetalKit.h>
class RendererMTL;
/// Metal texture
class TextureMTL : public Texture
{
public:
/// Constructor, called by Renderer::CreateTextureMTL
TextureMTL(RendererMTL *inRenderer, const Surface *inSurface); // Create a normal Texture
TextureMTL(RendererMTL *inRenderer, int inWidth, int inHeight); // Create a render target (depth only)
virtual ~TextureMTL() override;
/// Bind texture to the pixel shader
virtual void Bind() const override;
/// Access to the metal texture
id<MTLTexture> GetTexture() const { return mTexture; }
private:
RendererMTL * mRenderer;
id<MTLTexture> mTexture;
};

View File

@@ -0,0 +1,98 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/MTL/TextureMTL.h>
#include <Renderer/MTL/RendererMTL.h>
#include <Renderer/MTL/FatalErrorIfFailedMTL.h>
#include <Image/BlitSurface.h>
TextureMTL::TextureMTL(RendererMTL *inRenderer, const Surface *inSurface) :
Texture(inSurface->GetWidth(), inSurface->GetHeight()),
mRenderer(inRenderer)
{
ESurfaceFormat format = inSurface->GetFormat();
MTLPixelFormat mt_format = MTLPixelFormatBGRA8Unorm;
switch (format)
{
case ESurfaceFormat::A4L4:
case ESurfaceFormat::A8L8:
case ESurfaceFormat::A4R4G4B4:
case ESurfaceFormat::R8G8B8:
case ESurfaceFormat::B8G8R8:
case ESurfaceFormat::X8R8G8B8:
case ESurfaceFormat::X8B8G8R8:
case ESurfaceFormat::A8R8G8B8:
case ESurfaceFormat::A8B8G8R8: mt_format = MTLPixelFormatBGRA8Unorm; format = ESurfaceFormat::A8R8G8B8; break;
case ESurfaceFormat::L8: mt_format = MTLPixelFormatR8Unorm; break;
case ESurfaceFormat::A8: mt_format = MTLPixelFormatA8Unorm; break;
case ESurfaceFormat::R5G6B5:
case ESurfaceFormat::X1R5G5B5:
case ESurfaceFormat::X4R4G4B4: mt_format = MTLPixelFormatB5G6R5Unorm; format = ESurfaceFormat::R5G6B5; break;
case ESurfaceFormat::A1R5G5B5: mt_format = MTLPixelFormatA1BGR5Unorm; break;
case ESurfaceFormat::Invalid:
default: JPH_ASSERT(false); break;
}
// Blit the surface to another temporary surface if the format changed
const Surface *surface = inSurface;
Ref<Surface> tmp;
if (format != inSurface->GetFormat())
{
tmp = new SoftwareSurface(mWidth, mHeight, format);
BlitSurface(inSurface, tmp);
surface = tmp;
}
// Create descriptor
MTLTextureDescriptor *descriptor = [[MTLTextureDescriptor alloc] init];
descriptor.textureType = MTLTextureType2D;
descriptor.usage = MTLTextureUsageShaderRead;
descriptor.pixelFormat = mt_format;
descriptor.width = mWidth;
descriptor.height = mHeight;
descriptor.storageMode = MTLStorageModeManaged;
MTLRegion region =
{
{ 0, 0, 0 },
{ NSUInteger(mWidth), NSUInteger(mHeight), 1}
};
// Create texture
mTexture = [inRenderer->GetDevice() newTextureWithDescriptor: descriptor];
surface->Lock(ESurfaceLockMode::Read);
[mTexture replaceRegion: region mipmapLevel:0 withBytes: surface->GetData() bytesPerRow: surface->GetStride()];
surface->UnLock();
[descriptor release];
}
TextureMTL::TextureMTL(RendererMTL *inRenderer, int inWidth, int inHeight) :
Texture(inWidth, inHeight),
mRenderer(inRenderer)
{
MTLTextureDescriptor *descriptor = [[MTLTextureDescriptor alloc] init];
descriptor.textureType = MTLTextureType2D;
descriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
descriptor.pixelFormat = MTLPixelFormatDepth32Float;
descriptor.width = mWidth;
descriptor.height = mHeight;
descriptor.storageMode = MTLStorageModePrivate;
mTexture = [inRenderer->GetDevice() newTextureWithDescriptor: descriptor];
[descriptor release];
}
TextureMTL::~TextureMTL()
{
[mTexture release];
}
void TextureMTL::Bind() const
{
[mRenderer->GetRenderEncoder() setFragmentTexture: mTexture atIndex: 0];
}

View File

@@ -0,0 +1,24 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/VertexShader.h>
#include <MetalKit/MetalKit.h>
/// Vertex shader handle for Metal
class VertexShaderMTL : public VertexShader
{
public:
/// Constructor
VertexShaderMTL(id<MTLFunction> inFunction) : mFunction(inFunction) { }
virtual ~VertexShaderMTL() override { [mFunction release]; }
/// Access to the function
id<MTLFunction> GetFunction() const { return mFunction; }
private:
id<MTLFunction> mFunction;
};

View File

@@ -0,0 +1,70 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
/// Defines how primitives should be rendered
class PipelineState
{
public:
/// Describes the input layout of the vertex shader
enum class EInputDescription
{
Position, ///< 3 float position
Color, ///< 4 uint8 color
Normal, ///< 3 float normal
TexCoord, ///< 2 float texture coordinate
InstanceColor, ///< 4 uint8 per instance color
InstanceTransform, ///< 4x4 float per instance transform
InstanceInvTransform, ///< 4x4 float per instance inverse transform
};
/// In which draw pass to use this pipeline state
enum class EDrawPass
{
Shadow,
Normal
};
/// The type of topology to emit
enum class ETopology
{
Triangle,
Line
};
/// Fill mode of the triangles
enum class EFillMode
{
Solid,
Wireframe
};
/// If depth write / depth test is on
enum class EDepthTest
{
Off,
On
};
/// How to blend the pixel from the shader in the back buffer
enum class EBlendMode
{
Write,
AlphaBlend,
};
/// How to cull triangles
enum class ECullMode
{
Backface,
FrontFace,
};
/// Destructor
virtual ~PipelineState() = default;
/// Make this pipeline state active (any primitives rendered after this will use this state)
virtual void Activate() = 0;
};

View File

@@ -0,0 +1,15 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
/// Pixel shader handle
class PixelShader : public RefTarget<PixelShader>
{
public:
/// Destructor
virtual ~PixelShader() = default;
};

View File

@@ -0,0 +1,28 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
class RenderPrimitive;
/// Buffer that holds a list of instances (usually model transform etc.) for instance based rendering
class RenderInstances : public RefTarget<RenderInstances>
{
public:
/// Destructor
virtual ~RenderInstances() = default;
/// Erase all instance data
virtual void Clear() = 0;
/// Instance buffer management functions
virtual void CreateBuffer(int inNumInstances, int inInstanceSize) = 0;
virtual void * Lock() = 0;
virtual void Unlock() = 0;
/// Draw the instances when context has been set by Renderer::BindShader
virtual void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const = 0;
};

View File

@@ -0,0 +1,43 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/RenderPrimitive.h>
void RenderPrimitive::ReleaseVertexBuffer()
{
mNumVtx = 0;
mNumVtxToDraw = 0;
mVtxSize = 0;
}
void RenderPrimitive::ReleaseIndexBuffer()
{
mNumIdx = 0;
mNumIdxToDraw = 0;
}
void RenderPrimitive::Clear()
{
ReleaseVertexBuffer();
ReleaseIndexBuffer();
}
void RenderPrimitive::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData)
{
ReleaseVertexBuffer();
mNumVtx = inNumVtx;
mNumVtxToDraw = inNumVtx;
mVtxSize = inVtxSize;
}
void RenderPrimitive::CreateIndexBuffer(int inNumIdx, const uint32 *inData)
{
ReleaseIndexBuffer();
mNumIdx = inNumIdx;
mNumIdxToDraw = inNumIdx;
}

View File

@@ -0,0 +1,54 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
/// Simple wrapper around vertex and index buffers
class RenderPrimitive : public RefTarget<RenderPrimitive>, public RefTargetVirtual
{
public:
/// Destructor
virtual ~RenderPrimitive() override = default;
/// Erase all primitive data
void Clear();
/// Check if this primitive contains any data
bool IsEmpty() const { return mNumVtx == 0 && mNumIdx == 0; }
/// Vertex buffer management functions
virtual void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr) = 0;
virtual void ReleaseVertexBuffer();
virtual void * LockVertexBuffer() = 0;
virtual void UnlockVertexBuffer() = 0;
int GetNumVtx() const { return mNumVtx; }
int GetNumVtxToDraw() const { return mNumVtxToDraw; }
void SetNumVtxToDraw(int inUsed) { mNumVtxToDraw = inUsed; }
/// Index buffer management functions
virtual void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr) = 0;
virtual void ReleaseIndexBuffer();
virtual uint32 * LockIndexBuffer() = 0;
virtual void UnlockIndexBuffer() = 0;
int GetNumIdx() const { return mNumIdx; }
int GetNumIdxToDraw() const { return mNumIdxToDraw; }
void SetNumIdxToDraw(int inUsed) { mNumIdxToDraw = inUsed; }
/// Draw the primitive
virtual void Draw() const = 0;
/// Implement RefTargetVirtual, so we can conveniently use this class as DebugRenderer::Batch
virtual void AddRef() override { RefTarget<RenderPrimitive>::AddRef(); }
virtual void Release() override { RefTarget<RenderPrimitive>::Release(); }
protected:
int mNumVtx = 0;
int mNumVtxToDraw = 0;
int mVtxSize = 0;
int mNumIdx = 0;
int mNumIdxToDraw = 0;
};

View File

@@ -0,0 +1,89 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/Renderer.h>
Renderer::~Renderer()
{
if (mWindow != nullptr)
mWindow->SetWindowResizeListener({});
}
void Renderer::Initialize(ApplicationWindow *inWindow)
{
// Store window
mWindow = inWindow;
mWindow->SetWindowResizeListener([this]() { OnWindowResize(); });
}
static Mat44 sPerspectiveInfiniteReverseZ(float inFovY, float inAspect, float inNear, float inYSign)
{
float height = 1.0f / Tan(0.5f * inFovY);
float width = height / inAspect;
return Mat44(Vec4(width, 0.0f, 0.0f, 0.0f), Vec4(0.0f, inYSign * height, 0.0f, 0.0f), Vec4(0.0f, 0.0f, 0.0f, -1.0f), Vec4(0.0f, 0.0f, inNear, 0.0f));
}
bool Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale)
{
// Mark that we're in the frame
JPH_ASSERT(!mInFrame);
mInFrame = true;
// Store state
mCameraState = inCamera;
// Light properties
Vec3 light_pos = inWorldScale * Vec3(250, 250, 250);
Vec3 light_tgt = Vec3::sZero();
Vec3 light_up = Vec3(0, 1, 0);
Vec3 light_fwd = (light_tgt - light_pos).Normalized();
float light_fov = DegreesToRadians(20.0f);
float light_near = 1.0f;
// Camera properties
Vec3 cam_pos = Vec3(inCamera.mPos - mBaseOffset);
float camera_fovy = inCamera.mFOVY;
float camera_aspect = static_cast<float>(mWindow->GetWindowWidth()) / mWindow->GetWindowHeight();
float camera_fovx = 2.0f * ATan(camera_aspect * Tan(0.5f * camera_fovy));
float camera_near = 0.01f * inWorldScale;
// Calculate camera frustum
mCameraFrustum = Frustum(cam_pos, inCamera.mForward, inCamera.mUp, camera_fovx, camera_fovy, camera_near);
// Calculate light frustum
mLightFrustum = Frustum(light_pos, light_fwd, light_up, light_fov, light_fov, light_near);
// Camera projection and view
mVSBuffer.mProjection = sPerspectiveInfiniteReverseZ(camera_fovy, camera_aspect, camera_near, mPerspectiveYSign);
Vec3 tgt = cam_pos + inCamera.mForward;
mVSBuffer.mView = Mat44::sLookAt(cam_pos, tgt, inCamera.mUp);
// Light projection and view
mVSBuffer.mLightProjection = sPerspectiveInfiniteReverseZ(light_fov, 1.0f, light_near, mPerspectiveYSign);
mVSBuffer.mLightView = Mat44::sLookAt(light_pos, light_tgt, light_up);
// Camera ortho projection and view
mVSBufferOrtho.mProjection = Mat44(Vec4(2.0f / mWindow->GetWindowWidth(), 0.0f, 0.0f, 0.0f), Vec4(0.0f, -mPerspectiveYSign * 2.0f / mWindow->GetWindowHeight(), 0.0f, 0.0f), Vec4(0.0f, 0.0f, -1.0f, 0.0f), Vec4(-1.0f, mPerspectiveYSign * 1.0f, 0.0f, 1.0f));
mVSBufferOrtho.mView = Mat44::sIdentity();
// Light projection and view are unused in ortho mode
mVSBufferOrtho.mLightView = Mat44::sIdentity();
mVSBufferOrtho.mLightProjection = Mat44::sIdentity();
// Set constants for pixel shader
mPSBuffer.mCameraPos = Vec4(cam_pos, 0);
mPSBuffer.mLightPos = Vec4(light_pos, 0);
return true;
}
void Renderer::EndFrame()
{
// Mark that we're no longer in the frame
JPH_ASSERT(mInFrame);
mInFrame = false;
}

View File

@@ -0,0 +1,126 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Image/Surface.h>
#include <Window/ApplicationWindow.h>
#include <Renderer/Frustum.h>
#include <Renderer/PipelineState.h>
#include <Renderer/VertexShader.h>
#include <Renderer/PixelShader.h>
#include <Renderer/RenderPrimitive.h>
#include <Renderer/RenderInstances.h>
#include <memory>
// Forward declares
class Texture;
/// Camera setup
struct CameraState
{
CameraState() : mPos(RVec3::sZero()), mForward(0, 0, -1), mUp(0, 1, 0), mFOVY(DegreesToRadians(70.0f)) { }
RVec3 mPos; ///< Camera position
Vec3 mForward; ///< Camera forward vector
Vec3 mUp; ///< Camera up vector
float mFOVY; ///< Field of view in radians in up direction
};
/// Responsible for rendering primitives to the screen
class Renderer
{
public:
/// Destructor
virtual ~Renderer();
/// Initialize renderer
virtual void Initialize(ApplicationWindow *inWindow);
/// Start / end drawing a frame
virtual bool BeginFrame(const CameraState &inCamera, float inWorldScale);
virtual void EndShadowPass() = 0;
virtual void EndFrame();
/// Switch between orthographic and 3D projection mode
virtual void SetProjectionMode() = 0;
virtual void SetOrthoMode() = 0;
/// Create texture from an image surface
virtual Ref<Texture> CreateTexture(const Surface *inSurface) = 0;
/// Compile a vertex shader
virtual Ref<VertexShader> CreateVertexShader(const char *inName) = 0;
/// Compile a pixel shader
virtual Ref<PixelShader> CreatePixelShader(const char *inName) = 0;
/// Create pipeline state object that defines the complete state of how primitives should be rendered
virtual unique_ptr<PipelineState> CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) = 0;
/// Create a render primitive
virtual RenderPrimitive * CreateRenderPrimitive(PipelineState::ETopology inType) = 0;
/// Create render instances object to allow drawing batches of objects
virtual RenderInstances * CreateRenderInstances() = 0;
/// Get the shadow map texture
virtual Texture * GetShadowMap() const = 0;
/// Get the camera state / frustum (only valid between BeginFrame() / EndFrame())
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 { JPH_ASSERT(mInFrame); return mLightFrustum; }
/// How many frames our pipeline is
inline static const uint cFrameCount = 2;
/// Size of the shadow map will be cShadowMapSize x cShadowMapSize pixels
inline static const uint cShadowMapSize = 4096;
/// Which frame is currently rendering (to keep track of which buffers are free to overwrite)
uint GetCurrentFrameIndex() const { JPH_ASSERT(mInFrame); return mFrameIndex; }
/// Get the window we're rendering to
ApplicationWindow * GetWindow() const { return mWindow; }
/// Callback when the window resizes and the back buffer needs to be adjusted
virtual void OnWindowResize() = 0;
/// Create a platform specific Renderer instance
static Renderer * sCreate();
protected:
struct VertexShaderConstantBuffer
{
Mat44 mView;
Mat44 mProjection;
Mat44 mLightView;
Mat44 mLightProjection;
};
struct PixelShaderConstantBuffer
{
Vec4 mCameraPos;
Vec4 mLightPos;
};
ApplicationWindow * mWindow = nullptr; ///< The window we're rendering to
float mPerspectiveYSign = 1.0f; ///< Sign for the Y coordinate in the projection matrix (1 for DX, -1 for Vulkan)
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;
Frustum mLightFrustum;
uint mFrameIndex = 0; ///< Current frame index (0 or 1)
VertexShaderConstantBuffer mVSBuffer;
VertexShaderConstantBuffer mVSBufferOrtho;
PixelShaderConstantBuffer mPSBuffer;
};

View File

@@ -0,0 +1,29 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
/// Forward declares
class Surface;
class Texture : public RefTarget<Texture>
{
public:
/// Constructor
Texture(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight) { }
virtual ~Texture() = default;
/// Get dimensions of texture
inline int GetWidth() const { return mWidth; }
inline int GetHeight() const { return mHeight; }
/// Bind texture to the pixel shader
virtual void Bind() const = 0;
protected:
int mWidth;
int mHeight;
};

View File

@@ -0,0 +1,21 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <vulkan/vulkan.h>
/// Simple wrapper class to manage a Vulkan buffer
class BufferVK
{
public:
VkBuffer mBuffer = VK_NULL_HANDLE;
VkDeviceMemory mMemory = VK_NULL_HANDLE;
VkDeviceSize mOffset = 0;
VkDeviceSize mSize = 0;
VkBufferUsageFlags mUsage;
VkMemoryPropertyFlags mProperties;
VkDeviceSize mAllocatedSize;
};

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/VK/ConstantBufferVK.h>
#include <Renderer/VK/RendererVK.h>
#include <Renderer/VK/FatalErrorIfFailedVK.h>
ConstantBufferVK::ConstantBufferVK(RendererVK *inRenderer, VkDeviceSize inBufferSize) :
mRenderer(inRenderer)
{
mRenderer->CreateBuffer(inBufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mBuffer);
}
ConstantBufferVK::~ConstantBufferVK()
{
mRenderer->FreeBuffer(mBuffer);
}
void *ConstantBufferVK::MapInternal()
{
void *data = nullptr;
FatalErrorIfFailed(vkMapMemory(mRenderer->GetDevice(), mBuffer.mMemory, mBuffer.mOffset, mBuffer.mSize, 0, &data));
return data;
}
void ConstantBufferVK::Unmap()
{
vkUnmapMemory(mRenderer->GetDevice(), mBuffer.mMemory);
}

View File

@@ -0,0 +1,30 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/VK/BufferVK.h>
class RendererVK;
/// A binary blob that can be used to pass constants to a shader
class ConstantBufferVK
{
public:
/// Constructor
ConstantBufferVK(RendererVK *inRenderer, VkDeviceSize inBufferSize);
~ConstantBufferVK();
/// Map / unmap buffer (get pointer to data). This will discard all data in the buffer.
template <typename T> T * Map() { return reinterpret_cast<T *>(MapInternal()); }
void Unmap();
VkBuffer GetBuffer() const { return mBuffer.mBuffer; }
private:
void * MapInternal();
RendererVK * mRenderer;
BufferVK mBuffer;
};

View File

@@ -0,0 +1,14 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/VK/FatalErrorIfFailedVK.h>
#include <Utils/Log.h>
void FatalErrorIfFailed(VkResult inVkResult)
{
if (inVkResult != VK_SUCCESS)
FatalError("Vulkan error returned: %d", inVkResult);
}

View File

@@ -0,0 +1,11 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <vulkan/vulkan.h>
/// Convert Vulkan error to readable text and alert
void FatalErrorIfFailed(VkResult inVkResult);

View File

@@ -0,0 +1,175 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/VK/PipelineStateVK.h>
#include <Renderer/VK/RendererVK.h>
#include <Renderer/VK/FatalErrorIfFailedVK.h>
PipelineStateVK::PipelineStateVK(RendererVK *inRenderer, const VertexShaderVK *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderVK *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode) :
mRenderer(inRenderer),
mVertexShader(inVertexShader),
mPixelShader(inPixelShader)
{
VkPipelineShaderStageCreateInfo shader_stages[] = { inVertexShader->mStageInfo, inPixelShader->mStageInfo };
// TODO: This doesn't follow the SPIR-V alignment rules
Array<VkVertexInputAttributeDescription> attribute_descriptions;
VkVertexInputAttributeDescription temp_vtx = { }, temp_instance = { };
temp_instance.binding = 1;
uint instance_alignment = 1;
for (uint i = 0; i < inInputDescriptionCount; ++i)
switch (inInputDescription[i])
{
case EInputDescription::Position:
case EInputDescription::Normal:
temp_vtx.format = VK_FORMAT_R32G32B32_SFLOAT;
attribute_descriptions.push_back(temp_vtx);
temp_vtx.offset += 3 * sizeof(float);
break;
case EInputDescription::Color:
temp_vtx.format = VK_FORMAT_R8G8B8A8_UNORM;
attribute_descriptions.push_back(temp_vtx);
temp_vtx.offset += 4 * sizeof(uint8);
break;
case EInputDescription::TexCoord:
temp_vtx.format = VK_FORMAT_R32G32_SFLOAT;
attribute_descriptions.push_back(temp_vtx);
temp_vtx.offset += 2 * sizeof(float);
break;
case EInputDescription::InstanceColor:
instance_alignment = max(instance_alignment, 4u);
temp_instance.format = VK_FORMAT_R8G8B8A8_UNORM;
attribute_descriptions.push_back(temp_instance);
temp_instance.offset += 4 * sizeof(uint8);
break;
case EInputDescription::InstanceTransform:
case EInputDescription::InstanceInvTransform:
instance_alignment = max(instance_alignment, 16u);
temp_instance.format = VK_FORMAT_R32G32B32A32_SFLOAT;
for (int j = 0; j < 4; ++j)
{
attribute_descriptions.push_back(temp_instance);
temp_instance.offset += 4 * sizeof(float);
}
break;
}
for (uint32 i = 0; i < uint32(attribute_descriptions.size()); ++i)
attribute_descriptions[i].location = i;
VkVertexInputBindingDescription binding_description[2];
binding_description[0].binding = 0;
binding_description[0].stride = temp_vtx.offset;
binding_description[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
binding_description[1].binding = 1;
binding_description[1].stride = AlignUp(temp_instance.offset, instance_alignment);
binding_description[1].inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
VkPipelineVertexInputStateCreateInfo vertex_input_info = {};
vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertex_input_info.vertexBindingDescriptionCount = temp_instance.offset > 0? 2 : 1;
vertex_input_info.pVertexBindingDescriptions = binding_description;
vertex_input_info.vertexAttributeDescriptionCount = uint32(attribute_descriptions.size());
vertex_input_info.pVertexAttributeDescriptions = attribute_descriptions.data();
VkPipelineInputAssemblyStateCreateInfo input_assembly = {};
input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
input_assembly.topology = inTopology == ETopology::Triangle? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST : VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
input_assembly.primitiveRestartEnable = VK_FALSE;
VkPipelineViewportStateCreateInfo viewport_state = {};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport_state.viewportCount = 1;
viewport_state.scissorCount = 1;
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = inFillMode == EFillMode::Solid? VK_POLYGON_MODE_FILL : VK_POLYGON_MODE_LINE;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = inCullMode == ECullMode::Backface? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_FRONT_BIT;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineDepthStencilStateCreateInfo depth_stencil = {};
depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil.depthTestEnable = inDepthTest == EDepthTest::On? VK_TRUE : VK_FALSE;
depth_stencil.depthWriteEnable = inDepthTest == EDepthTest::On? VK_TRUE : VK_FALSE;
depth_stencil.depthCompareOp = VK_COMPARE_OP_GREATER_OR_EQUAL; // Reverse-Z, greater is closer
VkPipelineColorBlendAttachmentState color_blend_attachment = {};
color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
switch (inBlendMode)
{
case EBlendMode::Write:
color_blend_attachment.blendEnable = VK_FALSE;
break;
case EBlendMode::AlphaBlend:
color_blend_attachment.blendEnable = VK_TRUE;
color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
}
VkPipelineColorBlendStateCreateInfo color_blending = {};
color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
color_blending.logicOpEnable = VK_FALSE;
color_blending.logicOp = VK_LOGIC_OP_COPY;
color_blending.attachmentCount = 1;
color_blending.pAttachments = &color_blend_attachment;
VkDynamicState dynamic_states[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamic_state = {};
dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic_state.dynamicStateCount = std::size(dynamic_states);
dynamic_state.pDynamicStates = dynamic_states;
VkGraphicsPipelineCreateInfo pipeline_info = {};
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_info.stageCount = std::size(shader_stages);
pipeline_info.pStages = shader_stages;
pipeline_info.pVertexInputState = &vertex_input_info;
pipeline_info.pInputAssemblyState = &input_assembly;
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pDepthStencilState = &depth_stencil;
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_state;
pipeline_info.layout = mRenderer->GetPipelineLayout();
pipeline_info.renderPass = inDrawPass == EDrawPass::Normal? mRenderer->GetRenderPass() : mRenderer->GetRenderPassShadow();
FatalErrorIfFailed(vkCreateGraphicsPipelines(mRenderer->GetDevice(), VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &mGraphicsPipeline));
}
PipelineStateVK::~PipelineStateVK()
{
vkDeviceWaitIdle(mRenderer->GetDevice());
vkDestroyPipeline(mRenderer->GetDevice(), mGraphicsPipeline, nullptr);
}
void PipelineStateVK::Activate()
{
vkCmdBindPipeline(mRenderer->GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mGraphicsPipeline);
}

View File

@@ -0,0 +1,30 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/PipelineState.h>
#include <Renderer/VK/VertexShaderVK.h>
#include <Renderer/VK/PixelShaderVK.h>
class RendererVK;
/// Vulkan pipeline state object
class PipelineStateVK : public PipelineState
{
public:
/// Constructor
PipelineStateVK(RendererVK *inRenderer, const VertexShaderVK *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderVK *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode);
virtual ~PipelineStateVK() override;
/// Make this pipeline state active (any primitives rendered after this will use this state)
virtual void Activate() override;
private:
RendererVK * mRenderer;
RefConst<VertexShaderVK> mVertexShader;
RefConst<PixelShaderVK> mPixelShader;
VkPipeline mGraphicsPipeline;
};

View File

@@ -0,0 +1,34 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/PixelShader.h>
#include <vulkan/vulkan.h>
/// Pixel shader handle for Vulkan
class PixelShaderVK : public PixelShader
{
public:
/// Constructor
PixelShaderVK(VkDevice inDevice, VkShaderModule inShaderModule) :
mDevice(inDevice),
mStageInfo()
{
mStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
mStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
mStageInfo.module = inShaderModule;
mStageInfo.pName = "main";
}
/// Destructor
virtual ~PixelShaderVK() override
{
vkDestroyShaderModule(mDevice, mStageInfo.module, nullptr);
}
VkDevice mDevice;
VkPipelineShaderStageCreateInfo mStageInfo;
};

View File

@@ -0,0 +1,57 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/VK/RenderInstancesVK.h>
#include <Renderer/VK/RenderPrimitiveVK.h>
#include <Renderer/VK/FatalErrorIfFailedVK.h>
void RenderInstancesVK::Clear()
{
mRenderer->FreeBuffer(mInstancesBuffer);
}
void RenderInstancesVK::CreateBuffer(int inNumInstances, int inInstanceSize)
{
Clear();
mRenderer->CreateBuffer(VkDeviceSize(inNumInstances) * inInstanceSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mInstancesBuffer);
}
void *RenderInstancesVK::Lock()
{
void *data;
FatalErrorIfFailed(vkMapMemory(mRenderer->GetDevice(), mInstancesBuffer.mMemory, mInstancesBuffer.mOffset, mInstancesBuffer.mSize, 0, &data));
return data;
}
void RenderInstancesVK::Unlock()
{
vkUnmapMemory(mRenderer->GetDevice(), mInstancesBuffer.mMemory);
}
void RenderInstancesVK::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const
{
if (inNumInstances <= 0)
return;
VkCommandBuffer command_buffer = mRenderer->GetCommandBuffer();
RenderPrimitiveVK *primitive = static_cast<RenderPrimitiveVK *>(inPrimitive);
VkBuffer buffers[] = { primitive->mVertexBuffer.mBuffer, mInstancesBuffer.mBuffer };
VkDeviceSize offsets[] = { 0, 0 };
vkCmdBindVertexBuffers(command_buffer, 0, 2, buffers, offsets);
if (primitive->mIndexBuffer.mBuffer == VK_NULL_HANDLE)
{
vkCmdDraw(command_buffer, primitive->mNumVtxToDraw, inNumInstances, 0, inStartInstance);
}
else
{
vkCmdBindIndexBuffer(command_buffer, primitive->mIndexBuffer.mBuffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(command_buffer, primitive->mNumIdxToDraw, inNumInstances, 0, 0, inStartInstance);
}
}

View File

@@ -0,0 +1,35 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/VK/RendererVK.h>
#include <Renderer/RenderInstances.h>
class RenderPrimitive;
/// Vulkan implementation of a render instances object
class RenderInstancesVK : public RenderInstances
{
public:
/// Constructor
RenderInstancesVK(RendererVK *inRenderer) : mRenderer(inRenderer) { }
virtual ~RenderInstancesVK() override { Clear(); }
/// Erase all instance data
virtual void Clear() override;
/// Instance buffer management functions
virtual void CreateBuffer(int inNumInstances, int inInstanceSize) override;
virtual void * Lock() override;
virtual void Unlock() override;
/// Draw the instances when context has been set by Renderer::BindShader
virtual void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const override;
private:
RendererVK * mRenderer;
BufferVK mInstancesBuffer;
};

View File

@@ -0,0 +1,100 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/VK/RenderPrimitiveVK.h>
#include <Renderer/VK/FatalErrorIfFailedVK.h>
void RenderPrimitiveVK::ReleaseVertexBuffer()
{
mRenderer->FreeBuffer(mVertexBuffer);
mVertexBufferDeviceLocal = false;
RenderPrimitive::ReleaseVertexBuffer();
}
void RenderPrimitiveVK::ReleaseIndexBuffer()
{
mRenderer->FreeBuffer(mIndexBuffer);
mIndexBufferDeviceLocal = false;
RenderPrimitive::ReleaseIndexBuffer();
}
void RenderPrimitiveVK::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData)
{
RenderPrimitive::CreateVertexBuffer(inNumVtx, inVtxSize, inData);
VkDeviceSize size = VkDeviceSize(inNumVtx) * inVtxSize;
if (inData != nullptr)
{
mRenderer->CreateDeviceLocalBuffer(inData, size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, mVertexBuffer);
mVertexBufferDeviceLocal = true;
}
else
mRenderer->CreateBuffer(size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mVertexBuffer);
}
void *RenderPrimitiveVK::LockVertexBuffer()
{
JPH_ASSERT(!mVertexBufferDeviceLocal);
void *data;
FatalErrorIfFailed(vkMapMemory(mRenderer->GetDevice(), mVertexBuffer.mMemory, mVertexBuffer.mOffset, VkDeviceSize(mNumVtx) * mVtxSize, 0, &data));
return data;
}
void RenderPrimitiveVK::UnlockVertexBuffer()
{
vkUnmapMemory(mRenderer->GetDevice(), mVertexBuffer.mMemory);
}
void RenderPrimitiveVK::CreateIndexBuffer(int inNumIdx, const uint32 *inData)
{
RenderPrimitive::CreateIndexBuffer(inNumIdx, inData);
VkDeviceSize size = VkDeviceSize(inNumIdx) * sizeof(uint32);
if (inData != nullptr)
{
mRenderer->CreateDeviceLocalBuffer(inData, size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, mIndexBuffer);
mIndexBufferDeviceLocal = true;
}
else
mRenderer->CreateBuffer(size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mIndexBuffer);
}
uint32 *RenderPrimitiveVK::LockIndexBuffer()
{
JPH_ASSERT(!mIndexBufferDeviceLocal);
void *data;
vkMapMemory(mRenderer->GetDevice(), mIndexBuffer.mMemory, mIndexBuffer.mOffset, VkDeviceSize(mNumIdx) * sizeof(uint32), 0, &data);
return reinterpret_cast<uint32 *>(data);
}
void RenderPrimitiveVK::UnlockIndexBuffer()
{
vkUnmapMemory(mRenderer->GetDevice(), mIndexBuffer.mMemory);
}
void RenderPrimitiveVK::Draw() const
{
VkCommandBuffer command_buffer = mRenderer->GetCommandBuffer();
VkBuffer vertex_buffers[] = { mVertexBuffer.mBuffer };
VkDeviceSize offsets[] = { 0 };
vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, offsets);
if (mIndexBuffer.mBuffer == VK_NULL_HANDLE)
{
vkCmdDraw(command_buffer, mNumVtxToDraw, 1, 0, 0);
}
else
{
vkCmdBindIndexBuffer(command_buffer, mIndexBuffer.mBuffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(command_buffer, mNumIdxToDraw, 1, 0, 0, 0);
}
}

View File

@@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/RenderPrimitive.h>
#include <Renderer/VK/RendererVK.h>
#include <Renderer/VK/BufferVK.h>
/// Vulkan implementation of a render primitive
class RenderPrimitiveVK : public RenderPrimitive
{
public:
/// Constructor
RenderPrimitiveVK(RendererVK *inRenderer) : mRenderer(inRenderer) { }
virtual ~RenderPrimitiveVK() override { Clear(); }
/// Vertex buffer management functions
virtual void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr) override;
virtual void ReleaseVertexBuffer() override;
virtual void * LockVertexBuffer() override;
virtual void UnlockVertexBuffer() override;
/// Index buffer management functions
virtual void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr) override;
virtual void ReleaseIndexBuffer() override;
virtual uint32 * LockIndexBuffer() override;
virtual void UnlockIndexBuffer() override;
/// Draw the primitive
virtual void Draw() const override;
private:
friend class RenderInstancesVK;
RendererVK * mRenderer;
BufferVK mVertexBuffer;
bool mVertexBufferDeviceLocal = false;
BufferVK mIndexBuffer;
bool mIndexBufferDeviceLocal = false;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/Renderer.h>
#include <Renderer/VK/ConstantBufferVK.h>
#include <Renderer/VK/TextureVK.h>
#include <Jolt/Core/UnorderedMap.h>
#include <vulkan/vulkan.h>
/// Vulkan renderer
class RendererVK : public Renderer
{
public:
/// Destructor
virtual ~RendererVK() override;
// See: Renderer
virtual void Initialize(ApplicationWindow *inWindow) override;
virtual bool BeginFrame(const CameraState &inCamera, float inWorldScale) override;
virtual void EndShadowPass() override;
virtual void EndFrame() override;
virtual void SetProjectionMode() override;
virtual void SetOrthoMode() override;
virtual Ref<Texture> CreateTexture(const Surface *inSurface) override;
virtual Ref<VertexShader> CreateVertexShader(const char *inName) override;
virtual Ref<PixelShader> CreatePixelShader(const char *inName) override;
virtual unique_ptr<PipelineState> CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) override;
virtual RenderPrimitive * CreateRenderPrimitive(PipelineState::ETopology inType) override;
virtual RenderInstances * CreateRenderInstances() override;
virtual Texture * GetShadowMap() const override { return mShadowMap.GetPtr(); }
virtual void OnWindowResize() override;
VkDevice GetDevice() const { return mDevice; }
VkDescriptorPool GetDescriptorPool() const { return mDescriptorPool; }
VkDescriptorSetLayout GetDescriptorSetLayoutTexture() const { return mDescriptorSetLayoutTexture; }
VkSampler GetTextureSamplerRepeat() const { return mTextureSamplerRepeat; }
VkSampler GetTextureSamplerShadow() const { return mTextureSamplerShadow; }
VkRenderPass GetRenderPassShadow() const { return mRenderPassShadow; }
VkRenderPass GetRenderPass() const { return mRenderPass; }
VkPipelineLayout GetPipelineLayout() const { return mPipelineLayout; }
VkCommandBuffer GetCommandBuffer() { JPH_ASSERT(mInFrame); return mCommandBuffers[mFrameIndex]; }
VkCommandBuffer StartTempCommandBuffer();
void EndTempCommandBuffer(VkCommandBuffer inCommandBuffer);
void AllocateMemory(VkDeviceSize inSize, uint32 inMemoryTypeBits, VkMemoryPropertyFlags inProperties, VkDeviceMemory &outMemory);
void FreeMemory(VkDeviceMemory inMemory, VkDeviceSize inSize);
void CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer);
void CopyBuffer(VkBuffer inSrc, VkBuffer inDst, VkDeviceSize inSize);
void CreateDeviceLocalBuffer(const void *inData, VkDeviceSize inSize, VkBufferUsageFlags inUsage, BufferVK &outBuffer);
void FreeBuffer(BufferVK &ioBuffer);
unique_ptr<ConstantBufferVK> CreateConstantBuffer(VkDeviceSize inBufferSize);
void CreateImage(uint32 inWidth, uint32 inHeight, VkFormat inFormat, VkImageTiling inTiling, VkImageUsageFlags inUsage, VkMemoryPropertyFlags inProperties, VkImage &outImage, VkDeviceMemory &outMemory);
void DestroyImage(VkImage inImage, VkDeviceMemory inMemory);
VkImageView CreateImageView(VkImage inImage, VkFormat inFormat, VkImageAspectFlags inAspectFlags);
VkFormat FindDepthFormat();
private:
uint32 FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties);
void FreeBufferInternal(BufferVK &ioBuffer);
VkSurfaceFormatKHR SelectFormat(VkPhysicalDevice inDevice);
void CreateSwapChain(VkPhysicalDevice inDevice);
void DestroySwapChain();
void UpdateViewPortAndScissorRect(uint32 inWidth, uint32 inHeight);
VkSemaphore AllocateSemaphore();
void FreeSemaphore(VkSemaphore inSemaphore);
VkInstance mInstance = VK_NULL_HANDLE;
#ifdef JPH_DEBUG
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
#endif
VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
VkPhysicalDeviceMemoryProperties mMemoryProperties;
VkDevice mDevice = VK_NULL_HANDLE;
uint32 mGraphicsQueueIndex = 0;
uint32 mPresentQueueIndex = 0;
VkQueue mGraphicsQueue = VK_NULL_HANDLE;
VkQueue mPresentQueue = VK_NULL_HANDLE;
VkSurfaceKHR mSurface = VK_NULL_HANDLE;
VkSwapchainKHR mSwapChain = VK_NULL_HANDLE;
bool mSubOptimalSwapChain = false;
Array<VkImage> mSwapChainImages;
VkFormat mSwapChainImageFormat;
VkExtent2D mSwapChainExtent;
Array<VkImageView> mSwapChainImageViews;
VkImage mDepthImage = VK_NULL_HANDLE;
VkDeviceMemory mDepthImageMemory = VK_NULL_HANDLE;
VkImageView mDepthImageView = VK_NULL_HANDLE;
VkDescriptorSetLayout mDescriptorSetLayoutUBO = VK_NULL_HANDLE;
VkDescriptorSetLayout mDescriptorSetLayoutTexture = VK_NULL_HANDLE;
VkDescriptorPool mDescriptorPool = VK_NULL_HANDLE;
VkDescriptorSet mDescriptorSets[cFrameCount];
VkDescriptorSet mDescriptorSetsOrtho[cFrameCount];
VkSampler mTextureSamplerShadow = VK_NULL_HANDLE;
VkSampler mTextureSamplerRepeat = VK_NULL_HANDLE;
VkRenderPass mRenderPassShadow = VK_NULL_HANDLE;
VkRenderPass mRenderPass = VK_NULL_HANDLE;
VkPipelineLayout mPipelineLayout = VK_NULL_HANDLE;
VkFramebuffer mShadowFrameBuffer = VK_NULL_HANDLE;
Array<VkFramebuffer> mSwapChainFramebuffers;
uint32 mImageIndex = 0;
VkCommandPool mCommandPool = VK_NULL_HANDLE;
VkCommandBuffer mCommandBuffers[cFrameCount];
Array<VkSemaphore> mAvailableSemaphores;
Array<VkSemaphore> mImageAvailableSemaphores;
Array<VkSemaphore> mRenderFinishedSemaphores;
VkFence mInFlightFences[cFrameCount];
Ref<TextureVK> mShadowMap;
unique_ptr<ConstantBufferVK> mVertexShaderConstantBufferProjection[cFrameCount];
unique_ptr<ConstantBufferVK> mVertexShaderConstantBufferOrtho[cFrameCount];
unique_ptr<ConstantBufferVK> mPixelShaderConstantBuffer[cFrameCount];
struct Key
{
bool operator == (const Key &inRHS) const
{
return mSize == inRHS.mSize && mUsage == inRHS.mUsage && mProperties == inRHS.mProperties;
}
VkDeviceSize mSize;
VkBufferUsageFlags mUsage;
VkMemoryPropertyFlags mProperties;
};
JPH_MAKE_HASH_STRUCT(Key, KeyHasher, t.mSize, t.mUsage, t.mProperties)
// We try to recycle buffers from frame to frame
using BufferCache = UnorderedMap<Key, Array<BufferVK>, KeyHasher>;
BufferCache mFreedBuffers[cFrameCount];
BufferCache mBufferCache;
// Smaller allocations (from cMinAllocSize to cMaxAllocSize) will be done in blocks of cBlockSize bytes.
// We do this because there is a limit to the number of allocations that we can make in Vulkan.
static constexpr VkDeviceSize cMinAllocSize = 512;
static constexpr VkDeviceSize cMaxAllocSize = 65536;
static constexpr VkDeviceSize cBlockSize = 524288;
JPH_MAKE_HASH_STRUCT(Key, MemKeyHasher, t.mUsage, t.mProperties, t.mSize)
struct Memory
{
VkDeviceMemory mMemory;
VkDeviceSize mOffset;
};
using MemoryCache = UnorderedMap<Key, Array<Memory>, KeyHasher>;
MemoryCache mMemoryCache;
uint32 mNumAllocations = 0;
uint32 mMaxNumAllocations = 0;
VkDeviceSize mTotalAllocated = 0;
VkDeviceSize mMaxTotalAllocated = 0;
};

View File

@@ -0,0 +1,180 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/VK/TextureVK.h>
#include <Renderer/VK/RendererVK.h>
#include <Renderer/VK/FatalErrorIfFailedVK.h>
#include <Image/BlitSurface.h>
TextureVK::TextureVK(RendererVK *inRenderer, const Surface *inSurface) :
Texture(inSurface->GetWidth(), inSurface->GetHeight()),
mRenderer(inRenderer)
{
ESurfaceFormat format = inSurface->GetFormat();
VkFormat vk_format = VK_FORMAT_B8G8R8A8_UNORM;
switch (format)
{
case ESurfaceFormat::A4L4: vk_format = VK_FORMAT_R8G8_UNORM; format = ESurfaceFormat::A8L8; break;
case ESurfaceFormat::L8: vk_format = VK_FORMAT_R8_UNORM; break;
case ESurfaceFormat::A8: vk_format = VK_FORMAT_A8_UNORM_KHR; break;
case ESurfaceFormat::A8L8: vk_format = VK_FORMAT_R8G8_UNORM; break;
case ESurfaceFormat::R5G6B5: vk_format = VK_FORMAT_B5G6R5_UNORM_PACK16; break;
case ESurfaceFormat::X1R5G5B5: vk_format = VK_FORMAT_B5G5R5A1_UNORM_PACK16; format = ESurfaceFormat::A1R5G5B5; break;
case ESurfaceFormat::X4R4G4B4: vk_format = VK_FORMAT_B4G4R4A4_UNORM_PACK16; format = ESurfaceFormat::A4R4G4B4; break;
case ESurfaceFormat::A1R5G5B5: vk_format = VK_FORMAT_B5G5R5A1_UNORM_PACK16; break;
case ESurfaceFormat::A4R4G4B4: vk_format = VK_FORMAT_B4G4R4A4_UNORM_PACK16; break;
case ESurfaceFormat::R8G8B8: vk_format = VK_FORMAT_B8G8R8_UNORM; break;
case ESurfaceFormat::B8G8R8: vk_format = VK_FORMAT_B8G8R8_UNORM; break;
case ESurfaceFormat::X8R8G8B8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; format = ESurfaceFormat::A8R8G8B8; break;
case ESurfaceFormat::X8B8G8R8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; format = ESurfaceFormat::X8R8G8B8; break;
case ESurfaceFormat::A8R8G8B8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; break;
case ESurfaceFormat::A8B8G8R8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; format = ESurfaceFormat::A8R8G8B8; break;
case ESurfaceFormat::Invalid:
default: JPH_ASSERT(false); break;
}
// Blit the surface to another temporary surface if the format changed
const Surface *surface = inSurface;
Ref<Surface> tmp;
if (format != inSurface->GetFormat())
{
tmp = new SoftwareSurface(mWidth, mHeight, format);
BlitSurface(inSurface, tmp);
surface = tmp;
}
int bpp = surface->GetBytesPerPixel();
VkDeviceSize image_size = VkDeviceSize(mWidth) * mHeight * bpp;
VkDevice device = mRenderer->GetDevice();
BufferVK staging_buffer;
mRenderer->CreateBuffer(image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging_buffer);
// Copy data to upload texture
surface->Lock(ESurfaceLockMode::Read);
void *data;
vkMapMemory(device, staging_buffer.mMemory, staging_buffer.mOffset, image_size, 0, &data);
for (int y = 0; y < mHeight; ++y)
memcpy(reinterpret_cast<uint8 *>(data) + y * mWidth * bpp, surface->GetData() + y * surface->GetStride(), mWidth * bpp);
vkUnmapMemory(device, staging_buffer.mMemory);
surface->UnLock();
// Create destination image
mRenderer->CreateImage(mWidth, mHeight, vk_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mImage, mImageMemory);
VkCommandBuffer command_buffer = mRenderer->StartTempCommandBuffer();
// Make the image suitable for transferring to
TransitionImageLayout(command_buffer, mImage, vk_format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
// Copy the data to the destination image
VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent = { uint32(mWidth), uint32(mHeight), 1 };
vkCmdCopyBufferToImage(command_buffer, staging_buffer.mBuffer, mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
// Make the image suitable for sampling
TransitionImageLayout(command_buffer, mImage, vk_format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
mRenderer->EndTempCommandBuffer(command_buffer);
// Destroy temporary buffer
mRenderer->FreeBuffer(staging_buffer);
CreateImageViewAndDescriptorSet(vk_format, VK_IMAGE_ASPECT_COLOR_BIT, mRenderer->GetTextureSamplerRepeat());
}
TextureVK::TextureVK(RendererVK *inRenderer, int inWidth, int inHeight) :
Texture(inWidth, inHeight),
mRenderer(inRenderer)
{
VkFormat vk_format = mRenderer->FindDepthFormat();
// Create render target
mRenderer->CreateImage(mWidth, mHeight, vk_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mImage, mImageMemory);
CreateImageViewAndDescriptorSet(vk_format, VK_IMAGE_ASPECT_DEPTH_BIT, mRenderer->GetTextureSamplerShadow());
}
TextureVK::~TextureVK()
{
if (mImage != VK_NULL_HANDLE)
{
VkDevice device = mRenderer->GetDevice();
vkDeviceWaitIdle(device);
vkDestroyImageView(device, mImageView, nullptr);
mRenderer->DestroyImage(mImage, mImageMemory);
}
}
void TextureVK::Bind() const
{
if (mDescriptorSet != VK_NULL_HANDLE)
vkCmdBindDescriptorSets(mRenderer->GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mRenderer->GetPipelineLayout(), 1, 1, &mDescriptorSet, 0, nullptr);
}
void TextureVK::CreateImageViewAndDescriptorSet(VkFormat inFormat, VkImageAspectFlags inAspectFlags, VkSampler inSampler)
{
VkDevice device = mRenderer->GetDevice();
// Create image view
mImageView = mRenderer->CreateImageView(mImage, inFormat, inAspectFlags);
// Allocate descriptor set for binding the texture
VkDescriptorSetLayout layout = mRenderer->GetDescriptorSetLayoutTexture();
VkDescriptorSetAllocateInfo descriptor_set_alloc_info = {};
descriptor_set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptor_set_alloc_info.descriptorPool = mRenderer->GetDescriptorPool();
descriptor_set_alloc_info.descriptorSetCount = 1;
descriptor_set_alloc_info.pSetLayouts = &layout;
FatalErrorIfFailed(vkAllocateDescriptorSets(device, &descriptor_set_alloc_info, &mDescriptorSet));
VkDescriptorImageInfo image_info = {};
image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image_info.imageView = mImageView;
image_info.sampler = inSampler;
VkWriteDescriptorSet descriptor_write = {};
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptor_write.dstSet = mDescriptorSet;
descriptor_write.dstBinding = 0;
descriptor_write.dstArrayElement = 0;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptor_write.descriptorCount = 1;
descriptor_write.pImageInfo = &image_info;
vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, nullptr);
}
void TextureVK::TransitionImageLayout(VkCommandBuffer inCommandBuffer, VkImage inImage, VkFormat inFormat, VkImageLayout inOldLayout, VkImageLayout inNewLayout)
{
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = inOldLayout;
barrier.newLayout = inNewLayout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = inImage;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.layerCount = 1;
if (inOldLayout == VK_IMAGE_LAYOUT_UNDEFINED && inNewLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
{
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(inCommandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
else if (inOldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && inNewLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(inCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
}

View File

@@ -0,0 +1,35 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/Texture.h>
#include <vulkan/vulkan.h>
class RendererVK;
class TextureVK : public Texture
{
public:
/// Constructor, called by Renderer::CreateTextureVK
TextureVK(RendererVK *inRenderer, const Surface *inSurface); // Create a normal TextureVK
TextureVK(RendererVK *inRenderer, int inWidth, int inHeight); // Create a render target (depth only)
virtual ~TextureVK() override;
/// Bind texture to the pixel shader
virtual void Bind() const override;
VkImageView GetImageView() const { return mImageView; }
private:
void CreateImageViewAndDescriptorSet(VkFormat inFormat, VkImageAspectFlags inAspectFlags, VkSampler inSampler);
void TransitionImageLayout(VkCommandBuffer inCommandBuffer, VkImage inImage, VkFormat inFormat, VkImageLayout inOldLayout, VkImageLayout inNewLayout);
RendererVK * mRenderer;
VkImage mImage = VK_NULL_HANDLE;
VkDeviceMemory mImageMemory = VK_NULL_HANDLE;
VkImageView mImageView = VK_NULL_HANDLE;
VkDescriptorSet mDescriptorSet = VK_NULL_HANDLE;
};

View File

@@ -0,0 +1,34 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/VertexShader.h>
#include <vulkan/vulkan.h>
/// Vertex shader handle for Vulkan
class VertexShaderVK : public VertexShader
{
public:
/// Constructor
VertexShaderVK(VkDevice inDevice, VkShaderModule inShaderModule) :
mDevice(inDevice),
mStageInfo()
{
mStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
mStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
mStageInfo.module = inShaderModule;
mStageInfo.pName = "main";
}
/// Destructor
virtual ~VertexShaderVK() override
{
vkDestroyShaderModule(mDevice, mStageInfo.module, nullptr);
}
VkDevice mDevice;
VkPipelineShaderStageCreateInfo mStageInfo;
};

View File

@@ -0,0 +1,15 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
/// Vertex shader handle
class VertexShader : public RefTarget<VertexShader>
{
public:
/// Destructor
virtual ~VertexShader() = default;
};

View File

@@ -0,0 +1,333 @@
# Find Vulkan
find_package(Vulkan)
if (NOT CROSS_COMPILE_ARM AND (Vulkan_FOUND OR WIN32 OR ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")))
# We have Vulkan/DirectX so we can compile TestFramework
set(TEST_FRAMEWORK_AVAILABLE TRUE)
# Root
set(TEST_FRAMEWORK_ROOT ${PHYSICS_REPO_ROOT}/TestFramework)
# Source files
set(TEST_FRAMEWORK_SRC_FILES
${TEST_FRAMEWORK_ROOT}/Application/Application.cpp
${TEST_FRAMEWORK_ROOT}/Application/Application.h
${TEST_FRAMEWORK_ROOT}/Application/DebugUI.cpp
${TEST_FRAMEWORK_ROOT}/Application/DebugUI.h
${TEST_FRAMEWORK_ROOT}/Application/EntryPoint.h
${TEST_FRAMEWORK_ROOT}/External/Perlin.cpp
${TEST_FRAMEWORK_ROOT}/External/Perlin.h
${TEST_FRAMEWORK_ROOT}/External/stb_truetype.h
${TEST_FRAMEWORK_ROOT}/Image/BlitSurface.cpp
${TEST_FRAMEWORK_ROOT}/Image/BlitSurface.h
${TEST_FRAMEWORK_ROOT}/Image/LoadBMP.cpp
${TEST_FRAMEWORK_ROOT}/Image/LoadBMP.h
${TEST_FRAMEWORK_ROOT}/Image/LoadTGA.cpp
${TEST_FRAMEWORK_ROOT}/Image/LoadTGA.h
${TEST_FRAMEWORK_ROOT}/Image/Surface.cpp
${TEST_FRAMEWORK_ROOT}/Image/Surface.h
${TEST_FRAMEWORK_ROOT}/Image/ZoomImage.cpp
${TEST_FRAMEWORK_ROOT}/Image/ZoomImage.h
${TEST_FRAMEWORK_ROOT}/Input/Keyboard.h
${TEST_FRAMEWORK_ROOT}/Input/Mouse.h
${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.h
${TEST_FRAMEWORK_ROOT}/Renderer/Font.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/Font.h
${TEST_FRAMEWORK_ROOT}/Renderer/Frustum.h
${TEST_FRAMEWORK_ROOT}/Renderer/PipelineState.h
${TEST_FRAMEWORK_ROOT}/Renderer/PixelShader.h
${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.h
${TEST_FRAMEWORK_ROOT}/Renderer/RenderInstances.h
${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.h
${TEST_FRAMEWORK_ROOT}/Renderer/Texture.h
${TEST_FRAMEWORK_ROOT}/Renderer/VertexShader.h
${TEST_FRAMEWORK_ROOT}/TestFramework.cmake
${TEST_FRAMEWORK_ROOT}/TestFramework.h
${TEST_FRAMEWORK_ROOT}/UI/UIAnimation.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIAnimation.h
${TEST_FRAMEWORK_ROOT}/UI/UIAnimationSlide.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIAnimationSlide.h
${TEST_FRAMEWORK_ROOT}/UI/UIButton.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIButton.h
${TEST_FRAMEWORK_ROOT}/UI/UICheckBox.cpp
${TEST_FRAMEWORK_ROOT}/UI/UICheckBox.h
${TEST_FRAMEWORK_ROOT}/UI/UIComboBox.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIComboBox.h
${TEST_FRAMEWORK_ROOT}/UI/UIElement.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIElement.h
${TEST_FRAMEWORK_ROOT}/UI/UIEventListener.h
${TEST_FRAMEWORK_ROOT}/UI/UIHorizontalStack.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIHorizontalStack.h
${TEST_FRAMEWORK_ROOT}/UI/UIImage.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIImage.h
${TEST_FRAMEWORK_ROOT}/UI/UIManager.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIManager.h
${TEST_FRAMEWORK_ROOT}/UI/UISlider.cpp
${TEST_FRAMEWORK_ROOT}/UI/UISlider.h
${TEST_FRAMEWORK_ROOT}/UI/UIStaticText.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIStaticText.h
${TEST_FRAMEWORK_ROOT}/UI/UITextButton.cpp
${TEST_FRAMEWORK_ROOT}/UI/UITextButton.h
${TEST_FRAMEWORK_ROOT}/UI/UITexturedQuad.h
${TEST_FRAMEWORK_ROOT}/UI/UIVerticalStack.cpp
${TEST_FRAMEWORK_ROOT}/UI/UIVerticalStack.h
${TEST_FRAMEWORK_ROOT}/Utils/CustomMemoryHook.cpp
${TEST_FRAMEWORK_ROOT}/Utils/CustomMemoryHook.h
${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.h
${TEST_FRAMEWORK_ROOT}/Utils/Log.h
${TEST_FRAMEWORK_ROOT}/Utils/ReadData.cpp
${TEST_FRAMEWORK_ROOT}/Utils/ReadData.h
${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindow.h
)
if (WIN32)
# Windows source files
set(TEST_FRAMEWORK_SRC_FILES
${TEST_FRAMEWORK_SRC_FILES}
${TEST_FRAMEWORK_ROOT}/Input/Win/KeyboardWin.cpp
${TEST_FRAMEWORK_ROOT}/Input/Win/KeyboardWin.h
${TEST_FRAMEWORK_ROOT}/Input/Win/MouseWin.cpp
${TEST_FRAMEWORK_ROOT}/Input/Win/MouseWin.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/ConstantBufferDX12.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/ConstantBufferDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/CommandQueueDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/DescriptorHeapDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/FatalErrorIfFailedDX12.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/FatalErrorIfFailedDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PipelineStateDX12.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PipelineStateDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PixelShaderDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RendererDX12.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RendererDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderInstancesDX12.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderInstancesDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderPrimitiveDX12.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderPrimitiveDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.h
${TEST_FRAMEWORK_ROOT}/Renderer/DX12/VertexShaderDX12.h
${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.cpp
${TEST_FRAMEWORK_ROOT}/Utils/Log.cpp
${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowWin.cpp
${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowWin.h
)
# HLSL vertex shaders
set(TEST_FRAMEWORK_SRC_FILES_SHADERS
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/VertexConstants.h
)
set(TEST_FRAMEWORK_HLSL_VERTEX_SHADERS
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/FontVertexShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/LineVertexShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/TriangleDepthVertexShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/TriangleVertexShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/UIVertexShader.hlsl
)
set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS})
set_source_files_properties(${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T vs_5_0")
# HLSL pixel shaders
set(TEST_FRAMEWORK_HLSL_PIXEL_SHADERS
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/FontPixelShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/LinePixelShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/TriangleDepthPixelShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/TrianglePixelShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/UIPixelShader.hlsl
${PHYSICS_REPO_ROOT}/Assets/Shaders/DX/UIPixelShaderUntextured.hlsl
)
set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS})
set_source_files_properties(${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T ps_5_0")
endif()
if (LINUX)
# Linux source files
set(TEST_FRAMEWORK_SRC_FILES
${TEST_FRAMEWORK_SRC_FILES}
${TEST_FRAMEWORK_ROOT}/Input/Linux/KeyboardLinux.cpp
${TEST_FRAMEWORK_ROOT}/Input/Linux/KeyboardLinux.h
${TEST_FRAMEWORK_ROOT}/Input/Linux/MouseLinux.cpp
${TEST_FRAMEWORK_ROOT}/Input/Linux/MouseLinux.h
${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.cpp
${TEST_FRAMEWORK_ROOT}/Utils/Log.cpp
${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowLinux.cpp
${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowLinux.h
)
endif()
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
# macOS source files
set(TEST_FRAMEWORK_SRC_FILES
${TEST_FRAMEWORK_SRC_FILES}
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/FatalErrorIfFailedMTL.mm
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/FatalErrorIfFailedMTL.h
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/PipelineStateMTL.mm
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/PipelineStateMTL.h
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/PixelShaderMTL.h
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/RendererMTL.mm
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/RendererMTL.h
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/RenderInstancesMTL.mm
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/RenderInstancesMTL.h
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/RenderPrimitiveMTL.mm
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/RenderPrimitiveMTL.h
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/TextureMTL.mm
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/TextureMTL.h
${TEST_FRAMEWORK_ROOT}/Renderer/MTL/VertexShaderMTL.h
${TEST_FRAMEWORK_ROOT}/Input/MacOS/KeyboardMacOS.mm
${TEST_FRAMEWORK_ROOT}/Input/MacOS/KeyboardMacOS.h
${TEST_FRAMEWORK_ROOT}/Input/MacOS/MouseMacOS.mm
${TEST_FRAMEWORK_ROOT}/Input/MacOS/MouseMacOS.h
${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.mm
${TEST_FRAMEWORK_ROOT}/Utils/Log.mm
${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowMacOS.mm
${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowMacOS.h
)
# Metal shaders
set(TEST_FRAMEWORK_SRC_FILES_SHADERS
${PHYSICS_REPO_ROOT}/Assets/Shaders/MTL/VertexConstants.h
)
set(TEST_FRAMEWORK_METAL_SHADERS
${PHYSICS_REPO_ROOT}/Assets/Shaders/MTL/FontShader.metal
${PHYSICS_REPO_ROOT}/Assets/Shaders/MTL/LineShader.metal
${PHYSICS_REPO_ROOT}/Assets/Shaders/MTL/TriangleShader.metal
${PHYSICS_REPO_ROOT}/Assets/Shaders/MTL/UIShader.metal
)
# Compile Metal shaders
foreach(SHADER ${TEST_FRAMEWORK_METAL_SHADERS})
cmake_path(GET SHADER FILENAME AIR_SHADER)
set(AIR_SHADER "${CMAKE_CURRENT_BINARY_DIR}/${AIR_SHADER}.air")
add_custom_command(OUTPUT ${AIR_SHADER}
COMMAND xcrun -sdk macosx metal -c ${SHADER} -o ${AIR_SHADER}
DEPENDS ${SHADER}
COMMENT "Compiling ${SHADER}")
list(APPEND TEST_FRAMEWORK_AIR_SHADERS ${AIR_SHADER})
endforeach()
# Link Metal shaders
set(TEST_FRAMEWORK_METAL_LIB ${PHYSICS_REPO_ROOT}/Assets/Shaders/MTL/Shaders.metallib)
add_custom_command(OUTPUT ${TEST_FRAMEWORK_METAL_LIB}
COMMAND xcrun -sdk macosx metallib -o ${TEST_FRAMEWORK_METAL_LIB} ${TEST_FRAMEWORK_AIR_SHADERS}
DEPENDS ${TEST_FRAMEWORK_AIR_SHADERS}
COMMENT "Linking shaders")
endif()
# Include the Vulkan library
if (Vulkan_FOUND)
# Vulkan source files
set(TEST_FRAMEWORK_SRC_FILES
${TEST_FRAMEWORK_SRC_FILES}
${TEST_FRAMEWORK_ROOT}/Renderer/VK/BufferVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/ConstantBufferVK.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/VK/ConstantBufferVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/FatalErrorIfFailedVK.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/VK/FatalErrorIfFailedVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/PipelineStateVK.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/VK/PipelineStateVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/PixelShaderVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/RendererVK.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/VK/RendererVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderInstancesVK.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderInstancesVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderPrimitiveVK.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderPrimitiveVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/TextureVK.cpp
${TEST_FRAMEWORK_ROOT}/Renderer/VK/TextureVK.h
${TEST_FRAMEWORK_ROOT}/Renderer/VK/VertexShaderVK.h
)
# GLSL headers
set(TEST_FRAMEWORK_SRC_FILES_SHADERS
${TEST_FRAMEWORK_SRC_FILES_SHADERS}
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/VertexConstants.h
)
set(TEST_FRAMEWORK_GLSL_SHADERS
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/FontVertexShader.vert
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/LineVertexShader.vert
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/TriangleDepthVertexShader.vert
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/TriangleVertexShader.vert
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/UIVertexShader.vert
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/FontPixelShader.frag
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/LinePixelShader.frag
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/TriangleDepthPixelShader.frag
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/TrianglePixelShader.frag
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/UIPixelShader.frag
${PHYSICS_REPO_ROOT}/Assets/Shaders/VK/UIPixelShaderUntextured.frag
)
# Compile GLSL shaders
foreach(SHADER ${TEST_FRAMEWORK_GLSL_SHADERS})
set(SPV_SHADER ${SHADER}.spv)
add_custom_command(OUTPUT ${SPV_SHADER}
COMMAND ${Vulkan_GLSLC_EXECUTABLE} ${SHADER} -o ${SPV_SHADER}
DEPENDS ${SHADER}
COMMENT "Compiling ${SHADER}")
list(APPEND TEST_FRAMEWORK_SPV_SHADERS ${SPV_SHADER})
endforeach()
endif()
# Assets used by the test framework
set(TEST_FRAMEWORK_ASSETS
${PHYSICS_REPO_ROOT}/Assets/Fonts/Roboto-Regular.ttf
${PHYSICS_REPO_ROOT}/Assets/UI.tga
${TEST_FRAMEWORK_SRC_FILES_SHADERS}
${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS}
${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS}
${TEST_FRAMEWORK_SPV_SHADERS}
${TEST_FRAMEWORK_METAL_LIB}
)
# Group source files
source_group(TREE ${TEST_FRAMEWORK_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES})
# Group shader files
source_group(TREE ${PHYSICS_REPO_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_GLSL_SHADERS} ${TEST_FRAMEWORK_METAL_SHADERS})
# Group intermediate files
source_group(Intermediate FILES ${TEST_FRAMEWORK_SPV_SHADERS} ${TEST_FRAMEWORK_METAL_LIB})
# Create TestFramework lib
add_library(TestFramework STATIC ${TEST_FRAMEWORK_SRC_FILES} ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_GLSL_SHADERS} ${TEST_FRAMEWORK_SPV_SHADERS} ${TEST_FRAMEWORK_METAL_SHADERS} ${TEST_FRAMEWORK_METAL_LIB})
target_include_directories(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT})
target_precompile_headers(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT}/TestFramework.h)
if (Vulkan_FOUND)
# Vulkan configuration
target_include_directories(TestFramework PUBLIC ${Vulkan_INCLUDE_DIRS})
target_link_libraries(TestFramework LINK_PUBLIC Jolt ${Vulkan_LIBRARIES})
if (JPH_ENABLE_VULKAN)
target_compile_definitions(TestFramework PRIVATE JPH_ENABLE_VULKAN)
endif()
endif()
if (WIN32)
# Windows configuration
target_link_libraries(TestFramework LINK_PUBLIC Jolt dxguid.lib dinput8.lib dxgi.lib d3d12.lib d3dcompiler.lib shcore.lib)
endif()
if (LINUX)
# Linux configuration
target_link_libraries(TestFramework LINK_PUBLIC Jolt X11)
endif()
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
# macOS configuration
target_link_libraries(TestFramework LINK_PUBLIC Jolt "-framework Cocoa -framework Metal -framework MetalKit -framework GameController")
# Make sure that all test framework assets move to the Resources folder in the package
foreach(ASSET_FILE ${TEST_FRAMEWORK_ASSETS})
string(REPLACE ${PHYSICS_REPO_ROOT}/Assets "Resources" ASSET_DST ${ASSET_FILE})
get_filename_component(ASSET_DST ${ASSET_DST} DIRECTORY)
set_source_files_properties(${ASSET_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION ${ASSET_DST})
endforeach()
# Ignore PCH files for .mm files
foreach(SRC_FILE ${TEST_FRAMEWORK_SRC_FILES})
if (SRC_FILE MATCHES "\.mm")
set_source_files_properties(${SRC_FILE} PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
endif()
endforeach()
endif()
else()
# No graphics framework found
set(TEST_FRAMEWORK_AVAILABLE FALSE)
endif()

View File

@@ -0,0 +1,62 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Jolt.h>
// Disable common warnings
JPH_SUPPRESS_WARNINGS
JPH_CLANG_SUPPRESS_WARNING("-Wheader-hygiene")
#ifdef JPH_DOUBLE_PRECISION
JPH_CLANG_SUPPRESS_WARNING("-Wdouble-promotion")
#endif // JPH_DOUBLE_PRECISION
JPH_CLANG_SUPPRESS_WARNING("-Wswitch-enum")
JPH_CLANG_SUPPRESS_WARNING("-Wswitch")
JPH_MSVC_SUPPRESS_WARNING(4061) // enumerator 'X' in switch of enum 'X' is not explicitly handled by a case label
JPH_MSVC_SUPPRESS_WARNING(4062) // enumerator 'X' in switch of enum 'X' is not handled
#ifdef JPH_PLATFORM_WINDOWS
// Targeting Windows 10 and above
#define WINVER 0x0A00
#define _WIN32_WINNT 0x0A00
JPH_SUPPRESS_WARNING_PUSH
JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
JPH_MSVC_SUPPRESS_WARNING(5204) // implements.h(65): warning C5204: 'Microsoft::WRL::CloakedIid<IMarshal>': class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed correctly
JPH_MSVC_SUPPRESS_WARNING(4265) // implements.h(1449): warning C4265: 'Microsoft::WRL::FtmBase': class has virtual functions, but its non-trivial destructor is not virtual; instances of this class may not be destructed correctly
JPH_MSVC_SUPPRESS_WARNING(5220) // implements.h(1648): warning C5220: 'Microsoft::WRL::Details::RuntimeClassImpl<Microsoft::WRL::RuntimeClassFlags<2>,true,false,true,IWeakReference>::refcount_': a non-static data member with a volatile qualified type no longer implies
JPH_MSVC_SUPPRESS_WARNING(4986) // implements.h(2343): warning C4986: 'Microsoft::WRL::Details::RuntimeClassImpl<RuntimeClassFlagsT,true,true,false,I0,TInterfaces...>::GetWeakReference': exception specification does not match previous declaration
JPH_MSVC2026_PLUS_SUPPRESS_WARNING(4865) // wingdi.h(2806,1): '<unnamed-enum-DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER>': the underlying type will change from 'int' to '__int64' when '/Zc:enumTypes' is specified on the command line
#define WIN32_LEAN_AND_MEAN
#define Ellipse DrawEllipse // Windows.h defines a name that we would like to use
#include <windows.h>
#undef Ellipse
#undef min // We'd like to use std::min and max instead of the ones defined in windows.h
#undef max
#undef DrawText // We don't want this to map to DrawTextW
#include <d3d12.h>
#include <dxgi1_6.h>
#include <wrl.h> // for ComPtr
JPH_SUPPRESS_WARNING_POP
using Microsoft::WRL::ComPtr;
#elif defined(JPH_PLATFORM_LINUX)
#define Font X11Font
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#undef Font
#undef Success
#undef None
#undef Convex
#endif
using namespace JPH;
using namespace JPH::literals;
using namespace std;

View File

@@ -0,0 +1,11 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UIAnimation.h>
JPH_IMPLEMENT_RTTI_VIRTUAL_BASE(UIAnimation)
{
}

View File

@@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/RTTI.h>
class UIElement;
/// Base class for UI element animations
class UIAnimation
{
public:
JPH_DECLARE_RTTI_VIRTUAL_BASE(JPH_NO_EXPORT, UIAnimation)
/// Destructor
virtual ~UIAnimation() = default;
///@name Interface
virtual void Init(UIElement *inElement) { }
virtual bool Update(UIElement *inElement, float inDeltaTime) { return true; } ///< Returns false when done
virtual void Exit(UIElement *inElement) { }
};
using UIAnimationVector = Array<UIAnimation *>;

View File

@@ -0,0 +1,82 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/Renderer.h>
#include <Window/ApplicationWindow.h>
#include <UI/UIAnimationSlide.h>
#include <UI/UIElement.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_ABSTRACT(UIAnimationSlide)
{
JPH_ADD_BASE_CLASS(UIAnimationSlide, UIAnimation)
}
UIAnimationSlide::UIAnimationSlide(EMode inMode, int inSlideDistanceH, int inSlideDistanceV, float inTimeBeforeSlide, float inSlideTime) :
mSlideMode(inMode),
mSlideDistanceH(inSlideDistanceH),
mSlideDistanceV(inSlideDistanceV),
mTimeBeforeSlide(inTimeBeforeSlide),
mSlideTime(inSlideTime)
{
}
void UIAnimationSlide::Init(UIElement *inElement)
{
mTargetRelativeX = inElement->GetRelativeX();
mTargetRelativeY = inElement->GetRelativeY();
ApplicationWindow *window = inElement->GetManager()->GetRenderer()->GetWindow();
int dl = inElement->GetX();
int dr = window->GetWindowWidth() - (inElement->GetX() + inElement->GetWidth());
int dt = inElement->GetY();
int db = window->GetWindowHeight() - (inElement->GetY() + inElement->GetHeight());
if (min(dl, dr) < min(dt, db))
{
mInitialRelativeX = mTargetRelativeX + (dl < dr? -mSlideDistanceH : mSlideDistanceH);
mInitialRelativeY = mTargetRelativeY;
}
else
{
mInitialRelativeX = mTargetRelativeX;
mInitialRelativeY = mTargetRelativeY + (dt < db? -mSlideDistanceV : mSlideDistanceV);
}
if (mSlideMode == SLIDE_ON_SCREEN)
inElement->SetAnimatedVisible(true);
mTime = 0.0f;
}
bool UIAnimationSlide::Update(UIElement *inElement, float inDeltaTime)
{
mTime += inDeltaTime;
float factor = (mTime - mTimeBeforeSlide) / mSlideTime;
if (factor >= 1.0f)
return false;
if (factor < 0.0f)
factor = 0.0f;
if (mSlideMode == SLIDE_OFF_SCREEN)
factor = 1.0f - factor;
float x = mInitialRelativeX * (1.0f - factor) + mTargetRelativeX * factor;
float y = mInitialRelativeY * (1.0f - factor) + mTargetRelativeY * factor;
inElement->SetRelativeX((int)x);
inElement->SetRelativeY((int)y);
return true;
}
void UIAnimationSlide::Exit(UIElement *inElement)
{
inElement->SetRelativeX(mTargetRelativeX);
inElement->SetRelativeY(mTargetRelativeY);
inElement->SetAnimatedVisible(mSlideMode == SLIDE_ON_SCREEN);
}

Some files were not shown because too many files have changed in this diff Show More