src/render/software/SDL_render_sw.c
changeset 10612 6b2307dbec54
parent 10489 1e1ce9f6d215
child 10737 3406a0f8b041
     1.1 --- a/src/render/software/SDL_render_sw.c	Sun Nov 13 23:09:42 2016 -0800
     1.2 +++ b/src/render/software/SDL_render_sw.c	Tue Nov 15 01:12:27 2016 -0800
     1.3 @@ -239,7 +239,10 @@
     1.4      SDL_SetSurfaceAlphaMod(texture->driverdata, texture->a);
     1.5      SDL_SetSurfaceBlendMode(texture->driverdata, texture->blendMode);
     1.6  
     1.7 -    if (texture->access == SDL_TEXTUREACCESS_STATIC) {
     1.8 +    /* Only RLE encode textures without an alpha channel since the RLE coder
     1.9 +     * discards the color values of pixels with an alpha value of zero.
    1.10 +     */
    1.11 +    if (texture->access == SDL_TEXTUREACCESS_STATIC && !Amask) {
    1.12          SDL_SetSurfaceRLE(texture->driverdata, 1);
    1.13      }
    1.14  
    1.15 @@ -599,9 +602,15 @@
    1.16      SDL_Surface *surface = SW_ActivateRenderer(renderer);
    1.17      SDL_Surface *src = (SDL_Surface *) texture->driverdata;
    1.18      SDL_Rect final_rect, tmp_rect;
    1.19 -    SDL_Surface *surface_rotated, *surface_scaled;
    1.20 -    int retval, dstwidth, dstheight, abscenterx, abscentery;
    1.21 +    SDL_Surface *src_clone, *src_rotated, *src_scaled;
    1.22 +    SDL_Surface *mask = NULL, *mask_rotated = NULL;
    1.23 +    int retval = 0, dstwidth, dstheight, abscenterx, abscentery;
    1.24      double cangle, sangle, px, py, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y;
    1.25 +    SDL_BlendMode blendmode;
    1.26 +    Uint8 alphaMod, rMod, gMod, bMod;
    1.27 +    int applyModulation = SDL_FALSE;
    1.28 +    int blitRequired = SDL_FALSE;
    1.29 +    int isOpaque = SDL_FALSE;
    1.30  
    1.31      if (!surface) {
    1.32          return -1;
    1.33 @@ -617,69 +626,104 @@
    1.34      final_rect.w = (int)dstrect->w;
    1.35      final_rect.h = (int)dstrect->h;
    1.36  
    1.37 -    /* SDLgfx_rotateSurface doesn't accept a source rectangle, so crop and scale if we need to */
    1.38      tmp_rect = final_rect;
    1.39      tmp_rect.x = 0;
    1.40      tmp_rect.y = 0;
    1.41 -    if (srcrect->w == final_rect.w && srcrect->h == final_rect.h && srcrect->x == 0 && srcrect->y == 0) {
    1.42 -        surface_scaled = src; /* but if we don't need to, just use the original */
    1.43 -        retval = 0;
    1.44 -    } else {
    1.45 -        SDL_Surface *blit_src = src;
    1.46 -        Uint32 colorkey;
    1.47 -        SDL_BlendMode blendMode;
    1.48 -        Uint8 alphaMod, r, g, b;
    1.49 -        SDL_bool cloneSource = SDL_FALSE;
    1.50  
    1.51 -        surface_scaled = SDL_CreateRGBSurface(SDL_SWSURFACE, final_rect.w, final_rect.h, src->format->BitsPerPixel,
    1.52 -                                              src->format->Rmask, src->format->Gmask,
    1.53 -                                              src->format->Bmask, src->format->Amask );
    1.54 -        if (!surface_scaled) {
    1.55 -            return -1;
    1.56 +    /* It is possible to encounter an RLE encoded surface here and locking it is
    1.57 +     * necessary because this code is going to access the pixel buffer directly.
    1.58 +     */
    1.59 +    if (SDL_MUSTLOCK(src)) {
    1.60 +        SDL_LockSurface(src);
    1.61 +    }
    1.62 +
    1.63 +    /* Clone the source surface but use its pixel buffer directly.
    1.64 +     * The original source surface must be treated as read-only.
    1.65 +     */
    1.66 +    src_clone = SDL_CreateRGBSurfaceFrom(src->pixels, src->w, src->h, src->format->BitsPerPixel, src->pitch,
    1.67 +                                         src->format->Rmask, src->format->Gmask,
    1.68 +                                         src->format->Bmask, src->format->Amask);
    1.69 +    if (src_clone == NULL) {
    1.70 +        if (SDL_MUSTLOCK(src)) {
    1.71 +            SDL_UnlockSurface(src);
    1.72          }
    1.73 +        return -1;
    1.74 +    }
    1.75  
    1.76 -        /* copy the color key, alpha mod, blend mode, and color mod so the scaled surface behaves like the source */
    1.77 -        if (SDL_GetColorKey(src, &colorkey) == 0) {
    1.78 -            SDL_SetColorKey(surface_scaled, SDL_TRUE, colorkey);
    1.79 -            cloneSource = SDL_TRUE;
    1.80 -        }
    1.81 -        SDL_GetSurfaceAlphaMod(src, &alphaMod); /* these will be copied to surface_scaled below if necessary */
    1.82 -        SDL_GetSurfaceBlendMode(src, &blendMode);
    1.83 -        SDL_GetSurfaceColorMod(src, &r, &g, &b);
    1.84 +    SDL_GetSurfaceBlendMode(src, &blendmode);
    1.85 +    SDL_GetSurfaceAlphaMod(src, &alphaMod);
    1.86 +    SDL_GetSurfaceColorMod(src, &rMod, &gMod, &bMod);
    1.87  
    1.88 -        /* now we need to blit the src into surface_scaled. since we want to copy the colors from the source to
    1.89 -         * surface_scaled rather than blend them, etc. we'll need to disable the blend mode, alpha mod, etc.
    1.90 -         * but we don't want to modify src (in case it's being used on other threads), so we'll need to clone it
    1.91 -         * before changing the blend options
    1.92 -         */
    1.93 -        cloneSource |= blendMode != SDL_BLENDMODE_NONE || (alphaMod & r & g & b) != 255;
    1.94 -        if (cloneSource) {
    1.95 -            blit_src = SDL_ConvertSurface(src, src->format, src->flags); /* clone src */
    1.96 -            if (!blit_src) {
    1.97 -                SDL_FreeSurface(surface_scaled);
    1.98 -                return -1;
    1.99 -            }
   1.100 -            SDL_SetSurfaceAlphaMod(blit_src, 255); /* disable all blending options in blit_src */
   1.101 -            SDL_SetSurfaceBlendMode(blit_src, SDL_BLENDMODE_NONE);
   1.102 -            SDL_SetColorKey(blit_src, 0, 0);
   1.103 -            SDL_SetSurfaceColorMod(blit_src, 255, 255, 255);
   1.104 -            SDL_SetSurfaceRLE(blit_src, 0); /* don't RLE encode a surface we'll only use once */
   1.105 +    /* SDLgfx_rotateSurface only accepts 32-bit surfaces with a 8888 layout. Everything else has to be converted. */
   1.106 +    if (src->format->BitsPerPixel != 32 || SDL_PIXELLAYOUT(src->format->format) != SDL_PACKEDLAYOUT_8888 || !src->format->Amask) {
   1.107 +        blitRequired = SDL_TRUE;
   1.108 +    }
   1.109  
   1.110 -            SDL_SetSurfaceAlphaMod(surface_scaled, alphaMod); /* copy blending options to surface_scaled */
   1.111 -            SDL_SetSurfaceBlendMode(surface_scaled, blendMode);
   1.112 -            SDL_SetSurfaceColorMod(surface_scaled, r, g, b);
   1.113 -        }
   1.114 +    /* If scaling and cropping is necessary, it has to be taken care of before the rotation. */
   1.115 +    if (!(srcrect->w == final_rect.w && srcrect->h == final_rect.h && srcrect->x == 0 && srcrect->y == 0)) {
   1.116 +        blitRequired = SDL_TRUE;
   1.117 +    }
   1.118  
   1.119 -        retval = SDL_BlitScaled(blit_src, srcrect, surface_scaled, &tmp_rect);
   1.120 -        if (blit_src != src) {
   1.121 -            SDL_FreeSurface(blit_src);
   1.122 +    /* The color and alpha modulation has to be applied before the rotation when using the NONE and MOD blend modes. */
   1.123 +    if ((blendmode == SDL_BLENDMODE_NONE || blendmode == SDL_BLENDMODE_MOD) && (alphaMod & rMod & gMod & bMod) != 255) {
   1.124 +        applyModulation = SDL_TRUE;
   1.125 +        SDL_SetSurfaceAlphaMod(src_clone, alphaMod);
   1.126 +        SDL_SetSurfaceColorMod(src_clone, rMod, gMod, bMod);
   1.127 +    }
   1.128 +
   1.129 +    /* Opaque surfaces are much easier to handle with the NONE blend mode. */
   1.130 +    if (blendmode == SDL_BLENDMODE_NONE && !src->format->Amask && alphaMod == 255) {
   1.131 +        isOpaque = SDL_TRUE;
   1.132 +    }
   1.133 +
   1.134 +    /* The NONE blend mode requires a mask for non-opaque surfaces. This mask will be used
   1.135 +     * to clear the pixels in the destination surface. The other steps are explained below.
   1.136 +     */
   1.137 +    if (blendmode == SDL_BLENDMODE_NONE && !isOpaque) {
   1.138 +        mask = SDL_CreateRGBSurface(0, final_rect.w, final_rect.h, 32,
   1.139 +                                    0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
   1.140 +        if (mask == NULL) {
   1.141 +            retval = -1;
   1.142 +        } else {
   1.143 +            SDL_SetSurfaceBlendMode(mask, SDL_BLENDMODE_MOD);
   1.144          }
   1.145      }
   1.146  
   1.147 +    /* Create a new surface should there be a format mismatch or if scaling, cropping,
   1.148 +     * or modulation is required. It's possible to use the source surface directly otherwise.
   1.149 +     */
   1.150 +    if (!retval && (blitRequired || applyModulation)) {
   1.151 +        SDL_Rect scale_rect = tmp_rect;
   1.152 +        src_scaled = SDL_CreateRGBSurface(0, final_rect.w, final_rect.h, 32,
   1.153 +                                          0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
   1.154 +        if (src_scaled == NULL) {
   1.155 +            retval = -1;
   1.156 +        } else {
   1.157 +            SDL_SetSurfaceBlendMode(src_clone, SDL_BLENDMODE_NONE);
   1.158 +            retval = SDL_BlitScaled(src_clone, srcrect, src_scaled, &scale_rect);
   1.159 +            SDL_FreeSurface(src_clone);
   1.160 +            src_clone = src_scaled;
   1.161 +            src_scaled = NULL;
   1.162 +        }
   1.163 +    }
   1.164 +
   1.165 +    /* SDLgfx_rotateSurface is going to make decisions depending on the blend mode. */
   1.166 +    SDL_SetSurfaceBlendMode(src_clone, blendmode);
   1.167 +
   1.168      if (!retval) {
   1.169          SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, angle, &dstwidth, &dstheight, &cangle, &sangle);
   1.170 -        surface_rotated = SDLgfx_rotateSurface(surface_scaled, angle, dstwidth/2, dstheight/2, GetScaleQuality(), flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL, dstwidth, dstheight, cangle, sangle);
   1.171 -        if(surface_rotated) {
   1.172 +        src_rotated = SDLgfx_rotateSurface(src_clone, angle, dstwidth/2, dstheight/2, GetScaleQuality(), flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL, dstwidth, dstheight, cangle, sangle);
   1.173 +        if (src_rotated == NULL) {
   1.174 +            retval = -1;
   1.175 +        }
   1.176 +        if (!retval && mask != NULL) {
   1.177 +            /* The mask needed for the NONE blend mode gets rotated with the same parameters. */
   1.178 +            mask_rotated = SDLgfx_rotateSurface(mask, angle, dstwidth/2, dstheight/2, SDL_FALSE, 0, 0, dstwidth, dstheight, cangle, sangle);
   1.179 +            if (mask_rotated == NULL) {
   1.180 +                retval = -1;
   1.181 +            }
   1.182 +        }
   1.183 +        if (!retval) {
   1.184              /* Find out where the new origin is by rotating the four final_rect points around the center and then taking the extremes */
   1.185              abscenterx = final_rect.x + (int)center->x;
   1.186              abscentery = final_rect.y + (int)center->y;
   1.187 @@ -715,13 +759,69 @@
   1.188              tmp_rect.w = dstwidth;
   1.189              tmp_rect.h = dstheight;
   1.190  
   1.191 -            retval = SDL_BlitSurface(surface_rotated, NULL, surface, &tmp_rect);
   1.192 -            SDL_FreeSurface(surface_rotated);
   1.193 +            /* The NONE blend mode needs some special care with non-opaque surfaces.
   1.194 +             * Other blend modes or opaque surfaces can be blitted directly.
   1.195 +             */
   1.196 +            if (blendmode != SDL_BLENDMODE_NONE || isOpaque) {
   1.197 +                if (applyModulation == SDL_FALSE) {
   1.198 +                    /* If the modulation wasn't already applied, make it happen now. */
   1.199 +                    SDL_SetSurfaceAlphaMod(src_rotated, alphaMod);
   1.200 +                    SDL_SetSurfaceColorMod(src_rotated, rMod, gMod, bMod);
   1.201 +                }
   1.202 +                retval = SDL_BlitSurface(src_rotated, NULL, surface, &tmp_rect);
   1.203 +            } else {
   1.204 +                /* The NONE blend mode requires three steps to get the pixels onto the destination surface.
   1.205 +                 * First, the area where the rotated pixels will be blitted to get set to zero.
   1.206 +                 * This is accomplished by simply blitting a mask with the NONE blend mode.
   1.207 +                 * The colorkey set by the rotate function will discard the correct pixels.
   1.208 +                 */
   1.209 +                SDL_Rect mask_rect = tmp_rect;
   1.210 +                SDL_SetSurfaceBlendMode(mask_rotated, SDL_BLENDMODE_NONE);
   1.211 +                retval = SDL_BlitSurface(mask_rotated, NULL, surface, &mask_rect);
   1.212 +                if (!retval) {
   1.213 +                    /* The next step copies the alpha value. This is done with the BLEND blend mode and
   1.214 +                     * by modulating the source colors with 0. Since the destination is all zeros, this
   1.215 +                     * will effectively set the destination alpha to the source alpha.
   1.216 +                     */
   1.217 +                    SDL_SetSurfaceColorMod(src_rotated, 0, 0, 0);
   1.218 +                    mask_rect = tmp_rect;
   1.219 +                    retval = SDL_BlitSurface(src_rotated, NULL, surface, &mask_rect);
   1.220 +                    if (!retval) {
   1.221 +                        /* The last step gets the color values in place. The ADD blend mode simply adds them to
   1.222 +                         * the destination (where the color values are all zero). However, because the ADD blend
   1.223 +                         * mode modulates the colors with the alpha channel, a surface without an alpha mask needs
   1.224 +                         * to be created. This makes all source pixels opaque and the colors get copied correctly.
   1.225 +                         */
   1.226 +                        SDL_Surface *src_rotated_rgb;
   1.227 +                        src_rotated_rgb = SDL_CreateRGBSurfaceFrom(src_rotated->pixels, src_rotated->w, src_rotated->h,
   1.228 +                                                                   src_rotated->format->BitsPerPixel, src_rotated->pitch,
   1.229 +                                                                   src_rotated->format->Rmask, src_rotated->format->Gmask,
   1.230 +                                                                   src_rotated->format->Bmask, 0);
   1.231 +                        if (src_rotated_rgb == NULL) {
   1.232 +                            retval = -1;
   1.233 +                        } else {
   1.234 +                            SDL_SetSurfaceBlendMode(src_rotated_rgb, SDL_BLENDMODE_ADD);
   1.235 +                            retval = SDL_BlitSurface(src_rotated_rgb, NULL, surface, &tmp_rect);
   1.236 +                            SDL_FreeSurface(src_rotated_rgb);
   1.237 +                        }
   1.238 +                    }
   1.239 +                }
   1.240 +                SDL_FreeSurface(mask_rotated);
   1.241 +            }
   1.242 +            if (src_rotated != NULL) {
   1.243 +                SDL_FreeSurface(src_rotated);
   1.244 +            }
   1.245          }
   1.246      }
   1.247  
   1.248 -    if (surface_scaled != src) {
   1.249 -        SDL_FreeSurface(surface_scaled);
   1.250 +    if (SDL_MUSTLOCK(src)) {
   1.251 +        SDL_UnlockSurface(src);
   1.252 +    }
   1.253 +    if (mask != NULL) {
   1.254 +        SDL_FreeSurface(mask);
   1.255 +    }
   1.256 +    if (src_clone != NULL) {
   1.257 +        SDL_FreeSurface(src_clone);
   1.258      }
   1.259      return retval;
   1.260  }