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,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;
};