[Libreoffice-commits] core.git: 5 commits - vcl/inc vcl/opengl vcl/source

Luboš Luňák l.lunak at collabora.com
Mon Jan 12 04:35:59 PST 2015


 vcl/inc/openglgdiimpl.hxx      |    9 +
 vcl/opengl/gdiimpl.cxx         |  189 ++++++++++++++++++++++++-----------------
 vcl/source/outdev/polyline.cxx |    2 
 3 files changed, 122 insertions(+), 78 deletions(-)

New commits:
commit e126f5cb095b2b0e0d3d505a68dfbeb6eefd82e4
Author: Luboš Luňák <l.lunak at collabora.com>
Date:   Thu Jan 8 18:09:49 2015 +0100

    implement optimized hairline drawing for opengl
    
    As a side effect, this prevents the lines from looking too wide,
    because of generic polygon drawing being a bit too generous with AA.
    
    Change-Id: I17314c39fd57e33ecbd10b8a8785c600bdc5b212

diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx
index a284236..0695693 100644
--- a/vcl/opengl/gdiimpl.cxx
+++ b/vcl/opengl/gdiimpl.cxx
@@ -1219,17 +1219,19 @@ bool OpenGLSalGraphicsImpl::drawPolyLine(
     //bool bDrawnOk = true;
     if( bIsHairline )
     {
-        // hairlines can benefit from a simplified tesselation
-        // e.g. for hairlines the linejoin style can be ignored
-        /*basegfx::B2DTrapezoidVector aB2DTrapVector;
-        basegfx::tools::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolygon, rLineWidth.getX() );
-
-        // draw tesselation result
-        const int nTrapCount = aB2DTrapVector.size();
-        if( nTrapCount > 0 )
-            bDrawnOk = drawFilledTrapezoids( &aB2DTrapVector[0], nTrapCount, fTransparency );
-
-        return bDrawnOk;*/
+        // hairlines can be drawn in a simpler way (the linejoin and linecap styles can be ignored)
+        PreDraw();
+        if( UseSolidAA( mnLineColor, fTransparency ))
+        {
+            for( sal_uInt32 j = 0; j < rPolygon.count() - 1; j++ )
+            {
+                const ::basegfx::B2DPoint& rPt1( rPolygon.getB2DPoint( j ) );
+                const ::basegfx::B2DPoint& rPt2( rPolygon.getB2DPoint(( j + 1 )) );
+                DrawLineAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+            }
+        }
+        PostDraw();
+        return true;
     }
 
     // get the area polygon for the line polygon
commit a477b66f1fa0847c716c886ab434d8e38247d641
Author: Luboš Luňák <l.lunak at collabora.com>
Date:   Wed Jan 7 17:43:39 2015 +0100

    do not set Y line width to 0
    
    I'm somewhat confused by why there needs to be a separate line width
    for X and Y, but apparently there is, and the latter shouldn't be just plain 0
    (otherwise a number of drawPolyLine() implementations either skip using
    a simpler path for the usual case of them being equal, or even plain to refuse
    work at all and cause a fall back).
    And I hope this doesn't lead to finding out that some of those implementation
    are actually buggy.
    
    Change-Id: I2dbbd1539c4a96d41935cce9ae6565872e2a459b

diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx
index ece1d6f..78eb139 100644
--- a/vcl/source/outdev/polyline.cxx
+++ b/vcl/source/outdev/polyline.cxx
@@ -302,7 +302,7 @@ bool OutputDevice::DrawPolyLineDirect( const basegfx::B2DPolygon& rB2DPolygon,
         // transform the line width if used
         if( fLineWidth != 0.0 )
         {
-            aB2DLineWidth = aTransform * ::basegfx::B2DVector( fLineWidth, 0.0 );
+            aB2DLineWidth = aTransform * ::basegfx::B2DVector( fLineWidth, fLineWidth );
         }
 
         // transform the polygon
commit ca1f918c327f82568e65f7f49e24b7158c63181f
Author: Luboš Luňák <l.lunak at collabora.com>
Date:   Mon Jan 12 13:24:47 2015 +0100

    make AA edges of objects look smoother (opengl)
    
    Change-Id: I66a04febdbfa673e0883ab6f574bb7768cad7953

diff --git a/vcl/inc/openglgdiimpl.hxx b/vcl/inc/openglgdiimpl.hxx
index cf7392d..2b815ee 100644
--- a/vcl/inc/openglgdiimpl.hxx
+++ b/vcl/inc/openglgdiimpl.hxx
@@ -64,7 +64,7 @@ protected:
 
     void ImplInitClipRegion();
     void ImplSetClipBit( const vcl::Region& rClip, GLuint nMask );
-
+    void ImplDrawLineAA( double nX1, double nY1, double nX2, double nY2, bool edge = false );
     bool CheckOffscreenTexture();
 
 public:
@@ -81,6 +81,7 @@ public:
     void DrawLines( sal_uInt32 nPoints, const SalPoint* pPtAry, bool bClose );
     void DrawLineAA( double nX1, double nY1, double nX2, double nY2 );
     void DrawLinesAA( sal_uInt32 nPoints, const SalPoint* pPtAry, bool bClose );
+    void DrawEdgeAA( double nX1, double nY1, double nX2, double nY2 );
     void DrawConvexPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry );
     void DrawConvexPolygon( const Polygon& rPolygon );
     void DrawRect( long nX, long nY, long nWidth, long nHeight );
diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx
index afdb8d9..a284236 100644
--- a/vcl/opengl/gdiimpl.cxx
+++ b/vcl/opengl/gdiimpl.cxx
@@ -472,7 +472,11 @@ void OpenGLSalGraphicsImpl::DrawLineAA( double nX1, double nY1, double nX2, doub
         glDrawArrays( GL_LINES, 0, 2 );
         return;
     }
+    ImplDrawLineAA( nX1, nY1, nX2, nY2 );
+}
 
+void OpenGLSalGraphicsImpl::ImplDrawLineAA( double nX1, double nY1, double nX2, double nY2, bool edge )
+{
     // Draw the line anti-aliased. Based on code with the following notice:
     /* Drawing nearly perfect 2D line segments in OpenGL
      * You can use this code however you want.
@@ -486,39 +490,52 @@ void OpenGLSalGraphicsImpl::DrawLineAA( double nX1, double nY1, double nX2, doub
     double y1 = nY1;
     double x2 = nX2;
     double y2 = nY2;
-    const int w = 1; // line width
+
+    // A special hack for drawing lines that are in fact AA edges of a shape. Make the line somewhat
+    // wider, but (done further below) draw the entire width as a gradient. This would be wrong for a line
+    // (too wide and seemingly less straight), but it makes the edges look smoother and the width difference
+    // is almost unnoticeable.
+    const double w = edge ? 1.4 : 1.0;
 
     double t;
     double R;
+    double f = w - static_cast<int>(w);
     //determine parameters t,R
-    switch( w )
+    if ( w>=0.0 && w<1.0 )
     {
-        case 0:
-            return;
-        case 1:
-            t=0.05;
-            R=0.768;
-        break;
-        case 2:
-            t=0.38;
-            R=1.08;
-        break;
-        case 3:
-            t=0.96;
-            R=1.08;
-            break;
-        case 4:
-            t=1.44;
-            R=1.08;
-            break;
-        case 5:
-            t=1.9;
-            R=1.08;
-            break;
-        default:
-            t=2.5+(w-6)*0.50;
-            R=1.08;
-            break;
+        t=0.05;
+        R=0.48+0.32*f;
+    }
+    else if ( w>=1.0 && w<2.0 )
+    {
+        t=0.05+f*0.33;
+        R=0.768+0.312*f;
+    }
+    else if ( w>=2.0 && w<3.0 )
+    {
+        t=0.38+f*0.58;
+        R=1.08;
+    }
+    else if ( w>=3.0 && w<4.0 )
+    {
+        t=0.96+f*0.48;
+        R=1.08;
+    }
+    else if ( w>=4.0 && w<5.0 )
+    {
+        t=1.44+f*0.46;
+        R=1.08;
+    }
+    else if ( w>=5.0 && w<6.0 )
+    {
+        t=1.9+f*0.6;
+        R=1.08;
+    }
+    else if ( w>=6.0 )
+    {
+        double ff=w-6.0;
+        t=2.5+ff*0.50;
+        R=1.08;
     }
 
     //determine angle of the line to horizontal
@@ -526,7 +543,7 @@ void OpenGLSalGraphicsImpl::DrawLineAA( double nX1, double nY1, double nX2, doub
     double Rx=0,Ry=0; //fading edge of a line
     double dx=x2-x1;
     double dy=y2-y1;
-    if ( w < 3)
+    if ( w < 3 )
     {   //approximate to make things even faster
         double m=dy/dx;
         //and calculate tx,ty,Rx,Ry
@@ -568,6 +585,13 @@ void OpenGLSalGraphicsImpl::DrawLineAA( double nX1, double nY1, double nX2, doub
         Rx=R*dx; Ry=R*dy;
     }
 
+    if( edge )
+    {   // See above.
+        Rx += tx;
+        Ry += ty;
+        tx = ty = 0;
+    }
+
     GLfloat vertices[]=
     {
 #define convertX( x ) GLfloat( (2 * (x)) / GetWidth()  - 1.0f)
@@ -606,6 +630,14 @@ void OpenGLSalGraphicsImpl::DrawLinesAA( sal_uInt32 nPoints, const SalPoint* pPt
         DrawLineAA( pPtAry[ nPoints - 1 ].mnX, pPtAry[ nPoints - 1 ].mnY, pPtAry[ 0 ].mnX, pPtAry[ 0 ].mnY );
 }
 
+void OpenGLSalGraphicsImpl::DrawEdgeAA( double nX1, double nY1, double nX2, double nY2 )
+{
+    assert( mrParent.getAntiAliasB2DDraw());
+    if( nX1 == nX2 || nY1 == nY2 )
+        return; //horizontal/vertical, no need for AA
+    ImplDrawLineAA( nX1, nY1, nX2, nY2, true );
+}
+
 void OpenGLSalGraphicsImpl::DrawConvexPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
 {
     std::vector<GLfloat> aVertices(nPoints * 2);
@@ -654,9 +686,7 @@ void OpenGLSalGraphicsImpl::DrawConvexPolygon( const Polygon& rPolygon )
             {
                 const Point& rPt1 = rPolygon.GetPoint( i );
                 const Point& rPt2 = rPolygon.GetPoint(( i + 1 ) % nPoints );
-                if( rPt1.getX() == rPt2.getX() || rPt1.getY() == rPt2.getY())
-                    continue; //horizontal/vertical, no need for AA
-                DrawLineAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+                DrawEdgeAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
             }
             UseSolid( lastSolidColor, lastSolidTransparency );
         }
@@ -751,9 +781,7 @@ void OpenGLSalGraphicsImpl::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPol
                 {
                     const ::basegfx::B2DPoint& rPt1( rPolygon.getB2DPoint( j ) );
                     const ::basegfx::B2DPoint& rPt2( rPolygon.getB2DPoint(( j + 1 ) % rPolygon.count()) );
-                    if( rPt1.getX() == rPt2.getX() || rPt1.getY() == rPt2.getY())
-                        continue; //horizontal/vertical, no need for AA
-                    DrawLineAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+                    DrawEdgeAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
                 }
             }
             UseSolid( lastSolidColor, lastSolidTransparency );
commit 67d6aef4c5970b6fc32617f6ee61512236072c6a
Author: Luboš Luňák <l.lunak at collabora.com>
Date:   Mon Jan 12 13:20:54 2015 +0100

    clean up resetting of solid color when using opengl AA
    
    118529d4644a and 011903894 might have been technically correct, but
    'mProgramIsSolidLineColor' is clearly a misnomer at best, and they'd
    have made it even more likely that this would break yet again.
    
    Change-Id: I1f01663e2abc0b1b0e557ae7241637a96e1a402a

diff --git a/vcl/inc/openglgdiimpl.hxx b/vcl/inc/openglgdiimpl.hxx
index 69c211d..cf7392d 100644
--- a/vcl/inc/openglgdiimpl.hxx
+++ b/vcl/inc/openglgdiimpl.hxx
@@ -57,8 +57,10 @@ protected:
     SalColor mnLineColor;
     SalColor mnFillColor;
 #ifdef DBG_UTIL
-    bool mProgramIsSolidLineColor;
+    bool mProgramIsSolidColor;
 #endif
+    SalColor mProgramSolidColor;
+    double mProgramSolidTransparency;
 
     void ImplInitClipRegion();
     void ImplSetClipBit( const vcl::Region& rClip, GLuint nMask );
@@ -84,7 +86,7 @@ public:
     void DrawRect( long nX, long nY, long nWidth, long nHeight );
     void DrawRect( const Rectangle& rRect );
     void DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry );
-    void DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, bool bLine, bool blockAA = false );
+    void DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, bool blockAA = false );
     void DrawRegionBand( const RegionBand& rRegion );
     void DrawTextureRect( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted = false );
     void DrawTexture( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted = false );
diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx
index e7149f7..afdb8d9 100644
--- a/vcl/opengl/gdiimpl.cxx
+++ b/vcl/opengl/gdiimpl.cxx
@@ -48,7 +48,7 @@ OpenGLSalGraphicsImpl::OpenGLSalGraphicsImpl(SalGraphics& rParent, SalGeometryPr
     , mnLineColor(SALCOLOR_NONE)
     , mnFillColor(SALCOLOR_NONE)
 #ifdef DBG_UTIL
-    , mProgramIsSolidLineColor(false)
+    , mProgramIsSolidColor(false)
 #endif
 {
 }
@@ -166,7 +166,7 @@ void OpenGLSalGraphicsImpl::PostDraw()
         mpProgram->Clean();
         mpProgram = NULL;
 #ifdef DBG_UTIL
-        mProgramIsSolidLineColor = false;
+        mProgramIsSolidColor = false;
 #endif
     }
 
@@ -198,7 +198,7 @@ void OpenGLSalGraphicsImpl::ImplSetClipBit( const vcl::Region& rClip, GLuint nMa
         if( rClip.getRegionBand() )
             DrawRegionBand( *rClip.getRegionBand() );
         else
-            DrawPolyPolygon( rClip.GetAsB2DPolyPolygon(), false, true );
+            DrawPolyPolygon( rClip.GetAsB2DPolyPolygon(), true );
     }
 
     glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
@@ -361,7 +361,7 @@ bool OpenGLSalGraphicsImpl::UseProgram( const OUString& rVertexShader, const OUS
         mpProgram->Clean();
     mpProgram = mpContext->UseProgram( rVertexShader, rFragmentShader );
 #ifdef DBG_UTIL
-    mProgramIsSolidLineColor = false; // UseSolid() will set to true if needed
+    mProgramIsSolidColor = false; // UseSolid() will set to true if needed
 #endif
     return ( mpProgram != NULL );
 }
@@ -374,8 +374,10 @@ bool OpenGLSalGraphicsImpl::UseSolid( SalColor nColor, sal_uInt8 nTransparency )
         return false;
     mpProgram->SetColor( "color", nColor, nTransparency );
 #ifdef DBG_UTIL
-    mProgramIsSolidLineColor = true;
+    mProgramIsSolidColor = true;
 #endif
+    mProgramSolidColor = nColor;
+    mProgramSolidTransparency = nTransparency / 100.0;
     return true;
 }
 
@@ -387,8 +389,10 @@ bool OpenGLSalGraphicsImpl::UseSolid( SalColor nColor, double fTransparency )
         return false;
     mpProgram->SetColorf( "color", nColor, fTransparency );
 #ifdef DBG_UTIL
-    mProgramIsSolidLineColor = true;
+    mProgramIsSolidColor = true;
 #endif
+    mProgramSolidColor = nColor;
+    mProgramSolidTransparency = fTransparency;
     return true;
 }
 
@@ -640,18 +644,22 @@ void OpenGLSalGraphicsImpl::DrawConvexPolygon( const Polygon& rPolygon )
         // may be a problem, if that is a real problem, the polygon areas itself needs to be
         // masked out for this or something.
 #ifdef DBG_UTIL
-        assert( mProgramIsSolidLineColor );
+        assert( mProgramIsSolidColor );
 #endif
-        UseSolidAA( mnLineColor );
-        for( i = 0; i < nPoints; ++i )
+        SalColor lastSolidColor = mProgramSolidColor;
+        double lastSolidTransparency = mProgramSolidTransparency;
+        if( UseSolidAA( lastSolidColor, lastSolidTransparency ))
         {
-            const Point& rPt1 = rPolygon.GetPoint( i );
-            const Point& rPt2 = rPolygon.GetPoint(( i + 1 ) % nPoints );
-            if( rPt1.getX() == rPt2.getX() || rPt1.getY() == rPt2.getY())
-                continue; //horizontal/vertical, no need for AA
-            DrawLineAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+            for( i = 0; i < nPoints; ++i )
+            {
+                const Point& rPt1 = rPolygon.GetPoint( i );
+                const Point& rPt2 = rPolygon.GetPoint(( i + 1 ) % nPoints );
+                if( rPt1.getX() == rPt2.getX() || rPt1.getY() == rPt2.getY())
+                    continue; //horizontal/vertical, no need for AA
+                DrawLineAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+            }
+            UseSolid( lastSolidColor, lastSolidTransparency );
         }
-        UseSolid( mnLineColor );
     }
 }
 
@@ -695,11 +703,11 @@ void OpenGLSalGraphicsImpl::DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPt
     else
     {
         const ::basegfx::B2DPolyPolygon aPolyPolygon( aPolygon );
-        DrawPolyPolygon( aPolyPolygon, false );
+        DrawPolyPolygon( aPolyPolygon );
     }
 }
 
-void OpenGLSalGraphicsImpl::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, bool bLine, bool blockAA )
+void OpenGLSalGraphicsImpl::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, bool blockAA )
 {
     ::std::vector< GLfloat > aVertices;
     GLfloat nWidth = GetWidth();
@@ -730,23 +738,26 @@ void OpenGLSalGraphicsImpl::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPol
         // may be a problem, if that is a real problem, the polygon areas itself needs to be
         // masked out for this or something.
 #ifdef DBG_UTIL
-        assert( mProgramIsSolidLineColor );
+        assert( mProgramIsSolidColor );
 #endif
-        bool bUseLineColor = bLine || mnLineColor != SALCOLOR_NONE;
-        UseSolidAA( bUseLineColor ? mnLineColor : mnFillColor );
-        for( sal_uInt32 i = 0; i < aSimplePolyPolygon.count(); i++ )
+        SalColor lastSolidColor = mProgramSolidColor;
+        double lastSolidTransparency = mProgramSolidTransparency;
+        if( UseSolidAA( lastSolidColor, lastSolidTransparency ))
         {
-            const basegfx::B2DPolygon& rPolygon( aSimplePolyPolygon.getB2DPolygon( i ) );
-            for( sal_uInt32 j = 0; j < rPolygon.count(); j++ )
+            for( sal_uInt32 i = 0; i < aSimplePolyPolygon.count(); i++ )
             {
-                const ::basegfx::B2DPoint& rPt1( rPolygon.getB2DPoint( j ) );
-                const ::basegfx::B2DPoint& rPt2( rPolygon.getB2DPoint(( j + 1 ) % rPolygon.count()) );
-                if( rPt1.getX() == rPt2.getX() || rPt1.getY() == rPt2.getY())
-                    continue; //horizontal/vertical, no need for AA
-                DrawLineAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+                const basegfx::B2DPolygon& rPolygon( aSimplePolyPolygon.getB2DPolygon( i ) );
+                for( sal_uInt32 j = 0; j < rPolygon.count(); j++ )
+                {
+                    const ::basegfx::B2DPoint& rPt1( rPolygon.getB2DPoint( j ) );
+                    const ::basegfx::B2DPoint& rPt2( rPolygon.getB2DPoint(( j + 1 ) % rPolygon.count()) );
+                    if( rPt1.getX() == rPt2.getX() || rPt1.getY() == rPt2.getY())
+                        continue; //horizontal/vertical, no need for AA
+                    DrawLineAA( rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY());
+                }
             }
+            UseSolid( lastSolidColor, lastSolidTransparency );
         }
-        UseSolid( bLine ? mnLineColor : mnFillColor );
     }
 
     CHECK_GL_ERROR();
@@ -1134,7 +1145,7 @@ bool OpenGLSalGraphicsImpl::drawPolyPolygon( const ::basegfx::B2DPolyPolygon& rP
         for( sal_uInt32 i = 0; i < rPolyPolygon.count(); i++ )
         {
             const ::basegfx::B2DPolyPolygon aOnePoly( rPolyPolygon.getB2DPolygon( i ) );
-            DrawPolyPolygon( aOnePoly, false );
+            DrawPolyPolygon( aOnePoly );
         }
     }
 
@@ -1217,7 +1228,7 @@ bool OpenGLSalGraphicsImpl::drawPolyLine(
         for( sal_uInt32 i = 0; i < aAreaPolyPoly.count(); i++ )
         {
             const ::basegfx::B2DPolyPolygon aOnePoly( aAreaPolyPoly.getB2DPolygon( i ) );
-            DrawPolyPolygon( aOnePoly, true );
+            DrawPolyPolygon( aOnePoly );
         }
     }
     PostDraw();
commit b8b49af63ebe91682c8cc049aa05b1ad714a93e3
Author: Luboš Luňák <l.lunak at collabora.com>
Date:   Wed Jan 7 18:06:25 2015 +0100

    fix confusion between transparency and opacity
    
    Change-Id: Ifa69f3272ebda2a61ac00d2affb8aebd4524f0fc

diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx
index feb8bdc..e7149f7 100644
--- a/vcl/opengl/gdiimpl.cxx
+++ b/vcl/opengl/gdiimpl.cxx
@@ -404,14 +404,14 @@ bool OpenGLSalGraphicsImpl::UseSolidAA( SalColor nColor, double fTransparency )
         return UseSolid( nColor );
     if( !UseProgram( "textureVertexShader", "linearGradientFragmentShader" ) )
         return false;
-    mpProgram->SetColorf( "start_color", nColor, 0.0f );
-    mpProgram->SetColorf( "end_color", nColor, fTransparency );
+    mpProgram->SetColorf( "start_color", nColor, fTransparency );
+    mpProgram->SetColorf( "end_color", nColor, 1.0f );
     return true;
 }
 
 bool OpenGLSalGraphicsImpl::UseSolidAA( SalColor nColor )
 {
-    return UseSolidAA( nColor, 1.0 );
+    return UseSolidAA( nColor, 0.0 );
 }
 
 bool OpenGLSalGraphicsImpl::UseInvert()


More information about the Libreoffice-commits mailing list