Merge "Fixing culling for magnifier use case" into devel/master
authorAdeel Kazmi <adeel.kazmi@samsung.com>
Mon, 4 Apr 2022 17:31:36 +0000 (17:31 +0000)
committerGerrit Code Review <gerrit@review>
Mon, 4 Apr 2022 17:31:36 +0000 (17:31 +0000)
automated-tests/src/dali/utc-Dali-CameraActor.cpp
dali/internal/event/actors/camera-actor-impl.cpp
dali/internal/render/common/render-item.cpp
dali/internal/render/common/render-item.h
dali/internal/update/manager/render-instruction-processor.cpp
dali/internal/update/render-tasks/scene-graph-camera.cpp
dali/internal/update/render-tasks/scene-graph-camera.h

index 3352ee9..0ec0ccb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
@@ -186,7 +186,7 @@ int UtcDaliCameraActorNewDefaultPerspectiveProjection(void)
 
   // All the properties should still be the default values
   // Defaults taken from scene-graph-camera.cpp
-  DALI_TEST_EQUALS(4.0f / 3.0f, actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
+  DALI_TEST_EQUALS(800.0f / 480.0f, actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
   DALI_TEST_EQUALS(45.0f * (Math::PI / 180.0f), actor.GetProperty(CameraActor::Property::FIELD_OF_VIEW).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
   DALI_TEST_EQUALS(800.0f, actor.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
   DALI_TEST_EQUALS(3.0f * 800.0f, actor.GetProperty(CameraActor::Property::FAR_PLANE_DISTANCE).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
@@ -2118,3 +2118,118 @@ int UtcDaliCameraActorGetTypeNegative(void)
   }
   END_TEST;
 }
+
+int UtcDaliCameraActorNewDefaultOrthogonalProjection01(void)
+{
+  TestApplication application;
+  tet_infoline("Test that changing to orthogonal projection and then adding to scene calculates the right defaults");
+
+  CameraActor actor = CameraActor::New();
+  DALI_TEST_CHECK(actor);
+
+  actor.SetProjectionMode(Camera::ORTHOGRAPHIC_PROJECTION);
+  application.GetScene().Add(actor);
+
+  // Test application screen size is 480x800
+  // Check that the properties match to that screen size
+  float value;
+  actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get(value);
+  DALI_TEST_EQUALS(800.0f / 480.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(800.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::FAR_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(4895.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::LEFT_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(-240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+  actor.GetProperty(CameraActor::Property::RIGHT_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::TOP_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+  actor.GetProperty(CameraActor::Property::BOTTOM_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(-400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(1600.0f, actor.GetProperty(Actor::Property::POSITION_Z).Get<float>(), TEST_LOCATION);
+
+  DALI_TEST_EQUALS(actor.GetProjectionMode(), Dali::Camera::ORTHOGRAPHIC_PROJECTION, TEST_LOCATION);
+  END_TEST;
+}
+
+int UtcDaliCameraActorNewDefaultOrthogonalProjection02(void)
+{
+  TestApplication application;
+  tet_infoline("Test that changing to orthogonal projection and then adding to scene calculates the right defaults");
+
+  CameraActor actor = CameraActor::New();
+  DALI_TEST_CHECK(actor);
+
+  actor.SetOrthographicProjection(Vector2::ZERO);
+  DALI_TEST_EQUALS(actor.GetProjectionMode(), Dali::Camera::ORTHOGRAPHIC_PROJECTION, TEST_LOCATION);
+  application.GetScene().Add(actor);
+
+  // Test application screen size is 480x800
+  // Check that the properties match to that screen size
+  float value;
+  actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get(value);
+  DALI_TEST_EQUALS(800.0f / 480.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(800.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::FAR_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(4895.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::LEFT_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(-240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+  actor.GetProperty(CameraActor::Property::RIGHT_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  actor.GetProperty(CameraActor::Property::TOP_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+  actor.GetProperty(CameraActor::Property::BOTTOM_PLANE_DISTANCE).Get(value);
+  DALI_TEST_EQUALS(-400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(1600.0f, actor.GetProperty(Actor::Property::POSITION_Z).Get<float>(), TEST_LOCATION);
+
+  DALI_TEST_EQUALS(actor.GetProjectionMode(), Dali::Camera::ORTHOGRAPHIC_PROJECTION, TEST_LOCATION);
+  END_TEST;
+}
+
+// Add tests for culling:
+//   add large actor just outside canvas, & rotate it 45% - should still be rendered
+//   Rotate back to 0, should be culled.
+
+int UtcDaliCameraActorCulling01(void)
+{
+  TestApplication application;
+  auto&           gfx = application.GetGraphicsController();
+
+  tet_infoline("Create a renderable actor and position it slightly to the left of the scene");
+  tet_infoline("The actor should not be rendered");
+
+  Actor a = CreateRenderableActor(CreateTexture(TextureType::TEXTURE_2D, Pixel::Format::RGBA8888, 200, 200));
+
+  a[Actor::Property::PARENT_ORIGIN] = ParentOrigin::CENTER_LEFT;
+  a[Actor::Property::ANCHOR_POINT]  = ParentOrigin::CENTER_RIGHT;
+  a[Actor::Property::POSITION]      = Vector3(-10.0f, 0.0f, 0.0f);
+
+  application.GetScene().Add(a);
+
+  application.SendNotification();
+  application.Render();
+
+  auto& cmdStack = gfx.mCommandBufferCallStack;
+  DALI_TEST_CHECK(!cmdStack.FindMethod("Draw") && !cmdStack.FindMethod("DrawIndexed"));
+
+  tet_infoline("Rotate the actor 45 degrees, the actor should now be rendered");
+  a[Actor::Property::ORIENTATION] = Quaternion(Dali::ANGLE_45, Vector3::ZAXIS);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_CHECK(cmdStack.FindMethod("Draw") || cmdStack.FindMethod("DrawIndexed"));
+
+  END_TEST;
+}
index 97affa3..d69462a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
@@ -197,19 +197,26 @@ void CameraActor::OnInitialize()
 
 void CameraActor::OnSceneConnectionInternal()
 {
-  // If the canvas size has not been set, then use the size of the scene we've been added to to set up the perspective projection
+  // If the canvas size has not been set, then use the size of the scene to which we've been added
+  // in order to set up the current projection
   if((mCanvasSize.width < Math::MACHINE_EPSILON_1000) || (mCanvasSize.height < Math::MACHINE_EPSILON_1000))
   {
-    SetPerspectiveProjection(GetScene().GetSize());
+    if(mProjectionMode == Dali::Camera::ORTHOGRAPHIC_PROJECTION)
+    {
+      SetOrthographicProjection(GetScene().GetSize());
+    }
+    else //if(mProjectionMode == Dali::Camera::PERSPECTIVE_PROJECTION)
+    {
+      SetPerspectiveProjection(GetScene().GetSize());
+    }
   }
 }
 
 void CameraActor::SetReflectByPlane(const Vector4& plane)
 {
-  SceneGraph::Camera* cam = const_cast<SceneGraph::Camera*>(GetCamera());
-  if(cam)
+  if(mSceneObject)
   {
-    cam->SetReflectByPlane(plane);
+    SetReflectByPlaneMessage(GetEventThreadServices(), *mSceneObject, plane);
   }
 }
 
@@ -386,6 +393,7 @@ bool CameraActor::GetInvertYAxis() const
 
 void CameraActor::SetPerspectiveProjection(const Size& size)
 {
+  SetProjectionMode(Dali::Camera::PERSPECTIVE_PROJECTION);
   mCanvasSize = size;
 
   if((size.width < Math::MACHINE_EPSILON_1000) || (size.height < Math::MACHINE_EPSILON_1000))
@@ -425,34 +433,56 @@ void CameraActor::SetPerspectiveProjection(const Size& size)
   const float aspectRatio = width / height;
 
   // sceneObject is being used in a separate thread; queue a message to set
-  SetProjectionMode(Dali::Camera::PERSPECTIVE_PROJECTION);
   SetFieldOfView(fieldOfView);
   SetNearClippingPlane(nearClippingPlane);
   SetFarClippingPlane(farClippingPlane);
+  SetLeftClippingPlane(width * -0.5f);
+  SetRightClippingPlane(width * 0.5f);
+  SetTopClippingPlane(height * 0.5f);     // Top is +ve to keep consistency with orthographic values
+  SetBottomClippingPlane(height * -0.5f); // Bottom is -ve to keep consistency with orthographic values
   SetAspectRatio(aspectRatio);
   SetZ(cameraZ);
 }
 
 void CameraActor::SetOrthographicProjection(const Vector2& size)
 {
+  SetProjectionMode(Dali::Camera::ORTHOGRAPHIC_PROJECTION);
+  mCanvasSize = size;
+
+  if((size.width < Math::MACHINE_EPSILON_1000) || (size.height < Math::MACHINE_EPSILON_1000))
+  {
+    // If the size given is invalid, i.e. ZERO, then check if we've been added to a scene
+    if(OnScene())
+    {
+      // We've been added to a scene already, set the canvas size to the scene's size
+      mCanvasSize = GetScene().GetSize();
+    }
+    else
+    {
+      // We've not been added to a scene yet, so just return.
+      // We'll set the canvas size when we get added to a scene later
+      return;
+    }
+  }
+
   // Choose near, far and Z parameters to match the SetPerspectiveProjection above.
   float nearClippingPlane;
   float farClippingPlane;
   float cameraZ;
   CalculateClippingAndZ(size.width, size.height, nearClippingPlane, farClippingPlane, cameraZ);
-  SetOrthographicProjection(-size.x * 0.5f, size.x * 0.5f, size.y * 0.5f, -size.y * 0.5f, nearClippingPlane, farClippingPlane);
+  SetOrthographicProjection(-size.x * 0.5f, size.x * 0.5f, size.y * 0.5f, size.y * -0.5f, nearClippingPlane, farClippingPlane);
   SetZ(cameraZ);
 }
 
 void CameraActor::SetOrthographicProjection(float left, float right, float top, float bottom, float near, float far)
 {
+  SetProjectionMode(Dali::Camera::ORTHOGRAPHIC_PROJECTION);
   SetLeftClippingPlane(left);
   SetRightClippingPlane(right);
   SetTopClippingPlane(top);
   SetBottomClippingPlane(bottom);
   SetNearClippingPlane(near);
   SetFarClippingPlane(far);
-  SetProjectionMode(Dali::Camera::ORTHOGRAPHIC_PROJECTION);
 }
 
 bool CameraActor::BuildPickingRay(const Vector2&  screenCoordinates,
index 5fb838d..4a4733f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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,57 @@ RenderItem::RenderItem()
 
 RenderItem::~RenderItem() = default;
 
+ClippingBox RenderItem::CalculateTransformSpaceAABB(const Matrix& transformMatrix, const Vector3& size)
+{
+  // Calculate extent vector of the AABB:
+  const float halfActorX = size.x * 0.5f;
+  const float halfActorY = size.y * 0.5f;
+
+  // To transform the actor bounds to the transformed 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(transformMatrix, -halfActorX, -halfActorY),
+                     Transform2D(transformMatrix, halfActorX, -halfActorY),
+                     Transform2D(transformMatrix, 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);
+
+  // Round outwards from center
+  int x = static_cast<int>(floor(aabb.x));
+  int y = static_cast<int>(floor(aabb.y));
+  int z = static_cast<int>(ceilf(aabb.z));
+  int w = static_cast<int>(ceilf(aabb.w));
+
+  return ClippingBox(x, y, z - x, fabsf(w - y));
+}
+
 ClippingBox RenderItem::CalculateViewportSpaceAABB(const Matrix& modelViewMatrix, const Vector3& size, const int viewportWidth, const int viewportHeight)
 {
   // Calculate extent vector of the AABB:
index d89ab22..66d7158 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_SCENE_GRAPH_RENDER_ITEM_H
 
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
@@ -52,6 +52,15 @@ struct RenderItem
   ~RenderItem();
 
   /**
+   * Produce a 2D AABB in transformed space
+   * See below for caveats.
+   *
+   * @param[in]    transformMatrix   The matrix for converting to a different space
+   * @param[in]    size              The size of the render item
+   */
+  static ClippingBox CalculateTransformSpaceAABB(const Matrix& transformMatrix, const Vector3& size);
+
+  /**
    * @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.
@@ -60,6 +69,8 @@ struct RenderItem
    *
    * 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.
    *
+   * Note: ASSUMES THAT THE VIEWPORT COVERS THE SCREEN AND THAT THE CANVAS SIZE AND VIEWPORT SIZE ARE THE SAME!!!!!  (Not the case for magnifier)
+   *
    * @param[in]    modelViewMatrix   The model view matrix
    * @param[in]    size              The size of the render item
    * @param[in]    viewportWidth     The width of the viewport to calculate for
index 1ff28a8..0365d5b 100644 (file)
@@ -208,19 +208,25 @@ inline void AddRendererToRenderList(BufferIndex         updateBufferIndex,
       SetNodeUpdateSize(node, isLayer3d, nodeWorldMatrix, nodeSize, nodeUpdateSize);
       nodeUpdateSizeSet = true;
 
-      const Vector3& scale    = node->GetWorldScale(updateBufferIndex);
-      const Vector3& halfSize = nodeUpdateSize * scale * 0.5f;
-      float          radius(halfSize.Length());
+      const Vector3& scale = node->GetWorldScale(updateBufferIndex);
+      const Vector3& size  = nodeUpdateSize * scale;
 
-      if(radius > Math::MACHINE_EPSILON_1000)
+      if(size.LengthSquared() > Math::MACHINE_EPSILON_1000)
       {
         Matrix::Multiply(nodeModelViewMatrix, nodeWorldMatrix, viewMatrix);
         nodeModelViewMatrixSet = true;
 
-        ClippingBox clippingBox = RenderItem::CalculateViewportSpaceAABB(nodeModelViewMatrix, nodeUpdateSize, viewport.width, viewport.height);
-        inside                  = clippingBox.Intersects(viewport);
+        // Assume actors are at z=0, compute AABB in view space & test rect intersection
+        // against z=0 plane boundaries for frustum. (NOT viewport). This should take into account
+        // magnification due to FOV etc.
+        ClippingBox boundingBox = RenderItem::CalculateTransformSpaceAABB(nodeModelViewMatrix, nodeUpdateSize);
+        ClippingBox clippingBox(camera.mLeftClippingPlane, camera.mBottomClippingPlane, camera.mRightClippingPlane - camera.mLeftClippingPlane, fabsf(camera.mBottomClippingPlane - camera.mTopClippingPlane));
+        inside = clippingBox.Intersects(boundingBox);
       }
     }
+    /*
+     * Note, the API Camera::CheckAABBInFrustum() can be used to test if a bounding box is (partially/fully) inside the view frustum.
+     */
   }
 
   if(inside)
index f7b2b1c..c6e9973 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
@@ -144,7 +144,7 @@ const Dali::Camera::Type           Camera::DEFAULT_TYPE(Dali::Camera::FREE_LOOK)
 const Dali::Camera::ProjectionMode Camera::DEFAULT_MODE(Dali::Camera::PERSPECTIVE_PROJECTION);
 const bool                         Camera::DEFAULT_INVERT_Y_AXIS(false);
 const float                        Camera::DEFAULT_FIELD_OF_VIEW(45.0f * (Math::PI / 180.0f));
-const float                        Camera::DEFAULT_ASPECT_RATIO(4.0f / 3.0f);
+const float                        Camera::DEFAULT_ASPECT_RATIO(800.0f / 480.0f);
 const float                        Camera::DEFAULT_LEFT_CLIPPING_PLANE(-240.0f);
 const float                        Camera::DEFAULT_RIGHT_CLIPPING_PLANE(240.0f);
 const float                        Camera::DEFAULT_TOP_CLIPPING_PLANE(-400.0f);
index 4b37ce4..23e1c54 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_SCENE_GRAPH_CAMERA_H
 
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
@@ -461,6 +461,17 @@ inline void SetFarClippingPlaneMessage(EventThreadServices& eventThreadServices,
   new(slot) LocalType(&camera, &Camera::SetFarClippingPlane, parameter);
 }
 
+inline void SetReflectByPlaneMessage(EventThreadServices& eventThreadServices, const Camera& camera, const Vector4& plane)
+{
+  using LocalType = MessageValue1<Camera, Vector4>;
+
+  // Reserve some memory inside the message queue
+  uint32_t* slot = eventThreadServices.ReserveMessageSlot(sizeof(LocalType));
+
+  // Construct message in the message queue memory; note that delete should not be called on the return value
+  new(slot) LocalType(&camera, &Camera::SetReflectByPlane, plane);
+}
+
 inline void SetTargetPositionMessage(EventThreadServices& eventThreadServices, const Camera& camera, const Vector3& parameter)
 {
   using LocalType = MessageValue1<Camera, Vector3>;