Fast bounding-box clipping feature 98/151998/9
authorTom Robinson <tom.robinson@samsung.com>
Fri, 22 Sep 2017 17:04:51 +0000 (18:04 +0100)
committerAdeel Kazmi <adeel.kazmi@samsung.com>
Fri, 6 Oct 2017 14:09:16 +0000 (15:09 +0100)
* API: Actor::Property::CLIPPING_MODE  enum: ClippingMode::CLIP_TO_BOUNDING_BOX

* Actors/Views with CLIP_TO_BOUNDING_BOX will also be rendered.

* Note: Demo will be pushed separately

Change-Id: I55d6da274861b4f4ef1473996c8572733964d47f

15 files changed:
automated-tests/src/dali/dali-test-suite-utils/test-gl-abstraction.cpp
automated-tests/src/dali/dali-test-suite-utils/test-gl-abstraction.h
automated-tests/src/dali/utc-Dali-Actor.cpp
dali/internal/common/math.cpp
dali/internal/common/math.h
dali/internal/render/common/render-algorithms.cpp
dali/internal/render/common/render-algorithms.h
dali/internal/render/common/render-item.cpp
dali/internal/render/common/render-item.h
dali/internal/render/common/render-manager.cpp
dali/internal/update/manager/render-instruction-processor.cpp
dali/internal/update/manager/render-task-processor.cpp
dali/internal/update/nodes/node.cpp
dali/internal/update/nodes/node.h
dali/public-api/actors/actor-enumerations.h

index c4bc01d..02439bc 100644 (file)
@@ -85,6 +85,7 @@ void TestGlAbstraction::Initialize()
   mEnableDisableTrace.Reset();
   mShaderTrace.Reset();
   mStencilFunctionTrace.Reset();
+  mScissorTrace.Reset();
   mTextureTrace.Reset();
   mTexParamaterTrace.Reset();
   mDrawTrace.Reset();
index 8408b10..b306c3e 100644 (file)
@@ -949,6 +949,15 @@ public:
     mScissorParams.y = y;
     mScissorParams.width = width;
     mScissorParams.height = height;
+
+    std::stringstream out;
+    out << x << ", " << y << ", " << width << ", " << height;
+    TraceCallStack::NamedParams namedParams;
+    namedParams["x"] = ToString( x );
+    namedParams["y"] = ToString( y );
+    namedParams["width"] = ToString( width );
+    namedParams["height"] = ToString( height );
+    mScissorTrace.PushCall( "Scissor", out.str(), namedParams );
   }
 
   inline void ShaderBinary(GLsizei n, const GLuint* shaders, GLenum binaryformat, const void* binary, GLsizei length)
@@ -1951,6 +1960,11 @@ public: // TEST FUNCTIONS
   inline void ResetStencilFunctionCallStack() { mStencilFunctionTrace.Reset(); }
   inline TraceCallStack& GetStencilFunctionTrace() { return mStencilFunctionTrace; }
 
+  //Methods for Scissor verification
+  inline void EnableScissorCallTrace(bool enable) { mScissorTrace.Enable(enable); }
+  inline void ResetScissorCallStack() { mScissorTrace.Reset(); }
+  inline TraceCallStack& GetScissorTrace() { return mScissorTrace; }
+
   //Methods for Uniform function verification
   inline void EnableSetUniformCallTrace(bool enable) { mSetUniformTrace.Enable(enable); }
   inline void ResetSetUniformCallStack() { mSetUniformTrace.Reset(); }
@@ -2189,6 +2203,7 @@ private:
   TraceCallStack mDrawTrace;
   TraceCallStack mDepthFunctionTrace;
   TraceCallStack mStencilFunctionTrace;
+  TraceCallStack mScissorTrace;
   TraceCallStack mSetUniformTrace;
 
   // Shaders & Uniforms
index 439f2d1..ba04838 100644 (file)
@@ -3988,7 +3988,7 @@ void CheckColorMask( TestGlAbstraction& glAbstraction, bool maskValue )
 int UtcDaliActorPropertyClippingP(void)
 {
   // This test checks the clippingMode property.
-  tet_infoline( "Testing Actor::Property::CLIPPING_MODE P" );
+  tet_infoline( "Testing Actor::Property::ClippingMode: P" );
   TestApplication application;
 
   Actor actor = Actor::New();
@@ -4005,7 +4005,7 @@ int UtcDaliActorPropertyClippingP(void)
     DALI_TEST_EQUALS<int>( value, ClippingMode::DISABLED, TEST_LOCATION );
   }
 
-  // Check setting the property.
+  // Check setting the property to the stencil mode.
   actor.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_CHILDREN );
 
   // Check the new value was set.
@@ -4018,13 +4018,25 @@ int UtcDaliActorPropertyClippingP(void)
     DALI_TEST_EQUALS<int>( value, ClippingMode::CLIP_CHILDREN, TEST_LOCATION );
   }
 
+  // Check setting the property to the scissor mode.
+  actor.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX );
+
+  // Check the new value was set.
+  getValue = actor.GetProperty( Actor::Property::CLIPPING_MODE );
+  getValueResult = getValue.Get( value );
+  DALI_TEST_CHECK( getValueResult );
+
+  if( getValueResult )
+  {
+    DALI_TEST_EQUALS<int>( value, ClippingMode::CLIP_TO_BOUNDING_BOX, TEST_LOCATION );
+  }
   END_TEST;
 }
 
 int UtcDaliActorPropertyClippingN(void)
 {
   // Negative test case for Clipping.
-  tet_infoline( "Testing Actor::Property::CLIPPING_MODE N" );
+  tet_infoline( "Testing Actor::Property::ClippingMode: N" );
   TestApplication application;
 
   Actor actor = Actor::New();
@@ -4059,7 +4071,7 @@ int UtcDaliActorPropertyClippingN(void)
 int UtcDaliActorPropertyClippingActor(void)
 {
   // This test checks that an actor is correctly setup for clipping.
-  tet_infoline( "Testing Actor::Property::CLIPPING_MODE actor" );
+  tet_infoline( "Testing Actor::Property::ClippingMode: CLIP_CHILDREN actor" );
   TestApplication application;
 
   TestGlAbstraction& glAbstraction = application.GetGlAbstraction();
@@ -4095,7 +4107,7 @@ int UtcDaliActorPropertyClippingActor(void)
 int UtcDaliActorPropertyClippingActorEnableThenDisable(void)
 {
   // This test checks that an actor is correctly setup for clipping and then correctly setup when clipping is disabled
-  tet_infoline( "Testing Actor::Property::CLIPPING_MODE actor enable and then disable" );
+  tet_infoline( "Testing Actor::Property::ClippingMode: CLIP_CHILDREN actor enable and then disable" );
   TestApplication application;
 
   TestGlAbstraction& glAbstraction = application.GetGlAbstraction();
@@ -4141,12 +4153,11 @@ int UtcDaliActorPropertyClippingActorEnableThenDisable(void)
   END_TEST;
 }
 
-
 int UtcDaliActorPropertyClippingNestedChildren(void)
 {
   // This test checks that a hierarchy of actors are clipped correctly by
   // writing to and reading from the correct bit-planes of the stencil buffer.
-  tet_infoline( "Testing Actor::Property::CLIPPING_MODE nested children" );
+  tet_infoline( "Testing Actor::Property::ClippingMode: CLIP_CHILDREN nested children" );
   TestApplication application;
   TestGlAbstraction& glAbstraction = application.GetGlAbstraction();
   TraceCallStack& stencilTrace = glAbstraction.GetStencilFunctionTrace();
@@ -4223,7 +4234,7 @@ int UtcDaliActorPropertyClippingNestedChildren(void)
 int UtcDaliActorPropertyClippingActorDrawOrder(void)
 {
   // This test checks that a hierarchy of actors are drawn in the correct order when clipping is enabled.
-  tet_infoline( "Testing Actor::Property::CLIPPING_MODE draw order" );
+  tet_infoline( "Testing Actor::Property::ClippingMode: CLIP_CHILDREN draw order" );
   TestApplication application;
   TestGlAbstraction& glAbstraction = application.GetGlAbstraction();
   TraceCallStack& enabledDisableTrace = glAbstraction.GetEnableDisableTrace();
@@ -4312,6 +4323,134 @@ int UtcDaliActorPropertyClippingActorDrawOrder(void)
   END_TEST;
 }
 
+int UtcDaliActorPropertyScissorClippingActor(void)
+{
+  // This test checks that an actor is correctly setup for clipping.
+  tet_infoline( "Testing Actor::Property::ClippingMode: CLIP_TO_BOUNDING_BOX actor" );
+  TestApplication application;
+
+  TestGlAbstraction& glAbstraction = application.GetGlAbstraction();
+  TraceCallStack& scissorTrace = glAbstraction.GetScissorTrace();
+  TraceCallStack& enabledDisableTrace = glAbstraction.GetEnableDisableTrace();
+
+  const Vector2 stageSize( TestApplication::DEFAULT_SURFACE_WIDTH, TestApplication::DEFAULT_SURFACE_HEIGHT );
+  const Vector2 imageSize( 16.0f, 16.0f );
+
+  // Create a clipping actor.
+  Actor clippingActorA = CreateActorWithContent();
+  // Note: Scissor coords are have flipped Y values compared with DALi's coordinate system.
+  // We choose BOTTOM_LEFT to give us x=0, y=0 starting coordinates for the first test.
+  clippingActorA.SetParentOrigin( ParentOrigin::BOTTOM_LEFT );
+  clippingActorA.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
+  clippingActorA.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX );
+  Stage::GetCurrent().Add( clippingActorA );
+
+  // Gather the call trace.
+  GenerateTrace( application, enabledDisableTrace, scissorTrace );
+
+  // Check we are writing to the color buffer.
+  CheckColorMask( glAbstraction, true );
+
+  // Check scissor test was enabled.
+  DALI_TEST_CHECK( enabledDisableTrace.FindMethodAndParams( "Enable", "3089" ) );                                   // 3089 = 0xC11 (GL_SCISSOR_TEST)
+
+  // Check the scissor was set, and the coordinates are correct.
+  std::stringstream compareParametersString;
+  compareParametersString << "0, 0, " << imageSize.x << ", " << imageSize.y;
+  DALI_TEST_CHECK( scissorTrace.FindMethodAndParams( "Scissor", compareParametersString.str() ) );                  // Compare with 0, 0, 16, 16
+
+  clippingActorA.SetParentOrigin( ParentOrigin::TOP_RIGHT );
+  clippingActorA.SetAnchorPoint( AnchorPoint::TOP_RIGHT );
+
+  // Gather the call trace.
+  GenerateTrace( application, enabledDisableTrace, scissorTrace );
+
+  // Check the scissor was set, and the coordinates are correct.
+  compareParametersString.str( std::string() );
+  compareParametersString.clear();
+  compareParametersString << ( stageSize.x - imageSize.x ) << ", " << ( stageSize.y - imageSize.y ) << ", " << imageSize.x << ", " << imageSize.y;
+  DALI_TEST_CHECK( scissorTrace.FindMethodAndParams( "Scissor", compareParametersString.str() ) );                  // Compare with 464, 784, 16, 16
+
+  END_TEST;
+}
+
+int UtcDaliActorPropertyScissorClippingActorNested(void)
+{
+  // This test checks that an actor is correctly setup for clipping.
+  tet_infoline( "Testing Actor::Property::ClippingMode: CLIP_TO_BOUNDING_BOX actor nested" );
+  TestApplication application;
+
+  TestGlAbstraction& glAbstraction = application.GetGlAbstraction();
+  TraceCallStack& scissorTrace = glAbstraction.GetScissorTrace();
+  TraceCallStack& enabledDisableTrace = glAbstraction.GetEnableDisableTrace();
+
+  const Vector2 stageSize( TestApplication::DEFAULT_SURFACE_WIDTH, TestApplication::DEFAULT_SURFACE_HEIGHT );
+  const Vector2 imageSize( 16.0f, 16.0f );
+
+  /* Create a nest of 2 scissors to test nesting (intersecting clips).
+
+     A is drawn first - with scissor clipping on
+     B is drawn second - also with scissor clipping on
+     C is the generated clipping region, the intersection ( A ∩ B )
+
+           ┏━━━━━━━┓                   ┌───────┐
+           ┃     B ┃                   │     B │
+       ┌───╂┄┄┄┐   ┃               ┌┄┄┄╆━━━┓   │
+       │   ┃   ┊   ┃     ━━━━━>    ┊   ┃ C ┃   │
+       │   ┗━━━┿━━━┛               ┊   ┗━━━╃───┘
+       │ A     │                   ┊ A     ┊
+       └───────┘                   └┄┄┄┄┄┄┄┘
+
+     We then reposition B around each corner of A to test the 4 overlap combinations (thus testing intersecting works correctly).
+  */
+
+  // Create a clipping actor.
+  Actor clippingActorA = CreateActorWithContent();
+  // Note: Scissor coords are have flipped Y values compared with DALi's coordinate system.
+  // We choose BOTTOM_LEFT to give us x=0, y=0 starting coordinates for the first test.
+  clippingActorA.SetParentOrigin( ParentOrigin::CENTER );
+  clippingActorA.SetAnchorPoint( AnchorPoint::CENTER );
+  clippingActorA.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX );
+  Stage::GetCurrent().Add( clippingActorA );
+
+  // Create a child clipping actor.
+  Actor clippingActorB = CreateActorWithContent();
+  clippingActorB.SetParentOrigin( ParentOrigin::CENTER );
+  clippingActorB.SetAnchorPoint( AnchorPoint::CENTER );
+  clippingActorB.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX );
+  clippingActorA.Add( clippingActorB );
+
+  // positionModifiers is an array of positions to position B around.
+  // expect is an array of expected scissor clip coordinate results.
+  const Vector2 positionModifiers[4] = { Vector2( 1.0f, 1.0f ),     Vector2( -1.0f, 1.0f ),    Vector2( -1.0f, -1.0f ),   Vector2( 1.0f, -1.0f )    };
+  const Vector4 expect[4] =            { Vector4( 240, 392, 8, 8 ), Vector4( 232, 392, 8, 8 ), Vector4( 232, 400, 8, 8 ), Vector4( 240, 400, 8, 8 ) };
+
+  // Loop through each overlap combination.
+  for( unsigned int test = 0u; test < 4u; ++test )
+  {
+    // Position the child clipping actor so it intersects with the 1st clipping actor. This changes each loop.
+    const Vector2 position = ( imageSize / 2.0f ) * positionModifiers[test];
+    clippingActorB.SetPosition( position.x, position.y );
+
+    // Gather the call trace.
+    GenerateTrace( application, enabledDisableTrace, scissorTrace );
+
+    // Check we are writing to the color buffer.
+    CheckColorMask( glAbstraction, true );
+
+    // Check scissor test was enabled.
+    DALI_TEST_CHECK( enabledDisableTrace.FindMethodAndParams( "Enable", "3089" ) );                                   // 3089 = 0xC11 (GL_SCISSOR_TEST)
+
+    // Check the scissor was set, and the coordinates are correct.
+    const Vector4& expectResults( expect[test] );
+    std::stringstream compareParametersString;
+    compareParametersString << expectResults.x << ", " << expectResults.y << ", " << expectResults.z << ", " << expectResults.w;
+    DALI_TEST_CHECK( scissorTrace.FindMethodAndParams( "Scissor", compareParametersString.str() ) );                  // Compare with the expected result
+  }
+
+  END_TEST;
+}
+
 int UtcDaliActorPropertyClippingActorWithRendererOverride(void)
 {
   // This test checks that an actor with clipping will be ignored if overridden by the Renderer properties.
@@ -4344,6 +4483,19 @@ int UtcDaliActorPropertyClippingActorWithRendererOverride(void)
   DALI_TEST_CHECK( !stencilTrace.FindMethod( "StencilMask" ) );
   DALI_TEST_CHECK( !stencilTrace.FindMethod( "StencilOp" ) );
 
+  // Check that scissor clipping is overriden by the renderer properties.
+  TraceCallStack& scissorTrace = glAbstraction.GetScissorTrace();
+
+  actorDepth1Clip.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX );
+
+  // Gather the call trace.
+  GenerateTrace( application, enabledDisableTrace, scissorTrace );
+
+  // Check the stencil buffer was not enabled.
+  DALI_TEST_CHECK( !enabledDisableTrace.FindMethodAndParams( "Enable", "3089" ) );    // 3089 = 0xC11 (GL_SCISSOR_TEST)
+
+  DALI_TEST_CHECK( !scissorTrace.FindMethod( "StencilFunc" ) );
+
   END_TEST;
 }
 
index 89a83d5..f856a37 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 // CLASS HEADER
 #include <dali/internal/common/math.h>
 
-//EXTERNAL INCLUDES
+// EXTERNAL INCLUDES
 #include <cmath>
 
+// INTERNAL INCLUDES
+#include <dali/internal/render/common/performance-monitor.h>
+#include <dali/public-api/common/constants.h>
+#include <dali/public-api/math/vector2.h>
+#include <dali/public-api/math/matrix.h>
+
 void Dali::Internal::TransformVector3( Vec3 result, const Mat4 m, const Vec3 v )
 {
 #ifndef __ARM_NEON__
@@ -53,8 +59,24 @@ void Dali::Internal::TransformVector3( Vec3 result, const Mat4 m, const Vec3 v )
 #endif
 }
 
+Dali::Vector2 Dali::Internal::Transform2D( const Dali::Matrix& matrix, const float x, const float y )
+{
+  MATH_INCREASE_BY( PerformanceMonitor::FLOAT_POINT_MULTIPLY, 4 );
+
+  const float* matrixArray( matrix.AsFloat() );
+
+  // The following optimizations are applied:
+  // Matrix[8 -> 11] are optimized out.
+  // Matrix[12 -> 15] are always multiplied by 1.
+  // z & w results (if we were doing a transformation to a Vector4) are unneeded and so not calculated.
+  // As we always multiply by component, we do not store the coordinates in a Vector2 to avoid creation.
+  // Note: For this reason the NEON SIMD version is no faster than the Dali::Matrix '*' Vector4 operator, and therefore not used.
+  return Dali::Vector2( x * matrixArray[0] + y * matrixArray[4] + matrixArray[12], x * matrixArray[1] + y * matrixArray[5] + matrixArray[13] );
+}
+
 float Dali::Internal::Length( const Vec3 v )
 {
   return sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
 }
 
+
index ebb6291..af398ec 100644 (file)
@@ -1,8 +1,8 @@
-#ifndef __DALI_INTERNAL_MATH_H__
-#define __DALI_INTERNAL_MATH_H__
+#ifndef DALI_INTERNAL_MATH_H
+#define DALI_INTERNAL_MATH_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,9 @@
 namespace Dali
 {
 
+class Vector2;
+class Matrix;
+
 namespace Internal
 {
 
@@ -29,7 +32,7 @@ typedef float Vec4[4];
 typedef float Mat4[16];
 
 /**
- * @brief Applies a transformation matrix to a vector
+ * @brief Applies a transformation matrix to a Vector3
  *
  * @param[out] result The transformed vector
  * @param[in] m The transformation matrix
@@ -38,6 +41,17 @@ typedef float Mat4[16];
 void TransformVector3( Vec3 result, const Mat4 m, const Vec3 v );
 
 /**
+ * @brief Applies a transformation matrix to a Vector2
+ * Note: This performs an optimized 2D transformation.
+ *
+ * @param[in] matrix      The matrix to transform by
+ * @param[in] x           The x coordinate to multiply the matrix by
+ * @param[in] y           The y coordinate to multiply the matrix by
+ * @return                A Vector2 containing the resulting coordinates
+ */
+Vector2 Transform2D( const Matrix& matrix, const float x, const float y );
+
+/**
  * @brief Computes the length of a vector3
  *
  * @param[in] v The vector
@@ -49,4 +63,4 @@ float Length( const Vec3 v );
 
 } // namespace Dali
 
-#endif  //__DALI_INTERNAL_MATH_H__
+#endif  // DALI_INTERNAL_MATH_H
index e31b8fe..9a28418 100644 (file)
@@ -55,44 +55,42 @@ const int DaliStencilFunctionToGL[]  = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL,
 // Note: These MUST be in the same order as Dali::StencilOperation enum.
 const int DaliStencilOperationToGL[] = { GL_ZERO, GL_KEEP, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP };
 
-} // Unnamed namespace
-
 /**
- * Sets up the scissor test if required.
- * @param[in] renderList The render list from which to get the clipping flag
- * @param[in] context The context
+ * @brief Find the intersection of two AABB rectangles.
+ * This is a logical AND operation. IE. The intersection is the area overlapped by both rectangles.
+ * @param[in]     aabbA                  Rectangle A
+ * @param[in]     aabbB                  Rectangle B
+ * @return                               The intersection of rectangle A & B (result is a rectangle)
  */
-inline void SetScissorTest( const RenderList& renderList, Context& context )
+inline ClippingBox IntersectAABB( const ClippingBox& aabbA, const ClippingBox& aabbB )
 {
-  // Scissor testing
-  if( renderList.IsClipping() )
-  {
-    context.SetScissorTest( true );
+  ClippingBox intersectionBox;
 
-    const Dali::ClippingBox& clip = renderList.GetClippingBox();
-    context.Scissor( clip.x, clip.y, clip.width, clip.height );
-  }
-  else
-  {
-    context.SetScissorTest( false );
-  }
+  // First calculate the largest starting positions in X and Y.
+  intersectionBox.x = std::max( aabbA.x, aabbB.x );
+  intersectionBox.y = std::max( aabbA.y, aabbB.y );
+
+  // Now calculate the smallest ending positions, and take the largest starting
+  // positions from the result, to get the width and height respectively.
+  // If the two boxes do not intersect at all, then we need a 0 width and height clipping area.
+  // We use max here to clamp both width and height to >= 0 for this use-case.
+  intersectionBox.width =  std::max( std::min( aabbA.x + aabbA.width,  aabbB.x + aabbB.width  ) - intersectionBox.x, 0 );
+  intersectionBox.height = std::max( std::min( aabbA.y + aabbA.height, aabbB.y + aabbB.height ) - intersectionBox.y, 0 );
+
+  return intersectionBox;
 }
 
 /**
  * @brief Set up the stencil and color buffer for automatic clipping (StencilMode::AUTO).
  * @param[in]     item                     The current RenderItem about to be rendered
  * @param[in]     context                  The context
- * @param[in/out] lastStencilDepth         The stencil depth of the last renderer drawn.
+ * @param[in/out] lastClippingDepth        The stencil depth of the last renderer drawn.
  * @param[in/out] lastClippingId           The clipping ID of the last renderer drawn.
  */
-inline void SetupClipping( const RenderItem& item, Context& context, uint32_t& lastStencilDepth, uint32_t& lastClippingId )
+inline void SetupStencilClipping( const RenderItem& item, Context& context, uint32_t& lastClippingDepth, uint32_t& lastClippingId )
 {
   const Dali::Internal::SceneGraph::Node* node = item.mNode;
   const uint32_t clippingId = node->GetClippingId();
-
-  // Turn the color buffer on as we always want to render this renderer, regardless of clipping hierarchy.
-  context.ColorMask( true );
-
   // If there is no clipping Id, then either we haven't reached a clipping Node yet, or there aren't any.
   // Either way we can skip clipping setup for this renderer.
   if( clippingId == 0u )
@@ -102,17 +100,16 @@ inline void SetupClipping( const RenderItem& item, Context& context, uint32_t& l
     return;
   }
 
-  const ClippingMode::Type clippingMode( node->GetClippingMode() );
-  const uint32_t currentStencilDepth( node->GetClippingDepth() );
-
   context.EnableStencilBuffer( true );
 
+  const uint32_t clippingDepth = node->GetClippingDepth();
+
   // Pre-calculate a mask which has all bits set up to and including the current clipping depth.
   // EG. If depth is 3, the mask would be "111" in binary.
-  const uint32_t currentDepthMask = ( 1u << currentStencilDepth ) - 1u;
+  const uint32_t currentDepthMask = ( 1u << clippingDepth ) - 1u;
 
-  // If we have a clipping mode specified, we are writing to the stencil buffer.
-  if( clippingMode != ClippingMode::DISABLED )
+  // Are we are writing to the stencil buffer?
+  if( item.mNode->GetClippingMode() == Dali::ClippingMode::CLIP_CHILDREN )
   {
     // We are writing to the stencil buffer.
     // If clipping Id is 1, this is the first clipping renderer within this render-list.
@@ -123,8 +120,8 @@ inline void SetupClipping( const RenderItem& item, Context& context, uint32_t& l
       context.StencilMask( 0xff );
       context.Clear( GL_STENCIL_BUFFER_BIT, Context::CHECK_CACHED_VALUES );
     }
-    else if( ( currentStencilDepth < lastStencilDepth ) ||
-           ( ( currentStencilDepth == lastStencilDepth ) && ( clippingId > lastClippingId ) ) )
+    else if( ( clippingDepth < lastClippingDepth ) ||
+           ( ( clippingDepth == lastClippingDepth ) && ( clippingId > lastClippingId ) ) )
     {
       // The above if() statement tests if we need to clear some (not all) stencil bit-planes.
       // We need to do this if either of the following are true:
@@ -141,7 +138,7 @@ inline void SetupClipping( const RenderItem& item, Context& context, uint32_t& l
 
     // We keep track of the last clipping Id and depth so we can determine when we are
     // moving back up the scene graph and require some of the stencil bit-planes to be deleted.
-    lastStencilDepth = currentStencilDepth;
+    lastClippingDepth = clippingDepth;
     lastClippingId = clippingId;
 
     // We only ever write to bit-planes up to the current depth as we may need
@@ -168,32 +165,164 @@ inline void SetupClipping( const RenderItem& item, Context& context, uint32_t& l
 }
 
 /**
- * @brief Set up the stencil and color buffer based on the current Renderers properties.
+ * @brief Sets up the depth buffer for reading and writing based on the current render item.
+ * The items read and write mode are used if specified.
+ *  - If AUTO is selected for reading, the decision will be based on the Layer Behavior.
+ *  - If AUTO is selected for writing, the decision will be based on the items opacity.
+ * @param[in]     item                The RenderItem to set up the depth buffer for.
+ * @param[in]     context             The context used to execute GL commands.
+ * @param[in]     depthTestEnabled    True if depth testing has been enabled.
+ * @param[in/out] firstDepthBufferUse Initialize to true on the first call, this method will set it to false afterwards.
+ */
+inline void SetupDepthBuffer( const RenderItem& item, Context& context, bool depthTestEnabled, bool& firstDepthBufferUse )
+{
+  // Set up whether or not to write to the depth buffer.
+  const DepthWriteMode::Type depthWriteMode = item.mRenderer->GetDepthWriteMode();
+  // Most common mode (AUTO) is tested first.
+  const bool enableDepthWrite = ( ( depthWriteMode == DepthWriteMode::AUTO ) && depthTestEnabled && item.mIsOpaque ) ||
+                                ( depthWriteMode == DepthWriteMode::ON );
+
+  // Set up whether or not to read from (test) the depth buffer.
+  const DepthTestMode::Type depthTestMode = item.mRenderer->GetDepthTestMode();
+  // Most common mode (AUTO) is tested first.
+  const bool enableDepthTest = ( ( depthTestMode == DepthTestMode::AUTO ) && depthTestEnabled ) ||
+                               ( depthTestMode == DepthTestMode::ON );
+
+  // Is the depth buffer in use?
+  if( enableDepthWrite || enableDepthTest )
+  {
+    // The depth buffer must be enabled if either reading or writing.
+    context.EnableDepthBuffer( true );
+
+    // Set up the depth mask based on our depth write setting.
+    context.DepthMask( enableDepthWrite );
+
+    // Look-up the GL depth function from the Dali::DepthFunction enum, and set it.
+    context.DepthFunc( DaliDepthToGLDepthTable[ item.mRenderer->GetDepthFunction() ] );
+
+    // If this is the first use of the depth buffer this RenderTask, perform a clear.
+    // Note: We could do this at the beginning of the RenderTask and rely on the
+    // context cache to ignore the clear if not required, but, we would have to enable
+    // the depth buffer to do so, which could be a redundant enable.
+    if( DALI_UNLIKELY( firstDepthBufferUse ) )
+    {
+      // This is the first time the depth buffer is being written to or read.
+      firstDepthBufferUse = false;
+
+      // Note: The buffer will only be cleared if written to since a previous clear.
+      context.Clear( GL_DEPTH_BUFFER_BIT, Context::CHECK_CACHED_VALUES );
+    }
+  }
+  else
+  {
+    // The depth buffer is not being used by this renderer, so we must disable it to stop it being tested.
+    context.EnableDepthBuffer( false );
+  }
+}
+
+} // Unnamed namespace
+
+
+/**
+ * @brief This method is responsible for making decisions on when to apply and unapply scissor clipping, and what rectangular dimensions should be used.
+ * A stack of scissor clips at each depth of clipping is maintained, so it can be applied and unapplied.
+ * As the clips are hierarchical, this RenderItems AABB is clipped against the current "active" scissor bounds via an intersection operation.
  * @param[in]     item                     The current RenderItem about to be rendered
  * @param[in]     context                  The context
- * @param[in/out] usedStencilBuffer        True if the stencil buffer has been used so far within this RenderList. Used by StencilMode::ON.
- * @param[in/out] lastStencilDepth         The stencil depth of the last renderer drawn. Used by the clipping feature.
- * @param[in/out] lastClippingId           The clipping ID of the last renderer drawn.   Used by the clipping feature.
  */
-inline void SetupStencilBuffer( const RenderItem& item, Context& context, bool& usedStencilBuffer, uint32_t& lastStencilDepth, uint32_t& lastClippingId )
+inline void RenderAlgorithms::SetupScissorClipping( const RenderItem& item, Context& context )
+{
+  // Get the number of child scissors in the stack (do not include layer or root box).
+  size_t childStackDepth = mScissorStack.size() - 1u;
+  const uint32_t scissorDepth = item.mNode->GetScissorDepth();
+  const bool clippingNode = item.mNode->GetClippingMode() == Dali::ClippingMode::CLIP_TO_BOUNDING_BOX;
+  bool traversedUpTree = false;
+
+  // If we are using scissor clipping and we are at the same depth (or less), we need to undo previous clips.
+  // We do this by traversing up the scissor clip stack and then apply the appropriate clip for the current render item.
+  // To know this, we use clippingDepth. This value is set on *every* node, but only increased as clipping nodes are hit depth-wise.
+  // So we know if we are at depth 4 and the stackDepth is 5, that we have gone up.
+  // If the depth is the same then we are effectively part of a different sub-tree from the parent, we must also remove the current clip.
+  // Note: Stack depth must always be at least 1, as we will have the layer or stage size as the root value.
+  if( ( childStackDepth > 0u ) && ( scissorDepth < childStackDepth ) )
+  {
+    while( scissorDepth < childStackDepth )
+    {
+      mScissorStack.pop_back();
+      --childStackDepth;
+    }
+
+    // We traversed up the tree, we need to apply a new scissor rectangle (unless we are at the root).
+    traversedUpTree = true;
+  }
+
+  // If we are on a clipping node, or we have traveled up the tree and gone back past a clipping node, may need to apply a new scissor clip.
+  if( clippingNode || traversedUpTree )
+  {
+    // First, check if we are a clipping node.
+    if( clippingNode )
+    {
+      // This is a clipping node. We generate the AABB for this node and intersect it with the previous intersection further up the tree.
+
+      // Get the AABB bounding box for the current render item.
+      const ClippingBox scissorBox( item.CalculateViewportSpaceAABB( mViewportRectangle.width, mViewportRectangle.height ) );
+
+      // Get the AABB for the parent item that we must intersect with.
+      const ClippingBox& parentBox( mScissorStack.back() );
+
+      // We must reduce the clipping area based on the parents area to allow nested clips. This is a set intersection function.
+      // We add the new scissor box to the stack so we can return to it if needed.
+      mScissorStack.emplace_back( IntersectAABB( parentBox, scissorBox ) );
+    }
+
+    // The scissor test is enabled if we have any children on the stack, OR, if there are none but it is a user specified layer scissor box.
+    // IE. It is not enabled if we are at the top of the stack and the layer does not have a specified clipping box.
+    const bool scissorEnabled = ( mScissorStack.size() > 0u ) || mHasLayerScissor;
+
+    // Enable the scissor test based on the above calculation
+    context.SetScissorTest( scissorEnabled );
+
+    // If scissor is enabled, we use the calculated screen-space coordinates (now in the stack).
+    if( scissorEnabled )
+    {
+      ClippingBox useScissorBox( mScissorStack.back() );
+      context.Scissor( useScissorBox.x, useScissorBox.y, useScissorBox.width, useScissorBox.height );
+    }
+  }
+}
+
+inline void RenderAlgorithms::SetupClipping( const RenderItem& item, Context& context, bool& usedStencilBuffer, uint32_t& lastClippingDepth, uint32_t& lastClippingId )
 {
+  RenderMode::Type renderMode = RenderMode::AUTO;
   const Renderer *renderer = item.mRenderer;
+  if( renderer )
+  {
+    renderMode = renderer->GetRenderMode();
+  }
 
   // Setup the stencil using either the automatic clipping feature, or, the manual per-renderer stencil API.
   // Note: This switch is in order of most likely value first.
-  RenderMode::Type renderMode = renderer->GetRenderMode();
   switch( renderMode )
   {
     case RenderMode::AUTO:
     {
-      // The automatic clipping feature will manage the stencil functions and color buffer mask.
-      SetupClipping( item, context, lastStencilDepth, lastClippingId );
+      // Turn the color buffer on as we always want to render this renderer, regardless of clipping hierarchy.
+      context.ColorMask( true );
+
+      // The automatic clipping feature will manage the scissor and stencil functions.
+      // As both scissor and stencil clips can be nested, we may be simultaneously traversing up the scissor tree, requiring a scissor to be un-done. Whilst simultaneously adding a new stencil clip.
+      // We process both based on our current and old clipping depths for each mode.
+      // Both methods with return rapidly if there is nothing to be done for that type of clipping.
+      SetupScissorClipping( item, context );
+      SetupStencilClipping( item, context, lastClippingDepth, lastClippingId );
       break;
     }
 
     case RenderMode::NONE:
     case RenderMode::COLOR:
     {
+      // No clipping is performed for these modes.
+      // Note: We do not turn off scissor clipping as it may be used for the whole layer.
       // The stencil buffer will not be used at all.
       context.EnableStencilBuffer( false );
 
@@ -233,71 +362,7 @@ inline void SetupStencilBuffer( const RenderItem& item, Context& context, bool&
   }
 }
 
-/**
- * @brief Sets up the depth buffer for reading and writing based on the current render item.
- * The items read and write mode are used if specified.
- *  - If AUTO is selected for reading, the decision will be based on the Layer Behavior.
- *  - If AUTO is selected for writing, the decision will be based on the items opacity.
- * @param[in]     item                The RenderItem to set up the depth buffer for.
- * @param[in]     context             The context used to execute GL commands.
- * @param[in]     depthTestEnabled    True if depth testing has been enabled.
- * @param[in/out] firstDepthBufferUse Initialise to true on the first call, this method will set it to false afterwards.
- */
-inline void SetupDepthBuffer( const RenderItem& item, Context& context, bool depthTestEnabled, bool& firstDepthBufferUse )
-{
-  // Set up whether or not to write to the depth buffer.
-  const DepthWriteMode::Type depthWriteMode = item.mRenderer->GetDepthWriteMode();
-  // Most common mode (AUTO) is tested first.
-  const bool enableDepthWrite = ( ( depthWriteMode == DepthWriteMode::AUTO ) && depthTestEnabled && item.mIsOpaque ) ||
-                                ( depthWriteMode == DepthWriteMode::ON );
-
-  // Set up whether or not to read from (test) the depth buffer.
-  const DepthTestMode::Type depthTestMode = item.mRenderer->GetDepthTestMode();
-  // Most common mode (AUTO) is tested first.
-  const bool enableDepthTest = ( ( depthTestMode == DepthTestMode::AUTO ) && depthTestEnabled ) ||
-                               ( depthTestMode == DepthTestMode::ON );
-
-  // Is the depth buffer in use?
-  if( enableDepthWrite || enableDepthTest )
-  {
-    // The depth buffer must be enabled if either reading or writing.
-    context.EnableDepthBuffer( true );
-
-    // Set up the depth mask based on our depth write setting.
-    context.DepthMask( enableDepthWrite );
-
-    // Look-up the GL depth function from the Dali::DepthFunction enum, and set it.
-    context.DepthFunc( DaliDepthToGLDepthTable[ item.mRenderer->GetDepthFunction() ] );
-
-    // If this is the first use of the depth buffer this RenderTask, perform a clear.
-    // Note: We could do this at the beginning of the RenderTask and rely on the
-    // context cache to ignore the clear if not required, but, we would have to enable
-    // the depth buffer to do so, which could be a redundant enable.
-    if( DALI_UNLIKELY( firstDepthBufferUse ) )
-    {
-      // This is the first time the depth buffer is being written to or read.
-      firstDepthBufferUse = false;
-
-      // Note: The buffer will only be cleared if written to since a previous clear.
-      context.Clear( GL_DEPTH_BUFFER_BIT, Context::CHECK_CACHED_VALUES );
-    }
-  }
-  else
-  {
-    // The depth buffer is not being used by this renderer, so we must disable it to stop it being tested.
-    context.EnableDepthBuffer( false );
-  }
-}
-
-/**
- * @brief Process a render-list.
- * @param[in] renderList       The render-list to process.
- * @param[in] context          The GL context.
- * @param[in] buffer           The current render buffer index (previous update buffer)
- * @param[in] viewMatrix       The view matrix from the appropriate camera.
- * @param[in] projectionMatrix The projection matrix from the appropriate camera.
- */
-inline void ProcessRenderList(
+inline void RenderAlgorithms::ProcessRenderList(
   const RenderList& renderList,
   Context& context,
   BufferIndex bufferIndex,
@@ -306,49 +371,67 @@ inline void ProcessRenderList(
 {
   DALI_PRINT_RENDER_LIST( renderList );
 
-  SetScissorTest( renderList, context );
-
   // Note: The depth buffer is enabled or disabled on a per-renderer basis.
   // Here we pre-calculate the value to use if these modes are set to AUTO.
   const bool autoDepthTestMode( !( renderList.GetSourceLayer()->IsDepthTestDisabled() ) && renderList.HasColorRenderItems() );
   const std::size_t count = renderList.Count();
-  uint32_t lastStencilDepth( 0u );
+  uint32_t lastClippingDepth( 0u );
   uint32_t lastClippingId( 0u );
   bool usedStencilBuffer( false );
   bool firstDepthBufferUse( true );
+  mViewportRectangle = context.GetViewport();
+  mHasLayerScissor = false;
 
+  // Setup Scissor testing (for both viewport and per-node scissor)
+  mScissorStack.clear();
+  if( renderList.IsClipping() )
+  {
+    context.SetScissorTest( true );
+    const ClippingBox& layerScissorBox = renderList.GetClippingBox();
+    context.Scissor( layerScissorBox.x, layerScissorBox.y, layerScissorBox.width, layerScissorBox.height );
+    mScissorStack.push_back( layerScissorBox );
+    mHasLayerScissor = true;
+  }
+  else
+  {
+    // We are not performing a layer clip. Add the viewport as the root scissor rectangle.
+    context.SetScissorTest( false );
+    mScissorStack.push_back( mViewportRectangle );
+  }
+
+  // Loop through all RenderList in the RenderList, set up any prerequisites to render them, then perform the render.
   for( size_t index( 0u ); index < count; ++index )
   {
     const RenderItem& item = renderList.GetItem( index );
     DALI_PRINT_RENDER_ITEM( item );
 
-    // Set up the depth buffer based on per-renderer flags.
-    // If the per renderer flags are set to "ON" or "OFF", they will always override any Layer depth mode or
-    // draw-mode state, such as Overlays.
-    // If the flags are set to "AUTO", the behaviour then depends on the type of renderer. Overlay Renderers will always
-    // disable depth testing and writing. Color Renderers will enable them if the Layer does.
-    SetupDepthBuffer( item, context, autoDepthTestMode, firstDepthBufferUse );
-
-    // Set up the stencil buffer based on both the Renderer and Actor APIs.
+    // Set up clipping based on both the Renderer and Actor APIs.
     // The Renderer API will be used if specified. If AUTO, the Actors automatic clipping feature will be used.
-    SetupStencilBuffer( item, context, usedStencilBuffer, lastStencilDepth, lastClippingId );
-
-    // Render the item
-    item.mRenderer->Render( context,
-                            bufferIndex,
-                            *item.mNode,
-                            item.mModelMatrix,
-                            item.mModelViewMatrix,
-                            viewMatrix,
-                            projectionMatrix,
-                            item.mSize,
-                            !item.mIsOpaque );
+    SetupClipping( item, context, usedStencilBuffer, lastClippingDepth, lastClippingId );
+
+    if( DALI_LIKELY( item.mRenderer ) )
+    {
+      // Set up the depth buffer based on per-renderer flags.
+      // If the per renderer flags are set to "ON" or "OFF", they will always override any Layer depth mode or
+      // draw-mode state, such as Overlays.
+      // If the flags are set to "AUTO", the behavior then depends on the type of renderer. Overlay Renderers will always
+      // disable depth testing and writing. Color Renderers will enable them if the Layer does.
+      SetupDepthBuffer( item, context, autoDepthTestMode, firstDepthBufferUse );
+
+      // Render the item.
+      item.mRenderer->Render( context, bufferIndex, *item.mNode, item.mModelMatrix, item.mModelViewMatrix,
+                              viewMatrix, projectionMatrix, item.mSize, !item.mIsOpaque );
+    }
   }
 }
 
-void ProcessRenderInstruction( const RenderInstruction& instruction,
-                               Context& context,
-                               BufferIndex bufferIndex )
+RenderAlgorithms::RenderAlgorithms()
+  : mViewportRectangle(),
+    mHasLayerScissor( false )
+{
+}
+
+void RenderAlgorithms::ProcessRenderInstruction( const RenderInstruction& instruction, Context& context, BufferIndex bufferIndex )
 {
   DALI_PRINT_RENDER_INSTRUCTION( instruction, bufferIndex );
 
@@ -370,16 +453,13 @@ void ProcessRenderInstruction( const RenderInstruction& instruction,
 
       if( renderList && !renderList->IsEmpty() )
       {
-        ProcessRenderList( *renderList,
-                           context,
-                           bufferIndex,
-                           *viewMatrix,
-                           *projectionMatrix );
+        ProcessRenderList( *renderList, context, bufferIndex, *viewMatrix, *projectionMatrix );
       }
     }
   }
 }
 
+
 } // namespace Render
 
 } // namespace Internal
index 0346ef7..5d4e44f 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_RENDER_ALGORITHMS_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 // INTERNAL INCLUDES
 #include <dali/internal/common/buffer-index.h>
+#include <dali/internal/render/common/render-list.h>
 
 namespace Dali
 {
+
 namespace Internal
 {
 class Context;
@@ -30,21 +32,88 @@ class Context;
 namespace SceneGraph
 {
 class RenderInstruction;
-class TextureCache;
+struct RenderItem;
 }
 
 namespace Render
 {
 
 /**
- * Process a render-instruction.
- * @param[in] instruction The render-instruction to process.
- * @param[in] context The GL context.
- * @param[in] bufferIndex The current render buffer index (previous update buffer)
+ * @brief The responsibility of the RenderAlgorithms object is to action renders required by a RenderInstruction.
  */
-void ProcessRenderInstruction( const SceneGraph::RenderInstruction& instruction,
-                               Context& context,
-                               BufferIndex bufferIndex );
+class RenderAlgorithms
+{
+  public:
+
+    /**
+     * Constructor.
+     */
+    RenderAlgorithms();
+
+    /**
+     * Process a render-instruction.
+     * @param[in] instruction The render-instruction to process.
+     * @param[in] context     The GL context.
+     * @param[in] bufferIndex The current render buffer index (previous update buffer)
+     */
+    void ProcessRenderInstruction( const SceneGraph::RenderInstruction& instruction, Context& context, BufferIndex bufferIndex );
+
+  private:
+
+    /**
+     * @brief Calculate a 2D AABB (axis aligned bounding box) in screen space.
+     * The RenderItems dimensions are translated and a Z value of 0 is assumed for this purpose.
+     * No projection is performed, but rotation on Z is supported.
+     * @param[in] item The RenderItem to generate an AABB for
+     * @return         The generated AABB in screen space
+     */
+    inline Dali::ClippingBox CalculateScreenSpaceAABB( const Dali::Internal::SceneGraph::RenderItem& item );
+
+    /**
+     * @brief Perform any scissor clipping related operations based on the current RenderItem.
+     * This includes:
+     *  - Determining if any action is to be taken (so the method can be exited early if not).
+     *  - If the node is a clipping node, apply the nodes clip intersected with the current/parent scissor clip.
+     *  - If we have gone up the scissor hierarchy, and need to un-apply a scissor clip.
+     *  - Disable scissor clipping completely if it is not needed
+     * @param[in] item     The current RenderItem (about to be rendered)
+     * @param[in] context  The current Context
+     */
+    inline void SetupScissorClipping( const Dali::Internal::SceneGraph::RenderItem& item, Context& context );
+
+    /**
+     * @brief Set up the clipping based on the specified clipping settings.
+     * @param[in]     item                     The current RenderItem (about to be rendered)
+     * @param[in]     context                  The context
+     * @param[in/out] usedStencilBuffer        True if the stencil buffer has been used so far within this RenderList. Used by StencilMode::ON.
+     * @param[in/out] lastClippingDepth        The stencil depth of the last renderer drawn. Used by the clipping feature.
+     * @param[in/out] lastClippingId           The clipping ID of the last renderer drawn.   Used by the clipping feature.
+     */
+    inline void SetupClipping( const Dali::Internal::SceneGraph::RenderItem& item, Context& context, bool& usedStencilBuffer, uint32_t& lastClippingDepth, uint32_t& lastClippingId );
+
+    /**
+     * @brief Process a render-list.
+     * @param[in] renderList       The render-list to process.
+     * @param[in] context          The GL context.
+     * @param[in] buffer           The current render buffer index (previous update buffer)
+     * @param[in] viewMatrix       The view matrix from the appropriate camera.
+     * @param[in] projectionMatrix The projection matrix from the appropriate camera.
+     */
+    inline void ProcessRenderList( const Dali::Internal::SceneGraph::RenderList& renderList, Context& context, BufferIndex bufferIndex, const Matrix& viewMatrix, const Matrix& projectionMatrix );
+
+    // Prevent copying:
+    RenderAlgorithms( RenderAlgorithms& rhs );
+    RenderAlgorithms& operator=( const RenderAlgorithms& rhs );
+
+
+    // Member variables:
+
+    using ScissorStackType = std::vector<Dali::ClippingBox>;      ///< The container type used to maintain the applied scissor hierarchy
+
+    ScissorStackType                        mScissorStack;        ///< Contains the currently applied scissor hierarchy (so we can undo clips)
+    Dali::ClippingBox                       mViewportRectangle;   ///< The viewport dimensions, used to translate AABBs to scissor coordinates
+    bool                                    mHasLayerScissor:1;   ///< Marks if the currently process render instruction has a layer-based clipping region
+};
 
 } // namespace Render
 
index 335492f..ffbddf0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 // INTERNAL INCLUDES
 #include <dali/internal/common/memory-pool-object-allocator.h>
 #include <dali/internal/render/renderers/render-renderer.h>
+#include <dali/internal/common/math.h>
 
 namespace
 {
@@ -56,11 +57,64 @@ RenderItem::~RenderItem()
 {
 }
 
+
+ClippingBox RenderItem::CalculateViewportSpaceAABB( const int viewportWidth, const int viewportHeight ) const
+{
+  // Calculate extent vector of the AABB:
+  const float halfActorX = mSize.x * 0.5f;
+  const float halfActorY = mSize.y * 0.5f;
+
+  // To transform the actor bounds to screen-space, We do a fast, 2D version of a matrix multiply optimized for 2D quads.
+  // This reduces float multiplications from 64 (16 * 4) to 12 (4 * 3).
+  // We create an array of 4 corners and directly initialize the first 3 with the matrix multiplication result of the respective corner.
+  // This causes the construction of the vector arrays contents in-place for optimization.
+  // We place the coords into the array in clockwise order, so we know opposite corners are always i + 2 from corner i.
+  // We skip the 4th corner here as we can calculate that from the other 3, bypassing matrix multiplication.
+  // Note: The below transform methods use a fast (2D) matrix multiply (only 4 multiplications are done).
+  Vector2 corners[4]{ Transform2D( mModelViewMatrix, -halfActorX, -halfActorY ),
+                      Transform2D( mModelViewMatrix,  halfActorX, -halfActorY ),
+                      Transform2D( mModelViewMatrix,  halfActorX,  halfActorY ) };
+
+  // As we are dealing with a rectangle, we can do a fast calculation to get the 4th corner from knowing the other 3 (even if rotated).
+  corners[3] = Vector2( corners[0] + ( corners[2] - corners[1] ) );
+
+  // Calculate the AABB:
+  // We use knowledge that opposite corners will be the max/min of each other. Doing this reduces the normal 12 branching comparisons to 3.
+  // The standard equivalent min/max code of the below would be:
+  //       Vector2 AABBmax( std::max( corners[0].x, std::max( corners[1].x, std::max( corners[3].x, corners[2].x ) ) ),
+  //                        std::max( corners[0].y, std::max( corners[1].y, std::max( corners[3].y, corners[2].y ) ) ) );
+  //       Vector2 AABBmin( std::min( corners[0].x, std::min( corners[1].x, std::min( corners[3].x, corners[2].x ) ) ),
+  //                        std::min( corners[0].y, std::min( corners[1].y, std::min( corners[3].y, corners[2].y ) ) ) );
+  unsigned int smallestX = 0u;
+  // Loop 3 times to find the index of the smallest X value.
+  // Note: We deliberately do NOT unroll the code here as this hampers the compilers output.
+  for( unsigned int i = 1u; i < 4u; ++i )
+  {
+    if( corners[i].x < corners[smallestX].x )
+    {
+      smallestX = i;
+    }
+  }
+
+  // As we are dealing with a rectangle, we can assume opposite corners are the largest.
+  // So without doing min/max branching, we can fetch the min/max values of all the remaining X/Y coords from this one index.
+  Vector4 aabb( corners[smallestX].x, corners[( smallestX + 3u ) % 4].y, corners[( smallestX + 2u ) % 4].x, corners[( smallestX + 1u ) % 4].y );
+
+  // Convert maximums to extents.
+  aabb.z -= aabb.x;
+  aabb.w -= aabb.y;
+
+  // Return the AABB in screen-space pixels (x, y, width, height).
+  // Note: This is a algebraic simplification of: ( viewport.x - aabb.width ) / 2 - ( ( aabb.width / 2 ) + aabb.x ) per axis.
+  return ClippingBox( ( viewportWidth / 2 ) - aabb.z - aabb.x, ( viewportHeight / 2 ) - aabb.w - aabb.y, aabb.z, aabb.w );
+}
+
 void RenderItem::operator delete( void* ptr )
 {
   gRenderItemPool.Free( static_cast<RenderItem*>( ptr ) );
 }
 
+
 } // namespace SceneGraph
 
 } // namespace Internal
index 5648ebd..7dbd3fb 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_SCENE_GRAPH_RENDER_ITEM_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,6 +55,21 @@ struct RenderItem
   ~RenderItem();
 
   /**
+   * @brief This method is an optimized calculation of a viewport-space AABB (Axis-Aligned-Bounding-Box).
+   *
+   * We use the model-view-matrix, but we do not use projection. Therefore we assume Z = 0.
+   * As the box is Axis-Aligned (in viewport space) rotations on Z axis are correctly delt with by expanding the box as necessary.
+   * Rotations on X & Y axis will resize the AABB, but it will not handle the projection error due to the new coordinates having non-zero Z values.
+   *
+   * Note: We pass in the viewport dimensions rather than allow the caller to modify the raw AABB in order to optimally generate the final result.
+   *
+   * @param[in]    viewportWidth     The width of the viewport to calculate for
+   * @param[in]    viewportHeight    The height of the viewport to calculate for
+   * @return                         The AABB coordinates in viewport-space (x, y, width, height)
+   */
+  ClippingBox CalculateViewportSpaceAABB( const int viewportWidth, const int viewportHeight ) const;
+
+  /**
    * Overriden delete operator.
    * Deletes the RenderItem from its global memory pool
    * @param[in] A pointer to the RenderItem to delete.
index b391961..f19c956 100644 (file)
@@ -59,6 +59,7 @@ struct RenderManager::Impl
     glSyncAbstraction( glSyncAbstraction ),
     renderQueue(),
     instructions(),
+    renderAlgorithms(),
     backgroundColor( Dali::Stage::DEFAULT_BACKGROUND_COLOR ),
     frameCount( 0 ),
     renderBufferIndex( SceneGraphBuffers::INITIAL_UPDATE_BUFFER_INDEX ),
@@ -104,6 +105,7 @@ struct RenderManager::Impl
   // Render instructions describe what should be rendered during RenderManager::Render()
   // Owned by RenderManager. Update manager updates instructions for the next frame while we render the current one
   RenderInstructionContainer                instructions;
+  Render::RenderAlgorithms                  renderAlgorithms;        ///< The RenderAlgorithms object is used to action the renders required by a RenderInstruction
 
   Vector4                                   backgroundColor;         ///< The glClear color used at the beginning of each frame.
 
@@ -530,9 +532,7 @@ void RenderManager::DoRender( RenderInstruction& instruction )
     mImpl->context.SetScissorTest( false );
   }
 
-  Render::ProcessRenderInstruction( instruction,
-                                    mImpl->context,
-                                    mImpl->renderBufferIndex );
+  mImpl->renderAlgorithms.ProcessRenderInstruction( instruction, mImpl->context, mImpl->renderBufferIndex );
 
   if( instruction.mRenderTracker && ( instruction.mFrameBuffer != NULL ) )
   {
index edb03f0..b4035e8 100644 (file)
@@ -162,7 +162,7 @@ inline void AddRendererToRenderList( BufferIndex updateBufferIndex,
   bool inside( true );
   const Node* node = renderable.mNode;
 
-  if( cull && !renderable.mRenderer->GetShader().HintEnabled( Dali::Shader::Hint::MODIFIES_GEOMETRY ) )
+  if( cull && renderable.mRenderer && !renderable.mRenderer->GetShader().HintEnabled( Dali::Shader::Hint::MODIFIES_GEOMETRY ) )
   {
     const Vector4& boundingSphere = node->GetBoundingSphere();
     inside = ( boundingSphere.w > Math::MACHINE_EPSILON_1000 ) &&
@@ -171,20 +171,28 @@ inline void AddRendererToRenderList( BufferIndex updateBufferIndex,
 
   if( inside )
   {
-    Renderer::Opacity opacity = renderable.mRenderer->GetOpacity( updateBufferIndex, *renderable.mNode );
+    Renderer::Opacity opacity = renderable.mRenderer ? renderable.mRenderer->GetOpacity( updateBufferIndex, *renderable.mNode ) : Renderer::OPAQUE;
     if( opacity != Renderer::TRANSPARENT )
     {
       // Get the next free RenderItem.
       RenderItem& item = renderList.GetNextFreeItem();
-      item.mRenderer = &renderable.mRenderer->GetRenderer();
+
       item.mNode = renderable.mNode;
-      item.mTextureSet = renderable.mRenderer->GetTextures();
       item.mIsOpaque = ( opacity == Renderer::OPAQUE );
-      item.mDepthIndex = renderable.mRenderer->GetDepthIndex();
-
       if( !isLayer3d )
       {
-        item.mDepthIndex += renderable.mNode->GetDepthIndex();
+        item.mDepthIndex = renderable.mNode->GetDepthIndex();
+      }
+
+      if( DALI_LIKELY( renderable.mRenderer ) )
+      {
+        item.mRenderer =   &renderable.mRenderer->GetRenderer();
+        item.mTextureSet =  renderable.mRenderer->GetTextures();
+        item.mDepthIndex += renderable.mRenderer->GetDepthIndex();
+      }
+      else
+      {
+        item.mRenderer = nullptr;
       }
 
       // Save ModelView matrix onto the item.
@@ -327,7 +335,10 @@ inline void RenderInstructionProcessor::SortRenderItems( BufferIndex bufferIndex
     {
       RenderItem& item = renderList.GetItem( index );
 
-      item.mRenderer->SetSortAttributes( bufferIndex, mSortingHelper[ index ] );
+      if( item.mRenderer )
+      {
+        item.mRenderer->SetSortAttributes( bufferIndex, mSortingHelper[ index ] );
+      }
 
       // texture set
       mSortingHelper[ index ].textureSet = item.mTextureSet;
index 18cb3ee..6c8a5ef 100644 (file)
@@ -87,15 +87,17 @@ Layer* FindLayer( Node& node )
  * Rebuild the Layer::colorRenderables and overlayRenderables members,
  * including only renderers which are included in the current render-task.
  *
- * @param[in] updateBufferIndex The current update buffer index.
- * @param[in] node The current node of the scene-graph.
- * @param[in] currentLayer The current layer containing lists of opaque/transparent renderables.
- * @param[in] renderTask The current render-task.
- * @param[in] inheritedDrawMode The draw mode of the parent
- * @param[in] parentDepthIndex The inherited parent node depth index
- * @param[in] currentClippingId The current Clipping Id
- *              Note: ClippingId is passed by reference, so it is permanently modified when traversing back up the tree for uniqueness.
- * @param[in] clippingDepth The current clipping depth
+ * @param[in]  updateBufferIndex The current update buffer index.
+ * @param[in]  node The current node of the scene-graph.
+ * @param[in]  currentLayer The current layer containing lists of opaque/transparent renderables.
+ * @param[in]  renderTask The current render-task.
+ * @param[in]  inheritedDrawMode The draw mode of the parent
+ * @param[in]  parentDepthIndex The inherited parent node depth index
+ * @param[in]  currentClippingId The current Clipping Id
+ *               Note: ClippingId is passed by reference, so it is permanently modified when traversing back up the tree for uniqueness.
+ * @param[in]  clippingDepth The current stencil clipping depth
+ * @param[in]  clippingDepth The current scissor clipping depth
+ * @param[out] clippingUsed  Gets set to true if any clipping nodes have been found
  */
 void AddRenderablesForTask( BufferIndex updateBufferIndex,
                             Node& node,
@@ -103,7 +105,9 @@ void AddRenderablesForTask( BufferIndex updateBufferIndex,
                             RenderTask& renderTask,
                             int inheritedDrawMode,
                             uint32_t& currentClippingId,
-                            uint32_t clippingDepth )
+                            uint32_t clippingDepth,
+                            uint32_t scissorDepth,
+                            bool& clippingUsed )
 {
   // Short-circuit for invisible nodes
   if( !node.IsVisible( updateBufferIndex ) )
@@ -134,16 +138,33 @@ void AddRenderablesForTask( BufferIndex updateBufferIndex,
 
   DALI_ASSERT_DEBUG( NULL != layer );
 
+  const unsigned int count = node.GetRendererCount();
+
   // Update the clipping Id and depth for this node (if clipping is enabled).
-  if( DALI_UNLIKELY( node.GetClippingMode() != ClippingMode::DISABLED ) )
+  const Dali::ClippingMode::Type clippingMode = node.GetClippingMode();
+  if( DALI_UNLIKELY( clippingMode != ClippingMode::DISABLED ) )
   {
-    ++currentClippingId; // This modifies the reference passed in as well as the local value, causing the value to be global to the recursion.
-    ++clippingDepth;     // This only modifies the local value (which is passed in when the method recurses).
+    if( DALI_LIKELY( clippingMode == ClippingMode::CLIP_TO_BOUNDING_BOX ) )
+    {
+      ++scissorDepth;        // This only modifies the local value (which is passed in when the method recurses).
+      // If we do not have any renderers, create one to house the scissor operation.
+      if( count == 0u )
+      {
+        layer->colorRenderables.PushBack( Renderable( &node, nullptr ) );
+      }
+    }
+    else
+    {
+      // We only need clipping Id for stencil clips. This means we can deliberately avoid modifying it for bounding box clips,
+      // thus allowing bounding box clipping to still detect clip depth changes without turning on the stencil buffer for non-clipped nodes.
+      ++currentClippingId;   // This modifies the reference passed in as well as the local value, causing the value to be global to the recursion.
+      ++clippingDepth;       // This only modifies the local value (which is passed in when the method recurses).
+    }
+    clippingUsed = true;
   }
   // Set the information in the node.
-  node.SetClippingInformation( currentClippingId, clippingDepth );
+  node.SetClippingInformation( currentClippingId, clippingDepth, scissorDepth );
 
-  const unsigned int count = node.GetRendererCount();
   for( unsigned int i = 0; i < count; ++i )
   {
     SceneGraph::Renderer* renderer = node.GetRendererAt( i );
@@ -165,7 +186,7 @@ void AddRenderablesForTask( BufferIndex updateBufferIndex,
   for( NodeIter iter = children.Begin(); iter != endIter; ++iter )
   {
     Node& child = **iter;
-    AddRenderablesForTask( updateBufferIndex, child, *layer, renderTask, inheritedDrawMode, currentClippingId, clippingDepth );
+    AddRenderablesForTask( updateBufferIndex, child, *layer, renderTask, inheritedDrawMode, currentClippingId, clippingDepth, scissorDepth, clippingUsed );
   }
 }
 
@@ -248,10 +269,9 @@ void RenderTaskProcessor::Process( BufferIndex updateBufferIndex,
                              renderTask,
                              sourceNode->GetDrawMode(),
                              clippingId,
-                             0u );
-
-      // If the clipping Id is still 0 after adding all Renderables, there is no clipping required for this RenderTaskList.
-      hasClippingNodes = clippingId != 0u;
+                             0u,
+                             0u,
+                             hasClippingNodes );
 
       mRenderInstructionProcessor.Prepare( updateBufferIndex,
                                   sortedLayers,
@@ -267,6 +287,7 @@ void RenderTaskProcessor::Process( BufferIndex updateBufferIndex,
   // Now that the off screen renders are done we can process on screen render tasks.
   // Reset the clipping Id for the OnScreen render tasks.
   clippingId = 0u;
+  hasClippingNodes = false;
   for ( RenderTaskList::RenderTaskContainer::Iterator iter = taskContainer.Begin(); endIter != iter; ++iter )
   {
     RenderTask& renderTask = **iter;
@@ -308,10 +329,9 @@ void RenderTaskProcessor::Process( BufferIndex updateBufferIndex,
                              renderTask,
                              sourceNode->GetDrawMode(),
                              clippingId,
-                             0u );
-
-      // If the clipping Id is still 0 after adding all Renderables, there is no clipping required for this RenderTaskList.
-      hasClippingNodes = clippingId != 0;
+                             0u,
+                             0u,
+                             hasClippingNodes );
 
       mRenderInstructionProcessor.Prepare( updateBufferIndex,
                                   sortedLayers,
index a785e3d..5030a03 100644 (file)
@@ -99,6 +99,7 @@ Node::Node()
   mExclusiveRenderTask( NULL ),
   mChildren(),
   mClippingDepth( 0u ),
+  mScissorDepth( 0u ),
   mDepthIndex( 0u ),
   mRegenerateUniformMap( 0 ),
   mDirtyFlags( AllFlags ),
index 2539404..93e6a3f 100644 (file)
@@ -140,13 +140,14 @@ public:
    * A value is calculated that can be used during sorting to increase sort speed.
    * @param[in] clippingId The Clipping ID of the node to set
    * @param[in] clippingDepth The Clipping Depth of the node to set
+   * @param[in] scissorDepth The Scissor Clipping Depth of the node to set
    */
-  void SetClippingInformation( const uint32_t clippingId, const uint32_t clippingDepth )
+  void SetClippingInformation( const uint32_t clippingId, const uint32_t clippingDepth, const uint32_t scissorDepth )
   {
-    // We only set up the sort value if we have a clipping depth, IE. At least 1 clipping node has been hit.
+    // We only set up the sort value if we have a stencil clipping depth, IE. At least 1 clipping node has been hit.
     // If not, if we traverse down a clipping tree and back up, and there is another
     // node on the parent, this will have a non-zero clipping ID that must be ignored
-    if( DALI_LIKELY( clippingDepth > 0u ) )
+    if( clippingDepth > 0u )
     {
       mClippingDepth = clippingDepth;
 
@@ -161,6 +162,10 @@ public:
       // If we do not have a clipping depth, then set this to 0 so we do not have a Clipping ID either.
       mClippingSortModifier = 0u;
     }
+
+    // The scissor depth does not modify the clipping sort modifier (as scissor clips are 2D only).
+    // For this reason we can always update the member variable.
+    mScissorDepth = scissorDepth;
   }
 
   /**
@@ -182,6 +187,15 @@ public:
   }
 
   /**
+   * Gets the Scissor Clipping Depth for this node.
+   * @return The Scissor Clipping Depth for this node.
+   */
+  uint32_t GetScissorDepth() const
+  {
+    return mScissorDepth;
+  }
+
+  /**
    * Sets the clipping mode for this node.
    * @param[in] clippingMode The ClippingMode to set
    */
@@ -865,7 +879,8 @@ protected:
 
   CollectedUniformMap                mCollectedUniformMap[2]; ///< Uniform maps of the node
   unsigned int                       mUniformMapChanged[2];   ///< Records if the uniform map has been altered this frame
-  uint32_t                           mClippingDepth;          ///< The number of clipping nodes deep this node is
+  uint32_t                           mClippingDepth;          ///< The number of stencil clipping nodes deep this node is
+  uint32_t                           mScissorDepth;           ///< The number of scissor clipping nodes deep this node is
 
   uint32_t                           mDepthIndex;             ///< Depth index of the node
 
index a60e8c7..3d15ba0 100644 (file)
@@ -175,7 +175,6 @@ enum Type
  */
 namespace ClippingMode
 {
-
 /**
  * @brief Enumeration for ClippingMode types.
  * @SINCE_1_2_5
@@ -183,7 +182,8 @@ namespace ClippingMode
 enum Type
 {
   DISABLED,                     ///< This Actor will not clip its children. @SINCE_1_2_5
-  CLIP_CHILDREN,                ///< This Actor will clip all children to within its boundaries (the actor will also be visible itself). @SINCE_1_2_5
+  CLIP_CHILDREN,                ///< This Actor will clip itself and all children to within the pixel areas of this actors renderers. @SINCE_1_2_5
+  CLIP_TO_BOUNDING_BOX          ///< This Actor will clip itself and all children to within a screen-aligned rectangle encompassing its boundaries. @SINCE_1_2.61
 };
 
 } // namespace ClippingMode