Skip to content

Commit

Permalink
Implemented a Metal based renderer on macOS (#1450)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrouwe authored Jan 10, 2025
1 parent f25e6c5 commit 582b1ef
Show file tree
Hide file tree
Showing 32 changed files with 1,272 additions and 64 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/*.jor
/detlog.txt
/Assets/Shaders/VK/*.spv
/Assets/Shaders/MTL/*.metallib
39 changes: 39 additions & 0 deletions Assets/Shaders/MTL/FontShader.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <metal_stdlib>

using namespace metal;

#include "VertexConstants.h"

constexpr sampler alphaTextureSampler(mag_filter::linear, min_filter::linear);

struct FontVertex
{
float3 vPos [[attribute(0)]];
float2 vTex [[attribute(1)]];
uchar4 vCol [[attribute(2)]];
};

struct FontOut
{
float4 oPosition [[position]];
float2 oTex;
float4 oColor;
};

vertex FontOut FontVertexShader(FontVertex vert [[stage_in]], constant VertexShaderConstantBuffer *constants [[buffer(2)]])
{
FontOut out;
out.oPosition = constants->Projection * constants->View * float4(vert.vPos, 1.0);
out.oTex = vert.vTex;
out.oColor = float4(vert.vCol) / 255.0;
return out;
}

fragment float4 FontPixelShader(FontOut in [[stage_in]], texture2d<float> alphaTexture [[texture(0)]])
{
const float4 sample = alphaTexture.sample(alphaTextureSampler, in.oTex);
if (sample.x < 0.5)
discard_fragment();

return float4(in.oColor.xyz, sample.x);
}
30 changes: 30 additions & 0 deletions Assets/Shaders/MTL/LineShader.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <metal_stdlib>

using namespace metal;

#include "VertexConstants.h"

struct LineVertex
{
float3 iPosition [[attribute(0)]];
uchar4 iColor [[attribute(1)]];
};

struct LineOut
{
float4 oPosition [[position]];
float4 oColor;
};

vertex LineOut LineVertexShader(LineVertex vert [[stage_in]], constant VertexShaderConstantBuffer *constants [[buffer(2)]])
{
LineOut out;
out.oPosition = constants->Projection * constants->View * float4(vert.iPosition, 1.0);
out.oColor = float4(vert.iColor) / 255.0;
return out;
}

fragment float4 LinePixelShader(LineOut in [[stage_in]])
{
return in.oColor;
}
199 changes: 199 additions & 0 deletions Assets/Shaders/MTL/TriangleShader.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#include <metal_stdlib>

using namespace metal;

#include "VertexConstants.h"

constexpr sampler depthSampler(mag_filter::nearest, min_filter::nearest);

struct Vertex
{
float3 vPos [[attribute(0)]];
float3 vNorm [[attribute(1)]];
float2 vTex [[attribute(2)]];
uchar4 vCol [[attribute(3)]];
float4 iModel0 [[attribute(4)]];
float4 iModel1 [[attribute(5)]];
float4 iModel2 [[attribute(6)]];
float4 iModel3 [[attribute(7)]];
float4 iModelInvTrans0 [[attribute(8)]];
float4 iModelInvTrans1 [[attribute(9)]];
float4 iModelInvTrans2 [[attribute(10)]];
float4 iModelInvTrans3 [[attribute(11)]];
uchar4 iCol [[attribute(12)]];
};

struct TriangleOut
{
float4 oPosition [[position]];
float3 oNormal;
float3 oWorldPos;
float2 oTex;
float4 oPositionL;
float4 oColor;
};

vertex TriangleOut TriangleVertexShader(Vertex vert [[stage_in]], constant VertexShaderConstantBuffer *constants [[buffer(2)]])
{
TriangleOut out;

// Convert input matrices
float4x4 iModel(vert.iModel0, vert.iModel1, vert.iModel2, vert.iModel3);
float4x4 iModelInvTrans(vert.iModelInvTrans0, vert.iModelInvTrans1, vert.iModelInvTrans2, vert.iModelInvTrans3);

// Get world position
float4 pos = float4(vert.vPos, 1.0f);
float4 world_pos = iModel * pos;

// Transform the position from world space to homogeneous projection space
float4 proj_pos = constants->View * world_pos;
proj_pos = constants->Projection * proj_pos;
out.oPosition = proj_pos;

// Transform the position from world space to projection space of the light
float4 proj_lpos = constants->LightView * world_pos;
proj_lpos = constants->LightProjection * proj_lpos;
out.oPositionL = proj_lpos;

// output normal
float4 norm = float4(vert.vNorm, 0.0f);
out.oNormal = normalize(iModelInvTrans * norm).xyz;

// output world position of the vertex
out.oWorldPos = world_pos.xyz;

// output texture coordinates
out.oTex = vert.vTex;

// output color
out.oColor = float4(vert.vCol) * float4(vert.iCol) / (255.0 * 255.0);

return out;
}

fragment float4 TrianglePixelShader(TriangleOut vert [[stage_in]], constant PixelShaderConstantBuffer *constants, texture2d<float> depthTexture [[texture(0)]])
{
// Constants
float AmbientFactor = 0.3;
float3 DiffuseColor = float3(vert.oColor.r, vert.oColor.g, vert.oColor.b);
float3 SpecularColor = float3(1, 1, 1);
float SpecularPower = 100.0;
float bias = 1.0e-7;

// Homogenize position in light space
float3 position_l = vert.oPositionL.xyz / vert.oPositionL.w;

// Calculate dot product between direction to light and surface normal and clamp between [0, 1]
float3 view_dir = normalize(constants->CameraPos - vert.oWorldPos);
float3 world_to_light = constants->LightPos - vert.oWorldPos;
float3 light_dir = normalize(world_to_light);
float3 normal = normalize(vert.oNormal);
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.0, 1.0);

// Calculate texture coordinates in light depth texture
float2 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 (vert.oColor.a > 0 // Alpha = 0 means don't receive shadows
&& tex_coord.x == clamp(tex_coord.x, 0.0, 1.0) && tex_coord.y == clamp(tex_coord.y, 0.0, 1.0))
{
// 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;
float2 offsets[] = {
float2(-1.5 * width, -1.5 * height),
float2(-0.5 * width, -1.5 * height),
float2(0.5 * width, -1.5 * height),
float2(1.5 * width, -1.5 * height),

float2(-1.5 * width, -0.5 * height),
float2(-0.5 * width, -0.5 * height),
float2(0.5 * width, -0.5 * height),
float2(1.5 * width, -0.5 * height),

float2(-1.5 * width, 0.5 * height),
float2(-0.5 * width, 0.5 * height),
float2(0.5 * width, 0.5 * height),
float2(1.5 * width, 0.5 * height),

float2(-1.5 * width, 1.5 * height),
float2(-0.5 * width, 1.5 * height),
float2(0.5 * width, 1.5 * height),
float2(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)
shadow_factor += depthTexture.sample(depthSampler, tex_coord + offsets[i]).x <= light_depth? 1.0 : 0.0;
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.0, 1.0), SpecularPower) : 0.0;

// Apply procedural pattern based on the uv coordinates
bool2 less_half = (vert.oTex - floor(vert.oTex)) < float2(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
float2 dx = dfdx(vert.oTex), dy = dfdy(vert.oTex);
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
return float4(clamp((AmbientFactor + diffuse * shadow_factor) * darken_factor * DiffuseColor + SpecularColor * specular * shadow_factor, 0, 1), 1);
}

struct DepthOut
{
float4 oPosition [[position]];
};

vertex DepthOut TriangleDepthVertexShader(Vertex vert [[stage_in]], constant VertexShaderConstantBuffer *constants [[buffer(2)]])
{
DepthOut out;

// Check if the alpha = 0
if (vert.vCol.a * vert.iCol.a == 0.0)
{
// Don't draw the triangle by moving it to an invalid location
out.oPosition = float4(0, 0, 0, 0);
}
else
{
// Convert input matrix
float4x4 iModel(vert.iModel0, vert.iModel1, vert.iModel2, vert.iModel3);

// Transform the position from world space to homogeneous projection space for the light
float4 pos = float4(vert.vPos, 1.0f);
pos = iModel * pos;
pos = constants->LightView * pos;
pos = constants->LightProjection * pos;
out.oPosition = pos;
}

return out;
}

fragment float4 TriangleDepthPixelShader(DepthOut in [[stage_in]])
{
// We only write depth, so this shader does nothing
return float4(0.0, 0.0, 0.0, 1.0);
}
41 changes: 41 additions & 0 deletions Assets/Shaders/MTL/UIShader.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <metal_stdlib>

using namespace metal;

#include "VertexConstants.h"

constexpr sampler uiTextureSampler(mag_filter::linear, min_filter::linear);

struct UIVertex
{
float3 vPos [[attribute(0)]];
float2 vTex [[attribute(1)]];
uchar4 vCol [[attribute(2)]];
};

struct UIOut
{
float4 oPosition [[position]];
float2 oTex;
float4 oColor;
};

vertex UIOut UIVertexShader(UIVertex vert [[stage_in]], constant VertexShaderConstantBuffer *constants [[buffer(2)]])
{
UIOut out;
out.oPosition = constants->Projection * constants->View * float4(vert.vPos, 1.0);
out.oTex = vert.vTex;
out.oColor = float4(vert.vCol) / 255.0;
return out;
}

fragment float4 UIPixelShader(UIOut in [[stage_in]], texture2d<float> uiTexture [[texture(0)]])
{
const float4 sample = uiTexture.sample(uiTextureSampler, in.oTex);
return sample * in.oColor;
}

fragment float4 UIPixelShaderUntextured(UIOut in [[stage_in]])
{
return in.oColor;
}
13 changes: 13 additions & 0 deletions Assets/Shaders/MTL/VertexConstants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
struct VertexShaderConstantBuffer
{
float4x4 View; // view matrix
float4x4 Projection; // projection matrix
float4x4 LightView; // view matrix of the light
float4x4 LightProjection; // projection matrix of the light
};

struct PixelShaderConstantBuffer
{
float3 CameraPos;
float3 LightPos;
};
2 changes: 1 addition & 1 deletion Build/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ include(CMakeDependentOption)
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)
cmake_dependent_option(JPH_ENABLE_VULKAN "Enable Vulkan" ON "LINUX" OFF)

# 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
Expand Down
3 changes: 1 addition & 2 deletions Build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ To implement your custom memory allocator override Allocate, Free, Reallocate, A
</details>

<details>
<summary>Linux</summary>
<summary>Linux (Ubuntu)</summary>
<ul>
<li>Install clang (apt-get install clang)</li>
<li>Install cmake (apt-get install cmake)</li>
Expand All @@ -152,7 +152,6 @@ To implement your custom memory allocator override Allocate, Free, Reallocate, A
<ul>
<li>Install XCode</li>
<li>Download CMake 3.23+ (https://cmake.org/download/)</li>
<li>If you want to build the Samples or JoltViewer, install the <a href="https://vulkan.lunarg.com/sdk/home#mac">Vulkan SDK</a></li>
<li>Run: ./cmake_xcode_macos.sh</li>
<li>This will open XCode with a newly generated project</li>
<li>Build and run the project</li>
Expand Down
3 changes: 2 additions & 1 deletion Docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
* Added binary serialization to `SkeletalAnimation`.
* Added support for RISC-V, LoongArch and PowerPC (Little Endian) CPUs.
* Added the ability to add a sub shape at a specified index in a MutableCompoundShape rather than at the end.
* The Samples and JoltViewer can run on Linux/macOS using Vulkan now. Make sure to install the Vulkan SDK before compiling (see: Build/ubuntu24_install_vulkan_sdk.sh or [download](https://vulkan.lunarg.com/sdk/home) the SDK).
* The Samples and JoltViewer can run on Linux using Vulkan. Make sure to install the Vulkan SDK before compiling (see: Build/ubuntu24_install_vulkan_sdk.sh).
* The Samples and JoltViewer can run on macOS using Metal.
* Added STLLocalAllocator which is an allocator that can be used in e.g. the Array class. It keeps a fixed size buffer for N elements and only when it runs out of space falls back to the heap.
* Added support for CharacterVirtual to override the inner rigid body ID. This can be used to make the simulation deterministic in e.g. client/server setups.
* Added OnContactPersisted, OnContactRemoved, OnCharacterContactPersisted and OnCharacterContactRemoved functions on CharacterContactListener to better match the interface of ContactListener.
Expand Down
13 changes: 1 addition & 12 deletions TestFramework/Application/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@
#include <Jolt/Core/Factory.h>
#include <Jolt/RegisterTypes.h>
#include <Renderer/DebugRendererImp.h>
#ifdef JPH_ENABLE_VULKAN
#include <Renderer/VK/RendererVK.h>
#elif defined(JPH_ENABLE_DIRECTX)
#include <Renderer/DX12/RendererDX12.h>
#endif
#ifdef JPH_PLATFORM_WINDOWS
#include <crtdbg.h>
#include <Input/Win/KeyboardWin.h>
Expand Down Expand Up @@ -84,13 +79,7 @@ Application::Application([[maybe_unused]] const String &inCommandLine) :
mWindow->Initialize();

// Create renderer
#ifdef JPH_ENABLE_VULKAN
mRenderer = new RendererVK;
#elif defined(JPH_ENABLE_DIRECTX)
mRenderer = new RendererDX12;
#else
#error No renderer defined
#endif
mRenderer = Renderer::sCreate();
mRenderer->Initialize(mWindow);

// Create font
Expand Down
Loading

0 comments on commit 582b1ef

Please sign in to comment.