Browse Source

Implement Vulkan renderer (#1420)

This is an alternative to the DirectX renderer and will be used to render the samples on other platforms than Windows.
Jorrit Rouwe 7 months ago
parent
commit
f55e0e31d0
69 changed files with 4093 additions and 1283 deletions
  1. 1 1
      .github/workflows/build.yml
  2. 1 0
      .gitignore
  3. 17 0
      Assets/Shaders/FontPixelShader.frag
  4. 2 0
      Assets/Shaders/FontPixelShader.hlsl
  5. 17 0
      Assets/Shaders/FontVertexShader.vert
  6. 10 0
      Assets/Shaders/LinePixelShader.frag
  7. 14 0
      Assets/Shaders/LineVertexShader.vert
  8. 6 0
      Assets/Shaders/TriangleDepthPixelShader.frag
  9. 31 0
      Assets/Shaders/TriangleDepthVertexShader.vert
  10. 110 0
      Assets/Shaders/TrianglePixelShader.frag
  11. 48 0
      Assets/Shaders/TriangleVertexShader.vert
  12. 13 0
      Assets/Shaders/UIPixelShader.frag
  13. 10 0
      Assets/Shaders/UIPixelShaderUntextured.frag
  14. 17 0
      Assets/Shaders/UIVertexShader.vert
  15. 7 0
      Assets/Shaders/VertexConstantsVK.h
  16. 3 0
      Build/CMakeLists.txt
  17. 16 1
      TestFramework/Application/Application.cpp
  18. 3 3
      TestFramework/Renderer/DX12/CommandQueueDX12.h
  19. 8 8
      TestFramework/Renderer/DX12/ConstantBufferDX12.cpp
  20. 6 6
      TestFramework/Renderer/DX12/ConstantBufferDX12.h
  21. 1 1
      TestFramework/Renderer/DX12/DescriptorHeapDX12.h
  22. 1 1
      TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.cpp
  23. 1 1
      TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.h
  24. 135 0
      TestFramework/Renderer/DX12/PipelineStateDX12.cpp
  25. 27 0
      TestFramework/Renderer/DX12/PipelineStateDX12.h
  26. 17 0
      TestFramework/Renderer/DX12/PixelShaderDX12.h
  27. 20 18
      TestFramework/Renderer/DX12/RenderInstancesDX12.cpp
  28. 37 0
      TestFramework/Renderer/DX12/RenderInstancesDX12.h
  29. 148 0
      TestFramework/Renderer/DX12/RenderPrimitiveDX12.cpp
  30. 45 0
      TestFramework/Renderer/DX12/RenderPrimitiveDX12.h
  31. 708 0
      TestFramework/Renderer/DX12/RendererDX12.cpp
  32. 111 0
      TestFramework/Renderer/DX12/RendererDX12.h
  33. 16 24
      TestFramework/Renderer/DX12/TextureDX12.cpp
  34. 32 0
      TestFramework/Renderer/DX12/TextureDX12.h
  35. 17 0
      TestFramework/Renderer/DX12/VertexShaderDX12.h
  36. 45 65
      TestFramework/Renderer/DebugRendererImp.cpp
  37. 5 18
      TestFramework/Renderer/DebugRendererImp.h
  38. 11 11
      TestFramework/Renderer/Font.cpp
  39. 1 0
      TestFramework/Renderer/Font.h
  40. 0 84
      TestFramework/Renderer/PipelineState.cpp
  41. 36 13
      TestFramework/Renderer/PipelineState.h
  42. 15 0
      TestFramework/Renderer/PixelShader.h
  43. 7 16
      TestFramework/Renderer/RenderInstances.h
  44. 0 121
      TestFramework/Renderer/RenderPrimitive.cpp
  45. 16 24
      TestFramework/Renderer/RenderPrimitive.h
  46. 18 705
      TestFramework/Renderer/Renderer.cpp
  47. 49 98
      TestFramework/Renderer/Renderer.h
  48. 5 20
      TestFramework/Renderer/Texture.h
  49. 35 0
      TestFramework/Renderer/VK/BufferVK.h
  50. 32 0
      TestFramework/Renderer/VK/ConstantBufferVK.cpp
  51. 30 0
      TestFramework/Renderer/VK/ConstantBufferVK.h
  52. 14 0
      TestFramework/Renderer/VK/FatalErrorIfFailedVK.cpp
  53. 11 0
      TestFramework/Renderer/VK/FatalErrorIfFailedVK.h
  54. 189 0
      TestFramework/Renderer/VK/PipelineStateVK.cpp
  55. 30 0
      TestFramework/Renderer/VK/PipelineStateVK.h
  56. 34 0
      TestFramework/Renderer/VK/PixelShaderVK.h
  57. 54 0
      TestFramework/Renderer/VK/RenderInstancesVK.cpp
  58. 35 0
      TestFramework/Renderer/VK/RenderInstancesVK.h
  59. 100 0
      TestFramework/Renderer/VK/RenderPrimitiveVK.cpp
  60. 44 0
      TestFramework/Renderer/VK/RenderPrimitiveVK.h
  61. 1112 0
      TestFramework/Renderer/VK/RendererVK.cpp
  62. 123 0
      TestFramework/Renderer/VK/RendererVK.h
  63. 180 0
      TestFramework/Renderer/VK/TextureVK.cpp
  64. 35 0
      TestFramework/Renderer/VK/TextureVK.h
  65. 34 0
      TestFramework/Renderer/VK/VertexShaderVK.h
  66. 15 0
      TestFramework/Renderer/VertexShader.h
  67. 101 24
      TestFramework/TestFramework.cmake
  68. 20 20
      TestFramework/UI/UIManager.cpp
  69. 1 0
      TestFramework/UI/UIManager.h

+ 1 - 1
.github/workflows/build.yml

@@ -193,7 +193,7 @@ jobs:
         update: true
         update: true
     - name: Configure CMake
     - name: Configure CMake
       working-directory: ${{github.workspace}}/Build
       working-directory: ${{github.workspace}}/Build
-      run: ./cmake_linux_mingw.sh ${{matrix.build_type}} g++ -DBUILD_SHARED_LIBS=${{matrix.shared_lib}}
+      run: ./cmake_linux_mingw.sh ${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.shared_lib}}
     - name: Build
     - name: Build
       run: cmake --build Build/MinGW_${{matrix.build_type}} -j $(nproc)
       run: cmake --build Build/MinGW_${{matrix.build_type}} -j $(nproc)
     - name: Test
     - name: Test

+ 1 - 0
.gitignore

@@ -6,3 +6,4 @@
 /snapshot.bin
 /snapshot.bin
 /*.jor
 /*.jor
 /detlog.txt
 /detlog.txt
+/Assets/Shaders/*.spv

+ 17 - 0
Assets/Shaders/FontPixelShader.frag

@@ -0,0 +1,17 @@
+#version 450
+
+layout(set = 1, binding = 0) uniform sampler2D texSampler;
+
+layout(location = 0) in vec2 iTex;
+layout(location = 1) in vec4 iColor;
+
+layout(location = 0) out vec4 oColor;
+
+void main()
+{
+	float t = texture(texSampler, iTex).x;
+	if (t < 0.5)
+		discard;
+	
+	oColor = vec4(iColor.xyz, t);
+}

+ 2 - 0
Assets/Shaders/FontPixelShader.hlsl

@@ -18,6 +18,8 @@ PS_OUTPUT main(PS_INPUT In)
 	PS_OUTPUT Output;
 	PS_OUTPUT Output;
 
 
 	float t = ShaderTexture.Sample(SampleType, In.Tex).r;
 	float t = ShaderTexture.Sample(SampleType, In.Tex).r;
+	if (t < 0.5)
+		discard;
 
 
 	Output.RGBColor = float4(In.Color.rgb, t);
 	Output.RGBColor = float4(In.Color.rgb, t);
 
 

+ 17 - 0
Assets/Shaders/FontVertexShader.vert

@@ -0,0 +1,17 @@
+#version 450
+
+#include "VertexConstantsVK.h"
+
+layout(location = 0) in vec3 vPos;
+layout(location = 1) in vec2 vTex;
+layout(location = 2) in vec4 vCol;
+
+layout(location = 0) out vec2 oTex;
+layout(location = 1) out vec4 oColor;
+
+void main() 
+{
+	gl_Position = c.Projection * c.View * vec4(vPos, 1.0f);
+	oTex = vTex;
+	oColor = vCol;
+}

+ 10 - 0
Assets/Shaders/LinePixelShader.frag

@@ -0,0 +1,10 @@
+#version 450
+
+layout(location = 0) in vec4 iColor;
+
+layout(location = 0) out vec4 oColor;
+
+void main()
+{
+    oColor = iColor;
+}

+ 14 - 0
Assets/Shaders/LineVertexShader.vert

@@ -0,0 +1,14 @@
+#version 450
+
+#include "VertexConstantsVK.h"
+
+layout(location = 0) in vec3 iPosition;
+layout(location = 1) in vec4 iColor;
+
+layout(location = 0) out vec4 oColor;
+
+void main() 
+{
+    gl_Position = c.Projection * c.View * vec4(iPosition, 1.0);
+    oColor = iColor;
+}

+ 6 - 0
Assets/Shaders/TriangleDepthPixelShader.frag

@@ -0,0 +1,6 @@
+#version 450
+
+// We only write depth, so this shader does nothing
+void main()
+{
+}

+ 31 - 0
Assets/Shaders/TriangleDepthVertexShader.vert

@@ -0,0 +1,31 @@
+#version 450
+
+#include "VertexConstantsVK.h"
+
+layout(location = 0) in vec3 vPos;
+layout(location = 1) in vec3 vNorm;
+layout(location = 2) in vec2 vTex;
+layout(location = 3) in vec4 vCol;
+
+layout(location = 4) in mat4 iModel;
+layout(location = 8) in mat4 iModelInvTrans;
+layout(location = 12) in vec4 iCol;
+
+void main() 
+{
+	// Check if the alpha = 0
+	if (vCol.a * iCol.a == 0.0)
+	{
+		// Don't draw the triangle by moving it to an invalid location
+		gl_Position = vec4(0, 0, 0, 0);
+	}
+	else
+	{
+		// Transform the position from world space to homogeneous projection space for the light
+		vec4 pos = vec4(vPos, 1.0f);
+		pos = iModel * pos;
+		pos = c.LightView * pos;
+		pos = c.LightProjection * pos;
+		gl_Position = pos;
+	}
+}

+ 110 - 0
Assets/Shaders/TrianglePixelShader.frag

@@ -0,0 +1,110 @@
+#version 450
+
+layout(binding = 1) uniform PixelShaderConstantBuffer
+{
+	vec3	CameraPos;
+	vec3	LightPos;
+} c;
+
+layout(location = 0) in vec3 iNormal;
+layout(location = 1) in vec3 iWorldPos;
+layout(location = 2) in vec2 iTex;
+layout(location = 3) in vec4 iPositionL;
+layout(location = 4) in vec4 iColor;
+
+layout(location = 0) out vec4 oColor;
+
+layout(set = 1, binding = 0) uniform sampler2DShadow LightDepthSampler;
+
+void main()
+{
+	// Constants
+	float AmbientFactor = 0.3;
+	vec3 DiffuseColor = vec3(iColor.r, iColor.g, iColor.b);
+	vec3 SpecularColor = vec3(1, 1, 1);
+	float SpecularPower = 100.0;
+	float bias = 1.0e-7;
+
+	// Homogenize position in light space
+	vec3 position_l = iPositionL.xyz / iPositionL.w;
+
+	// Calculate dot product between direction to light and surface normal and clamp between [0, 1]
+	vec3 view_dir = normalize(c.CameraPos - iWorldPos);
+	vec3 world_to_light = c.LightPos - iWorldPos;
+	vec3 light_dir = normalize(world_to_light);
+	vec3 normal = normalize(iNormal);
+	if (dot(view_dir, normal) < 0) // If we're viewing the triangle from the back side, flip the normal to get the correct lighting
+		normal = -normal;
+	float normal_dot_light_dir = clamp(dot(normal, light_dir), 0, 1);
+
+	// Calculate texture coordinates in light depth texture
+	vec2 tex_coord;
+	tex_coord.x = position_l.x / 2.0 + 0.5;
+	tex_coord.y = position_l.y / 2.0 + 0.5;
+
+	// Check that the texture coordinate is inside the depth texture, if not we don't know if it is lit or not so we assume lit
+	float shadow_factor = 1.0;
+	if (iColor.a > 0 // Alpha = 0 means don't receive shadows
+		&& tex_coord.x == clamp(tex_coord.x, 0, 1) && tex_coord.y == clamp(tex_coord.y, 0, 1))
+	{
+		// Modify shadow bias according to the angle between the normal and the light dir
+		float modified_bias = bias * tan(acos(normal_dot_light_dir));
+		modified_bias = min(modified_bias, 10.0 * bias);
+		
+		// Get texture size
+		float width = 1.0 / 4096;
+		float height = 1.0 / 4096;
+
+		// Samples to take
+		uint num_samples = 16;
+		vec2 offsets[] = { 
+			vec2(-1.5 * width, -1.5 * height),
+			vec2(-0.5 * width, -1.5 * height),
+			vec2(0.5 * width, -1.5 * height),
+			vec2(1.5 * width, -1.5 * height),
+
+			vec2(-1.5 * width, -0.5 * height),
+			vec2(-0.5 * width, -0.5 * height),
+			vec2(0.5 * width, -0.5 * height),
+			vec2(1.5 * width, -0.5 * height),
+
+			vec2(-1.5 * width, 0.5 * height),
+			vec2(-0.5 * width, 0.5 * height),
+			vec2(0.5 * width, 0.5 * height),
+			vec2(1.5 * width, 0.5 * height),
+
+			vec2(-1.5 * width, 1.5 * height),
+			vec2(-0.5 * width, 1.5 * height),
+			vec2(0.5 * width, 1.5 * height),
+			vec2(1.5 * width, 1.5 * height),
+		};
+
+		// Calculate depth of this pixel relative to the light
+		float light_depth = position_l.z + modified_bias;
+
+		// Sample shadow factor
+		shadow_factor = 0.0;
+		for (uint i = 0; i < num_samples; ++i)
+		{
+			vec3 location_and_reference = vec3(tex_coord + offsets[i], light_depth);
+			shadow_factor += texture(LightDepthSampler, location_and_reference);
+		}
+		shadow_factor /= num_samples;
+	}
+
+	// Calculate diffuse and specular
+	float diffuse = normal_dot_light_dir;
+	float specular = diffuse > 0.0? pow(clamp(-dot(reflect(light_dir, normal), view_dir), 0, 1), SpecularPower) : 0.0;
+
+	// Apply procedural pattern based on the uv coordinates
+	bvec2 less_half = lessThan(iTex - floor(iTex), vec2(0.5, 0.5));
+	float darken_factor = less_half.r ^^ less_half.g? 0.5 : 1.0;
+
+	// Fade out checkerboard pattern when it tiles too often
+	vec2 dx = dFdx(iTex), dy = dFdy(iTex);
+	float texel_distance = sqrt(dot(dx, dx) + dot(dy, dy));
+	darken_factor = mix(darken_factor, 0.75, clamp(5.0 * texel_distance - 1.5, 0.0, 1.0));
+
+	// Calculate color
+	oColor = vec4(clamp((AmbientFactor + diffuse * shadow_factor) * darken_factor * DiffuseColor + SpecularColor * specular * shadow_factor, 0, 1), 1);
+}

+ 48 - 0
Assets/Shaders/TriangleVertexShader.vert

@@ -0,0 +1,48 @@
+#version 450
+
+#include "VertexConstantsVK.h"
+
+layout(location = 0) in vec3 vPos;
+layout(location = 1) in vec3 vNorm;
+layout(location = 2) in vec2 vTex;
+layout(location = 3) in vec4 vCol;
+
+layout(location = 4) in mat4 iModel;
+layout(location = 8) in mat4 iModelInvTrans;
+layout(location = 12) in vec4 iCol;
+
+layout(location = 0) out vec3 oNormal;
+layout(location = 1) out vec3 oWorldPos;
+layout(location = 2) out vec2 oTex;
+layout(location = 3) out vec4 oPositionL;
+layout(location = 4) out vec4 oColor;
+
+void main() 
+{
+	// Get world position
+	vec4 pos = vec4(vPos, 1.0f);
+	vec4 world_pos = iModel * pos;
+
+	// Transform the position from world space to homogeneous projection space
+	vec4 proj_pos = c.View * world_pos;
+	proj_pos = c.Projection * proj_pos;
+	gl_Position = proj_pos;
+
+	// Transform the position from world space to projection space of the light
+	vec4 proj_lpos = c.LightView * world_pos;
+	proj_lpos = c.LightProjection * proj_lpos;
+	oPositionL = proj_lpos;
+
+	// output normal
+	vec4 norm = vec4(vNorm, 0.0f);
+	oNormal = normalize(iModelInvTrans * norm).xyz;
+
+	// output world position of the vertex
+	oWorldPos = world_pos.xyz;
+
+	// output texture coordinates
+	oTex = vTex;
+
+	// output color
+	oColor = vCol * iCol;
+}

+ 13 - 0
Assets/Shaders/UIPixelShader.frag

@@ -0,0 +1,13 @@
+#version 450
+
+layout(set = 1, binding = 0) uniform sampler2D texSampler;
+
+layout(location = 0) in vec4 iColor;
+layout(location = 1) in vec2 iTex;
+
+layout(location = 0) out vec4 oColor;
+
+void main()
+{
+	oColor = iColor * texture(texSampler, iTex);
+}

+ 10 - 0
Assets/Shaders/UIPixelShaderUntextured.frag

@@ -0,0 +1,10 @@
+#version 450
+
+layout(location = 0) in vec4 iColor;
+
+layout(location = 0) out vec4 oColor;
+
+void main()
+{
+	oColor = iColor;
+}

+ 17 - 0
Assets/Shaders/UIVertexShader.vert

@@ -0,0 +1,17 @@
+#version 450
+
+#include "VertexConstantsVK.h"
+
+layout(location = 0) in vec3 vPos;
+layout(location = 1) in vec2 vTex;
+layout(location = 2) in vec4 vCol;
+
+layout(location = 0) out vec4 oColor;
+layout(location = 1) out vec2 oTex;
+
+void main() 
+{
+	gl_Position = c.Projection * c.View * vec4(vPos, 1.0f);
+	oTex = vTex;
+	oColor = vCol;
+}

+ 7 - 0
Assets/Shaders/VertexConstantsVK.h

@@ -0,0 +1,7 @@
+layout(binding = 0) uniform VertexShaderConstantBuffer
+{
+	mat4	View;
+	mat4	Projection;
+	mat4	LightView;
+	mat4	LightProjection;
+} c;

+ 3 - 0
Build/CMakeLists.txt

@@ -106,6 +106,9 @@ include(CMakeDependentOption)
 # Windows Store only supports the DLL version
 # Windows Store only supports the DLL version
 cmake_dependent_option(USE_STATIC_MSVC_RUNTIME_LIBRARY "Use the static MSVC runtime library" ON "MSVC;NOT WINDOWS_STORE" OFF)
 cmake_dependent_option(USE_STATIC_MSVC_RUNTIME_LIBRARY "Use the static MSVC runtime library" ON "MSVC;NOT WINDOWS_STORE" OFF)
 
 
+# Enable Vulkan instead of DirectX
+cmake_dependent_option(JPH_ENABLE_VULKAN "Enable Vulkan" OFF "WIN32" ON)
+
 # Determine which configurations exist
 # Determine which configurations exist
 if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # Only do this when we're at the top level, see: https://gitlab.kitware.com/cmake/cmake/-/issues/24181
 if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # Only do this when we're at the top level, see: https://gitlab.kitware.com/cmake/cmake/-/issues/24181
 	if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
 	if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")

+ 16 - 1
TestFramework/Application/Application.cpp

@@ -14,6 +14,11 @@
 #include <Jolt/RegisterTypes.h>
 #include <Jolt/RegisterTypes.h>
 #include <Renderer/DebugRendererImp.h>
 #include <Renderer/DebugRendererImp.h>
 #include <crtdbg.h>
 #include <crtdbg.h>
+#ifdef JPH_ENABLE_VULKAN
+	#include <Renderer/VK/RendererVK.h>
+#else
+	#include <Renderer/DX12/RendererDX12.h>
+#endif
 
 
 // Constructor
 // Constructor
 Application::Application() :
 Application::Application() :
@@ -52,7 +57,11 @@ Application::Application() :
 		DisableCustomMemoryHook dcmh;
 		DisableCustomMemoryHook dcmh;
 
 
 		// Create renderer
 		// Create renderer
-		mRenderer = new Renderer;
+	#ifdef JPH_ENABLE_VULKAN
+		mRenderer = new RendererVK;
+	#else
+		mRenderer = new RendererDX12;
+	#endif
 		mRenderer->Initialize();
 		mRenderer->Initialize();
 
 
 		// Create font
 		// Create font
@@ -226,6 +235,12 @@ void Application::Run()
 			// Start rendering
 			// Start rendering
 			mRenderer->BeginFrame(mWorldCamera, GetWorldScale());
 			mRenderer->BeginFrame(mWorldCamera, GetWorldScale());
 
 
+			// Draw from light
+			static_cast<DebugRendererImp *>(mDebugRenderer)->DrawShadowPass();
+
+			// Start drawing normally
+			mRenderer->EndShadowPass();
+
 			// Draw debug information
 			// Draw debug information
 			static_cast<DebugRendererImp *>(mDebugRenderer)->Draw();
 			static_cast<DebugRendererImp *>(mDebugRenderer)->Draw();
 
 

+ 3 - 3
TestFramework/Renderer/CommandQueue.h → TestFramework/Renderer/DX12/CommandQueueDX12.h

@@ -4,14 +4,14 @@
 
 
 #pragma once
 #pragma once
 
 
-#include <Renderer/FatalErrorIfFailed.h>
+#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
 
 
 /// Holds a number of DirectX operations with logic to wait for completion
 /// Holds a number of DirectX operations with logic to wait for completion
-class CommandQueue
+class CommandQueueDX12
 {
 {
 public:
 public:
 	/// Destructor
 	/// Destructor
-										~CommandQueue()
+										~CommandQueueDX12()
 	{
 	{
 		WaitUntilFinished();
 		WaitUntilFinished();
 
 

+ 8 - 8
TestFramework/Renderer/ConstantBuffer.cpp → TestFramework/Renderer/DX12/ConstantBufferDX12.cpp

@@ -4,24 +4,24 @@
 
 
 #include <TestFramework.h>
 #include <TestFramework.h>
 
 
-#include <Renderer/ConstantBuffer.h>
-#include <Renderer/Renderer.h>
-#include <Renderer/FatalErrorIfFailed.h>
+#include <Renderer/DX12/ConstantBufferDX12.h>
+#include <Renderer/DX12/RendererDX12.h>
+#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
 
 
-ConstantBuffer::ConstantBuffer(Renderer *inRenderer, uint64 inBufferSize) :
+ConstantBufferDX12::ConstantBufferDX12(RendererDX12 *inRenderer, uint64 inBufferSize) :
 	mRenderer(inRenderer)
 	mRenderer(inRenderer)
 {
 {
 	mBuffer = mRenderer->CreateD3DResourceOnUploadHeap(inBufferSize);
 	mBuffer = mRenderer->CreateD3DResourceOnUploadHeap(inBufferSize);
 	mBufferSize = inBufferSize;
 	mBufferSize = inBufferSize;
 }
 }
 
 
-ConstantBuffer::~ConstantBuffer()
+ConstantBufferDX12::~ConstantBufferDX12()
 {
 {
 	if (mBuffer != nullptr)
 	if (mBuffer != nullptr)
 		mRenderer->RecycleD3DResourceOnUploadHeap(mBuffer.Get(), mBufferSize);
 		mRenderer->RecycleD3DResourceOnUploadHeap(mBuffer.Get(), mBufferSize);
 }
 }
 
 
-void *ConstantBuffer::MapInternal()
+void *ConstantBufferDX12::MapInternal()
 {
 {
 	void *mapped_resource;
 	void *mapped_resource;
 	D3D12_RANGE range = { 0, 0 }; // We're not going to read
 	D3D12_RANGE range = { 0, 0 }; // We're not going to read
@@ -29,12 +29,12 @@ void *ConstantBuffer::MapInternal()
 	return mapped_resource;
 	return mapped_resource;
 }
 }
 
 
-void ConstantBuffer::Unmap()
+void ConstantBufferDX12::Unmap()
 {
 {
 	mBuffer->Unmap(0, nullptr);
 	mBuffer->Unmap(0, nullptr);
 }
 }
 
 
-void ConstantBuffer::Bind(int inSlot)
+void ConstantBufferDX12::Bind(int inSlot)
 {
 {
 	mRenderer->GetCommandList()->SetGraphicsRootConstantBufferView(inSlot, mBuffer->GetGPUVirtualAddress());
 	mRenderer->GetCommandList()->SetGraphicsRootConstantBufferView(inSlot, mBuffer->GetGPUVirtualAddress());
 }
 }

+ 6 - 6
TestFramework/Renderer/ConstantBuffer.h → TestFramework/Renderer/DX12/ConstantBufferDX12.h

@@ -4,15 +4,15 @@
 
 
 #pragma once
 #pragma once
 
 
-class Renderer;
+class RendererDX12;
 
 
 /// A binary blob that can be used to pass constants to a shader
 /// A binary blob that can be used to pass constants to a shader
-class ConstantBuffer
+class ConstantBufferDX12
 {
 {
 public:
 public:
 	/// Constructor
 	/// Constructor
-										ConstantBuffer(Renderer *inRenderer, uint64 inBufferSize);
-										~ConstantBuffer();
+										ConstantBufferDX12(RendererDX12 *inRenderer, uint64 inBufferSize);
+										~ConstantBufferDX12();
 
 
 	/// Map / unmap buffer (get pointer to data). This will discard all data in the buffer.
 	/// 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()); }
 	template <typename T> T *			Map()											{ return reinterpret_cast<T *>(MapInternal()); }
@@ -22,11 +22,11 @@ public:
 	void								Bind(int inSlot);
 	void								Bind(int inSlot);
 
 
 private:
 private:
-	friend class Renderer;
+	friend class RendererDX12;
 
 
 	void *								MapInternal();
 	void *								MapInternal();
 
 
-	Renderer *							mRenderer;
+	RendererDX12 *						mRenderer;
 	ComPtr<ID3D12Resource>				mBuffer;
 	ComPtr<ID3D12Resource>				mBuffer;
 	uint64								mBufferSize;
 	uint64								mBufferSize;
 };
 };

+ 1 - 1
TestFramework/Renderer/DescriptorHeap.h → TestFramework/Renderer/DX12/DescriptorHeapDX12.h

@@ -5,7 +5,7 @@
 #pragma once
 #pragma once
 
 
 /// DirectX descriptor heap, used to allocate handles for resources to bind them to shaders
 /// DirectX descriptor heap, used to allocate handles for resources to bind them to shaders
-class DescriptorHeap
+class DescriptorHeapDX12
 {
 {
 public:
 public:
 	/// Initialize the heap
 	/// Initialize the heap

+ 1 - 1
TestFramework/Renderer/FatalErrorIfFailed.cpp → TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.cpp

@@ -6,7 +6,7 @@
 
 
 #include <system_error>
 #include <system_error>
 
 
-#include <Renderer/FatalErrorIfFailed.h>
+#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
 #include <Jolt/Core/StringTools.h>
 #include <Jolt/Core/StringTools.h>
 #include <Utils/Log.h>
 #include <Utils/Log.h>
 
 

+ 1 - 1
TestFramework/Renderer/FatalErrorIfFailed.h → TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.h

@@ -4,6 +4,6 @@
 
 
 #pragma once
 #pragma once
 
 
-/// Convert DirectX error codes to exceptions
+/// Convert DirectX error to readable text and alert
 void FatalErrorIfFailed(HRESULT inHResult);
 void FatalErrorIfFailed(HRESULT inHResult);
 
 

+ 135 - 0
TestFramework/Renderer/DX12/PipelineStateDX12.cpp

@@ -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;
+	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());
+}

+ 27 - 0
TestFramework/Renderer/DX12/PipelineStateDX12.h

@@ -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;
+};

+ 17 - 0
TestFramework/Renderer/DX12/PixelShaderDX12.h

@@ -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
+};

+ 20 - 18
TestFramework/Renderer/RenderInstances.cpp → TestFramework/Renderer/DX12/RenderInstancesDX12.cpp

@@ -1,14 +1,14 @@
 // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
 // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
-// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
 // SPDX-License-Identifier: MIT
 // SPDX-License-Identifier: MIT
 
 
 #include <TestFramework.h>
 #include <TestFramework.h>
 
 
-#include <Renderer/RenderInstances.h>
-#include <Renderer/RenderPrimitive.h>
-#include <Renderer/FatalErrorIfFailed.h>
+#include <Renderer/DX12/RenderInstancesDX12.h>
+#include <Renderer/DX12/RenderPrimitiveDX12.h>
+#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
 
 
-void RenderInstances::Clear()
+void RenderInstancesDX12::Clear()
 {
 {
 	if (mInstanceBuffer != nullptr)
 	if (mInstanceBuffer != nullptr)
 		mRenderer->RecycleD3DResourceOnUploadHeap(mInstanceBuffer.Get(), mInstanceBufferSize);
 		mRenderer->RecycleD3DResourceOnUploadHeap(mInstanceBuffer.Get(), mInstanceBufferSize);
@@ -18,7 +18,7 @@ void RenderInstances::Clear()
 	mInstanceSize = 0;
 	mInstanceSize = 0;
 }
 }
 
 
-void RenderInstances::CreateBuffer(int inNumInstances, int inInstanceSize)
+void RenderInstancesDX12::CreateBuffer(int inNumInstances, int inInstanceSize)
 {
 {
 	if (mInstanceBuffer == nullptr || mInstanceBufferSize < inNumInstances * inInstanceSize)
 	if (mInstanceBuffer == nullptr || mInstanceBufferSize < inNumInstances * inInstanceSize)
 	{
 	{
@@ -37,7 +37,7 @@ void RenderInstances::CreateBuffer(int inNumInstances, int inInstanceSize)
 	mInstanceSize = inInstanceSize;
 	mInstanceSize = inInstanceSize;
 }
 }
 
 
-void *RenderInstances::Lock()
+void *RenderInstancesDX12::Lock()
 {
 {
 	uint32 *mapped_resource;
 	uint32 *mapped_resource;
 	D3D12_RANGE range = { 0, 0 };
 	D3D12_RANGE range = { 0, 0 };
@@ -45,27 +45,29 @@ void *RenderInstances::Lock()
 	return mapped_resource;
 	return mapped_resource;
 }
 }
 
 
-void RenderInstances::Unlock()
+void RenderInstancesDX12::Unlock()
 {
 {
 	mInstanceBuffer->Unmap(0, nullptr);
 	mInstanceBuffer->Unmap(0, nullptr);
 }
 }
 
 
-void RenderInstances::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const
+void RenderInstancesDX12::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const
 {
 {
 	if (inNumInstances <= 0)
 	if (inNumInstances <= 0)
 		return;
 		return;
 
 
+	RenderPrimitiveDX12 *primitive = static_cast<RenderPrimitiveDX12 *>(inPrimitive);
+
 	ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList();
 	ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList();
 
 
 	// Set topology
 	// Set topology
-	command_list->IASetPrimitiveTopology(inPrimitive->mType);
+	command_list->IASetPrimitiveTopology(primitive->mType == PipelineState::ETopology::Triangle? D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST : D3D_PRIMITIVE_TOPOLOGY_LINELIST);
 
 
 	D3D12_VERTEX_BUFFER_VIEW vb_view[2];
 	D3D12_VERTEX_BUFFER_VIEW vb_view[2];
 
 
 	// Vertex buffer
 	// Vertex buffer
-	vb_view[0].BufferLocation = inPrimitive->mVtxBuffer->GetGPUVirtualAddress();
-	vb_view[0].StrideInBytes = inPrimitive->mVtxSize;
-	vb_view[0].SizeInBytes = inPrimitive->mNumVtxToDraw * inPrimitive->mVtxSize;
+	vb_view[0].BufferLocation = primitive->mVtxBuffer->GetGPUVirtualAddress();
+	vb_view[0].StrideInBytes = primitive->mVtxSize;
+	vb_view[0].SizeInBytes = primitive->mNumVtxToDraw * primitive->mVtxSize;
 
 
 	// Instances buffer
 	// Instances buffer
 	vb_view[1].BufferLocation = mInstanceBuffer->GetGPUVirtualAddress();
 	vb_view[1].BufferLocation = mInstanceBuffer->GetGPUVirtualAddress();
@@ -74,21 +76,21 @@ void RenderInstances::Draw(RenderPrimitive *inPrimitive, int inStartInstance, in
 
 
 	command_list->IASetVertexBuffers(0, 2, &vb_view[0]);
 	command_list->IASetVertexBuffers(0, 2, &vb_view[0]);
 
 
-	if (inPrimitive->mIdxBuffer == nullptr)
+	if (primitive->mIdxBuffer == nullptr)
 	{
 	{
 		// Draw instanced primitive
 		// Draw instanced primitive
-		command_list->DrawInstanced(inPrimitive->mNumVtxToDraw, inNumInstances, 0, inStartInstance);
+		command_list->DrawInstanced(primitive->mNumVtxToDraw, inNumInstances, 0, inStartInstance);
 	}
 	}
 	else
 	else
 	{
 	{
 		// Set index buffer
 		// Set index buffer
 		D3D12_INDEX_BUFFER_VIEW ib_view;
 		D3D12_INDEX_BUFFER_VIEW ib_view;
-		ib_view.BufferLocation = inPrimitive->mIdxBuffer->GetGPUVirtualAddress();
-		ib_view.SizeInBytes = inPrimitive->mNumIdxToDraw * sizeof(uint32);
+		ib_view.BufferLocation = primitive->mIdxBuffer->GetGPUVirtualAddress();
+		ib_view.SizeInBytes = primitive->mNumIdxToDraw * sizeof(uint32);
 		ib_view.Format = DXGI_FORMAT_R32_UINT;
 		ib_view.Format = DXGI_FORMAT_R32_UINT;
 		command_list->IASetIndexBuffer(&ib_view);
 		command_list->IASetIndexBuffer(&ib_view);
 
 
 		// Draw instanced primitive
 		// Draw instanced primitive
-		command_list->DrawIndexedInstanced(inPrimitive->mNumIdxToDraw, inNumInstances, 0, 0, inStartInstance);
+		command_list->DrawIndexedInstanced(primitive->mNumIdxToDraw, inNumInstances, 0, 0, inStartInstance);
 	}
 	}
 }
 }

+ 37 - 0
TestFramework/Renderer/DX12/RenderInstancesDX12.h

@@ -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;
+	int						mInstanceBufferSize = 0;
+	int						mInstanceSize = 0;
+};

+ 148 - 0
TestFramework/Renderer/DX12/RenderPrimitiveDX12.cpp

@@ -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);
+	}
+}

+ 45 - 0
TestFramework/Renderer/DX12/RenderPrimitiveDX12.h

@@ -0,0 +1,45 @@
+// 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:
+	/// 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;
+};

+ 708 - 0
TestFramework/Renderer/DX12/RendererDX12.cpp

@@ -0,0 +1,708 @@
+// 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 <Jolt/Core/Profiler.h>
+#include <Utils/ReadData.h>
+#include <Utils/Log.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 = mWindowWidth;
+	depth_stencil_desc.Height = mWindowHeight;
+	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()
+{
+	Renderer::Initialize();
+
+#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(mhWnd, 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 = mWindowWidth;
+	swap_chain_desc.BufferDesc.Height = mWindowHeight;
+	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 = mhWnd;
+	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()
+{
+	Renderer::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, mWindowWidth, mWindowHeight, 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();
+}
+
+void 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);
+}
+
+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>(mWindowWidth), static_cast<float>(mWindowHeight), 0.0f, 1.0f };
+	mCommandList->RSSetViewports(1, &viewport);
+
+	// Set scissor rect
+	D3D12_RECT scissor_rect = { 0, 0, static_cast<LONG>(mWindowWidth), static_cast<LONG>(mWindowHeight) };
+	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 *inFileName)
+{
+	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
+	Array<uint8> data = ReadData((String(inFileName) + ".hlsl").c_str());
+
+	// Compile source
+	ComPtr<ID3DBlob> shader_blob, error_blob;
+	HRESULT hr = D3DCompile(&data[0],
+							(uint)data.size(),
+							inFileName,
+							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 *inFileName)
+{
+	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
+	Array<uint8> data = ReadData((String(inFileName) + ".hlsl").c_str());
+
+	// Compile source
+	ComPtr<ID3DBlob> shader_blob, error_blob;
+	HRESULT hr = D3DCompile(&data[0],
+							(uint)data.size(),
+							inFileName,
+							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);
+}

+ 111 - 0
TestFramework/Renderer/DX12/RendererDX12.h

@@ -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() override;
+	virtual void					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 *inFileName) override;
+	virtual Ref<PixelShader>		CreatePixelShader(const char *inFileName) 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
+};

+ 16 - 24
TestFramework/Renderer/Texture.cpp → TestFramework/Renderer/DX12/TextureDX12.cpp

@@ -1,28 +1,25 @@
 // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
 // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
-// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
 // SPDX-License-Identifier: MIT
 // SPDX-License-Identifier: MIT
 
 
 #include <TestFramework.h>
 #include <TestFramework.h>
 
 
-#include <Renderer/Texture.h>
+#include <Renderer/DX12/TextureDX12.h>
+#include <Renderer/DX12/RendererDX12.h>
+#include <Renderer/DX12/FatalErrorIfFailedDX12.h>
 #include <Image/BlitSurface.h>
 #include <Image/BlitSurface.h>
-#include <Renderer/Renderer.h>
-#include <Renderer/FatalErrorIfFailed.h>
 
 
-Texture::Texture(Renderer *inRenderer, const Surface *inSurface) :
+TextureDX12::TextureDX12(RendererDX12 *inRenderer, const Surface *inSurface) :
+	Texture(inSurface->GetWidth(), inSurface->GetHeight()),
 	mRenderer(inRenderer)
 	mRenderer(inRenderer)
 {
 {
-	// Store dimensions
-	mWidth = inSurface->GetWidth();
-	mHeight = inSurface->GetHeight();
-
 	// Create description
 	// Create description
 	D3D12_RESOURCE_DESC desc = {};
 	D3D12_RESOURCE_DESC desc = {};
 	desc.MipLevels = 1;
 	desc.MipLevels = 1;
 	ESurfaceFormat format = inSurface->GetFormat();
 	ESurfaceFormat format = inSurface->GetFormat();
 	switch (format)
 	switch (format)
 	{
 	{
-	case ESurfaceFormat::A4L4:			desc.Format = DXGI_FORMAT_R8G8_UNORM;		break;
+	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::L8:			desc.Format = DXGI_FORMAT_R8_UNORM;			break;
 	case ESurfaceFormat::A8:			desc.Format = DXGI_FORMAT_A8_UNORM;			break;
 	case ESurfaceFormat::A8:			desc.Format = DXGI_FORMAT_A8_UNORM;			break;
 	case ESurfaceFormat::A8L8:			desc.Format = DXGI_FORMAT_R8G8_UNORM;		break;
 	case ESurfaceFormat::A8L8:			desc.Format = DXGI_FORMAT_R8G8_UNORM;		break;
@@ -128,13 +125,10 @@ Texture::Texture(Renderer *inRenderer, const Surface *inSurface) :
 	inRenderer->RecycleD3DResourceOnUploadHeap(upload_resource.Get(), required_size);
 	inRenderer->RecycleD3DResourceOnUploadHeap(upload_resource.Get(), required_size);
 }
 }
 
 
-Texture::Texture(Renderer *inRenderer, int inWidth, int inHeight) :
+TextureDX12::TextureDX12(RendererDX12 *inRenderer, int inWidth, int inHeight) :
+	Texture(inWidth, inHeight),
 	mRenderer(inRenderer)
 	mRenderer(inRenderer)
 {
 {
-	// Store dimensions
-	mWidth = inWidth;
-	mHeight = inHeight;
-
 	// Allocate depth stencil buffer
 	// Allocate depth stencil buffer
 	D3D12_CLEAR_VALUE clear_value = {};
 	D3D12_CLEAR_VALUE clear_value = {};
 	clear_value.Format = DXGI_FORMAT_D32_FLOAT;
 	clear_value.Format = DXGI_FORMAT_D32_FLOAT;
@@ -182,7 +176,7 @@ Texture::Texture(Renderer *inRenderer, int inWidth, int inHeight) :
 	inRenderer->GetDevice()->CreateShaderResourceView(mTexture.Get(), &srv_desc, mSRV);
 	inRenderer->GetDevice()->CreateShaderResourceView(mTexture.Get(), &srv_desc, mSRV);
 }
 }
 
 
-Texture::~Texture()
+TextureDX12::~TextureDX12()
 {
 {
 	if (mSRV.ptr != 0)
 	if (mSRV.ptr != 0)
 		mRenderer->GetSRVHeap().Free(mSRV);
 		mRenderer->GetSRVHeap().Free(mSRV);
@@ -194,17 +188,12 @@ Texture::~Texture()
 		mRenderer->RecycleD3DObject(mTexture.Get());
 		mRenderer->RecycleD3DObject(mTexture.Get());
 }
 }
 
 
-void Texture::Bind(int inSlot) const
-{
-	mRenderer->GetCommandList()->SetGraphicsRootDescriptorTable(inSlot, mRenderer->GetSRVHeap().ConvertToGPUHandle(mSRV));
-}
-
-void Texture::ClearRenderTarget()
+void TextureDX12::Bind() const
 {
 {
-	mRenderer->GetCommandList()->ClearDepthStencilView(mDSV, D3D12_CLEAR_FLAG_DEPTH, 0, 0, 0, nullptr);
+	mRenderer->GetCommandList()->SetGraphicsRootDescriptorTable(2 /* All shaders use slot 2 to bind their texture */, mRenderer->GetSRVHeap().ConvertToGPUHandle(mSRV));
 }
 }
 
 
-void Texture::SetAsRenderTarget(bool inSet) const
+void TextureDX12::SetAsRenderTarget(bool inSet) const
 {
 {
 	if (inSet)
 	if (inSet)
 	{
 	{
@@ -228,6 +217,9 @@ void Texture::SetAsRenderTarget(bool inSet) const
 		// Set scissor rect
 		// Set scissor rect
 		D3D12_RECT scissor_rect = { 0, 0, static_cast<LONG>(mWidth), static_cast<LONG>(mHeight) };
 		D3D12_RECT scissor_rect = { 0, 0, static_cast<LONG>(mWidth), static_cast<LONG>(mHeight) };
 		mRenderer->GetCommandList()->RSSetScissorRects(1, &scissor_rect);
 		mRenderer->GetCommandList()->RSSetScissorRects(1, &scissor_rect);
+
+		// Clear the render target
+		mRenderer->GetCommandList()->ClearDepthStencilView(mDSV, D3D12_CLEAR_FLAG_DEPTH, 0, 0, 0, nullptr);
 	}
 	}
 	else
 	else
 	{
 	{

+ 32 - 0
TestFramework/Renderer/DX12/TextureDX12.h

@@ -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
+};

+ 17 - 0
TestFramework/Renderer/DX12/VertexShaderDX12.h

@@ -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
+};

+ 45 - 65
TestFramework/Renderer/DebugRendererImp.cpp

@@ -6,8 +6,6 @@
 
 
 #include <Renderer/DebugRendererImp.h>
 #include <Renderer/DebugRendererImp.h>
 #include <Renderer/Renderer.h>
 #include <Renderer/Renderer.h>
-#include <Renderer/RenderPrimitive.h>
-#include <Renderer/Texture.h>
 #include <Renderer/Font.h>
 #include <Renderer/Font.h>
 
 
 #ifndef JPH_DEBUG_RENDERER
 #ifndef JPH_DEBUG_RENDERER
@@ -22,55 +20,46 @@ DebugRendererImp::DebugRendererImp(Renderer *inRenderer, const Font *inFont) :
 	mFont(inFont)
 	mFont(inFont)
 {
 {
 	// Create input layout for lines
 	// Create input layout for lines
-	const D3D12_INPUT_ELEMENT_DESC line_vertex_desc[] =
+	const PipelineState::EInputDescription line_vertex_desc[] =
 	{
 	{
-		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
+		PipelineState::EInputDescription::Position,
+		PipelineState::EInputDescription::Color
 	};
 	};
 
 
 	// Lines
 	// Lines
-	ComPtr<ID3DBlob> vtx_line = mRenderer->CreateVertexShader("Assets/Shaders/LineVertexShader.hlsl");
-	ComPtr<ID3DBlob> pix_line = mRenderer->CreatePixelShader("Assets/Shaders/LinePixelShader.hlsl");
-	mLineState = mRenderer->CreatePipelineState(vtx_line.Get(), line_vertex_desc, ARRAYSIZE(line_vertex_desc), pix_line.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
+	Ref<VertexShader> vtx_line = mRenderer->CreateVertexShader("Assets/Shaders/LineVertexShader");
+	Ref<PixelShader> pix_line = mRenderer->CreatePixelShader("Assets/Shaders/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
 	// Create input layout for triangles
-	const D3D12_INPUT_ELEMENT_DESC triangles_vertex_desc[] =
+	const PipelineState::EInputDescription triangles_vertex_desc[] =
 	{
 	{
-		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "INSTANCE_TRANSFORM", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_TRANSFORM", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_TRANSFORM", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_TRANSFORM", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_INV_TRANSFORM", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 64, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_INV_TRANSFORM", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 80, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_INV_TRANSFORM", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 96, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_INV_TRANSFORM", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 112, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
-		{ "INSTANCE_COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 1, 128, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },
+		PipelineState::EInputDescription::Position,
+		PipelineState::EInputDescription::Normal,
+		PipelineState::EInputDescription::TexCoord,
+		PipelineState::EInputDescription::Color,
+		PipelineState::EInputDescription::InstanceTransform,
+		PipelineState::EInputDescription::InstanceInvTransform,
+		PipelineState::EInputDescription::InstanceColor
 	};
 	};
 
 
 	// Triangles
 	// Triangles
-	ComPtr<ID3DBlob> vtx_triangle = mRenderer->CreateVertexShader("Assets/Shaders/TriangleVertexShader.hlsl");
-	ComPtr<ID3DBlob> pix_triangle  = mRenderer->CreatePixelShader("Assets/Shaders/TrianglePixelShader.hlsl");
-	mTriangleStateBF = mRenderer->CreatePipelineState(vtx_triangle.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_triangle.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
-	mTriangleStateFF = mRenderer->CreatePipelineState(vtx_triangle.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_triangle.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace);
-	mTriangleStateWire = mRenderer->CreatePipelineState(vtx_triangle.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_triangle.Get(), D3D12_FILL_MODE_WIREFRAME, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
+	Ref<VertexShader> vtx_triangle = mRenderer->CreateVertexShader("Assets/Shaders/TriangleVertexShader");
+	Ref<PixelShader> pix_triangle  = mRenderer->CreatePixelShader("Assets/Shaders/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
 	// Shadow pass
-	ComPtr<ID3DBlob> vtx_shadow = mRenderer->CreateVertexShader("Assets/Shaders/TriangleDepthVertexShader.hlsl");
-	ComPtr<ID3DBlob> pix_shadow = mRenderer->CreatePixelShader("Assets/Shaders/TriangleDepthPixelShader.hlsl");
-	mShadowStateBF = mRenderer->CreatePipelineState(vtx_shadow.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_shadow.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
-	mShadowStateFF = mRenderer->CreatePipelineState(vtx_shadow.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_shadow.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace);
-	mShadowStateWire = mRenderer->CreatePipelineState(vtx_shadow.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_shadow.Get(), D3D12_FILL_MODE_WIREFRAME, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
-
-	// Create depth only texture (no color buffer, as seen from light)
-	mDepthTexture = mRenderer->CreateRenderTarget(4096, 4096);
+	Ref<VertexShader> vtx_shadow = mRenderer->CreateVertexShader("Assets/Shaders/TriangleDepthVertexShader");
+	Ref<PixelShader> pix_shadow = mRenderer->CreatePixelShader("Assets/Shaders/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
 	// Create instances buffer
 	for (uint n = 0; n < Renderer::cFrameCount; ++n)
 	for (uint n = 0; n < Renderer::cFrameCount; ++n)
-		mInstancesBuffer[n] = new RenderInstances(mRenderer);
+		mInstancesBuffer[n] = mRenderer->CreateRenderInstances();
 
 
 	// Create empty batch
 	// Create empty batch
 	Vertex empty_vertex { Float3(0, 0, 0), Float3(1, 0, 0), Float2(0, 0), Color::sWhite };
 	Vertex empty_vertex { Float3(0, 0, 0), Float3(1, 0, 0), Float2(0, 0), Color::sWhite };
@@ -100,7 +89,7 @@ DebugRenderer::Batch DebugRendererImp::CreateTriangleBatch(const Triangle *inTri
 	if (inTriangles == nullptr || inTriangleCount == 0)
 	if (inTriangles == nullptr || inTriangleCount == 0)
 		return mEmptyBatch;
 		return mEmptyBatch;
 
 
-	BatchImpl *primitive = new BatchImpl(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+	RenderPrimitive *primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
 	primitive->CreateVertexBuffer(3 * inTriangleCount, sizeof(Vertex), inTriangles);
 	primitive->CreateVertexBuffer(3 * inTriangleCount, sizeof(Vertex), inTriangles);
 
 
 	return primitive;
 	return primitive;
@@ -111,7 +100,7 @@ DebugRenderer::Batch DebugRendererImp::CreateTriangleBatch(const Vertex *inVerti
 	if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0)
 	if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0)
 		return mEmptyBatch;
 		return mEmptyBatch;
 
 
-	BatchImpl *primitive = new BatchImpl(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+	RenderPrimitive *primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
 	primitive->CreateVertexBuffer(inVertexCount, sizeof(Vertex), inVertices);
 	primitive->CreateVertexBuffer(inVertexCount, sizeof(Vertex), inVertices);
 	primitive->CreateIndexBuffer(inIndexCount, inIndices);
 	primitive->CreateIndexBuffer(inIndexCount, inIndices);
 
 
@@ -158,16 +147,14 @@ void DebugRendererImp::FinalizePrimitive()
 
 
 	if (mLockedPrimitive != nullptr)
 	if (mLockedPrimitive != nullptr)
 	{
 	{
-		BatchImpl *primitive = static_cast<BatchImpl *>(mLockedPrimitive.GetPtr());
-
 		// Unlock the primitive
 		// Unlock the primitive
-		primitive->UnlockVertexBuffer();
+		mLockedPrimitive->UnlockVertexBuffer();
 
 
 		// Set number of indices to draw
 		// Set number of indices to draw
-		primitive->SetNumVtxToDraw(int(mLockedVertices - mLockedVerticesStart));
+		mLockedPrimitive->SetNumVtxToDraw(int(mLockedVertices - mLockedVerticesStart));
 
 
 		// Add to draw list
 		// Add to draw list
-		mTempPrimitives[new Geometry(mLockedPrimitive, mLockedPrimitiveBounds)].mInstances.push_back({ Mat44::sIdentity(), Mat44::sIdentity(), Color::sWhite, mLockedPrimitiveBounds, 1.0f });
+		mTempPrimitives[new Geometry(mLockedPrimitive.GetPtr(), mLockedPrimitiveBounds)].mInstances.push_back({ Mat44::sIdentity(), Mat44::sIdentity(), Color::sWhite, mLockedPrimitiveBounds, 1.0f });
 		++mNumInstances;
 		++mNumInstances;
 
 
 		// Clear pointers
 		// Clear pointers
@@ -189,12 +176,11 @@ void DebugRendererImp::EnsurePrimitiveSpace(int inVtxSize)
 		FinalizePrimitive();
 		FinalizePrimitive();
 
 
 		// Create new
 		// Create new
-		BatchImpl *primitive = new BatchImpl(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-		primitive->CreateVertexBuffer(cVertexBufferSize, sizeof(Vertex));
-		mLockedPrimitive = primitive;
-
+		mLockedPrimitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
+		mLockedPrimitive->CreateVertexBuffer(cVertexBufferSize, sizeof(Vertex));
+		
 		// Lock buffers
 		// Lock buffers
-		mLockedVerticesStart = mLockedVertices = (Vertex *)primitive->LockVertexBuffer();
+		mLockedVerticesStart = mLockedVertices = (Vertex *)mLockedPrimitive->LockVertexBuffer();
 		mLockedVerticesEnd = mLockedVertices + cVertexBufferSize;
 		mLockedVerticesEnd = mLockedVertices + cVertexBufferSize;
 	}
 	}
 }
 }
@@ -240,7 +226,7 @@ void DebugRendererImp::DrawInstances(const Geometry *inGeometry, const Array<int
 			int start_idx = next_start_idx;
 			int start_idx = next_start_idx;
 			next_start_idx = inStartIdx[lod + 1];
 			next_start_idx = inStartIdx[lod + 1];
 			int num_instances = next_start_idx - start_idx;
 			int num_instances = next_start_idx - start_idx;
-			instances_buffer->Draw(static_cast<BatchImpl *>(geometry_lods[lod].mTriangleBatch.GetPtr()), start_idx, num_instances);
+			instances_buffer->Draw(static_cast<RenderPrimitive *>(geometry_lods[lod].mTriangleBatch.GetPtr()), start_idx, num_instances);
 		}
 		}
 	}
 	}
 }
 }
@@ -264,17 +250,17 @@ void DebugRendererImp::DrawLines()
 	// Draw the lines
 	// Draw the lines
 	if (!mLines.empty())
 	if (!mLines.empty())
 	{
 	{
-		RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_LINELIST);
-		primitive.CreateVertexBuffer((int)mLines.size() * 2, sizeof(Line) / 2);
-		void *data = primitive.LockVertexBuffer();
+		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));
 		memcpy(data, &mLines[0], mLines.size() * sizeof(Line));
-		primitive.UnlockVertexBuffer();
+		primitive->UnlockVertexBuffer();
 		mLineState->Activate();
 		mLineState->Activate();
-		primitive.Draw();
+		primitive->Draw();
 	}
 	}
 }
 }
 
 
-void DebugRendererImp::DrawTriangles()
+void DebugRendererImp::DrawShadowPass()
 {
 {
 	JPH_PROFILE_FUNCTION();
 	JPH_PROFILE_FUNCTION();
 
 
@@ -283,12 +269,6 @@ void DebugRendererImp::DrawTriangles()
 	// Finish the last primitive
 	// Finish the last primitive
 	FinalizePrimitive();
 	FinalizePrimitive();
 
 
-	// Render to shadow map texture first
-	mRenderer->SetRenderTarget(mDepthTexture);
-
-	// Clear the shadow map texture to max depth
-	mDepthTexture->ClearRenderTarget();
-
 	// Get the camera and light frustum for culling
 	// Get the camera and light frustum for culling
 	Vec3 camera_pos(mRenderer->GetCameraState().mPos - mRenderer->GetBaseOffset());
 	Vec3 camera_pos(mRenderer->GetCameraState().mPos - mRenderer->GetBaseOffset());
 	const Frustum &camera_frustum = mRenderer->GetCameraFrustum();
 	const Frustum &camera_frustum = mRenderer->GetCameraFrustum();
@@ -411,12 +391,12 @@ void DebugRendererImp::DrawTriangles()
 		for (InstanceMap::value_type &v : mWireframePrimitives)
 		for (InstanceMap::value_type &v : mWireframePrimitives)
 			DrawInstances(v.first, v.second.mLightStartIdx);
 			DrawInstances(v.first, v.second.mLightStartIdx);
 	}
 	}
+}
 
 
-	// Switch to the main render target
-	mRenderer->SetRenderTarget(nullptr);
-
+void DebugRendererImp::DrawTriangles()
+{
 	// Bind the shadow map texture
 	// Bind the shadow map texture
-	mDepthTexture->Bind(2);
+	mRenderer->GetShadowMap()->Bind();
 
 
 	if (!mPrimitives.empty() || !mTempPrimitives.empty())
 	if (!mPrimitives.empty() || !mTempPrimitives.empty())
 	{
 	{

+ 5 - 18
TestFramework/Renderer/DebugRendererImp.h

@@ -16,8 +16,7 @@
 	#undef JPH_DEBUG_RENDERER_EXPORT
 	#undef JPH_DEBUG_RENDERER_EXPORT
 #endif
 #endif
 
 
-#include <Renderer/RenderPrimitive.h>
-#include <Renderer/RenderInstances.h>
+#include <Renderer/Renderer.h>
 #include <Jolt/Core/Mutex.h>
 #include <Jolt/Core/Mutex.h>
 #include <Jolt/Core/UnorderedMap.h>
 #include <Jolt/Core/UnorderedMap.h>
 
 
@@ -41,6 +40,9 @@ public:
 	virtual void						DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode) 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;
 	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
 	/// Draw all primitives that were added
 	void								Draw();
 	void								Draw();
 
 
@@ -58,18 +60,6 @@ private:
 	void								ClearTriangles();
 	void								ClearTriangles();
 	void								ClearTexts();
 	void								ClearTexts();
 
 
-	/// Implementation specific batch object
-	class BatchImpl : public RefTargetVirtual, public RenderPrimitive
-	{
-	public:
-		JPH_OVERRIDE_NEW_DELETE
-
-										BatchImpl(Renderer *inRenderer, D3D_PRIMITIVE_TOPOLOGY inType) : RenderPrimitive(inRenderer, inType) { }
-
-		virtual void					AddRef() override			{ RenderPrimitive::AddRef(); }
-		virtual void					Release() override			{ if (--mRefCount == 0) delete this; }
-	};
-
 	/// Finalize the current locked primitive and add it to the primitives to draw
 	/// Finalize the current locked primitive and add it to the primitives to draw
 	void								FinalizePrimitive();
 	void								FinalizePrimitive();
 
 
@@ -88,9 +78,6 @@ private:
 	unique_ptr<PipelineState>			mShadowStateFF;
 	unique_ptr<PipelineState>			mShadowStateFF;
 	unique_ptr<PipelineState>			mShadowStateWire;
 	unique_ptr<PipelineState>			mShadowStateWire;
 
 
-	/// The shadow buffer (depth buffer rendered from the light)
-	Ref<Texture>						mDepthTexture;
-
 	/// Lock that protects the triangle batches from being accessed from multiple threads
 	/// Lock that protects the triangle batches from being accessed from multiple threads
 	Mutex								mPrimitivesLock;
 	Mutex								mPrimitivesLock;
 
 
@@ -149,7 +136,7 @@ private:
 	Ref<RenderInstances>				mInstancesBuffer[Renderer::cFrameCount];
 	Ref<RenderInstances>				mInstancesBuffer[Renderer::cFrameCount];
 
 
 	/// Primitive that is being built + its properties
 	/// Primitive that is being built + its properties
-	Batch								mLockedPrimitive;
+	Ref<RenderPrimitive>				mLockedPrimitive;
 	Vertex *							mLockedVerticesStart = nullptr;
 	Vertex *							mLockedVerticesStart = nullptr;
 	Vertex *							mLockedVertices = nullptr;
 	Vertex *							mLockedVertices = nullptr;
 	Vertex *							mLockedVerticesEnd = nullptr;
 	Vertex *							mLockedVerticesEnd = nullptr;

+ 11 - 11
TestFramework/Renderer/Font.cpp

@@ -196,20 +196,20 @@ Font::Create(const char *inFontName, int inCharHeight)
 	DeleteDC(dc);
 	DeleteDC(dc);
 
 
 	// Create input layout
 	// Create input layout
-	const D3D12_INPUT_ELEMENT_DESC vertex_desc[] =
+	const PipelineState::EInputDescription vertex_desc[] =
 	{
 	{
-		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
+		PipelineState::EInputDescription::Position,
+		PipelineState::EInputDescription::TexCoord,
+		PipelineState::EInputDescription::Color
 	};
 	};
 
 
 	// Load vertex shader
 	// Load vertex shader
-	ComPtr<ID3DBlob> vtx = mRenderer->CreateVertexShader("Assets/Shaders/FontVertexShader.hlsl");
+	Ref<VertexShader> vtx = mRenderer->CreateVertexShader("Assets/Shaders/FontVertexShader");
 
 
 	// Load pixel shader
 	// Load pixel shader
-	ComPtr<ID3DBlob> pix = mRenderer->CreatePixelShader("Assets/Shaders/FontPixelShader.hlsl");
+	Ref<PixelShader> pix = mRenderer->CreatePixelShader("Assets/Shaders/FontPixelShader");
 
 
-	mPipelineState = mRenderer->CreatePipelineState(vtx.Get(), vertex_desc, ARRAYSIZE(vertex_desc), pix.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaTest, PipelineState::ECullMode::Backface);
+	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
 	// Create texture
 	mTexture = mRenderer->CreateTexture(surface);
 	mTexture = mRenderer->CreateTexture(surface);
@@ -396,13 +396,13 @@ void Font::DrawText3D(Mat44Arg inTransform, const string_view &inText, ColorArg
 	if (inText.empty())
 	if (inText.empty())
 		return;
 		return;
 
 
-	RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-	if (CreateString(inTransform, inText, inColor, primitive))
+	Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
+	if (CreateString(inTransform, inText, inColor, *primitive))
 	{
 	{
-		mTexture->Bind(2);
+		mTexture->Bind();
 
 
 		mPipelineState->Activate();
 		mPipelineState->Activate();
 
 
-		primitive.Draw();
+		primitive->Draw();
 	}
 	}
 }
 }

+ 1 - 0
TestFramework/Renderer/Font.h

@@ -10,6 +10,7 @@
 #include <Renderer/RenderPrimitive.h>
 #include <Renderer/RenderPrimitive.h>
 #include <Renderer/Texture.h>
 #include <Renderer/Texture.h>
 #include <Renderer/PipelineState.h>
 #include <Renderer/PipelineState.h>
+#include <memory>
 
 
 class Renderer;
 class Renderer;
 class Texture;
 class Texture;

+ 0 - 84
TestFramework/Renderer/PipelineState.cpp

@@ -1,84 +0,0 @@
-// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
-// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
-// SPDX-License-Identifier: MIT
-
-#include <TestFramework.h>
-
-#include <Renderer/PipelineState.h>
-#include <Renderer/Renderer.h>
-#include <Renderer/FatalErrorIfFailed.h>
-
-PipelineState::PipelineState(Renderer *inRenderer, ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode) :
-	mRenderer(inRenderer)
-{
-	D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {};
-	pso_desc.InputLayout = { inInputDescription, inInputDescriptionCount };
-	pso_desc.pRootSignature = mRenderer->GetRootSignature();
-	pso_desc.VS = { inVertexShader->GetBufferPointer(), inVertexShader->GetBufferSize() };
-	pso_desc.PS = { inPixelShader->GetBufferPointer(), inPixelShader->GetBufferSize() };
-
-	pso_desc.RasterizerState.FillMode = inFillMode;
-	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::AlphaTest:
-		pso_desc.BlendState.AlphaToCoverageEnable = TRUE;
-		[[fallthrough]];
-
-	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;
-	pso_desc.DepthStencilState.StencilEnable = FALSE;
-
-	pso_desc.SampleMask = UINT_MAX;
-	pso_desc.PrimitiveTopologyType = inTopology;
-	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)));
-}
-
-PipelineState::~PipelineState()
-{
-	if (mPSO != nullptr)
-		mRenderer->RecycleD3DObject(mPSO.Get());
-}
-
-void PipelineState::Activate()
-{
-	mRenderer->GetCommandList()->SetPipelineState(mPSO.Get());
-}

+ 36 - 13
TestFramework/Renderer/PipelineState.h

@@ -4,12 +4,43 @@
 
 
 #pragma once
 #pragma once
 
 
-class Renderer;
-
 /// Defines how primitives should be rendered
 /// Defines how primitives should be rendered
 class PipelineState
 class PipelineState
 {
 {
 public:
 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
 	/// If depth write / depth test is on
 	enum class EDepthTest
 	enum class EDepthTest
 	{
 	{
@@ -22,7 +53,6 @@ public:
 	{
 	{
 		Write,
 		Write,
 		AlphaBlend,
 		AlphaBlend,
-		AlphaTest,						///< Alpha blend with alpha test enabled
 	};
 	};
 
 
 	/// How to cull triangles
 	/// How to cull triangles
@@ -32,16 +62,9 @@ public:
 		FrontFace,
 		FrontFace,
 	};
 	};
 
 
-	/// Constructor
-										PipelineState(Renderer *inRenderer, ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode);
-										~PipelineState();
+	/// Destructor
+	virtual								~PipelineState() = default;
 
 
 	/// Make this pipeline state active (any primitives rendered after this will use this state)
 	/// Make this pipeline state active (any primitives rendered after this will use this state)
-	void								Activate();
-
-private:
-	friend class Renderer;
-
-	Renderer *							mRenderer;
-	ComPtr<ID3D12PipelineState>			mPSO;
+	virtual void						Activate() = 0;
 };
 };

+ 15 - 0
TestFramework/Renderer/PixelShader.h

@@ -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;
+};

+ 7 - 16
TestFramework/Renderer/RenderInstances.h

@@ -4,7 +4,6 @@
 
 
 #pragma once
 #pragma once
 
 
-#include <Renderer/Renderer.h>
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/Reference.h>
 
 
 class RenderPrimitive;
 class RenderPrimitive;
@@ -13,25 +12,17 @@ class RenderPrimitive;
 class RenderInstances : public RefTarget<RenderInstances>
 class RenderInstances : public RefTarget<RenderInstances>
 {
 {
 public:
 public:
-	/// Constructor
-							RenderInstances(Renderer *inRenderer)											: mRenderer(inRenderer) { }
-							~RenderInstances()																{ Clear(); }
+	/// Destructor
+	virtual					~RenderInstances() = default;
 
 
 	/// Erase all instance data
 	/// Erase all instance data
-	void					Clear();
+	virtual void			Clear() = 0;
 
 
 	/// Instance buffer management functions
 	/// Instance buffer management functions
-	void					CreateBuffer(int inNumInstances, int inInstanceSize);
-	void *					Lock();
-	void					Unlock();
+	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
 	/// Draw the instances when context has been set by Renderer::BindShader
-	void					Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const;
-
-private:
-	Renderer *				mRenderer;
-
-	ComPtr<ID3D12Resource>	mInstanceBuffer;
-	int						mInstanceBufferSize = 0;
-	int						mInstanceSize = 0;
+	virtual void			Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const = 0;
 };
 };

+ 0 - 121
TestFramework/Renderer/RenderPrimitive.cpp

@@ -5,39 +5,18 @@
 #include <TestFramework.h>
 #include <TestFramework.h>
 
 
 #include <Renderer/RenderPrimitive.h>
 #include <Renderer/RenderPrimitive.h>
-#include <Renderer/FatalErrorIfFailed.h>
 
 
 void RenderPrimitive::ReleaseVertexBuffer()
 void RenderPrimitive::ReleaseVertexBuffer()
 {
 {
-	if (mVtxBuffer != nullptr)
-	{
-		if (mVtxBufferInUploadHeap)
-			mRenderer->RecycleD3DResourceOnUploadHeap(mVtxBuffer.Get(), mNumVtx * mVtxSize);
-		else
-			mRenderer->RecycleD3DObject(mVtxBuffer.Get());
-		mVtxBuffer = nullptr;
-	}
-
 	mNumVtx = 0;
 	mNumVtx = 0;
 	mNumVtxToDraw = 0;
 	mNumVtxToDraw = 0;
 	mVtxSize = 0;
 	mVtxSize = 0;
-	mVtxBufferInUploadHeap = false;
 }
 }
 
 
 void RenderPrimitive::ReleaseIndexBuffer()
 void RenderPrimitive::ReleaseIndexBuffer()
 {
 {
-	if (mIdxBuffer != nullptr)
-	{
-		if (mIdxBufferInUploadHeap)
-			mRenderer->RecycleD3DResourceOnUploadHeap(mIdxBuffer.Get(), mNumIdx * sizeof(uint32));
-		else
-			mRenderer->RecycleD3DObject(mIdxBuffer.Get());
-		mIdxBuffer = nullptr;
-	}
-
 	mNumIdx = 0;
 	mNumIdx = 0;
 	mNumIdxToDraw = 0;
 	mNumIdxToDraw = 0;
-	mIdxBufferInUploadHeap = false;
 }
 }
 
 
 void RenderPrimitive::Clear()
 void RenderPrimitive::Clear()
@@ -50,115 +29,15 @@ void RenderPrimitive::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void
 {
 {
 	ReleaseVertexBuffer();
 	ReleaseVertexBuffer();
 
 
-	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");)
-
 	mNumVtx = inNumVtx;
 	mNumVtx = inNumVtx;
 	mNumVtxToDraw = inNumVtx;
 	mNumVtxToDraw = inNumVtx;
 	mVtxSize = inVtxSize;
 	mVtxSize = inVtxSize;
 }
 }
 
 
-void *RenderPrimitive::LockVertexBuffer()
-{
-	void *mapped_resource;
-	D3D12_RANGE range = { 0, 0 };
-	FatalErrorIfFailed(mVtxBuffer->Map(0, &range, &mapped_resource));
-	return mapped_resource;
-}
-
-void RenderPrimitive::UnlockVertexBuffer()
-{
-	mVtxBuffer->Unmap(0, nullptr);
-}
-
 void RenderPrimitive::CreateIndexBuffer(int inNumIdx, const uint32 *inData)
 void RenderPrimitive::CreateIndexBuffer(int inNumIdx, const uint32 *inData)
 {
 {
 	ReleaseIndexBuffer();
 	ReleaseIndexBuffer();
 
 
-	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");)
-
 	mNumIdx = inNumIdx;
 	mNumIdx = inNumIdx;
 	mNumIdxToDraw = inNumIdx;
 	mNumIdxToDraw = inNumIdx;
 }
 }
-
-uint32 *RenderPrimitive::LockIndexBuffer()
-{
-	uint32 *mapped_resource;
-	D3D12_RANGE range = { 0, 0 };
-	FatalErrorIfFailed(mIdxBuffer->Map(0, &range, (void **)&mapped_resource));
-	return mapped_resource;
-}
-
-void RenderPrimitive::UnlockIndexBuffer()
-{
-	mIdxBuffer->Unmap(0, nullptr);
-}
-
-void RenderPrimitive::Draw() const
-{
-	ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList();
-
-	// Set topology
-	command_list->IASetPrimitiveTopology(mType);
-
-	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);
-	}
-}

+ 16 - 24
TestFramework/Renderer/RenderPrimitive.h

@@ -4,16 +4,14 @@
 
 
 #pragma once
 #pragma once
 
 
-#include <Renderer/Renderer.h>
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/Reference.h>
 
 
 /// Simple wrapper around vertex and index buffers
 /// Simple wrapper around vertex and index buffers
-class RenderPrimitive : public RefTarget<RenderPrimitive>
+class RenderPrimitive : public RefTarget<RenderPrimitive>, public RefTargetVirtual
 {
 {
 public:
 public:
-	/// Constructor
-							RenderPrimitive(Renderer *inRenderer, D3D_PRIMITIVE_TOPOLOGY inType)			: mRenderer(inRenderer), mType(inType) { }
-							~RenderPrimitive()																{ Clear(); }
+	/// Destructor
+	virtual 				~RenderPrimitive() override = default;
 
 
 	/// Erase all primitive data
 	/// Erase all primitive data
 	void					Clear();
 	void					Clear();
@@ -22,41 +20,35 @@ public:
 	bool					IsEmpty() const																	{ return mNumVtx == 0 && mNumIdx == 0; }
 	bool					IsEmpty() const																	{ return mNumVtx == 0 && mNumIdx == 0; }
 
 
 	/// Vertex buffer management functions
 	/// Vertex buffer management functions
-	void					CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr);
-	void					ReleaseVertexBuffer();
-	void *					LockVertexBuffer();
-	void					UnlockVertexBuffer();
+	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						GetNumVtx() const																{ return mNumVtx; }
 	int						GetNumVtxToDraw() const															{ return mNumVtxToDraw; }
 	int						GetNumVtxToDraw() const															{ return mNumVtxToDraw; }
 	void					SetNumVtxToDraw(int inUsed)														{ mNumVtxToDraw = inUsed; }
 	void					SetNumVtxToDraw(int inUsed)														{ mNumVtxToDraw = inUsed; }
 
 
 	/// Index buffer management functions
 	/// Index buffer management functions
-	void					CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr);
-	void					ReleaseIndexBuffer();
-	uint32 *				LockIndexBuffer();
-	void					UnlockIndexBuffer();
+	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						GetNumIdx() const																{ return mNumIdx; }
 	int						GetNumIdxToDraw() const															{ return mNumIdxToDraw; }
 	int						GetNumIdxToDraw() const															{ return mNumIdxToDraw; }
 	void					SetNumIdxToDraw(int inUsed)														{ mNumIdxToDraw = inUsed; }
 	void					SetNumIdxToDraw(int inUsed)														{ mNumIdxToDraw = inUsed; }
 
 
 	/// Draw the primitive
 	/// Draw the primitive
-	void					Draw() const;
+	virtual void			Draw() const = 0;
 
 
-private:
-	friend class RenderInstances;
+	/// 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(); }
 
 
-	Renderer *				mRenderer;
-
-	D3D_PRIMITIVE_TOPOLOGY	mType;
-
-	ComPtr<ID3D12Resource>	mVtxBuffer;
+protected:
 	int						mNumVtx = 0;
 	int						mNumVtx = 0;
 	int						mNumVtxToDraw = 0;
 	int						mNumVtxToDraw = 0;
 	int						mVtxSize = 0;
 	int						mVtxSize = 0;
-	bool					mVtxBufferInUploadHeap = false;
 
 
-	ComPtr<ID3D12Resource>	mIdxBuffer;
 	int						mNumIdx = 0;
 	int						mNumIdx = 0;
 	int						mNumIdxToDraw = 0;
 	int						mNumIdxToDraw = 0;
-	bool					mIdxBufferInUploadHeap = false;
 };
 };

+ 18 - 705
TestFramework/Renderer/Renderer.cpp

@@ -5,34 +5,12 @@
 #include <TestFramework.h>
 #include <TestFramework.h>
 
 
 #include <Renderer/Renderer.h>
 #include <Renderer/Renderer.h>
-#include <Renderer/Texture.h>
-#include <Renderer/FatalErrorIfFailed.h>
-#include <Jolt/Core/Profiler.h>
-#include <Utils/ReadData.h>
 #include <Utils/Log.h>
 #include <Utils/Log.h>
 
 
-#include <d3dcompiler.h>
 #include <shellscalingapi.h>
 #include <shellscalingapi.h>
-#ifdef JPH_DEBUG
-	#include <d3d12sdklayers.h>
-#endif
 
 
 static Renderer *sRenderer = nullptr;
 static Renderer *sRenderer = nullptr;
 
 
-struct VertexShaderConstantBuffer
-{
-	Mat44		mView;
-	Mat44		mProjection;
-	Mat44		mLightView;
-	Mat44		mLightProjection;
-};
-
-struct PixelShaderConstantBuffer
-{
-	Vec4		mCameraPos;
-	Vec4		mLightPos;
-};
-
 //--------------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------------
 // Called every time the application receives a message
 // Called every time the application receives a message
 //--------------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------------
@@ -63,99 +41,6 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l
 	return 0;
 	return 0;
 }
 }
 
 
-Renderer::~Renderer()
-{
-	// 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 Renderer::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 Renderer::CreateRenterTargets()
-{
-	// 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 Renderer::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 = mWindowWidth;
-	depth_stencil_desc.Height = mWindowHeight;
-	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 Renderer::Initialize()
 void Renderer::Initialize()
 {
 {
 	// Prevent this window from auto scaling
 	// Prevent this window from auto scaling
@@ -189,215 +74,6 @@ void Renderer::Initialize()
 	// Show window
 	// Show window
 	ShowWindow(mhWnd, SW_SHOW);
 	ShowWindow(mhWnd, SW_SHOW);
 
 
-#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(mhWnd, 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 = mWindowWidth;
-	swap_chain_desc.BufferDesc.Height = mWindowHeight;
-	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 = mhWnd;
-	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();
-
-	CreateRenterTargets();
-
-	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));
-	}
-
 	// Store global renderer now that we're done initializing
 	// Store global renderer now that we're done initializing
 	sRenderer = this;
 	sRenderer = this;
 }
 }
@@ -406,52 +82,23 @@ void Renderer::OnWindowResize()
 {
 {
 	JPH_ASSERT(!mInFrame);
 	JPH_ASSERT(!mInFrame);
 
 
-	// Wait for the previous frame to be rendered
-	WaitForGpu();
-
 	// Get new window size
 	// Get new window size
 	RECT rc;
 	RECT rc;
 	GetClientRect(mhWnd, &rc);
 	GetClientRect(mhWnd, &rc);
 	mWindowWidth = max<LONG>(rc.right - rc.left, 8);
 	mWindowWidth = max<LONG>(rc.right - rc.left, 8);
 	mWindowHeight = max<LONG>(rc.bottom - rc.top, 8);
 	mWindowHeight = max<LONG>(rc.bottom - rc.top, 8);
-
-	// 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, mWindowWidth, mWindowHeight, 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
-	CreateRenterTargets();
-
-	// Recreate depth buffer
-	CreateDepthBuffer();
 }
 }
 
 
-static Mat44 sPerspectiveInfiniteReverseZ(float inFovY, float inAspect, float inNear)
+static Mat44 sPerspectiveInfiniteReverseZ(float inFovY, float inAspect, float inNear, float inYSign)
 {
 {
 	float height = 1.0f / Tan(0.5f * inFovY);
 	float height = 1.0f / Tan(0.5f * inFovY);
 	float width = height / inAspect;
 	float width = height / inAspect;
 
 
-	return Mat44(Vec4(width, 0.0f, 0.0f, 0.0f), Vec4(0.0f, height, 0.0f, 0.0f), Vec4(0.0f, 0.0f, 0, -1.0f), Vec4(0.0f, 0.0f, inNear, 0.0f));
+	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));
 }
 }
 
 
 void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale)
 void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale)
 {
 {
-	JPH_PROFILE_FUNCTION();
-
 	// Mark that we're in the frame
 	// Mark that we're in the frame
 	JPH_ASSERT(!mInFrame);
 	JPH_ASSERT(!mInFrame);
 	mInFrame = true;
 	mInFrame = true;
@@ -459,37 +106,6 @@ void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale)
 	// Store state
 	// Store state
 	mCameraState = inCamera;
 	mCameraState = inCamera;
 
 
-	// 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);
-
-	// Set the main back buffer as render target
-	SetRenderTarget(nullptr);
-
-	// 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);
-
 	// Light properties
 	// Light properties
 	Vec3 light_pos = inWorldScale * Vec3(250, 250, 250);
 	Vec3 light_pos = inWorldScale * Vec3(250, 250, 250);
 	Vec3 light_tgt = Vec3::sZero();
 	Vec3 light_tgt = Vec3::sZero();
@@ -499,346 +115,43 @@ void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale)
 	float light_near = 1.0f;
 	float light_near = 1.0f;
 
 
 	// Camera properties
 	// Camera properties
+	Vec3 cam_pos = Vec3(inCamera.mPos - mBaseOffset);
 	float camera_fovy = inCamera.mFOVY;
 	float camera_fovy = inCamera.mFOVY;
 	float camera_aspect = static_cast<float>(GetWindowWidth()) / GetWindowHeight();
 	float camera_aspect = static_cast<float>(GetWindowWidth()) / GetWindowHeight();
 	float camera_fovx = 2.0f * ATan(camera_aspect * Tan(0.5f * camera_fovy));
 	float camera_fovx = 2.0f * ATan(camera_aspect * Tan(0.5f * camera_fovy));
 	float camera_near = 0.01f * inWorldScale;
 	float camera_near = 0.01f * inWorldScale;
 
 
-	// Set constants for vertex shader in projection mode
-	VertexShaderConstantBuffer *vs = mVertexShaderConstantBufferProjection[mFrameIndex]->Map<VertexShaderConstantBuffer>();
+	// 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
 	// Camera projection and view
-	vs->mProjection = sPerspectiveInfiniteReverseZ(camera_fovy, camera_aspect, camera_near);
-	Vec3 cam_pos = Vec3(inCamera.mPos - mBaseOffset);
+	mVSBuffer.mProjection = sPerspectiveInfiniteReverseZ(camera_fovy, camera_aspect, camera_near, mPerspectiveYSign);
 	Vec3 tgt = cam_pos + inCamera.mForward;
 	Vec3 tgt = cam_pos + inCamera.mForward;
-	vs->mView = Mat44::sLookAt(cam_pos, tgt, inCamera.mUp);
+	mVSBuffer.mView = Mat44::sLookAt(cam_pos, tgt, inCamera.mUp);
 
 
 	// Light projection and view
 	// Light projection and view
-	vs->mLightProjection = sPerspectiveInfiniteReverseZ(light_fov, 1.0f, light_near);
-	vs->mLightView = Mat44::sLookAt(light_pos, light_tgt, light_up);
-
-	mVertexShaderConstantBufferProjection[mFrameIndex]->Unmap();
-
-	// Set constants for vertex shader in ortho mode
-	vs = mVertexShaderConstantBufferOrtho[mFrameIndex]->Map<VertexShaderConstantBuffer>();
+	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
 	// Camera ortho projection and view
-	vs->mProjection = Mat44(Vec4(2.0f / mWindowWidth, 0.0f, 0.0f, 0.0f), Vec4(0.0f, -2.0f / mWindowHeight, 0.0f, 0.0f), Vec4(0.0f, 0.0f, -1.0f, 0.0f), Vec4(-1.0f, 1.0f, 0.0f, 1.0f));
-	vs->mView = Mat44::sIdentity();
+	mVSBufferOrtho.mProjection = Mat44(Vec4(2.0f / mWindowWidth, 0.0f, 0.0f, 0.0f), Vec4(0.0f, -mPerspectiveYSign * 2.0f / mWindowHeight, 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
 	// Light projection and view are unused in ortho mode
-	vs->mLightView = Mat44::sIdentity();
-	vs->mLightProjection = Mat44::sIdentity();
-
-	mVertexShaderConstantBufferOrtho[mFrameIndex]->Unmap();
-
-	// Switch to 3d projection mode
-	SetProjectionMode();
+	mVSBufferOrtho.mLightView = Mat44::sIdentity();
+	mVSBufferOrtho.mLightProjection = Mat44::sIdentity();
 
 
 	// Set constants for pixel shader
 	// Set constants for pixel shader
-	PixelShaderConstantBuffer *ps = mPixelShaderConstantBuffer[mFrameIndex]->Map<PixelShaderConstantBuffer>();
-	ps->mCameraPos = Vec4(cam_pos, 0);
-	ps->mLightPos = Vec4(light_pos, 0);
-	mPixelShaderConstantBuffer[mFrameIndex]->Unmap();
-
-	// Set the pixel shader constant buffer data.
-	mPixelShaderConstantBuffer[mFrameIndex]->Bind(1);
-
-	// 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);
+	mPSBuffer.mCameraPos = Vec4(cam_pos, 0);
+	mPSBuffer.mLightPos = Vec4(light_pos, 0);
 }
 }
 
 
 void Renderer::EndFrame()
 void Renderer::EndFrame()
 {
 {
-	JPH_PROFILE_FUNCTION();
-
 	// Mark that we're no longer in the frame
 	// Mark that we're no longer in the frame
 	JPH_ASSERT(mInFrame);
 	JPH_ASSERT(mInFrame);
 	mInFrame = false;
 	mInFrame = false;
-
-	// 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 Renderer::SetProjectionMode()
-{
-	JPH_ASSERT(mInFrame);
-
-	mVertexShaderConstantBufferProjection[mFrameIndex]->Bind(0);
-}
-
-void Renderer::SetOrthoMode()
-{
-	JPH_ASSERT(mInFrame);
-
-	mVertexShaderConstantBufferOrtho[mFrameIndex]->Bind(0);
-}
-
-Ref<Texture> Renderer::CreateTexture(const Surface *inSurface)
-{
-	return new Texture(this, inSurface);
-}
-
-Ref<Texture> Renderer::CreateRenderTarget(int inWidth, int inHeight)
-{
-	return new Texture(this, inWidth, inHeight);
-}
-
-void Renderer::SetRenderTarget(Texture *inRenderTarget)
-{
-	JPH_ASSERT(mInFrame);
-
-	// Unset the previous render target
-	if (mRenderTargetTexture != nullptr)
-		mRenderTargetTexture->SetAsRenderTarget(false);
-	mRenderTargetTexture = nullptr;
-
-	if (inRenderTarget == nullptr)
-	{
-		// 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>(mWindowWidth), static_cast<float>(mWindowHeight), 0.0f, 1.0f };
-		mCommandList->RSSetViewports(1, &viewport);
-
-		// Set scissor rect
-		D3D12_RECT scissor_rect = { 0, 0, static_cast<LONG>(mWindowWidth), static_cast<LONG>(mWindowHeight) };
-		mCommandList->RSSetScissorRects(1, &scissor_rect);
-	}
-	else
-	{
-		// Use the texture as render target
-		inRenderTarget->SetAsRenderTarget(true);
-		mRenderTargetTexture = inRenderTarget;
-	}
-}
-
-ComPtr<ID3DBlob> Renderer::CreateVertexShader(const char *inFileName)
-{
-	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
-	Array<uint8> data = ReadData(inFileName);
-
-	// Compile source
-	ComPtr<ID3DBlob> shader_blob, error_blob;
-	HRESULT hr = D3DCompile(&data[0],
-							(uint)data.size(),
-							inFileName,
-							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 shader_blob;
-}
-
-ComPtr<ID3DBlob> Renderer::CreatePixelShader(const char *inFileName)
-{
-	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
-	Array<uint8> data = ReadData(inFileName);
-
-	// Compile source
-	ComPtr<ID3DBlob> shader_blob, error_blob;
-	HRESULT hr = D3DCompile(&data[0],
-							(uint)data.size(),
-							inFileName,
-							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 shader_blob;
-}
-
-unique_ptr<ConstantBuffer> Renderer::CreateConstantBuffer(uint inBufferSize)
-{
-	return make_unique<ConstantBuffer>(this, inBufferSize);
-}
-
-unique_ptr<PipelineState> Renderer::CreatePipelineState(ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode)
-{
-	return make_unique<PipelineState>(this, inVertexShader, inInputDescription, inInputDescriptionCount, inPixelShader, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode);
-}
-
-ComPtr<ID3D12Resource> Renderer::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 Renderer::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 Renderer::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> Renderer::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> Renderer::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 Renderer::RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize)
-{
-	if (!mIsExiting)
-		mDelayCached[mFrameIndex][inSize].push_back(inResource);
-}
-
-void Renderer::RecycleD3DObject(ID3D12Object *inResource)
-{
-	if (!mIsExiting)
-		mDelayReleased[mFrameIndex].push_back(inResource);
 }
 }

+ 49 - 98
TestFramework/Renderer/Renderer.h

@@ -4,13 +4,13 @@
 
 
 #pragma once
 #pragma once
 
 
-#include <Jolt/Core/UnorderedMap.h>
 #include <Image/Surface.h>
 #include <Image/Surface.h>
 #include <Renderer/Frustum.h>
 #include <Renderer/Frustum.h>
-#include <Renderer/ConstantBuffer.h>
 #include <Renderer/PipelineState.h>
 #include <Renderer/PipelineState.h>
-#include <Renderer/CommandQueue.h>
-#include <Renderer/DescriptorHeap.h>
+#include <Renderer/VertexShader.h>
+#include <Renderer/PixelShader.h>
+#include <Renderer/RenderPrimitive.h>
+#include <Renderer/RenderInstances.h>
 #include <memory>
 #include <memory>
 
 
 // Forward declares
 // Forward declares
@@ -32,13 +32,10 @@ class Renderer
 {
 {
 public:
 public:
 	/// Destructor
 	/// Destructor
-									~Renderer();
+	virtual							~Renderer() = default;
 
 
 	/// Initialize DirectX
 	/// Initialize DirectX
-	void							Initialize();
-
-	/// Callback when the window resizes and the back buffer needs to be adjusted
-	void							OnWindowResize();
+	virtual void					Initialize();
 
 
 	/// Get window size
 	/// Get window size
 	int								GetWindowWidth()					{ return mWindowWidth; }
 	int								GetWindowWidth()					{ return mWindowWidth; }
@@ -47,42 +44,35 @@ public:
 	/// Access to the window handle
 	/// Access to the window handle
 	HWND							GetWindowHandle() const				{ return mhWnd; }
 	HWND							GetWindowHandle() const				{ return mhWnd; }
 
 
-	/// 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(); }
-	CommandQueue &					GetUploadQueue()					{ return mUploadQueue; }
-	DescriptorHeap &				GetDSVHeap()						{ return mDSVHeap; }
-	DescriptorHeap &				GetSRVHeap()						{ return mSRVHeap; }
-
 	/// Start / end drawing a frame
 	/// Start / end drawing a frame
-	void							BeginFrame(const CameraState &inCamera, float inWorldScale);
-	void							EndFrame();
+	virtual void					BeginFrame(const CameraState &inCamera, float inWorldScale);
+	virtual void					EndShadowPass() = 0;
+	virtual void					EndFrame();
 
 
 	/// Switch between orthographic and 3D projection mode
 	/// Switch between orthographic and 3D projection mode
-	void							SetProjectionMode();
-	void							SetOrthoMode();
+	virtual void					SetProjectionMode() = 0;
+	virtual void					SetOrthoMode() = 0;
 
 
 	/// Create texture from an image surface
 	/// Create texture from an image surface
-	Ref<Texture>					CreateTexture(const Surface *inSurface);
-
-	/// Create a texture to render to (currently depth buffer only)
-	Ref<Texture>					CreateRenderTarget(int inWidth, int inHeight);
-
-	/// Change the render target to a texture. Use nullptr to set back to the main render target.
-	void							SetRenderTarget(Texture *inRenderTarget);
+	virtual Ref<Texture>			CreateTexture(const Surface *inSurface) = 0;
 
 
 	/// Compile a vertex shader
 	/// Compile a vertex shader
-	ComPtr<ID3DBlob>				CreateVertexShader(const char *inFileName);
+	virtual Ref<VertexShader>		CreateVertexShader(const char *inFileName) = 0;
 
 
 	/// Compile a pixel shader
 	/// Compile a pixel shader
-	ComPtr<ID3DBlob>				CreatePixelShader(const char *inFileName);
-
-	/// Create a constant buffer for the shader
-	unique_ptr<ConstantBuffer>		CreateConstantBuffer(uint inBufferSize);
+	virtual Ref<PixelShader>		CreatePixelShader(const char *inFileName) = 0;
 
 
 	/// Create pipeline state object that defines the complete state of how primitives should be rendered
 	/// Create pipeline state object that defines the complete state of how primitives should be rendered
-	unique_ptr<PipelineState>		CreatePipelineState(ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode);
+	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())
 	/// Get the camera state / frustum (only valid between BeginFrame() / EndFrame())
 	const CameraState &				GetCameraState() const				{ JPH_ASSERT(mInFrame); return mCameraState; }
 	const CameraState &				GetCameraState() const				{ JPH_ASSERT(mInFrame); return mCameraState; }
@@ -98,80 +88,41 @@ public:
 	/// How many frames our pipeline is
 	/// How many frames our pipeline is
 	static const uint				cFrameCount = 2;
 	static const uint				cFrameCount = 2;
 
 
+	/// Size of the shadow map will be cShadowMapSize x cShadowMapSize pixels
+	static const uint				cShadowMapSize = 4096;
+
 	/// Which frame is currently rendering (to keep track of which buffers are free to overwrite)
 	/// Which frame is currently rendering (to keep track of which buffers are free to overwrite)
 	uint							GetCurrentFrameIndex() const		{ JPH_ASSERT(mInFrame); return mFrameIndex; }
 	uint							GetCurrentFrameIndex() const		{ JPH_ASSERT(mInFrame); return mFrameIndex; }
 
 
-	/// Create a buffer on the default heap (usable for permanent buffers)
-	ComPtr<ID3D12Resource>			CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize);
-
-	/// 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);
-
-private:
-	// Wait for pending GPU work to complete
-	void							WaitForGpu();
-
-	// Create render targets and their views
-	void							CreateRenterTargets();
-
-	// 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);
+	/// Callback when the window resizes and the back buffer needs to be adjusted
+	virtual void					OnWindowResize();
+
+protected:
+	struct VertexShaderConstantBuffer
+	{
+		Mat44						mView;
+		Mat44						mProjection;
+		Mat44						mLightView;
+		Mat44						mLightProjection;
+	};
+
+	struct PixelShaderConstantBuffer
+	{
+		Vec4						mCameraPos;
+		Vec4						mLightPos;
+	};
 
 
 	HWND							mhWnd;
 	HWND							mhWnd;
 	int								mWindowWidth = 1920;
 	int								mWindowWidth = 1920;
 	int								mWindowHeight = 1080;
 	int								mWindowHeight = 1080;
-	unique_ptr<ConstantBuffer>		mVertexShaderConstantBufferProjection[cFrameCount];
-	unique_ptr<ConstantBuffer>		mVertexShaderConstantBufferOrtho[cFrameCount];
-	unique_ptr<ConstantBuffer>		mPixelShaderConstantBuffer[cFrameCount];
+	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
 	bool							mInFrame = false;					///< If we're within a BeginFrame() / EndFrame() pair
 	CameraState						mCameraState;
 	CameraState						mCameraState;
 	RVec3							mBaseOffset { RVec3::sZero() };		///< Offset to subtract from the camera position to deal with large worlds
 	RVec3							mBaseOffset { RVec3::sZero() };		///< Offset to subtract from the camera position to deal with large worlds
 	Frustum							mCameraFrustum;
 	Frustum							mCameraFrustum;
 	Frustum							mLightFrustum;
 	Frustum							mLightFrustum;
-
-	// DirectX interfaces
-	ComPtr<IDXGIFactory4>			mDXGIFactory;
-	ComPtr<ID3D12Device>			mDevice;
-	DescriptorHeap					mRTVHeap;							///< Render target view heap
-	DescriptorHeap					mDSVHeap;							///< Depth stencil view heap
-	DescriptorHeap					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<Texture>					mRenderTargetTexture;				///< When rendering to a texture, this is the active texture
-	CommandQueue					mUploadQueue;						///< Queue used to upload resources to GPU memory
-
-	// Synchronization objects used to finish rendering and swapping before reusing a command queue
-	uint							mFrameIndex;						///< Current frame index (0 or 1)
-	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
+	uint							mFrameIndex = 0;					///< Current frame index (0 or 1)
+	VertexShaderConstantBuffer		mVSBuffer;
+	VertexShaderConstantBuffer		mVSBufferOrtho;
+	PixelShaderConstantBuffer		mPSBuffer;
 };
 };

+ 5 - 20
TestFramework/Renderer/Texture.h

@@ -7,38 +7,23 @@
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/Reference.h>
 
 
 /// Forward declares
 /// Forward declares
-class Renderer;
 class Surface;
 class Surface;
 
 
 class Texture : public RefTarget<Texture>
 class Texture : public RefTarget<Texture>
 {
 {
 public:
 public:
-	/// Constructor, called by Renderer::CreateTexture
-										Texture(Renderer *inRenderer, const Surface *inSurface);	// Create a normal texture
-										Texture(Renderer *inRenderer, int inWidth, int inHeight);	// Create a render target (depth only)
-										~Texture();
+	/// Constructor
+										Texture(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight) { }
+	virtual								~Texture() = default;
 
 
 	/// Get dimensions of texture
 	/// Get dimensions of texture
 	inline int							GetWidth() const		{ return mWidth; }
 	inline int							GetWidth() const		{ return mWidth; }
 	inline int							GetHeight() const		{ return mHeight; }
 	inline int							GetHeight() const		{ return mHeight; }
 
 
 	/// Bind texture to the pixel shader
 	/// Bind texture to the pixel shader
-	void								Bind(int inSlot) const;
-
-	/// Clear this texture (only possible for render targets)
-	void								ClearRenderTarget();
-
-	/// Activate this texture as the current render target, called by Renderer::SetRenderTarget
-	void								SetAsRenderTarget(bool inSet) const;
-
-private:
-	Renderer *							mRenderer;
+	virtual void						Bind() const = 0;
 
 
+protected:
 	int									mWidth;
 	int									mWidth;
 	int									mHeight;
 	int									mHeight;
-
-	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
 };
 };

+ 35 - 0
TestFramework/Renderer/VK/BufferVK.h

@@ -0,0 +1,35 @@
+// 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:
+	/// Free memory associated with a buffer
+	void						Free(VkDevice inDevice)
+	{
+		if (mBuffer != VK_NULL_HANDLE)
+		{
+			vkDestroyBuffer(inDevice, mBuffer, nullptr);
+			mBuffer = VK_NULL_HANDLE;
+		}
+
+		if (mMemory != VK_NULL_HANDLE)
+		{
+			vkFreeMemory(inDevice, mMemory, nullptr);
+			mMemory = VK_NULL_HANDLE;
+		}
+	}
+
+	VkBuffer					mBuffer = VK_NULL_HANDLE;
+	VkDeviceMemory				mMemory = VK_NULL_HANDLE;
+
+	VkBufferUsageFlags			mUsage;
+	VkMemoryPropertyFlags		mProperties;
+	VkDeviceSize				mSize = 0;
+};

+ 32 - 0
TestFramework/Renderer/VK/ConstantBufferVK.cpp

@@ -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, 0, mBuffer.mSize, 0, &data));
+	return data;
+}
+
+void ConstantBufferVK::Unmap()
+{
+	vkUnmapMemory(mRenderer->GetDevice(), mBuffer.mMemory);
+}

+ 30 - 0
TestFramework/Renderer/VK/ConstantBufferVK.h

@@ -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;
+};

+ 14 - 0
TestFramework/Renderer/VK/FatalErrorIfFailedVK.cpp

@@ -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);
+}

+ 11 - 0
TestFramework/Renderer/VK/FatalErrorIfFailedVK.h

@@ -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);
+

+ 189 - 0
TestFramework/Renderer/VK/PipelineStateVK.cpp

@@ -0,0 +1,189 @@
+// 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:
+			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::Normal:
+			temp_vtx.format = VK_FORMAT_R32G32B32_SFLOAT;
+			attribute_descriptions.push_back(temp_vtx);
+			temp_vtx.offset += 3 * sizeof(float);
+			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:
+			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;
+
+		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; // 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);
+}

+ 30 - 0
TestFramework/Renderer/VK/PipelineStateVK.h

@@ -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;
+};

+ 34 - 0
TestFramework/Renderer/VK/PixelShaderVK.h

@@ -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;
+};

+ 54 - 0
TestFramework/Renderer/VK/RenderInstancesVK.cpp

@@ -0,0 +1,54 @@
+// 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, 0, mInstancesBuffer.mSize, 0, &data));
+	return data;
+}
+
+void RenderInstancesVK::Unlock()
+{
+	vkUnmapMemory(mRenderer->GetDevice(), mInstancesBuffer.mMemory);
+}
+
+void RenderInstancesVK::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const
+{
+	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);
+	}
+}

+ 35 - 0
TestFramework/Renderer/VK/RenderInstancesVK.h

@@ -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;
+};

+ 100 - 0
TestFramework/Renderer/VK/RenderPrimitiveVK.cpp

@@ -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, 0, 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, 0, 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);
+	}
+}

+ 44 - 0
TestFramework/Renderer/VK/RenderPrimitiveVK.h

@@ -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;
+};

+ 1112 - 0
TestFramework/Renderer/VK/RendererVK.cpp

@@ -0,0 +1,1112 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <TestFramework.h>
+
+#include <Renderer/VK/RendererVK.h>
+#include <Renderer/VK/RenderPrimitiveVK.h>
+#include <Renderer/VK/RenderInstancesVK.h>
+#include <Renderer/VK/PipelineStateVK.h>
+#include <Renderer/VK/VertexShaderVK.h>
+#include <Renderer/VK/PixelShaderVK.h>
+#include <Renderer/VK/TextureVK.h>
+#include <Renderer/VK/FatalErrorIfFailedVK.h>
+#include <Utils/Log.h>
+#include <Utils/ReadData.h>
+#include <Jolt/Core/Profiler.h>
+#include <Jolt/Core/QuickSort.h>
+#include <Jolt/Core/RTTI.h>
+
+#include <vulkan/vulkan_win32.h>
+
+#ifdef JPH_DEBUG
+
+static VKAPI_ATTR VkBool32 VKAPI_CALL sVulkanDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT inSeverity, [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT inType, const VkDebugUtilsMessengerCallbackDataEXT *inCallbackData, [[maybe_unused]] void *inUserData)
+{
+	Trace("VK: %s", inCallbackData->pMessage);
+	JPH_ASSERT((inSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) == 0);
+	return VK_FALSE;
+}
+
+#endif // JPH_DEBUG
+
+RendererVK::~RendererVK()
+{
+	vkDeviceWaitIdle(mDevice);
+
+	// Destroy the shadow map
+	mShadowMap = nullptr;
+	vkDestroyFramebuffer(mDevice, mShadowFrameBuffer, nullptr);
+
+	// Release constant buffers
+	for (unique_ptr<ConstantBufferVK> &cb : mVertexShaderConstantBufferProjection)
+		cb = nullptr;
+	for (unique_ptr<ConstantBufferVK> &cb : mVertexShaderConstantBufferOrtho)
+		cb = nullptr;
+	for (unique_ptr<ConstantBufferVK> &cb : mPixelShaderConstantBuffer)
+		cb = nullptr;
+
+	// Free all buffers
+	for (BufferCache &bc : mFreedBuffers)
+		for (BufferCache::value_type &vt : bc)
+			for (BufferVK &bvk : vt.second)
+				bvk.Free(mDevice);
+	for (BufferCache::value_type &vt : mBufferCache)
+		for (BufferVK &bvk : vt.second)
+			bvk.Free(mDevice);
+
+	for (VkFence fence : mInFlightFences)
+		vkDestroyFence(mDevice, fence, nullptr);
+
+	for (VkSemaphore semaphore : mRenderFinishedSemaphores) 
+		vkDestroySemaphore(mDevice, semaphore, nullptr);
+	for (VkSemaphore semaphore : mImageAvailableSemaphores) 
+		vkDestroySemaphore(mDevice, semaphore, nullptr);
+
+	vkDestroyCommandPool(mDevice, mCommandPool, nullptr);
+
+	vkDestroyPipelineLayout(mDevice, mPipelineLayout, nullptr);
+
+	vkDestroyRenderPass(mDevice, mRenderPassShadow, nullptr);
+	vkDestroyRenderPass(mDevice, mRenderPass, nullptr);
+
+	vkDestroyDescriptorPool(mDevice, mDescriptorPool, nullptr);
+
+	vkDestroySampler(mDevice, mTextureSamplerShadow, nullptr);
+	vkDestroySampler(mDevice, mTextureSamplerRepeat, nullptr);
+
+	vkDestroyDescriptorSetLayout(mDevice, mDescriptorSetLayoutUBO, nullptr);
+	vkDestroyDescriptorSetLayout(mDevice, mDescriptorSetLayoutTexture, nullptr);
+
+	DestroySwapChain();
+
+	vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
+
+	vkDestroyDevice(mDevice, nullptr);
+
+#ifdef _DEBUG
+	PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)(void *)vkGetInstanceProcAddr(mInstance, "vkDestroyDebugUtilsMessengerEXT");
+	if (vkDestroyDebugUtilsMessengerEXT != nullptr)
+		vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr);
+#endif
+
+	 vkDestroyInstance(mInstance, nullptr);
+}
+
+void RendererVK::Initialize()
+{
+	Renderer::Initialize();
+
+	// Flip the sign of the projection matrix
+	mPerspectiveYSign = -1.0f;
+
+	// Required instance extensions
+	Array<const char *> required_instance_extensions;
+	required_instance_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
+	required_instance_extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
+
+	// Required device extensions
+	Array<const char *> required_device_extensions;
+	required_device_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+
+	// Query supported instance extensions
+	uint32 instance_extension_count = 0;
+	FatalErrorIfFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr));
+	Array<VkExtensionProperties> instance_extensions;
+	instance_extensions.resize(instance_extension_count);
+	FatalErrorIfFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions.data()));
+
+	// Query supported validation layers
+	uint32 validation_layer_count;
+	vkEnumerateInstanceLayerProperties(&validation_layer_count, nullptr);
+	Array<VkLayerProperties> validation_layers(validation_layer_count);
+	vkEnumerateInstanceLayerProperties(&validation_layer_count, validation_layers.data());
+
+	// Create Vulkan instance
+	VkInstanceCreateInfo instance_create_info = {};
+	instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+
+#ifdef JPH_DEBUG
+	// Enable validation layer if supported
+	const char *desired_validation_layers[] = { "VK_LAYER_KHRONOS_validation" };
+	for (const VkLayerProperties &p : validation_layers)
+		if (strcmp(desired_validation_layers[0], p.layerName) == 0)
+		{
+			instance_create_info.enabledLayerCount = 1;
+			instance_create_info.ppEnabledLayerNames = desired_validation_layers;
+			break;
+		}
+
+	// Setup debug messenger callback if the extension is supported
+	VkDebugUtilsMessengerCreateInfoEXT messenger_create_info = {};
+	for (const VkExtensionProperties &ext : instance_extensions)
+		if (strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, ext.extensionName) == 0)
+		{
+			messenger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+			messenger_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+			messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT;
+			messenger_create_info.pfnUserCallback = sVulkanDebugCallback;
+			instance_create_info.pNext = &messenger_create_info;
+			required_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+			break;
+		}
+#endif
+
+	instance_create_info.enabledExtensionCount = (uint32)required_instance_extensions.size();
+	instance_create_info.ppEnabledExtensionNames = required_instance_extensions.data();
+	FatalErrorIfFailed(vkCreateInstance(&instance_create_info, nullptr, &mInstance));
+
+#ifdef JPH_DEBUG
+	// Finalize debug messenger callback
+	PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)(std::uintptr_t)vkGetInstanceProcAddr(mInstance, "vkCreateDebugUtilsMessengerEXT");
+	if (vkCreateDebugUtilsMessengerEXT != nullptr)
+		FatalErrorIfFailed(vkCreateDebugUtilsMessengerEXT(mInstance, &messenger_create_info, nullptr, &mDebugMessenger));
+#endif
+
+	// Create surface
+	VkWin32SurfaceCreateInfoKHR surface_create_info = {};
+	surface_create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+	surface_create_info.hwnd = mhWnd;
+	surface_create_info.hinstance = GetModuleHandle(nullptr);
+	FatalErrorIfFailed(vkCreateWin32SurfaceKHR(mInstance, &surface_create_info, nullptr, &mSurface));
+
+	// Select device
+	uint32 device_count = 0;
+	FatalErrorIfFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, nullptr));
+	Array<VkPhysicalDevice> devices;
+	devices.resize(device_count);
+	FatalErrorIfFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, devices.data()));
+	struct Device
+	{
+		VkPhysicalDevice		mPhysicalDevice;
+		String					mName;
+		VkSurfaceFormatKHR		mFormat;
+		uint32					mGraphicsQueueIndex;
+		uint32					mPresentQueueIndex;
+		int						mScore;
+	};
+	Array<Device> available_devices;
+	for (VkPhysicalDevice device : devices)
+	{
+		// Get device properties
+		VkPhysicalDeviceProperties properties;
+		vkGetPhysicalDeviceProperties(device, &properties);
+
+		// Test if it is an appropriate type
+		int score = 0;
+		switch (properties.deviceType)
+		{
+		case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
+			score = 30;
+			break;
+		case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
+			score = 20;
+			break;
+		case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
+			score = 10;
+			break;
+		case VK_PHYSICAL_DEVICE_TYPE_OTHER:
+		case VK_PHYSICAL_DEVICE_TYPE_CPU:
+		case VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM:
+			continue;
+		}
+
+		// Check if the device supports all our required extensions
+		uint32 device_extension_count;
+		vkEnumerateDeviceExtensionProperties(device, nullptr, &device_extension_count, nullptr);
+		Array<VkExtensionProperties> available_extensions;
+		available_extensions.resize(device_extension_count);
+		vkEnumerateDeviceExtensionProperties(device, nullptr, &device_extension_count, available_extensions.data());
+		int found_extensions = 0;
+		for (const char *required_device_extension : required_device_extensions)
+			for (const VkExtensionProperties &ext : available_extensions)
+				if (strcmp(required_device_extension, ext.extensionName) == 0)
+				{
+					found_extensions++;
+					break;
+				}
+		if (found_extensions != int(required_device_extensions.size()))
+			continue;
+
+		// Find the right queues
+		uint32 queue_family_count = 0;
+		vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr);
+		Array<VkQueueFamilyProperties> queue_families;
+		queue_families.resize(queue_family_count);
+		vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data());
+		uint32 graphics_queue = ~uint32(0);
+		uint32 present_queue = ~uint32(0);
+		for (uint32 i = 0; i < uint32(queue_families.size()); ++i)
+		{
+			if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
+				graphics_queue = i;
+
+			VkBool32 present_support = false;
+			vkGetPhysicalDeviceSurfaceSupportKHR(device, i, mSurface, &present_support);
+			if (present_support)
+				present_queue = i;
+
+			if (graphics_queue != ~uint32(0) && present_queue != ~uint32(0))
+				break;
+		}
+		if (graphics_queue == ~uint32(0) || present_queue == ~uint32(0))
+			continue;
+
+		// Select surface format
+		VkSurfaceFormatKHR selected_format = SelectFormat(device);
+		if (selected_format.format == VK_FORMAT_UNDEFINED)
+			continue;
+
+		// Add the device
+		available_devices.push_back({ device, properties.deviceName, selected_format, graphics_queue, present_queue, score });
+	}
+	if (available_devices.empty())
+		FatalError("No Vulkan device found!");
+	QuickSort(available_devices.begin(), available_devices.end(), [](const Device &inLHS, const Device &inRHS) {
+		return inLHS.mScore > inRHS.mScore;
+	});
+	const Device &selected_device = available_devices[0];
+	Trace("Selected device: %s", selected_device.mName.c_str());
+	mPhysicalDevice = selected_device.mPhysicalDevice;
+
+	// Get memory properties
+	vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &mMemoryProperties);
+
+	// Create device
+	float queue_priority = 1.0f;
+	VkDeviceQueueCreateInfo queue_create_info[2] = {};
+	for (size_t i = 0; i < std::size(queue_create_info); ++i)
+	{
+		queue_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+		queue_create_info[i].queueCount = 1;
+		queue_create_info[i].pQueuePriorities = &queue_priority;
+	}
+	queue_create_info[0].queueFamilyIndex = selected_device.mGraphicsQueueIndex;
+	queue_create_info[1].queueFamilyIndex = selected_device.mPresentQueueIndex;
+	VkPhysicalDeviceFeatures device_features = {};
+	device_features.fillModeNonSolid = VK_TRUE;
+	VkDeviceCreateInfo device_create_info = {};
+	device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+	device_create_info.queueCreateInfoCount = selected_device.mGraphicsQueueIndex != selected_device.mPresentQueueIndex? 2 : 1;
+	device_create_info.pQueueCreateInfos = queue_create_info;
+	device_create_info.enabledLayerCount = instance_create_info.enabledLayerCount;
+	device_create_info.ppEnabledLayerNames = instance_create_info.ppEnabledLayerNames;
+	device_create_info.enabledExtensionCount = uint32(required_device_extensions.size());
+	device_create_info.ppEnabledExtensionNames = required_device_extensions.data();
+	device_create_info.pEnabledFeatures = &device_features;
+	FatalErrorIfFailed(vkCreateDevice(selected_device.mPhysicalDevice, &device_create_info, nullptr, &mDevice));
+
+	// Get the queues
+	mGraphicsQueueIndex = selected_device.mGraphicsQueueIndex;
+	mPresentQueueIndex = selected_device.mPresentQueueIndex;
+	vkGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+	vkGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue);
+
+	VkCommandPoolCreateInfo pool_info = {};
+	pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+	pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+	pool_info.queueFamilyIndex = selected_device.mGraphicsQueueIndex;
+	FatalErrorIfFailed(vkCreateCommandPool(mDevice, &pool_info, nullptr, &mCommandPool));
+
+	VkCommandBufferAllocateInfo command_buffer_info = {};
+	command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+	command_buffer_info.commandPool = mCommandPool;
+	command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+	command_buffer_info.commandBufferCount = 1;
+	for (uint32 i = 0; i < cFrameCount; ++i)
+		FatalErrorIfFailed(vkAllocateCommandBuffers(mDevice, &command_buffer_info, &mCommandBuffers[i]));
+
+	VkSemaphoreCreateInfo semaphore_info = {};
+	semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+	for (uint32 i = 0; i < cFrameCount; ++i)
+	{
+		FatalErrorIfFailed(vkCreateSemaphore(mDevice, &semaphore_info, nullptr, &mImageAvailableSemaphores[i]));
+		FatalErrorIfFailed(vkCreateSemaphore(mDevice, &semaphore_info, nullptr, &mRenderFinishedSemaphores[i]));
+	}
+
+	VkFenceCreateInfo fence_info = {};
+	fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+	fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+	for (uint32 i = 0; i < cFrameCount; ++i)
+		FatalErrorIfFailed(vkCreateFence(mDevice, &fence_info, nullptr, &mInFlightFences[i]));
+
+	// 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 descriptor set layout for the uniform buffers
+	VkDescriptorSetLayoutBinding ubo_layout_binding[2] = {};
+	ubo_layout_binding[0].binding = 0;
+	ubo_layout_binding[0].descriptorCount = 1;
+	ubo_layout_binding[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+	ubo_layout_binding[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+	ubo_layout_binding[1].binding = 1;
+	ubo_layout_binding[1].descriptorCount = 1;
+	ubo_layout_binding[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+	ubo_layout_binding[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+	VkDescriptorSetLayoutCreateInfo ubo_dsl = {};
+	ubo_dsl.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+	ubo_dsl.bindingCount = std::size(ubo_layout_binding);
+	ubo_dsl.pBindings = ubo_layout_binding;
+	FatalErrorIfFailed(vkCreateDescriptorSetLayout(mDevice, &ubo_dsl, nullptr, &mDescriptorSetLayoutUBO));
+
+	// Create descriptor set layout for the texture binding
+	VkDescriptorSetLayoutBinding texture_layout_binding = {};
+	texture_layout_binding.binding = 0;
+	texture_layout_binding.descriptorCount = 1;
+	texture_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	texture_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+	VkDescriptorSetLayoutCreateInfo texture_dsl = {};
+	texture_dsl.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+	texture_dsl.bindingCount = 1;
+	texture_dsl.pBindings = &texture_layout_binding;
+	FatalErrorIfFailed(vkCreateDescriptorSetLayout(mDevice, &texture_dsl, nullptr, &mDescriptorSetLayoutTexture));
+
+	// Create pipeline layout
+	VkPipelineLayoutCreateInfo pipeline_layout = {};
+	VkDescriptorSetLayout layout_handles[] = { mDescriptorSetLayoutUBO, mDescriptorSetLayoutTexture };
+	pipeline_layout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+	pipeline_layout.setLayoutCount = std::size(layout_handles);
+	pipeline_layout.pSetLayouts = layout_handles;
+	pipeline_layout.pushConstantRangeCount = 0;
+	FatalErrorIfFailed(vkCreatePipelineLayout(mDevice, &pipeline_layout, nullptr, &mPipelineLayout));
+
+	// Create descriptor pool
+	VkDescriptorPoolSize descriptor_pool_size = {};
+	descriptor_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+	descriptor_pool_size.descriptorCount = cFrameCount;
+	VkDescriptorPoolCreateInfo descriptor_info = {};
+	descriptor_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+	descriptor_info.poolSizeCount = 1;
+	descriptor_info.pPoolSizes = &descriptor_pool_size;
+	descriptor_info.maxSets = 256;
+	FatalErrorIfFailed(vkCreateDescriptorPool(mDevice, &descriptor_info, nullptr, &mDescriptorPool));
+
+	// Allocate descriptor sets for 3d rendering
+	Array<VkDescriptorSetLayout> layouts(cFrameCount, mDescriptorSetLayoutUBO);
+	VkDescriptorSetAllocateInfo descriptor_set_alloc_info = {};
+	descriptor_set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+	descriptor_set_alloc_info.descriptorPool = mDescriptorPool;
+	descriptor_set_alloc_info.descriptorSetCount = cFrameCount;
+	descriptor_set_alloc_info.pSetLayouts = layouts.data();
+	FatalErrorIfFailed(vkAllocateDescriptorSets(mDevice, &descriptor_set_alloc_info, mDescriptorSets));
+	for (uint i = 0; i < cFrameCount; i++)
+	{
+		VkDescriptorBufferInfo vs_buffer_info = {};
+		vs_buffer_info.buffer = mVertexShaderConstantBufferProjection[i]->GetBuffer();
+		vs_buffer_info.range = sizeof(VertexShaderConstantBuffer);
+
+		VkDescriptorBufferInfo ps_buffer_info = {};
+		ps_buffer_info.buffer = mPixelShaderConstantBuffer[i]->GetBuffer();
+		ps_buffer_info.range = sizeof(PixelShaderConstantBuffer);
+
+		VkWriteDescriptorSet descriptor_write[2] = {};
+		descriptor_write[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		descriptor_write[0].dstSet = mDescriptorSets[i];
+		descriptor_write[0].dstBinding = 0;
+		descriptor_write[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		descriptor_write[0].descriptorCount = 1;
+		descriptor_write[0].pBufferInfo = &vs_buffer_info;
+		descriptor_write[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		descriptor_write[1].dstSet = mDescriptorSets[i];
+		descriptor_write[1].dstBinding = 1;
+		descriptor_write[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		descriptor_write[1].descriptorCount = 1;
+		descriptor_write[1].pBufferInfo = &ps_buffer_info;
+		vkUpdateDescriptorSets(mDevice, 2, descriptor_write, 0, nullptr);
+	}
+
+	// Allocate descriptor sets for 2d rendering
+	FatalErrorIfFailed(vkAllocateDescriptorSets(mDevice, &descriptor_set_alloc_info, mDescriptorSetsOrtho));
+	for (uint i = 0; i < cFrameCount; i++)
+	{
+		VkDescriptorBufferInfo vs_buffer_info = {};
+		vs_buffer_info.buffer = mVertexShaderConstantBufferOrtho[i]->GetBuffer();
+		vs_buffer_info.range = sizeof(VertexShaderConstantBuffer);
+
+		VkWriteDescriptorSet descriptor_write = {};
+		descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		descriptor_write.dstSet = mDescriptorSetsOrtho[i];
+		descriptor_write.dstBinding = 0;
+		descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		descriptor_write.descriptorCount = 1;
+		descriptor_write.pBufferInfo = &vs_buffer_info;
+		vkUpdateDescriptorSets(mDevice, 1, &descriptor_write, 0, nullptr);
+	}
+
+	// Create regular texture sampler
+	VkSamplerCreateInfo sampler_info = {};
+	sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+	sampler_info.magFilter = VK_FILTER_LINEAR;
+	sampler_info.minFilter = VK_FILTER_LINEAR;
+	sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	sampler_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+	sampler_info.unnormalizedCoordinates = VK_FALSE;
+	sampler_info.compareEnable = VK_FALSE;
+	sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+	FatalErrorIfFailed(vkCreateSampler(mDevice, &sampler_info, nullptr, &mTextureSamplerRepeat));
+
+	// Create sampler for shadow maps
+	sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+	sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+	sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+	sampler_info.compareEnable = VK_TRUE;
+	sampler_info.compareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
+	FatalErrorIfFailed(vkCreateSampler(mDevice, &sampler_info, nullptr, &mTextureSamplerShadow));
+
+	{
+		// Create shadow render pass
+		VkAttachmentDescription shadowmap_attachment = {};
+		shadowmap_attachment.format = FindDepthFormat();
+		shadowmap_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+		shadowmap_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		shadowmap_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		shadowmap_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		shadowmap_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		shadowmap_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		shadowmap_attachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		VkAttachmentReference shadowmap_attachment_ref = {};
+		shadowmap_attachment_ref.attachment = 0;
+		shadowmap_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		VkSubpassDescription subpass_shadow = {};
+		subpass_shadow.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass_shadow.pDepthStencilAttachment = &shadowmap_attachment_ref;
+		VkSubpassDependency dependencies_shadow = {};
+		dependencies_shadow.srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies_shadow.dstSubpass = 0;
+		dependencies_shadow.srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+		dependencies_shadow.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		dependencies_shadow.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+		dependencies_shadow.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		VkRenderPassCreateInfo render_pass_shadow = {};
+		render_pass_shadow.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		render_pass_shadow.attachmentCount = 1;
+		render_pass_shadow.pAttachments = &shadowmap_attachment;
+		render_pass_shadow.subpassCount = 1;
+		render_pass_shadow.pSubpasses = &subpass_shadow;
+		render_pass_shadow.dependencyCount = 1;
+		render_pass_shadow.pDependencies = &dependencies_shadow;
+		FatalErrorIfFailed(vkCreateRenderPass(mDevice, &render_pass_shadow, nullptr, &mRenderPassShadow));
+	}
+
+	// Create depth only texture (no color buffer, as seen from light)
+	mShadowMap = new TextureVK(this, cShadowMapSize, cShadowMapSize);
+
+	// Create frame buffer for the shadow pass
+	VkImageView attachments[] = { mShadowMap->GetImageView() };
+	VkFramebufferCreateInfo frame_buffer_info = {};
+	frame_buffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+	frame_buffer_info.renderPass = mRenderPassShadow;
+	frame_buffer_info.attachmentCount = std::size(attachments);
+	frame_buffer_info.pAttachments = attachments;
+	frame_buffer_info.width = cShadowMapSize;
+	frame_buffer_info.height = cShadowMapSize;
+	frame_buffer_info.layers = 1;
+	FatalErrorIfFailed(vkCreateFramebuffer(mDevice, &frame_buffer_info, nullptr, &mShadowFrameBuffer));
+
+	{
+		// Create normal render pass
+		VkAttachmentDescription attachments_normal[2] = {};
+		VkAttachmentDescription &color_attachment = attachments_normal[0];
+		color_attachment.format = selected_device.mFormat.format;
+		color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+		color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+		VkAttachmentReference color_attachment_ref = {};
+		color_attachment_ref.attachment = 0;
+		color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		VkAttachmentDescription &depth_attachment = attachments_normal[1];
+		depth_attachment.format = FindDepthFormat();
+		depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+		depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		VkAttachmentReference depth_attachment_ref = {};
+		depth_attachment_ref.attachment = 1;
+		depth_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		VkSubpassDescription subpass_normal = {};
+		subpass_normal.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass_normal.colorAttachmentCount = 1;
+		subpass_normal.pColorAttachments = &color_attachment_ref;
+		subpass_normal.pDepthStencilAttachment = &depth_attachment_ref;
+		VkSubpassDependency dependencies_normal = {};
+		dependencies_normal.srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies_normal.dstSubpass = 0;
+		dependencies_normal.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+		dependencies_normal.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		dependencies_normal.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies_normal.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
+		VkRenderPassCreateInfo render_pass_normal = {};
+		render_pass_normal.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		render_pass_normal.attachmentCount = std::size(attachments_normal);
+		render_pass_normal.pAttachments = attachments_normal;
+		render_pass_normal.subpassCount = 1;
+		render_pass_normal.pSubpasses = &subpass_normal;
+		render_pass_normal.dependencyCount = 1;
+		render_pass_normal.pDependencies = &dependencies_normal;
+		FatalErrorIfFailed(vkCreateRenderPass(mDevice, &render_pass_normal, nullptr, &mRenderPass));
+	}
+
+	// Create the swap chain
+	CreateSwapChain(mPhysicalDevice);
+}
+
+VkSurfaceFormatKHR RendererVK::SelectFormat(VkPhysicalDevice inDevice)
+{
+	uint32 format_count;
+	vkGetPhysicalDeviceSurfaceFormatsKHR(inDevice, mSurface, &format_count, nullptr);
+	if (format_count == 0)
+		return { VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
+	Array<VkSurfaceFormatKHR> formats;
+	formats.resize(format_count);
+	vkGetPhysicalDeviceSurfaceFormatsKHR(inDevice, mSurface, &format_count, formats.data());
+
+	// Select BGRA8 UNORM format if available, otherwise the 1st format
+	for (const VkSurfaceFormatKHR &format : formats)
+		if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
+			return format;
+	return formats[0];
+}
+
+VkFormat RendererVK::FindDepthFormat()
+{
+	VkFormat candidates[] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT };
+	for (VkFormat format : candidates)
+	{
+		VkFormatProperties props;
+		vkGetPhysicalDeviceFormatProperties(mPhysicalDevice, format, &props);
+
+		if ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) == VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
+			return format;
+	}
+
+	FatalError("Failed to find format!");
+}
+
+void RendererVK::CreateSwapChain(VkPhysicalDevice inDevice)
+{
+	// Select the format
+	VkSurfaceFormatKHR format = SelectFormat(inDevice);
+	mSwapChainImageFormat = format.format;
+
+	// Determine swap chain extent
+	VkSurfaceCapabilitiesKHR capabilities;
+	vkGetPhysicalDeviceSurfaceCapabilitiesKHR(inDevice, mSurface, &capabilities);
+	mSwapChainExtent = capabilities.currentExtent;
+	if (mSwapChainExtent.width == UINT32_MAX || mSwapChainExtent.height == UINT32_MAX)
+		mSwapChainExtent = { uint32(mWindowWidth), uint32(mWindowHeight) };
+	mSwapChainExtent.width = Clamp(mSwapChainExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
+	mSwapChainExtent.height = Clamp(mSwapChainExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
+
+	// Early out if our window has been minimized
+	if (mSwapChainExtent.width == 0 || mSwapChainExtent.height == 0)
+		return;
+
+	// Create the swap chain
+	uint32 image_count = min(capabilities.minImageCount + 1, capabilities.maxImageCount);
+	VkSwapchainCreateInfoKHR swapchain_create_info = {};
+	swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+	swapchain_create_info.surface = mSurface;
+	swapchain_create_info.minImageCount = image_count;
+	swapchain_create_info.imageFormat = format.format;
+	swapchain_create_info.imageColorSpace = format.colorSpace;
+	swapchain_create_info.imageExtent = mSwapChainExtent;
+	swapchain_create_info.imageArrayLayers = 1;
+	swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+	uint32 queue_family_indices[] = { mGraphicsQueueIndex, mPresentQueueIndex };
+	if (mGraphicsQueueIndex != mPresentQueueIndex)
+	{
+		swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+		swapchain_create_info.queueFamilyIndexCount = 2;
+		swapchain_create_info.pQueueFamilyIndices = queue_family_indices;
+	}
+	else
+	{
+		swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	}
+	swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	swapchain_create_info.preTransform = capabilities.currentTransform;
+	swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+	swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
+	swapchain_create_info.clipped = VK_TRUE;
+	FatalErrorIfFailed(vkCreateSwapchainKHR(mDevice, &swapchain_create_info, nullptr, &mSwapChain));
+
+	// Get the swap chain images
+	mSwapChainImages.resize(image_count);
+	vkGetSwapchainImagesKHR(mDevice, mSwapChain, &image_count, mSwapChainImages.data());
+
+	// Create image views
+	mSwapChainImageViews.resize(image_count);
+	for (uint32 i = 0; i < image_count; ++i)
+		mSwapChainImageViews[i] = CreateImageView(mSwapChainImages[i], mSwapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT);
+
+	// Create depth buffer
+	VkFormat depth_format = FindDepthFormat();
+	CreateImage(mSwapChainExtent.width, mSwapChainExtent.height, depth_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mDepthImage, mDepthImageMemory);
+	mDepthImageView = CreateImageView(mDepthImage, depth_format, VK_IMAGE_ASPECT_DEPTH_BIT);
+
+	// Create frame buffers for the normal pass
+	mSwapChainFramebuffers.resize(image_count);
+	for (size_t i = 0; i < mSwapChainFramebuffers.size(); i++)
+	{
+		VkImageView attachments[] = { mSwapChainImageViews[i], mDepthImageView };
+		VkFramebufferCreateInfo frame_buffer_info = {};
+		frame_buffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+		frame_buffer_info.renderPass = mRenderPass;
+		frame_buffer_info.attachmentCount = std::size(attachments);
+		frame_buffer_info.pAttachments = attachments;
+		frame_buffer_info.width = mSwapChainExtent.width;
+		frame_buffer_info.height = mSwapChainExtent.height;
+		frame_buffer_info.layers = 1;
+		FatalErrorIfFailed(vkCreateFramebuffer(mDevice, &frame_buffer_info, nullptr, &mSwapChainFramebuffers[i]));
+	}
+}
+
+void RendererVK::DestroySwapChain()
+{
+	// Destroy depth buffer
+	if (mDepthImageView != VK_NULL_HANDLE)
+	{
+		vkDestroyImageView(mDevice, mDepthImageView, nullptr);
+		vkDestroyImage(mDevice, mDepthImage, nullptr);
+		vkFreeMemory(mDevice, mDepthImageMemory, nullptr);
+	}
+
+	for (VkFramebuffer frame_buffer : mSwapChainFramebuffers)
+		vkDestroyFramebuffer(mDevice, frame_buffer, nullptr);
+	mSwapChainFramebuffers.clear();
+
+	for (VkImageView view : mSwapChainImageViews)
+		vkDestroyImageView(mDevice, view, nullptr);
+	mSwapChainImageViews.clear();
+
+	if (mSwapChain != nullptr)
+	{
+		vkDestroySwapchainKHR(mDevice, mSwapChain, nullptr);
+		mSwapChain = nullptr;
+	}
+}
+
+void RendererVK::OnWindowResize()
+{
+	Renderer::OnWindowResize();
+
+	vkDeviceWaitIdle(mDevice);
+	DestroySwapChain();
+	CreateSwapChain(mPhysicalDevice);
+}
+
+void RendererVK::BeginFrame(const CameraState &inCamera, float inWorldScale)
+{
+	JPH_PROFILE_FUNCTION();
+
+	Renderer::BeginFrame(inCamera, inWorldScale);
+
+	// If we have no swap chain, bail out
+	if (mSwapChain == nullptr)
+		return;
+
+	// Update frame index
+	mFrameIndex = (mFrameIndex + 1) % cFrameCount;
+
+	// Wait for this frame to complete
+	vkWaitForFences(mDevice, 1, &mInFlightFences[mFrameIndex], VK_TRUE, UINT64_MAX);
+
+	VkResult result = vkAcquireNextImageKHR(mDevice, mSwapChain, UINT64_MAX, mImageAvailableSemaphores[mFrameIndex], VK_NULL_HANDLE, &mImageIndex);
+	if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
+	{
+		vkDeviceWaitIdle(mDevice);
+		DestroySwapChain();
+		CreateSwapChain(mPhysicalDevice);
+		if (mSwapChain == nullptr)
+			return;
+		result = vkAcquireNextImageKHR(mDevice, mSwapChain, UINT64_MAX, mImageAvailableSemaphores[mFrameIndex], VK_NULL_HANDLE, &mImageIndex);
+	}
+	FatalErrorIfFailed(result);
+
+	// Free buffers that weren't used this frame
+	for (BufferCache::value_type &vt : mBufferCache)
+		for (BufferVK &bvk : vt.second)
+			bvk.Free(mDevice);
+	mBufferCache.clear();
+
+	// Recycle the buffers that were freed
+	mBufferCache.swap(mFreedBuffers[mFrameIndex]);
+	
+	vkResetFences(mDevice, 1, &mInFlightFences[mFrameIndex]);
+
+	VkCommandBuffer command_buffer = GetCommandBuffer();
+	FatalErrorIfFailed(vkResetCommandBuffer(command_buffer, 0));
+
+	VkCommandBufferBeginInfo command_buffer_begin_info = {};
+	command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+	FatalErrorIfFailed(vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info));
+
+	// Begin the shadow pass
+	VkClearValue clear_value;
+	clear_value.depthStencil = { 0.0f, 0 };
+	VkRenderPassBeginInfo render_pass_begin_info = {};
+	render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+	render_pass_begin_info.renderPass = mRenderPassShadow;
+	render_pass_begin_info.framebuffer = mShadowFrameBuffer;
+	render_pass_begin_info.renderArea.extent = { cShadowMapSize, cShadowMapSize };
+	render_pass_begin_info.clearValueCount = 1;
+	render_pass_begin_info.pClearValues = &clear_value;
+	vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
+
+	// 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();
+
+	// Set constants for pixel shader
+	PixelShaderConstantBuffer *ps = mPixelShaderConstantBuffer[mFrameIndex]->Map<PixelShaderConstantBuffer>();
+	*ps = mPSBuffer;
+	mPixelShaderConstantBuffer[mFrameIndex]->Unmap();
+
+	// Set the view port and scissor rect to the shadow map size
+	UpdateViewPortAndScissorRect(cShadowMapSize, cShadowMapSize);
+
+	// Switch to 3d projection mode
+	SetProjectionMode();
+}
+
+void RendererVK::EndShadowPass()
+{
+	VkCommandBuffer command_buffer = GetCommandBuffer();
+
+	// End the shadow pass
+	vkCmdEndRenderPass(command_buffer);
+
+	// Begin the normal render pass
+	VkClearValue clear_values[2];
+	clear_values[0].color = {{ 0.098f, 0.098f, 0.439f, 1.000f }};
+	clear_values[1].depthStencil = { 0.0f, 0 }; // Reverse-Z clears to 0
+	VkRenderPassBeginInfo render_pass_begin_info = {};
+	render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+	render_pass_begin_info.renderPass = mRenderPass;
+	JPH_ASSERT(mImageIndex < mSwapChainFramebuffers.size());
+	render_pass_begin_info.framebuffer = mSwapChainFramebuffers[mImageIndex];
+	render_pass_begin_info.renderArea.extent = mSwapChainExtent;
+	render_pass_begin_info.clearValueCount = std::size(clear_values);
+	render_pass_begin_info.pClearValues = clear_values;
+	vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
+
+	// Set the view port and scissor rect to the screen size
+	UpdateViewPortAndScissorRect(mSwapChainExtent.width, mSwapChainExtent.height);
+}
+
+void RendererVK::EndFrame()
+{
+	JPH_PROFILE_FUNCTION();
+
+	// If we have no swap chain, bail out
+	if (mSwapChain == nullptr)
+	{
+		Renderer::EndFrame();
+		return;
+	}
+
+	VkCommandBuffer command_buffer = GetCommandBuffer();
+	vkCmdEndRenderPass(command_buffer);
+
+	FatalErrorIfFailed(vkEndCommandBuffer(command_buffer));
+
+	VkSemaphore wait_semaphores[] = { mImageAvailableSemaphores[mFrameIndex] };
+	VkSemaphore signal_semaphores[] = { mRenderFinishedSemaphores[mFrameIndex] };
+	VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+	VkSubmitInfo submit_info = {};
+	submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+	submit_info.waitSemaphoreCount = 1;
+	submit_info.pWaitSemaphores = wait_semaphores;
+	submit_info.pWaitDstStageMask = wait_stages;
+	submit_info.commandBufferCount = 1;
+	submit_info.pCommandBuffers = &command_buffer;
+	submit_info.signalSemaphoreCount = 1;
+	submit_info.pSignalSemaphores = signal_semaphores;
+	FatalErrorIfFailed(vkQueueSubmit(mGraphicsQueue, 1, &submit_info, mInFlightFences[mFrameIndex]));
+
+	VkSwapchainKHR swap_chains[] = { mSwapChain };
+	VkPresentInfoKHR present_info = {};
+	present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+	present_info.waitSemaphoreCount = 1;
+	present_info.pWaitSemaphores = signal_semaphores;
+	present_info.swapchainCount = 1;
+	present_info.pSwapchains = swap_chains;
+	present_info.pImageIndices = &mImageIndex;
+	vkQueuePresentKHR(mPresentQueue, &present_info);
+
+	Renderer::EndFrame();
+}
+
+void RendererVK::SetProjectionMode()
+{
+	JPH_ASSERT(mInFrame);
+
+	// Bind descriptor set for 3d rendering
+	vkCmdBindDescriptorSets(GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mPipelineLayout, 0, 1, &mDescriptorSets[mFrameIndex], 0, nullptr);
+}
+
+void RendererVK::SetOrthoMode()
+{
+	JPH_ASSERT(mInFrame);
+
+	// Bind descriptor set for 2d rendering
+	vkCmdBindDescriptorSets(GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mPipelineLayout, 0, 1, &mDescriptorSetsOrtho[mFrameIndex], 0, nullptr);
+}
+
+Ref<Texture> RendererVK::CreateTexture(const Surface *inSurface)
+{
+	return new TextureVK(this, inSurface);
+}
+
+Ref<VertexShader> RendererVK::CreateVertexShader(const char *inFileName)
+{
+	Array<uint8> data = ReadData((String(inFileName) + ".vert.spv").c_str());
+
+	VkShaderModuleCreateInfo create_info = {};
+	create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+	create_info.codeSize = data.size();
+	create_info.pCode = reinterpret_cast<const uint32 *>(data.data());
+	VkShaderModule shader_module;
+	FatalErrorIfFailed(vkCreateShaderModule(mDevice, &create_info, nullptr, &shader_module));
+
+	return new VertexShaderVK(mDevice, shader_module);
+}
+
+Ref<PixelShader> RendererVK::CreatePixelShader(const char *inFileName)
+{
+	Array<uint8> data = ReadData((String(inFileName) + ".frag.spv").c_str());
+
+	VkShaderModuleCreateInfo create_info = {};
+	create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+	create_info.codeSize = data.size();
+	create_info.pCode = reinterpret_cast<const uint32 *>(data.data());
+	VkShaderModule shader_module;
+	FatalErrorIfFailed(vkCreateShaderModule(mDevice, &create_info, nullptr, &shader_module));
+
+	return new PixelShaderVK(mDevice, shader_module);
+}
+
+unique_ptr<PipelineState> RendererVK::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<PipelineStateVK>(this, static_cast<const VertexShaderVK *>(inVertexShader), inInputDescription, inInputDescriptionCount, static_cast<const PixelShaderVK *>(inPixelShader), inDrawPass, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode);
+}
+
+RenderPrimitive *RendererVK::CreateRenderPrimitive(PipelineState::ETopology inType)
+{
+	return new RenderPrimitiveVK(this);
+}
+
+RenderInstances *RendererVK::CreateRenderInstances()
+{
+	return new RenderInstancesVK(this);
+}
+
+uint32 RendererVK::FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties)
+{
+	for (uint32 i = 0; i < mMemoryProperties.memoryTypeCount; i++)
+		if ((inTypeFilter & (1 << i))
+			&& (mMemoryProperties.memoryTypes[i].propertyFlags & inProperties) == inProperties)
+			return i;
+
+	FatalError("Failed to find memory type!");
+}
+
+void RendererVK::CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer)
+{
+	// Check the cache
+	BufferCache::iterator i = mBufferCache.find({ inSize, inUsage, inProperties });
+	if (i != mBufferCache.end() && !i->second.empty())
+	{
+		outBuffer = i->second.back();
+		i->second.pop_back();
+		return;
+	}
+
+	// Create a new buffer
+	outBuffer.mSize = inSize;
+	outBuffer.mUsage = inUsage;
+	outBuffer.mProperties = inProperties;
+
+	VkBufferCreateInfo create_info = {};
+	create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+	create_info.size = inSize;
+	create_info.usage = inUsage;
+	create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	FatalErrorIfFailed(vkCreateBuffer(mDevice, &create_info, nullptr, &outBuffer.mBuffer));
+
+	VkMemoryRequirements mem_requirements;
+	vkGetBufferMemoryRequirements(mDevice, outBuffer.mBuffer, &mem_requirements);
+
+	VkMemoryAllocateInfo alloc_info = {};
+	alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+	alloc_info.allocationSize = mem_requirements.size;
+	alloc_info.memoryTypeIndex = FindMemoryType(mem_requirements.memoryTypeBits, inProperties);
+
+	FatalErrorIfFailed(vkAllocateMemory(mDevice, &alloc_info, nullptr, &outBuffer.mMemory));
+
+	vkBindBufferMemory(mDevice, outBuffer.mBuffer, outBuffer.mMemory, 0);
+}
+
+VkCommandBuffer RendererVK::StartTempCommandBuffer()
+{
+	VkCommandBufferAllocateInfo alloc_info = {};
+	alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+	alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+	alloc_info.commandPool = mCommandPool;
+	alloc_info.commandBufferCount = 1;
+
+	VkCommandBuffer command_buffer;
+	vkAllocateCommandBuffers(mDevice, &alloc_info, &command_buffer);
+
+	VkCommandBufferBeginInfo begin_info = {};
+	begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+	begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+	vkBeginCommandBuffer(command_buffer, &begin_info);
+
+	return command_buffer;
+}
+
+void RendererVK::EndTempCommandBuffer(VkCommandBuffer inCommandBuffer)
+{
+	vkEndCommandBuffer(inCommandBuffer);
+
+	VkSubmitInfo submit_info = {};
+	submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+	submit_info.commandBufferCount = 1;
+	submit_info.pCommandBuffers = &inCommandBuffer;
+
+	vkQueueSubmit(mGraphicsQueue, 1, &submit_info, VK_NULL_HANDLE);
+	vkQueueWaitIdle(mGraphicsQueue); // Inefficient, but we only use this during initialization
+
+	vkFreeCommandBuffers(mDevice, mCommandPool, 1, &inCommandBuffer);
+}
+
+void RendererVK::CopyBuffer(VkBuffer inSrc, VkBuffer inDst, VkDeviceSize inSize)
+{
+	VkCommandBuffer command_buffer = StartTempCommandBuffer();
+
+	VkBufferCopy region = {};
+	region.size = inSize;
+	vkCmdCopyBuffer(command_buffer, inSrc, inDst, 1, &region);
+
+	EndTempCommandBuffer(command_buffer);
+}
+
+void RendererVK::CreateDeviceLocalBuffer(const void *inData, VkDeviceSize inSize, VkBufferUsageFlags inUsage, BufferVK &outBuffer)
+{
+	BufferVK staging_buffer;
+	CreateBuffer(inSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging_buffer);
+
+	void *data;
+	vkMapMemory(mDevice, staging_buffer.mMemory, 0, inSize, 0, &data);
+	memcpy(data, inData, (size_t)inSize);
+	vkUnmapMemory(mDevice, staging_buffer.mMemory);
+
+	CreateBuffer(inSize, inUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, outBuffer);
+
+	CopyBuffer(staging_buffer.mBuffer, outBuffer.mBuffer, inSize);
+
+	FreeBuffer(staging_buffer);
+}
+
+void RendererVK::FreeBuffer(BufferVK &ioBuffer)
+{
+	if (ioBuffer.mBuffer != VK_NULL_HANDLE)
+	{
+		JPH_ASSERT(mFrameIndex < cFrameCount);
+		mFreedBuffers[mFrameIndex][{ ioBuffer.mSize, ioBuffer.mUsage, ioBuffer.mProperties }].push_back(ioBuffer);
+	}
+}
+
+unique_ptr<ConstantBufferVK> RendererVK::CreateConstantBuffer(VkDeviceSize inBufferSize)
+{
+	return make_unique<ConstantBufferVK>(this, inBufferSize);
+}
+
+VkImageView RendererVK::CreateImageView(VkImage inImage, VkFormat inFormat, VkImageAspectFlags inAspectFlags)
+{
+	VkImageViewCreateInfo view_info = {};
+	view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+	view_info.image = inImage;
+	view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	view_info.format = inFormat;
+	view_info.subresourceRange.aspectMask = inAspectFlags;
+	view_info.subresourceRange.levelCount = 1;
+	view_info.subresourceRange.layerCount = 1;
+
+	VkImageView image_view;
+	FatalErrorIfFailed(vkCreateImageView(mDevice, &view_info, nullptr, &image_view));
+
+	return image_view;
+}
+
+void RendererVK::CreateImage(uint32 inWidth, uint32 inHeight, VkFormat inFormat, VkImageTiling inTiling, VkImageUsageFlags inUsage, VkMemoryPropertyFlags inProperties, VkImage &outImage, VkDeviceMemory &outMemory)
+{
+	VkImageCreateInfo image_info = {};
+	image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+	image_info.imageType = VK_IMAGE_TYPE_2D;
+	image_info.extent.width = inWidth;
+	image_info.extent.height = inHeight;
+	image_info.extent.depth = 1;
+	image_info.mipLevels = 1;
+	image_info.arrayLayers = 1;
+	image_info.format = inFormat;
+	image_info.tiling = inTiling;
+	image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	image_info.usage = inUsage;
+	image_info.samples = VK_SAMPLE_COUNT_1_BIT;
+	image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	FatalErrorIfFailed(vkCreateImage(mDevice, &image_info, nullptr, &outImage));
+
+	VkMemoryRequirements mem_requirements;
+	vkGetImageMemoryRequirements(mDevice, outImage, &mem_requirements);
+
+	VkMemoryAllocateInfo alloc_info = {};
+	alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+	alloc_info.allocationSize = mem_requirements.size;
+	alloc_info.memoryTypeIndex = FindMemoryType(mem_requirements.memoryTypeBits, inProperties);
+	FatalErrorIfFailed(vkAllocateMemory(mDevice, &alloc_info, nullptr, &outMemory));
+
+	vkBindImageMemory(mDevice, outImage, outMemory, 0);
+}
+
+void RendererVK::UpdateViewPortAndScissorRect(uint32 inWidth, uint32 inHeight)
+{
+	VkCommandBuffer command_buffer = GetCommandBuffer();
+
+	// Update the view port rect
+	VkViewport viewport = {};
+	viewport.x = 0.0f;
+	viewport.y = 0.0f;
+	viewport.width = (float)inWidth;
+	viewport.height = (float)inHeight;
+	viewport.minDepth = 0.0f;
+	viewport.maxDepth = 1.0f;
+	vkCmdSetViewport(command_buffer, 0, 1, &viewport);
+
+	// Update the scissor rect
+	VkRect2D scissor = {};
+	scissor.extent = { inWidth, inHeight };
+	vkCmdSetScissor(command_buffer, 0, 1, &scissor);
+}

+ 123 - 0
TestFramework/Renderer/VK/RendererVK.h

@@ -0,0 +1,123 @@
+// 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() override;
+	virtual void					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 *inFileName) override;
+	virtual Ref<PixelShader>		CreatePixelShader(const char *inFileName) 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							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);
+	VkImageView						CreateImageView(VkImage inImage, VkFormat inFormat, VkImageAspectFlags inAspectFlags);
+	VkFormat						FindDepthFormat();
+
+private:
+	uint32							FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties);
+	VkSurfaceFormatKHR				SelectFormat(VkPhysicalDevice inDevice);
+	void							CreateSwapChain(VkPhysicalDevice inDevice);
+	void							DestroySwapChain();
+	void							UpdateViewPortAndScissorRect(uint32 inWidth, uint32 inHeight);
+
+	VkInstance						mInstance = VK_NULL_HANDLE;
+	VkDebugUtilsMessengerEXT		mDebugMessenger = VK_NULL_HANDLE;
+	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;
+	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];
+	VkSemaphore						mImageAvailableSemaphores[cFrameCount];
+	VkSemaphore						mRenderFinishedSemaphores[cFrameCount];
+	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);
+
+	using BufferCache = UnorderedMap<Key, Array<BufferVK>, KeyHasher>;
+
+	BufferCache						mFreedBuffers[cFrameCount];
+	BufferCache						mBufferCache;
+};

+ 180 - 0
TestFramework/Renderer/VK/TextureVK.cpp

@@ -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, 0, 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);
+		vkDestroyImage(device, mImage, nullptr);
+		vkFreeMemory(device, mImageMemory, nullptr);
+	}
+}
+
+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);
+	}
+}

+ 35 - 0
TestFramework/Renderer/VK/TextureVK.h

@@ -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;
+};

+ 34 - 0
TestFramework/Renderer/VK/VertexShaderVK.h

@@ -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;
+};

+ 15 - 0
TestFramework/Renderer/VertexShader.h

@@ -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;
+};

+ 101 - 24
TestFramework/TestFramework.cmake

@@ -24,27 +24,38 @@ set(TEST_FRAMEWORK_SRC_FILES
 	${TEST_FRAMEWORK_ROOT}/Input/Mouse.h
 	${TEST_FRAMEWORK_ROOT}/Input/Mouse.h
 	${TEST_FRAMEWORK_ROOT}/Math/Perlin.cpp
 	${TEST_FRAMEWORK_ROOT}/Math/Perlin.cpp
 	${TEST_FRAMEWORK_ROOT}/Math/Perlin.h
 	${TEST_FRAMEWORK_ROOT}/Math/Perlin.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/ConstantBuffer.cpp
-	${TEST_FRAMEWORK_ROOT}/Renderer/ConstantBuffer.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/CommandQueue.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/DescriptorHeap.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/FatalErrorIfFailed.cpp
-	${TEST_FRAMEWORK_ROOT}/Renderer/FatalErrorIfFailed.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/ConstantBufferDX12.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/ConstantBufferDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/CommandQueueDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/DescriptorHeapDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/FatalErrorIfFailedDX12.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/FatalErrorIfFailedDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PipelineStateDX12.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PipelineStateDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PixelShaderDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RendererDX12.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RendererDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderInstancesDX12.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderInstancesDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderPrimitiveDX12.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderPrimitiveDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.cpp
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/DX12/VertexShaderDX12.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/Font.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/Font.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/Font.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/Font.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/Frustum.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/Frustum.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/PipelineState.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/PipelineState.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/PipelineState.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/PixelShader.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/RenderInstances.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/RenderInstances.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/RenderInstances.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/Texture.cpp
 	${TEST_FRAMEWORK_ROOT}/Renderer/Texture.h
 	${TEST_FRAMEWORK_ROOT}/Renderer/Texture.h
-	${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.cpp
-	${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.h
+	${TEST_FRAMEWORK_ROOT}/Renderer/VertexShader.h
 	${TEST_FRAMEWORK_ROOT}/TestFramework.cmake
 	${TEST_FRAMEWORK_ROOT}/TestFramework.cmake
 	${TEST_FRAMEWORK_ROOT}/TestFramework.h
 	${TEST_FRAMEWORK_ROOT}/TestFramework.h
 	${TEST_FRAMEWORK_ROOT}/UI/UIAnimation.cpp
 	${TEST_FRAMEWORK_ROOT}/UI/UIAnimation.cpp
@@ -83,27 +94,24 @@ set(TEST_FRAMEWORK_SRC_FILES
 	${TEST_FRAMEWORK_ROOT}/Utils/ReadData.h
 	${TEST_FRAMEWORK_ROOT}/Utils/ReadData.h
 )
 )
 
 
-# Group source files
-source_group(TREE ${TEST_FRAMEWORK_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES})
-
 # All shaders
 # All shaders
 set(TEST_FRAMEWORK_SRC_FILES_SHADERS
 set(TEST_FRAMEWORK_SRC_FILES_SHADERS
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/VertexConstants.h
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/VertexConstants.h
 )
 )
 
 
-# Vertex shaders
-set(TEST_FRAMEWORK_VERTEX_SHADERS
+# HLSL vertex shaders
+set(TEST_FRAMEWORK_HLSL_VERTEX_SHADERS
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/FontVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/FontVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/LineVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/LineVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/UIVertexShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/UIVertexShader.hlsl
 )
 )
-set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_VERTEX_SHADERS})
-set_source_files_properties(${TEST_FRAMEWORK_VERTEX_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T vs_5_0")
+set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS})
+set_source_files_properties(${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T vs_5_0")
 
 
-# Pixel shaders
-set(TEST_FRAMEWORK_PIXEL_SHADERS
+# HLSL pixel shaders
+set(TEST_FRAMEWORK_HLSL_PIXEL_SHADERS
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/FontPixelShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/FontPixelShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/LinePixelShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/LinePixelShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthPixelShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthPixelShader.hlsl
@@ -111,14 +119,83 @@ set(TEST_FRAMEWORK_PIXEL_SHADERS
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShader.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShaderUntextured.hlsl
 	${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShaderUntextured.hlsl
 )
 )
-set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_PIXEL_SHADERS})
-set_source_files_properties(${TEST_FRAMEWORK_PIXEL_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T ps_5_0")
+set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS})
+set_source_files_properties(${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T ps_5_0")
+
+# Include the Vulkan library
+find_package(Vulkan)
+if (Vulkan_FOUND)
+	# Vulkan source files
+	set(TEST_FRAMEWORK_SRC_FILES
+		${TEST_FRAMEWORK_SRC_FILES}
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/BufferVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/ConstantBufferVK.cpp
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/ConstantBufferVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/FatalErrorIfFailedVK.cpp
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/FatalErrorIfFailedVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/PipelineStateVK.cpp
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/PipelineStateVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/PixelShaderVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/RendererVK.cpp
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/RendererVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderInstancesVK.cpp
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderInstancesVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderPrimitiveVK.cpp
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderPrimitiveVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/TextureVK.cpp
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/TextureVK.h
+		${TEST_FRAMEWORK_ROOT}/Renderer/VK/VertexShaderVK.h
+	)
+
+	# GLSL headers
+	set(TEST_FRAMEWORK_SRC_FILES_SHADERS
+		${TEST_FRAMEWORK_SRC_FILES_SHADERS}
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/VertexConstantsVK.h
+	)
+
+	# GLSL shaders
+	set(TEST_FRAMEWORK_GLSL_SHADERS
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/FontVertexShader.vert
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/LineVertexShader.vert
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthVertexShader.vert
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleVertexShader.vert
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/UIVertexShader.vert
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/FontPixelShader.frag
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/LinePixelShader.frag
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthPixelShader.frag
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/TrianglePixelShader.frag
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShader.frag
+		${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShaderUntextured.frag
+	)
+
+	# Compile GLSL shaders
+	foreach(SHADER ${TEST_FRAMEWORK_GLSL_SHADERS})
+		set(SPV_SHADER ${SHADER}.spv)
+		add_custom_command(OUTPUT ${SPV_SHADER}
+			COMMAND ${Vulkan_GLSLC_EXECUTABLE} ${SHADER} -o ${SPV_SHADER}
+			DEPENDS ${SHADER}
+			COMMENT "Compiling ${SHADER}")
+		list(APPEND TEST_FRAMEWORK_SPV_SHADERS ${SPV_SHADER})
+	endforeach()
+endif()
+
+# Group source files
+source_group(TREE ${TEST_FRAMEWORK_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES})
 
 
 # Group shader files
 # Group shader files
-source_group(TREE ${PHYSICS_REPO_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES_SHADERS})
+source_group(TREE ${PHYSICS_REPO_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_GLSL_SHADERS} ${TEST_FRAMEWORK_SPV_SHADERS})
 
 
 # Create TestFramework lib
 # Create TestFramework lib
-add_library(TestFramework STATIC ${TEST_FRAMEWORK_SRC_FILES} ${TEST_FRAMEWORK_SRC_FILES_SHADERS})
+add_library(TestFramework STATIC ${TEST_FRAMEWORK_SRC_FILES} ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_SPV_SHADERS})
 target_include_directories(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT})
 target_include_directories(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT})
 target_link_libraries(TestFramework LINK_PUBLIC Jolt dxguid.lib dinput8.lib dxgi.lib d3d12.lib d3dcompiler.lib)
 target_link_libraries(TestFramework LINK_PUBLIC Jolt dxguid.lib dinput8.lib dxgi.lib d3d12.lib d3dcompiler.lib)
 target_precompile_headers(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT}/TestFramework.h)
 target_precompile_headers(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT}/TestFramework.h)
+
+# Enable Vulkan for the TestFramework
+if (Vulkan_FOUND)
+	target_include_directories(TestFramework PUBLIC ${Vulkan_INCLUDE_DIRS})
+	target_link_libraries(TestFramework LINK_PUBLIC Jolt ${Vulkan_LIBRARIES})
+	if (JPH_ENABLE_VULKAN)
+		target_compile_definitions(TestFramework PRIVATE JPH_ENABLE_VULKAN)
+	endif()
+endif()

+ 20 - 20
TestFramework/UI/UIManager.cpp

@@ -29,22 +29,22 @@ UIManager::UIManager(Renderer *inRenderer) :
 	SetHeight(mRenderer->GetWindowHeight());
 	SetHeight(mRenderer->GetWindowHeight());
 
 
 	// Create input layout
 	// Create input layout
-	const D3D12_INPUT_ELEMENT_DESC vertex_desc[] =
+	const PipelineState::EInputDescription vertex_desc[] =
 	{
 	{
-		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-		{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
+		PipelineState::EInputDescription::Position,
+		PipelineState::EInputDescription::TexCoord,
+		PipelineState::EInputDescription::Color
 	};
 	};
 
 
 	// Load vertex shader
 	// Load vertex shader
-	ComPtr<ID3DBlob> vtx = mRenderer->CreateVertexShader("Assets/Shaders/UIVertexShader.hlsl");
+	Ref<VertexShader> vtx = mRenderer->CreateVertexShader("Assets/Shaders/UIVertexShader");
 
 
 	// Load pixel shader
 	// Load pixel shader
-	ComPtr<ID3DBlob> pix_textured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShader.hlsl");
-	ComPtr<ID3DBlob> pix_untextured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShaderUntextured.hlsl");
+	Ref<PixelShader> pix_textured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShader");
+	Ref<PixelShader> pix_untextured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShaderUntextured");
 
 
-	mTextured = mRenderer->CreatePipelineState(vtx.Get(), vertex_desc, ARRAYSIZE(vertex_desc), pix_textured.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
-	mUntextured = mRenderer->CreatePipelineState(vtx.Get(), vertex_desc, ARRAYSIZE(vertex_desc), pix_untextured.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
+	mTextured = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix_textured, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
+	mUntextured = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix_untextured, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
 }
 }
 
 
 UIManager::~UIManager()
 UIManager::~UIManager()
@@ -264,8 +264,8 @@ void UIManager::DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITe
 	{
 	{
 		bool has_inner = inQuad.HasInnerPart();
 		bool has_inner = inQuad.HasInnerPart();
 
 
-		RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-		primitive.CreateVertexBuffer(has_inner? 9 * 6 : 6, sizeof(QuadVertex));
+		Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
+		primitive->CreateVertexBuffer(has_inner? 9 * 6 : 6, sizeof(QuadVertex));
 
 
 		float w = float(inQuad.mTexture->GetWidth()), h = float(inQuad.mTexture->GetHeight());
 		float w = float(inQuad.mTexture->GetWidth()), h = float(inQuad.mTexture->GetHeight());
 
 
@@ -277,7 +277,7 @@ void UIManager::DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITe
 		tx1 /= w; ty1 /= h;
 		tx1 /= w; ty1 /= h;
 		tx2 /= w; ty2 /= h;
 		tx2 /= w; ty2 /= h;
 
 
-		QuadVertex *v = (QuadVertex *)primitive.LockVertexBuffer();
+		QuadVertex *v = (QuadVertex *)primitive->LockVertexBuffer();
 
 
 		if (has_inner)
 		if (has_inner)
 		{
 		{
@@ -312,20 +312,20 @@ void UIManager::DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITe
 			sDrawQuad(v, x1, y1, x2, y2, tx1, ty1, tx2, ty2, inColor);
 			sDrawQuad(v, x1, y1, x2, y2, tx1, ty1, tx2, ty2, inColor);
 		}
 		}
 
 
-		primitive.UnlockVertexBuffer();
-		inQuad.mTexture->Bind(2);
+		primitive->UnlockVertexBuffer();
+		inQuad.mTexture->Bind();
 		mTextured->Activate();
 		mTextured->Activate();
-		primitive.Draw();
+		primitive->Draw();
 	}
 	}
 	else
 	else
 	{
 	{
-		RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-		primitive.CreateVertexBuffer(6, sizeof(QuadVertex));
-		QuadVertex *v = (QuadVertex *)primitive.LockVertexBuffer();
+		Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
+		primitive->CreateVertexBuffer(6, sizeof(QuadVertex));
+		QuadVertex *v = (QuadVertex *)primitive->LockVertexBuffer();
 		sDrawQuad(v, x1, y1, x2, y2, 0, 0, 0, 0, inColor);
 		sDrawQuad(v, x1, y1, x2, y2, 0, 0, 0, 0, inColor);
-		primitive.UnlockVertexBuffer();
+		primitive->UnlockVertexBuffer();
 		mUntextured->Activate();
 		mUntextured->Activate();
-		primitive.Draw();
+		primitive->Draw();
 	}
 	}
 }
 }
 
 

+ 1 - 0
TestFramework/UI/UIManager.h

@@ -6,6 +6,7 @@
 
 
 #include <UI/UIElement.h>
 #include <UI/UIElement.h>
 #include <UI/UITexturedQuad.h>
 #include <UI/UITexturedQuad.h>
+#include <Renderer/Renderer.h>
 #include <Renderer/PipelineState.h>
 #include <Renderer/PipelineState.h>
 #include <memory>
 #include <memory>