Metal: Support depthRange near > far

Metal doesn't natively support near > far depthRange, need to
emulate it via shader.

Bug: angleproject:2634
Change-Id: I1885cad3467478a0dcc4b25b7d41f980b9e03103
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1919282
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
diff --git a/src/compiler/translator/TranslatorMetal.cpp b/src/compiler/translator/TranslatorMetal.cpp
index 5c9d7f3..ce4e9ea 100644
--- a/src/compiler/translator/TranslatorMetal.cpp
+++ b/src/compiler/translator/TranslatorMetal.cpp
@@ -96,4 +96,32 @@
     return true;
 }
 
+// Metal needs to inverse the depth if depthRange is is reverse order, i.e. depth near > depth far
+// This is achieved by multiply the depth value with scale value stored in
+// driver uniform's depthRange.reserved
+bool TranslatorMetal::transformDepthBeforeCorrection(TIntermBlock *root,
+                                                     const TVariable *driverUniforms)
+{
+    // Create a symbol reference to "gl_Position"
+    const TVariable *position  = BuiltInVariable::gl_Position();
+    TIntermSymbol *positionRef = new TIntermSymbol(position);
+
+    // Create a swizzle to "gl_Position.z"
+    TVector<int> swizzleOffsetZ = {2};
+    TIntermSwizzle *positionZ   = new TIntermSwizzle(positionRef, swizzleOffsetZ);
+
+    // Create a ref to "depthRange.reserved"
+    TIntermBinary *viewportZScale = getDriverUniformDepthRangeReservedFieldRef(driverUniforms);
+
+    // Create the expression "gl_Position.z * depthRange.reserved".
+    TIntermBinary *zScale = new TIntermBinary(EOpMul, positionZ->deepCopy(), viewportZScale);
+
+    // Create the assignment "gl_Position.z = gl_Position.z * depthRange.reserved"
+    TIntermTyped *positionZLHS = positionZ->deepCopy();
+    TIntermBinary *assignment  = new TIntermBinary(TOperator::EOpAssign, positionZLHS, zScale);
+
+    // Append the assignment as a statement at the end of the shader.
+    return RunAtTheEndOfShader(this, root, assignment, &getSymbolTable());
+}
+
 }  // namespace sh
diff --git a/src/compiler/translator/TranslatorMetal.h b/src/compiler/translator/TranslatorMetal.h
index f70294a..d571e31 100644
--- a/src/compiler/translator/TranslatorMetal.h
+++ b/src/compiler/translator/TranslatorMetal.h
@@ -29,6 +29,9 @@
     ANGLE_NO_DISCARD bool translate(TIntermBlock *root,
                                     ShCompileOptions compileOptions,
                                     PerformanceDiagnostics *perfDiagnostics) override;
+
+    ANGLE_NO_DISCARD bool transformDepthBeforeCorrection(TIntermBlock *root,
+                                                         const TVariable *driverUniforms) override;
 };
 
 }  // namespace sh
diff --git a/src/compiler/translator/TranslatorVulkan.cpp b/src/compiler/translator/TranslatorVulkan.cpp
index 84e7fdd..98ba3b1 100644
--- a/src/compiler/translator/TranslatorVulkan.cpp
+++ b/src/compiler/translator/TranslatorVulkan.cpp
@@ -351,8 +351,9 @@
     depthRangeParamsFields->push_back(new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1, 1),
                                                  ImmutableString("diff"), TSourceLoc(),
                                                  SymbolType::AngleInternal));
+    // This additional field might be used by subclass such as TranslatorMetal.
     depthRangeParamsFields->push_back(new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1, 1),
-                                                 ImmutableString("dummyPacker"), TSourceLoc(),
+                                                 ImmutableString("reserved"), TSourceLoc(),
                                                  SymbolType::AngleInternal));
     TStructure *emulatedDepthRangeParams = new TStructure(
         symbolTable, kEmulatedDepthRangeParams, depthRangeParamsFields, SymbolType::AngleInternal);
@@ -938,6 +939,10 @@
         }
 
         // Append depth range translation to main.
+        if (!transformDepthBeforeCorrection(root, driverUniforms))
+        {
+            return false;
+        }
         if (!AppendVertexShaderDepthCorrectionToMain(this, root, &getSymbolTable()))
         {
             return false;
@@ -1001,4 +1006,12 @@
     return CreateDriverUniformRef(driverUniforms, kNegViewportYScale);
 }
 
+TIntermBinary *TranslatorVulkan::getDriverUniformDepthRangeReservedFieldRef(
+    const TVariable *driverUniforms) const
+{
+    TIntermBinary *depthRange = CreateDriverUniformRef(driverUniforms, kDepthRange);
+
+    return new TIntermBinary(EOpIndexDirectStruct, depthRange, CreateIndexNode(3));
+}
+
 }  // namespace sh
diff --git a/src/compiler/translator/TranslatorVulkan.h b/src/compiler/translator/TranslatorVulkan.h
index 121ebd4..6b3c71a 100644
--- a/src/compiler/translator/TranslatorVulkan.h
+++ b/src/compiler/translator/TranslatorVulkan.h
@@ -31,6 +31,8 @@
     bool shouldFlattenPragmaStdglInvariantAll() override;
 
     TIntermBinary *getDriverUniformNegViewportYScaleRef(const TVariable *driverUniforms) const;
+    TIntermBinary *getDriverUniformDepthRangeReservedFieldRef(
+        const TVariable *driverUniforms) const;
     // Subclass can call this method to transform the AST before writing the final output.
     // See TranslatorMetal.cpp.
     ANGLE_NO_DISCARD bool translateImpl(TIntermBlock *root,
@@ -38,6 +40,14 @@
                                         PerformanceDiagnostics *perfDiagnostics,
                                         const TVariable **driverUniformsOut,
                                         TOutputVulkanGLSL *outputGLSL);
+
+    // Give subclass such as TranslatorMetal a chance to do depth transform before
+    // TranslatorVulkan apply its own transform.
+    ANGLE_NO_DISCARD virtual bool transformDepthBeforeCorrection(TIntermBlock *root,
+                                                                 const TVariable *driverUniforms)
+    {
+        return true;
+    }
 };
 
 }  // namespace sh
diff --git a/src/libANGLE/renderer/metal/ContextMtl.h b/src/libANGLE/renderer/metal/ContextMtl.h
index 2202ba1..df78323 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.h
+++ b/src/libANGLE/renderer/metal/ContextMtl.h
@@ -385,7 +385,7 @@
         int32_t xfbBufferOffsets[4];
         uint32_t acbBufferOffsets[4];
 
-        // We'll use x, y, z for near / far / diff respectively.
+        // We'll use x, y, z, w for near / far / diff / zscale respectively.
         float depthRange[4];
     };
 
diff --git a/src/libANGLE/renderer/metal/ContextMtl.mm b/src/libANGLE/renderer/metal/ContextMtl.mm
index 84a2c0b..a0e89d5 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.mm
+++ b/src/libANGLE/renderer/metal/ContextMtl.mm
@@ -85,6 +85,11 @@
 
     return angle::Result::Continue;
 }
+
+bool NeedToInvertDepthRange(float near, float far)
+{
+    return near > far;
+}
 }  // namespace
 
 ContextMtl::ContextMtl(const gl::State &state, gl::ErrorSet *errorSet, DisplayMtl *display)
@@ -1205,6 +1210,12 @@
 
 void ContextMtl::updateDepthRange(float nearPlane, float farPlane)
 {
+    if (NeedToInvertDepthRange(nearPlane, farPlane))
+    {
+        // We also need to invert the depth in shader later by using scale value stored in driver
+        // uniform depthRange.reserved
+        std::swap(nearPlane, farPlane);
+    }
     mViewport.znear = nearPlane;
     mViewport.zfar  = farPlane;
     mDirtyBits.set(DIRTY_BIT_VIEWPORT);
@@ -1572,6 +1583,7 @@
     mDriverUniforms.depthRange[0] = depthRangeNear;
     mDriverUniforms.depthRange[1] = depthRangeFar;
     mDriverUniforms.depthRange[2] = depthRangeDiff;
+    mDriverUniforms.depthRange[3] = NeedToInvertDepthRange(depthRangeNear, depthRangeFar) ? -1 : 1;
 
     ASSERT(mRenderEncoder.valid());
     mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);