From 622adff628519c16991b4edacb7283660d1630e1 Mon Sep 17 00:00:00 2001 From: Bastien Bouclet Date: Sat, 30 Dec 2017 19:01:14 +0100 Subject: GRAPHICS: Speed up drawing of rounded square soft shadows When drawing a rounded rectangle, first its shadow is drawn, and then the rectangle proper is drawn over it. This optimization skips drawing a shadow rectangle that is entirely occluded by the main rectangle. A quick benchmark shows large improvements to the modern theme GUI draw speed. On a 1 GHz ARMv7 CPU with optimizations enabled, drawing the add game dialog @ 1920x1080 went from 976 ms to 136 ms. --- graphics/VectorRendererSpec.cpp | 135 ++++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 55 deletions(-) (limited to 'graphics') diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp index 028f62101a..50ef669315 100644 --- a/graphics/VectorRendererSpec.cpp +++ b/graphics/VectorRendererSpec.cpp @@ -3587,42 +3587,44 @@ drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { uint16 alpha = (_activeSurface->format.bytesPerPixel > 2) ? 4 : 8; // These constants ensure a border of 2px on the left and of each rounded square - int xstart = (x1 > 2) ? x1 - 2 : x1; - int ystart = y1; - int width = w + offset + 2; - int height = h + offset + 1; - + Common::Rect shadowRect(w + offset + 2, h + offset + 1); + shadowRect.translate((x1 > 2) ? x1 - 2 : x1, y1); + + // The rounded rectangle drawn on top of this shadow is guaranteed + // to occlude entirely the following rect with a non-transparent color. + // As an optimization, we don't draw the shadow inside of it. + Common::Rect occludingRect(x1, y1, x1 + w, y1 + h); + occludingRect.top += r; + occludingRect.bottom -= r; + + // Soft shadows are constructed by drawing increasingly + // darker and smaller rectangles on top of each other. for (int i = offset; i >= 0; i--) { int f, ddF_x, ddF_y; int x, y, px, py; - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + height - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + height - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(xstart, ystart); + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.left + r, shadowRect.top + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.right - r, shadowRect.top + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.left + r, shadowRect.bottom - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.right - r, shadowRect.bottom - r); - int short_h = height - (2 * r) + 2; PixelType color = _format.RGBToColor(0, 0, 0); BE_RESET(); - // HACK: As we are drawing circles exploting 8-axis symmetry, + // HACK: As we are drawing circles exploiting 8-axis symmetry, // there are 4 pixels on each circle which are drawn twice. // this is ok on filled circles, but when blending on surfaces, // we cannot let it blend twice. awful. uint32 hb = 0; + // Draw the top and bottom parts of the shadow. Those parts have rounded corners. while (x++ < y) { BE_ALGORITHM(); if (((1 << x) & hb) == 0) { blendFill(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha); - - // Will create a dark line of pixles if left out - if (hb > 0) { - blendFill(ptr_bl - y + px, ptr_br + y + px, color, (uint8)alpha); - } + blendFill(ptr_bl - y + px, ptr_br + y + px, color, (uint8)alpha); hb |= (1 << x); } @@ -3633,17 +3635,26 @@ drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { } } - ptr_fill += pitch * r; - while (short_h--) { - blendFill(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha); + // Draw the middle part of the shadow. This part is a rectangle with regular corners. + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(0, shadowRect.top + r); + for (int y2 = shadowRect.top + r; y2 < shadowRect.bottom - r + 1; y2++) { + + if (occludingRect.top <= y2 && y2 < occludingRect.bottom) { + if (shadowRect.left < occludingRect.left) { + blendFill(ptr_fill + shadowRect.left, ptr_fill + occludingRect.left, color, (uint8)alpha); + } + if (occludingRect.right < shadowRect.right + 1) { + blendFill(ptr_fill + occludingRect.right, ptr_fill + shadowRect.right + 1, color, (uint8)alpha); + } + } else { + blendFill(ptr_fill + shadowRect.left, ptr_fill + shadowRect.right + 1, color, (uint8)alpha); + } + ptr_fill += pitch; } - // Make shadow smaller each iteration, and move it one pixel inward - xstart += 1; - ystart += 1; - width -= 2; - height -= 2; + // Make shadow smaller each iteration + shadowRect.grow(-1); if (_shadowFillMode == kShadowExponential) // Multiply with expfactor @@ -3661,27 +3672,32 @@ drawRoundedSquareShadowClip(int x1, int y1, int r, int w, int h, int offset) { uint16 alpha = (_activeSurface->format.bytesPerPixel > 2) ? 4 : 8; // These constants ensure a border of 2px on the left and of each rounded square - int xstart = (x1 > 2) ? x1 - 2 : x1; - int ystart = y1; - int width = w + offset + 2; - int height = h + offset + 1; - + Common::Rect shadowRect(w + offset + 2, h + offset + 1); + shadowRect.translate((x1 > 2) ? x1 - 2 : x1, y1); + + // The rounded rectangle drawn on top of this shadow is guaranteed + // to occlude entirely the following rect with a non-transparent color. + // As an optimization, we don't draw the shadow inside of it. + Common::Rect occludingRect(x1, y1, x1 + w, y1 + h); + occludingRect.top += r; + occludingRect.bottom -= r; + + // Soft shadows are constructed by drawing increasingly + // darker and smaller rectangles on top of each other. for (int i = offset; i >= 0; i--) { int f, ddF_x, ddF_y; int x, y, px, py; - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + height - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + height - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(xstart, ystart); + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.left + r, shadowRect.top + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.right - r, shadowRect.top + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.left + r, shadowRect.bottom - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(shadowRect.right - r, shadowRect.bottom - r); - int short_h = height - (2 * r) + 2; PixelType color = _format.RGBToColor(0, 0, 0); BE_RESET(); - // HACK: As we are drawing circles exploting 8-axis symmetry, + // HACK: As we are drawing circles exploiting 8-axis symmetry, // there are 4 pixels on each circle which are drawn twice. // this is ok on filled circles, but when blending on surfaces, // we cannot let it blend twice. awful. @@ -3692,36 +3708,45 @@ drawRoundedSquareShadowClip(int x1, int y1, int r, int w, int h, int offset) { if (((1 << x) & hb) == 0) { blendFillClip(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha, - xstart + r - y, ystart + r - x); + shadowRect.left + r - y, shadowRect.top + r - x); + blendFillClip(ptr_bl - y + px, ptr_br + y + px, color, (uint8)alpha, + shadowRect.left + r - y, shadowRect.bottom - r + x); - // Will create a dark line of pixles if left out - if (hb > 0) { - blendFillClip(ptr_bl - y + px, ptr_br + y + px, color, (uint8)alpha, - xstart + r - y, ystart + height - r + x); - } hb |= (1 << x); } if (((1 << y) & hb) == 0) { - blendFillClip(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha, xstart + r - x, ystart + r - y); - blendFillClip(ptr_bl - x + py, ptr_br + x + py, color, (uint8)alpha, xstart + r - x, ystart + height - r + y); + blendFillClip(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha, + shadowRect.left + r - x, shadowRect.top + r - y); + blendFillClip(ptr_bl - x + py, ptr_br + x + py, color, (uint8)alpha, + shadowRect.left + r - x, shadowRect.bottom - r + y); + hb |= (1 << y); } } - ptr_fill += pitch * r; - int orig_short_h = short_h; - while (short_h--) { - blendFillClip(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha, - xstart, ystart + r + orig_short_h - short_h - 1); + // Draw the middle part of the shadow. This part is a rectangle with regular corners. + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(0, shadowRect.top + r); + for (int y2 = shadowRect.top + r; y2 < shadowRect.bottom - r + 1; y2++) { + + if (occludingRect.top <= y2 && y2 < occludingRect.bottom) { + if (shadowRect.left < occludingRect.left) { + blendFillClip(ptr_fill + shadowRect.left, ptr_fill + occludingRect.left, color, (uint8)alpha, + shadowRect.left, y2); + } + if (occludingRect.right < shadowRect.right + 1) { + blendFillClip(ptr_fill + occludingRect.right, ptr_fill + shadowRect.right + 1, color, (uint8)alpha, + occludingRect.right, y2); + } + } else { + blendFill(ptr_fill + shadowRect.left, ptr_fill + shadowRect.right + 1, color, (uint8)alpha); + } + ptr_fill += pitch; } - // Make shadow smaller each iteration, and move it one pixel inward - xstart += 1; - ystart += 1; - width -= 2; - height -= 2; + // Make shadow smaller each iteration + shadowRect.grow(-1); if (_shadowFillMode == kShadowExponential) // Multiply with expfactor -- cgit v1.2.3