[SDL] C++ / SDL using bool vs. SDL_bool

bazz mbazzinotti at gmail.com
Sat Mar 16 13:28:39 PDT 2013


> Date: Wed, 27 Feb 2013 11:11:31 -0300
> From: Sik the hedgehog <sik.the.hedgehog at gmail.com>
> To: SDL Development List <sdl at lists.libsdl.org>
> Subject: Re: [SDL] SDL_RenderGeometry implementation
> Message-ID:
> 	<CAEyBR+VFHhn6w65nXiTO86XrVrEBhSkg86n6XP2OjznRUft5oQ at mail.gmail.com>
> Content-Type: text/plain; charset=UTF-8
>
> The situation is different, though. The sensor API only works on
> Android because the entire SDL hardware support for it only works on
> Android.
>
> Here we'd be talking about having some video rendering functionality
> supported on some renderers and not supported on others - and not for
> hardware support reasons. If we applied this logic to the rest of the
> rendering API then we'd end up with everybody sticking to the few
> functions that work everywhere because the rest are unpredictable,
> effectively rendering them useless. Imagine if function A was
> supported on OpenGL but not Direct3D, but function B was supported in
> Direct3D but not OpenGL... Would you want such a scenario?
>

I haven't looked to see if anyone's taken a stab at it in Hg, but
here's a rough-draft implementation of a triangle renderer that could
be used for a software implementation:

  typedef struct SDL_Vertex
  {
    double x, y;
  } SDL_Vertex;

  int SDL_PointToVertex( SDL_Point *p,  SDL_Vertex *v )
  {
    if( !p || !v )
    {
      return( -1 );
    }

    v->x = p->x;
    v->y = p->y;

    return( 1 );
  }

  int SDL_VertexLength( SDL_Vertex *v, double *ret )
  {
    if( v && ret )
    {
      *ret = sqrt( v->x * v->x + v->y * v->y );
      return( 1 );
    }

    return( -1 );
  }
  int SDL_DotProduct( SDL_Vertex *a, SDL_Vertex *b,  int *ret )
  {
    if( !a || !b || !ret )
    {
      return( -1 );
    }

    *ret = a->p.x * b->p.x + a->p.y * b->p.y;

    return( 1 );
  }
  int SDL_VertexScale( SDL_Vertex *v, double factor,  SDL_Vertex *ret )
  {
    if( v && ret )
    {
      ret->x = v->x * factor;
      ret->y = v->y * factor;

      return( 1 );
    }

    return( -1 );
  }
  int SDL_VertexAdd( SDL_Vertex *a, SDL_Vertex *b,  SDL_Vertex *ret )
  {
    if( a && b && ret )
    {
      ret->x = a->x + b->x;
      ret->y = a->y + b->y;

      return( 1 );
    }

    return( -1 );
  }
  int SDL_VertexSubtract( SDL_Vertex *a, SDL_Vertex *b,  SDL_Vertex *ret )
  {
    if( a && b && ret )
    {
      ret->x = a->x - b->x;
      ret->y = a->y - b->y;

      return( 1 );
    }

    return( -1 );
  }
  int SDL_VertexEquality( SDL_Vertex *a, SDL_Vertex *b,  int *ret )
  {
    if( a && b && ret )
    {
      ret = a->x == b->x;
      ret *= a->y == b->y;

      return( 1 );
    }

    return( -1 );
  }
  int SDL_InlineVertexEquality( SDL_Vertex *a, SDL_Vertex *b )
  {
    static SDL_Vertex zerovert = { 0.0, 0.0 };
    int res = 0;

    SDL_VertexEquality( ( a ? a : &zerovert ), ( b ? b : &zerovert ),  &res );

    return( res );
  };

  int SDL_RenderTriangle( const SDL_Point *corners,  const texture
*tex, const SDL_Point *mapping,   SDL_Point *upperleft, texture
**result )
  {
    /* This test technique is the first listed here:
http://www.blackpawn.com/texts/pointinpoly/default.html */
    /* Lots of constants have been optimized out of this, e.g.
SDL_BuildTriangleRefs() */

    /* This is an implementation of a triangle renderer based on the second */
    /*  technique for testing whether a pixel is inside of a triangle, as */
    /*  described here: */
    /*    http://www.blackpawn.com/texts/pointinpoly/default.html */
    /*  The basic idea is that you apply some scaling factors to two vectors */
    /*  that represent two of the triangle's edges. If the value of either of */
    /*  these is negative, then from the 'origin corner' you moved AWAY from */
    /*  the body of the triangle, if either is over 1.0 then you went
PAST the */
    /*  body of the triangle, and if the two combined are over 1.0 then you */
    /*  went past the third edge of the triangle. */
    /* I took that set of formulas, aplied them to find the corners (well, */
    /*  three corners, it IS a parrallelogram, so finding the fourth is just */
    /*  addition) of individual destination pixels as translated to
the source */
    /*  texture, and built some looping to iterate the destination
pixels, AND */
    /*  to iterate the source pixels (specifically dividing each source pixel */
    /*  into two, producing a sub-pixel sampling system). */
    /* I've tried to optimize everything that only needs to be
calculated once */
    /*  so that it really does only calculate once. One instance involves */
    /*  testing a "count" variable, and isn't necessarily immediately
obvious. */

    if
    (
      !corners ||
      SDL_InlineVertexEquality( &( corners[ 0 ] ), &( corners[ 1 ] ) ) ||
      SDL_InlineVertexEquality( &( corners[ 1 ] ), &( corners[ 2 ] ) ) ||
      SDL_InlineVertexEquality( &( corners[ 2 ] ), &( corners[ 0 ] ) )
    )
    {
      return( -1 );
    }
    if( !tex )
    {
      return( -2 );
    }
    if
    (
      !mapping ||
      SDL_InlineVertexEquality( &( mapping[ 0 ] ), &( mapping[ 1 ] ) ) ||
      SDL_InlineVertexEquality( &( mapping[ 1 ] ), &( mapping[ 2 ] ) ) ||
      SDL_InlineVertexEquality( &( mapping[ 2 ] ), &( mapping[ 0 ] ) )
    )
    {
      return( -3 );
    }
    if( !upperleft )
    {
      return( -4 );
    }
    if( !result )
    {
      result( -5 );
    }

      /* There shouldn't be any references to an 'm', but just in
case, it's a */
      /*  two-vertex array. */
    SDL_Vertex dest[ 3 ], src[ 3 ], rbase, rref[ 2 ], read, scratch[ 2 ];
      /* We use scan to build bounding boxes, specifically for pixels. */
    SDL_Point scan = { 1, 1 };
    SDL_Rect bounds;
    double destdots[ 6 ], srcdots[ 6 ], destdiv, srcdiv, u[ 4 ], v[ 4 ];
    double uscan, vscan ui, vi, count = -1.0, rchan, gchan, bchan, achan;

      /* This describes our workspace. */
    SDL_EnclosePoints( corners, 3, (const SDL_Rect*)0, &bounds );

      /* And this actually builds it. */
    texture *ret = build_texture( bounds.w, bounds.h );

      /* Destination reference 1. */
    dest[ 0 ].x = ( corners[ 1 ].x - corners[ 0 ].x ) + 1;
    dest[ 0 ].y = ( corners[ 1 ].y - corners[ 0 ].y ) + 1;
      /* Destination reference 2. */
    dest[ 1 ].x = ( corners[ 2 ].x - corners[ 0 ].x ) + 1;
    dest[ 1 ].y = ( corners[ 2 ].y - corners[ 0 ].y ) + 1;
      /* We should be able to calculate these once, so we will. */
    SDL_DotProduct( &( dest[ 0 ] ), &( dest[ 0 ] ),  &( destdots[ 0 ] ) );
    SDL_DotProduct( &( dest[ 0 ] ), &( dest[ 1 ] ),  &( destdots[ 2 ] ) );
    SDL_DotProduct( &( dest[ 1 ] ), &( dest[ 1 ] ),  &( destdots[ 4 ] ) );
    SDL_DotProduct( &( dest[ 1 ] ), &( dest[ 0 ] ),  &( destdots[ 5 ] ) );
      /* Same with this. */
    destdiv = ( destdots[ 4 ] * destdots[ 0 ] ) - ( destdots[ 5 ] *
destdots[ 2 ] );

      /* Source reference 1. */
    src[ 0 ].x = mapping[ 1 ].x - mapping[ 0 ].x;
    src[ 0 ].y = mapping[ 1 ].y - mapping[ 0 ].y;
      /* Source reference 2. */
    src[ 1 ].x = mapping[ 2 ].x - mapping[ 0 ].x;
    src[ 1 ].y = mapping[ 2 ].y - mapping[ 0 ].y;
      /* As with dest. */
    SDL_DotProduct( &( src[ 0 ] ), &( src[ 0 ] ),  &( srcdots[ 0 ] ) );
    SDL_DotProduct( &( src[ 0 ] ), &( src[ 1 ] ),  &( srcdots[ 2 ] ) );
    SDL_DotProduct( &( src[ 1 ] ), &( src[ 1 ] ),  &( srcdots[ 4 ] ) );
    SDL_DotProduct( &( src[ 1 ] ), &( src[ 0 ] ),  &( srcdots[ 5 ] ) );
      /* Yada. */
    srcdiv = ( srcdots[ 4 ] * srcdots[ 0 ] ) - ( srcdots[ 5 ] * srcdots[ 2 ] );


    while( scan.y < bounds.h )
    {
      while( scan.x < bounds.w )
      {
          /* We only need to do this once per loop. This sets dest[ 2 ] to */
          /*  point to the center of the current pixel. */
        dest[ 2 ].x = scan.x - ( 0.5 + corners[ 0 ].x );
        dest[ 2 ].y = scan.y - ( 0.5 + corners[ 0 ].y );

          /* Calculate the pixel-center itself, in terms of u & v (which are */
          /*  the ratio of two sides of the triangle which will allow you to */
          /*  reach the point). */
        SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 1 ] ),  &( destdots[ 1 ] ) );
        SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 0 ] ),  &( destdots[ 3 ] ) );
        u[ 0 ] = ( ( destdots[ 0 ] * destdots[ 1 ] ) - ( destdots[ 2 ]
* destdots[ 3 ] ) ) / div;
        v[ 0 ] = ( ( destdots[ 4 ] * destdots[ 3 ] ) - ( destdots[ 5 ]
* destdots[ 1 ] ) ) / div;

          /* Test to see if the pixel is in bounds. */
        if( !( u[ 0 ] < 0.0 || v[ 0 ] < 0.0 || u[ 0 ] > 1.0 || v[ 0 ]
> 1.0 || u[ 0 ] + v[ 0 ] > 1.0 ) )
        {
          /* This bit of code is somewhat "heavy", but I've optimized what I */
          /*  think I can. */

          /* This bit involves moving to the corners again, but... */

            /* Bottom-left pixel-corner u & v. */
          dest[ 2 ].x -= 0.5;
          dest[ 2 ].y += 0.5;
          SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 1 ] ),  &( destdots[ 1 ] ) );
          SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 0 ] ),  &( destdots[ 3 ] ) );
          u[ 2 ] = ( ( destdots[ 0 ] * destdots[ 1 ] ) - ( destdots[ 2
] * destdots[ 3 ] ) ) / div;
          v[ 2 ] = ( ( destdots[ 4 ] * destdots[ 3 ] ) - ( destdots[ 5
] * destdots[ 1 ] ) ) / div;

            /* Top-left pixel-corner u & v. */
          dest[ 2 ].y -= 1;
          SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 1 ] ),  &( destdots[ 1 ] ) );
          SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 0 ] ),  &( destdots[ 3 ] ) );
          u[ 0 ] = ( ( destdots[ 0 ] * destdots[ 1 ] ) - ( destdots[ 2
] * destdots[ 3 ] ) ) / div;
          v[ 0 ] = ( ( destdots[ 4 ] * destdots[ 3 ] ) - ( destdots[ 5
] * destdots[ 1 ] ) ) / div;

            /* Top-right pixel-corner u & v. */
          dest[ 2 ].x += 1;
          SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 1 ] ),  &( destdots[ 1 ] ) );
          SDL_DotProduct( &( dest[ 2 ] ), &( dest[ 0 ] ),  &( destdots[ 3 ] ) );
          u[ 1 ] = ( ( destdots[ 0 ] * destdots[ 1 ] ) - ( destdots[ 2
] * destdots[ 3 ] ) ) / div;
          v[ 1 ] = ( ( destdots[ 4 ] * destdots[ 3 ] ) - ( destdots[ 5
] * destdots[ 1 ] ) ) / div;

          /* You may think that the bottom-right pixel is missing here. */
          /* It isn't. */
          /* We get the three sets of u/v values above so that we can
use them */
          /*  with the src[] vertexes to work out the position of three */
          /*  corners of the box within the texture that we'll be pulling */
          /*  from. Those u/v values are tied to the geometry instead of any */
          /*  space, so we can use them to translate locations like that. We */
          /*  then work out the vertexes of that box, and (only once, because */
          /*  that'll cover us for the rest of the iterations) work out how */
          /*  far to iterate the u & v values we use on THOSE vertexes
so that */
          /*  we average 4 samples per pixel (thereby catching "all" of the */
          /*  important pixels). The rest is simple application of the
formula */
          /*    P = A + u * ( C - A ) + v * ( B - A ) */
          /*  iterating u & v with the values that we just worked out,
so that */
          /*  we can figure out which texture pixels to sample. */

            /* Pixel base point. */
          SDL_VertexScale( &( src[ 0 ] ), v[ 0 ],  &( scratch[ 1 ] ) );
          SDL_VertexScale( &( src[ 1 ] ), u[ 0 ],  &( scratch[ 0 ] ) );
          SDL_VertexAdd( &( scratch[ 0 ] ), &( scratch[ 1 ] ),  &read );
          SDL_VertexAdd( &read, &( mapping[ 0 ] ),  &rbase );

            /* Pixel relative vector 1. */
          SDL_VertexScale( &( src[ 0 ] ), v[ 1 ],  &( scratch[ 1 ] ) );
          SDL_VertexScale( &( src[ 1 ] ), u[ 1 ],  &( scratch[ 0 ] ) );
          SDL_VertexAdd( &( scratch[ 0 ] ), &( scratch[ 1 ] ),  &read );
          SDL_VertexAdd( &read, &( mapping[ 0 ] ),  &( scratch[ 0 ] ) );
          SDL_VertexSubtract( &( scratch[ 0 ] ), &rbase,  &( rref[ 0 ] ) );

            /* Pixel relative vector 2. */
          SDL_VertexScale( &( src[ 0 ] ), v[ 2 ],  &( scratch[ 1 ] ) );
          SDL_VertexScale( &( src[ 1 ] ), u[ 2 ],  &( scratch[ 0 ] ) );
          SDL_VertexAdd( &( scratch[ 0 ] ), &( scratch[ 1 ] ),  &read );
          SDL_VertexAdd( &read, &( mapping[ 0 ] ),  &( scratch[ 0 ] ) );
          SDL_VertexSubtract( &( scratch[ 0 ] ), &rbase,  &( rref[ 1 ] ) );

            /* We only need to calculate ui & vi once, since the pixel size */
            /*  should be the same everwhere. To do this, we cheat by using */
            /*  count with a value that it should never have twice. */
          if( count == -1.0 )
          {
            double tmp;

            SDL_VertexLength( &( rref[ 1 ] ), &tmp );
            ui = ceil( tmp * 2 );
            ui = ui ? 1.0 / ui : 0.5;

            SDL_VertexLength( &( rref[ 0 ] ), &tmp );
            ui = ceil( tmp * 2 );
            vi = ceil( scratch );
          }

            /* Zero out the data channels. */
          rchan = 0.0;
          gchan = 0.0;
          bchan = 0.0;
          achan = 0.0;

            /* Zero out the iterators. */
          uscan = 0.0;
          vscan = 0.0;

            /* Note: this will keep us from calculating ui & vi again. */
          count = 0.0;

          while( uscan < 1.0 )
          {
            while( vscan < 1.0 )
            {
                /* Calculate the current point. */
              SDL_VertexScale( &( rref[ 0 ] ), vscan,  &( scratch[ 0 ] ) );
              SDL_VertexAdd( &( scratch[ 0 ] ), &( mapping[ 0 ] ),  &(
scratch[ 1 ] ) );
              SDL_VertexScale( &( rref[ 1 ] ), uscan,  &( scratch[ 0 ] ) );
              SDL_VertexAdd( &( scratch[ 0 ] ), &( scratch[ 1 ] ),  &read );

              /* If we wanted to be more accurate, we would do an edge and */
              /*  corner test here to produce an intensity scaling factor. */

                /* We would then use the factor before these additions... */
              rchan += tex->pixels[ floor( read.x ) ][ floor( read.y ) ].r;
              gchan += tex->pixels[ floor( read.x ) ][ floor( read.y ) ].g;
              bchan += tex->pixels[ floor( read.x ) ][ floor( read.y ) ].b;
              achan += tex->pixels[ floor( read.x ) ][ floor( read.y ) ].a;

                /* And add it to count, instead of 1.0. */
              count += 1.0;
              vscan += vi;
            }

            vscan = 0.0;
            uscan += ui;
          }

            /* Now for count's magic: it's intended to scale our colors */
            /*  back down to more reasonable values. */
          ret->pixels[ scan.x - 1 ][ scan.y - 1 ].r = rchan / count;
          ret->pixels[ scan.x - 1 ][ scan.y - 1 ].g = gchan / count;
          ret->pixels[ scan.x - 1 ][ scan.y - 1 ].b = bchan / count;
          ret->pixels[ scan.x - 1 ][ scan.y - 1 ].a = achan / count;
        }
        /* If we're outside of the triangle, all that we care about is */
        /*  iterating to the next pixel. */

          /* Increment recieving point. */
        ++scan.x;
      }

        /* Increment line. */
      scan.x = 0;
      ++scan.y;
    }

    *result = ret;
    return( 1 );
  }

It doesn't go the full mile when blending pixels, and it only supports
RGBA format, but it should hopefully be enough to work from. I'm
personally of the opinion that this handles at least part of the most
difficult portions of implementing a software renderer.

To handle different pixel formats, I'd suggest replacing the parts of
the code that actually touch the pixels with function handles.

A faster (but low quality) way to get pixel colors would be to replace
the code controlled with:
  if( !( u[ 0 ] < 0.0 || v[ 0 ] < 0.0 || u[ 0 ] > 1.0 || v[ 0 ] > 1.0
|| u[ 0 ] + v[ 0 ] > 1.0 ) )
with some code that just gets the color of the texture pixel closest
to the center of the destination pixel.



More information about the SDL mailing list