Ver código fonte

Fixed jitter when doing a scaled blit to a surface with clipping set

Fixes https://github.com/libsdl-org/SDL/issues/10953
Fixes https://github.com/libsdl-org/SDL/issues/12658
Sam Lantinga 5 dias atrás
pai
commit
bb557f1077
1 arquivos alterados com 79 adições e 130 exclusões
  1. 79 130
      src/video/SDL_surface.c

+ 79 - 130
src/video/SDL_surface.c

@@ -1089,15 +1089,42 @@ bool SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst
     return SDL_BlitSurfaceUnchecked(src, &r_src, dst, &r_dst);
 }
 
+static bool SDL_BlitSurfaceClippedScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)
+{
+    // We need to scale first, then blit into dst because we're clipping in the destination surface pixel coordinates
+    if (SDL_MUSTLOCK(src)) {
+        if (!SDL_LockSurface(src)) {
+            return false;
+        }
+    }
+
+    bool result;
+    int saved_w = src->w;
+    int saved_h = src->h;
+    void *saved_pixels = src->pixels;
+    src->w = srcrect->w;
+    src->h = srcrect->h;
+    src->pixels = (Uint8 *)src->pixels + srcrect->y * src->pitch + srcrect->x * SDL_BYTESPERPIXEL(src->format);
+    SDL_Surface *scaled = SDL_ScaleSurface(src, dstrect->w, dstrect->h, scaleMode);
+    if (scaled) {
+        result = SDL_BlitSurface(scaled, NULL, dst, dstrect);
+        SDL_DestroySurface(scaled);
+    } else {
+        result = false;
+    }
+    src->w = saved_w;
+    src->h = saved_h;
+    src->pixels = saved_pixels;
+
+    if (SDL_MUSTLOCK(src)) {
+        SDL_UnlockSurface(src);
+    }
+    return result;
+}
+
 bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)
 {
-    SDL_Rect *clip_rect;
-    double src_x0, src_y0, src_x1, src_y1;
-    double dst_x0, dst_y0, dst_x1, dst_y1;
-    SDL_Rect final_src, final_dst;
-    double scaling_w, scaling_h;
-    int src_w, src_h;
-    int dst_w, dst_h;
+    SDL_Rect r_src, r_dst;
 
     // Make sure the surfaces aren't locked
     if (!SDL_SurfaceValid(src) || !src->pixels) {
@@ -1120,148 +1147,70 @@ bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surfac
         return SDL_InvalidParamError("scaleMode");
     }
 
-    if (!srcrect) {
-        src_w = src->w;
-        src_h = src->h;
-    } else {
-        src_w = srcrect->w;
-        src_h = srcrect->h;
-    }
-
-    if (!dstrect) {
-        dst_w = dst->w;
-        dst_h = dst->h;
-    } else {
-        dst_w = dstrect->w;
-        dst_h = dstrect->h;
-    }
-
-    if (dst_w == src_w && dst_h == src_h) {
+    int src_w = srcrect ? srcrect->w : src->w;
+    int src_h = srcrect ? srcrect->h : src->h;
+    int dst_w = dstrect ? dstrect->w : dst->w;
+    int dst_h = dstrect ? dstrect->h : dst->h;
+    if (src_w == dst_w && src_h == dst_h) {
         // No scaling, defer to regular blit
         return SDL_BlitSurface(src, srcrect, dst, dstrect);
     }
 
-    if (src_w == 0) {
-        src_w = 1;
-    }
-    if (src_h == 0) {
-        src_h = 1;
+    if (src->w == 0 || src->h == 0) {
+        // Nothing to do
+        return true;
     }
 
-    scaling_w = (double)dst_w / src_w;
-    scaling_h = (double)dst_h / src_h;
+    // Full src surface
+    r_src.x = 0;
+    r_src.y = 0;
+    r_src.w = src->w;
+    r_src.h = src->h;
 
-    if (!dstrect) {
-        dst_x0 = 0;
-        dst_y0 = 0;
-        dst_x1 = dst_w;
-        dst_y1 = dst_h;
+    if (dstrect) {
+        r_dst.x = dstrect->x;
+        r_dst.y = dstrect->y;
+        r_dst.w = dstrect->w;
+        r_dst.h = dstrect->h;
     } else {
-        dst_x0 = dstrect->x;
-        dst_y0 = dstrect->y;
-        dst_x1 = dst_x0 + dst_w;
-        dst_y1 = dst_y0 + dst_h;
+        r_dst.x = 0;
+        r_dst.y = 0;
+        r_dst.w = dst->w;
+        r_dst.h = dst->h;
     }
 
-    if (!srcrect) {
-        src_x0 = 0;
-        src_y0 = 0;
-        src_x1 = src_w;
-        src_y1 = src_h;
-    } else {
-        src_x0 = srcrect->x;
-        src_y0 = srcrect->y;
-        src_x1 = src_x0 + src_w;
-        src_y1 = src_y0 + src_h;
-
-        // Clip source rectangle to the source surface
-
-        if (src_x0 < 0) {
-            dst_x0 -= src_x0 * scaling_w;
-            src_x0 = 0;
-        }
-
-        if (src_x1 > src->w) {
-            dst_x1 -= (src_x1 - src->w) * scaling_w;
-            src_x1 = src->w;
-        }
-
-        if (src_y0 < 0) {
-            dst_y0 -= src_y0 * scaling_h;
-            src_y0 = 0;
-        }
-
-        if (src_y1 > src->h) {
-            dst_y1 -= (src_y1 - src->h) * scaling_h;
-            src_y1 = src->h;
+    // clip the source rectangle to the source surface
+    if (srcrect) {
+        SDL_Rect desired, tmp;
+        desired.x = srcrect->x;
+        desired.y = srcrect->y;
+        desired.w = SDL_max(srcrect->w, 1);
+        desired.h = SDL_max(srcrect->h, 1);
+        if (SDL_GetRectIntersection(&desired, &r_src, &tmp) == false) {
+            return true;
         }
-    }
-
-    // Clip destination rectangle to the clip rectangle
-    clip_rect = &dst->clip_rect;
 
-    // Translate to clip space for easier calculations
-    dst_x0 -= clip_rect->x;
-    dst_x1 -= clip_rect->x;
-    dst_y0 -= clip_rect->y;
-    dst_y1 -= clip_rect->y;
-
-    if (dst_x0 < 0) {
-        src_x0 -= dst_x0 / scaling_w;
-        dst_x0 = 0;
-    }
-
-    if (dst_x1 > clip_rect->w) {
-        src_x1 -= (dst_x1 - clip_rect->w) / scaling_w;
-        dst_x1 = clip_rect->w;
-    }
-
-    if (dst_y0 < 0) {
-        src_y0 -= dst_y0 / scaling_h;
-        dst_y0 = 0;
-    }
+        // Shift dstrect, if srcrect origin has changed
+        r_dst.x += (tmp.x - desired.x) * r_dst.w / desired.w;
+        r_dst.y += (tmp.y - desired.y) * r_dst.h / desired.h;
+        r_dst.w += (tmp.w - desired.w) * r_dst.w / desired.w;
+        r_dst.h += (tmp.h - desired.h) * r_dst.h / desired.h;
 
-    if (dst_y1 > clip_rect->h) {
-        src_y1 -= (dst_y1 - clip_rect->h) / scaling_h;
-        dst_y1 = clip_rect->h;
+        // Update srcrect
+        r_src = tmp;
     }
 
-    // Translate back to surface coordinates
-    dst_x0 += clip_rect->x;
-    dst_x1 += clip_rect->x;
-    dst_y0 += clip_rect->y;
-    dst_y1 += clip_rect->y;
-
-    final_src.x = (int)SDL_round(src_x0);
-    final_src.y = (int)SDL_round(src_y0);
-    final_src.w = (int)SDL_round(src_x1 - src_x0);
-    final_src.h = (int)SDL_round(src_y1 - src_y0);
-
-    final_dst.x = (int)SDL_round(dst_x0);
-    final_dst.y = (int)SDL_round(dst_y0);
-    final_dst.w = (int)SDL_round(dst_x1 - dst_x0);
-    final_dst.h = (int)SDL_round(dst_y1 - dst_y0);
-
-    // Clip again
-    {
-        SDL_Rect tmp;
-        tmp.x = 0;
-        tmp.y = 0;
-        tmp.w = src->w;
-        tmp.h = src->h;
-        SDL_GetRectIntersection(&tmp, &final_src, &final_src);
+    SDL_Rect tmp;
+    if (SDL_GetRectIntersection(&r_dst, &dst->clip_rect, &tmp) == false) {
+        return true;
     }
 
-    // Clip again
-    SDL_GetRectIntersection(clip_rect, &final_dst, &final_dst);
-
-    if (final_dst.w <= 0 || final_dst.h <= 0 ||
-        final_src.w < 0 || final_src.h < 0) {
-        // No-op.
-        return true;
+    if (tmp.x != r_dst.x || tmp.y != r_dst.y || tmp.w != r_dst.w || tmp.h != r_dst.h) {
+        // Need to do a clipped and scaled blit
+        return SDL_BlitSurfaceClippedScaled(src, &r_src, dst, &r_dst, scaleMode);
     }
 
-    return SDL_BlitSurfaceUncheckedScaled(src, &final_src, dst, &final_dst, scaleMode);
+    return SDL_BlitSurfaceUncheckedScaled(src, &r_src, dst, &r_dst, scaleMode);
 }
 
 /**