/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include "test/instrset_detect.h" #if defined(HAVE_CONFIG_H) #include "config.h" #endif #include "common/fs.h" #include "common/stream.h" #include "common/system.h" #include "graphics/surface.h" #include "graphics/managed_surface.h" #include "common/algorithm.h" #include "common/endian.h" #include "common/util.h" #include "common/rect.h" #include "common/textconsole.h" #include "graphics/blit.h" #include "graphics/primitives.h" #include "graphics/transform_tools.h" #include "../system/null_osystem.h" #if NULL_OSYSTEM_IS_AVAILABLE #define BENCHMARK_TIME 1 #else #define BENCHMARK_TIME 0 #endif namespace OldTransparentSurface { using namespace Graphics; struct OldTransparentSurface : public Graphics::ManagedSurface { OldTransparentSurface(); OldTransparentSurface(const Graphics::ManagedSurface &surf, bool copyData = false); static PixelFormat getSupportedPixelFormat() { return PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); } Common::Rect blit(Graphics::ManagedSurface &target, int posX = 0, int posY = 0, int flipping = FLIP_NONE, Common::Rect *pPartRect = nullptr, uint color = MS_ARGB(255, 255, 255, 255), int width = -1, int height = -1, TSpriteBlendMode blend = BLEND_NORMAL); Common::Rect blitClip(Graphics::ManagedSurface &target, Common::Rect clippingArea, int posX = 0, int posY = 0, int flipping = FLIP_NONE, Common::Rect *pPartRect = nullptr, uint color = MS_ARGB(255, 255, 255, 255), int width = -1, int height = -1, TSpriteBlendMode blend = BLEND_NORMAL); OldTransparentSurface *scale(int16 newWidth, int16 newHeight, bool filtering = false) const; AlphaType _alphaMode; }; static const int kBModShift = 8; static const int kGModShift = 16; static const int kRModShift = 24; static const int kAModShift = 0; static const uint32 kBModMask = 0x0000ff00; static const uint32 kGModMask = 0x00ff0000; static const uint32 kRModMask = 0xff000000; static const uint32 kAModMask = 0x000000ff; static const uint32 kRGBModMask = (kRModMask | kGModMask | kBModMask); #ifdef SCUMM_LITTLE_ENDIAN static const int kAIndex = 0; static const int kBIndex = 1; static const int kGIndex = 2; static const int kRIndex = 3; #else static const int kAIndex = 3; static const int kBIndex = 2; static const int kGIndex = 1; static const int kRIndex = 0; #endif OldTransparentSurface::OldTransparentSurface() : ManagedSurface(), _alphaMode(ALPHA_FULL) {} OldTransparentSurface::OldTransparentSurface(const ManagedSurface &surf, bool copyData) : ManagedSurface(), _alphaMode(ALPHA_FULL) { if (copyData) { copyFrom(surf); } else { w = surf.w; h = surf.h; pitch = surf.pitch; format = surf.format; // We need to cast the const qualifier away here because 'pixels' // always needs to be writable. 'surf' however is a constant Surface, // thus getPixels will always return const pixel data. setPixels(const_cast(surf.getPixels())); } } /** * Optimized version of doBlit to be used w/opaque blitting (no alpha). */ static void doBlitOpaqueFast(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep) { byte *in; byte *out; for (uint32 i = 0; i < height; i++) { out = outo; in = ino; for (uint32 j = 0; j < width; j++) { memcpy(out, in, 4); out[kAIndex] = 0xFF; out += 4; in += inStep; } outo += pitch; ino += inoStep; } } /** * Optimized version of doBlit to be used w/binary blitting (blit or no-blit, no blending). */ static void doBlitBinaryFast(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep) { byte *in; byte *out; for (uint32 i = 0; i < height; i++) { out = outo; in = ino; for (uint32 j = 0; j < width; j++) { uint32 pix = *(uint32 *)in; int a = in[kAIndex]; if (a != 0) { // Full opacity (Any value not exactly 0 is Opaque here) *(uint32 *)out = pix; out[kAIndex] = 0xFF; } out += 4; in += inStep; } outo += pitch; ino += inoStep; } } /** * Optimized version of doBlit to be used with alpha blended blitting * @param ino a pointer to the input surface * @param outo a pointer to the output surface * @param width width of the input surface * @param height height of the input surface * @param pitch pitch of the output surface - that is, width in bytes of every row, usually bpp * width of the TARGET surface (the area we are blitting to might be smaller, do the math) * @inStep size in bytes to skip to address each pixel, usually bpp of the source surface * @inoStep width in bytes of every row on the *input* surface / kind of like pitch * @color colormod in 0xAARRGGBB format - 0xFFFFFFFF for no colormod */ template static void doBlitAlphaBlendImpl(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { byte *in; byte *out; byte ca = alphamod ? ((color >> kAModShift) & 0xFF) : 255; byte cr = rgbmod ? ((color >> kRModShift) & 0xFF) : 255; byte cg = rgbmod ? ((color >> kGModShift) & 0xFF) : 255; byte cb = rgbmod ? ((color >> kBModShift) & 0xFF) : 255; for (uint32 i = 0; i < height; i++) { out = outo; in = ino; for (uint32 j = 0; j < width; j++) { uint32 ina = in[kAIndex] * ca >> 8; if (ina != 0) { uint outb = (out[kBIndex] * (255 - ina) >> 8); uint outg = (out[kGIndex] * (255 - ina) >> 8); uint outr = (out[kRIndex] * (255 - ina) >> 8); out[kAIndex] = 255; out[kBIndex] = outb + (in[kBIndex] * ina * cb >> 16); out[kGIndex] = outg + (in[kGIndex] * ina * cg >> 16); out[kRIndex] = outr + (in[kRIndex] * ina * cr >> 16); } in += inStep; out += 4; } outo += pitch; ino += inoStep; } } static void doBlitAlphaBlend(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { bool rgbmod = ((color & kRGBModMask) != kRGBModMask); bool alphamod = ((color & kAModMask) != kAModMask); if (rgbmod) { if (alphamod) { doBlitAlphaBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } else { doBlitAlphaBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } } else { if (alphamod) { doBlitAlphaBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } else { doBlitAlphaBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } } } /** * Optimized version of doBlit to be used with additive blended blitting */ template static void doBlitAdditiveBlendImpl(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { byte *in; byte *out; byte ca = alphamod ? ((color >> kAModShift) & 0xFF) : 255; byte cr = rgbmod ? ((color >> kRModShift) & 0xFF) : 255; byte cg = rgbmod ? ((color >> kGModShift) & 0xFF) : 255; byte cb = rgbmod ? ((color >> kBModShift) & 0xFF) : 255; for (uint32 i = 0; i < height; i++) { out = outo; in = ino; for (uint32 j = 0; j < width; j++) { uint32 ina = in[kAIndex] * ca >> 8; if (ina != 0) { if (cb != 255) { out[kBIndex] = MIN(out[kBIndex] + ((in[kBIndex] * cb * ina) >> 16), 255u); } else { out[kBIndex] = MIN(out[kBIndex] + (in[kBIndex] * ina >> 8), 255u); } if (cg != 255) { out[kGIndex] = MIN(out[kGIndex] + ((in[kGIndex] * cg * ina) >> 16), 255u); } else { out[kGIndex] = MIN(out[kGIndex] + (in[kGIndex] * ina >> 8), 255u); } if (cr != 255) { out[kRIndex] = MIN(out[kRIndex] + ((in[kRIndex] * cr * ina) >> 16), 255u); } else { out[kRIndex] = MIN(out[kRIndex] + (in[kRIndex] * ina >> 8), 255u); } } in += inStep; out += 4; } outo += pitch; ino += inoStep; } } static void doBlitAdditiveBlend(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { bool rgbmod = ((color & kRGBModMask) != kRGBModMask); bool alphamod = ((color & kAModMask) != kAModMask); if (rgbmod) { if (alphamod) { doBlitAdditiveBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } else { doBlitAdditiveBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } } else { if (alphamod) { doBlitAdditiveBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } else { doBlitAdditiveBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } } } /** * Optimized version of doBlit to be used with subtractive blended blitting */ template static void doBlitSubtractiveBlendImpl(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { byte *in; byte *out; byte cr = rgbmod ? ((color >> kRModShift) & 0xFF) : 255; byte cg = rgbmod ? ((color >> kGModShift) & 0xFF) : 255; byte cb = rgbmod ? ((color >> kBModShift) & 0xFF) : 255; for (uint32 i = 0; i < height; i++) { out = outo; in = ino; for (uint32 j = 0; j < width; j++) { out[kAIndex] = 255; if (cb != 255) { out[kBIndex] = MAX(out[kBIndex] - ((in[kBIndex] * cb * (out[kBIndex]) * in[kAIndex]) >> 24), 0); } else { out[kBIndex] = MAX(out[kBIndex] - (in[kBIndex] * (out[kBIndex]) * in[kAIndex] >> 16), 0); } if (cg != 255) { out[kGIndex] = MAX(out[kGIndex] - ((in[kGIndex] * cg * (out[kGIndex]) * in[kAIndex]) >> 24), 0); } else { out[kGIndex] = MAX(out[kGIndex] - (in[kGIndex] * (out[kGIndex]) * in[kAIndex] >> 16), 0); } if (cr != 255) { out[kRIndex] = MAX(out[kRIndex] - ((in[kRIndex] * cr * (out[kRIndex]) * in[kAIndex]) >> 24), 0); } else { out[kRIndex] = MAX(out[kRIndex] - (in[kRIndex] * (out[kRIndex]) * in[kAIndex] >> 16), 0); } in += inStep; out += 4; } outo += pitch; ino += inoStep; } } static void doBlitSubtractiveBlend(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { bool rgbmod = ((color & kRGBModMask) != kRGBModMask); if (rgbmod) { doBlitSubtractiveBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } else { doBlitSubtractiveBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } } /** * Optimized version of doBlit to be used with multiply blended blitting */ template static void doBlitMultiplyBlendImpl(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { byte *in; byte *out; byte ca = alphamod ? ((color >> kAModShift) & 0xFF) : 255; byte cr = rgbmod ? ((color >> kRModShift) & 0xFF) : 255; byte cg = rgbmod ? ((color >> kGModShift) & 0xFF) : 255; byte cb = rgbmod ? ((color >> kBModShift) & 0xFF) : 255; for (uint32 i = 0; i < height; i++) { out = outo; in = ino; for (uint32 j = 0; j < width; j++) { uint32 ina = in[kAIndex] * ca >> 8; if (ina != 0) { if (cb != 255) { out[kBIndex] = MIN(out[kBIndex] * ((in[kBIndex] * cb * ina) >> 16) >> 8, 255u); } else { out[kBIndex] = MIN(out[kBIndex] * (in[kBIndex] * ina >> 8) >> 8, 255u); } if (cg != 255) { out[kGIndex] = MIN(out[kGIndex] * ((in[kGIndex] * cg * ina) >> 16) >> 8, 255u); } else { out[kGIndex] = MIN(out[kGIndex] * (in[kGIndex] * ina >> 8) >> 8, 255u); } if (cr != 255) { out[kRIndex] = MIN(out[kRIndex] * ((in[kRIndex] * cr * ina) >> 16) >> 8, 255u); } else { out[kRIndex] = MIN(out[kRIndex] * (in[kRIndex] * ina >> 8) >> 8, 255u); } } in += inStep; out += 4; } outo += pitch; ino += inoStep; } } static void doBlitMultiplyBlend(byte *ino, byte *outo, uint32 width, uint32 height, uint32 pitch, int32 inStep, int32 inoStep, uint32 color) { bool rgbmod = ((color & kRGBModMask) != kRGBModMask); bool alphamod = ((color & kAModMask) != kAModMask); if (rgbmod) { if (alphamod) { doBlitMultiplyBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } else { doBlitMultiplyBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } } else { if (alphamod) { doBlitMultiplyBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } else { doBlitMultiplyBlendImpl(ino, outo, width, height, pitch, inStep, inoStep, color); } } } Common::Rect OldTransparentSurface::blit(Graphics::ManagedSurface &target, int posX, int posY, int flipping, Common::Rect *pPartRect, uint color, int width, int height, TSpriteBlendMode blendMode) { Common::Rect retSize; retSize.top = 0; retSize.left = 0; retSize.setWidth(0); retSize.setHeight(0); // Check if we need to draw anything at all int ca = (color >> kAModShift) & 0xff; if (ca == 0) { return retSize; } // Create an encapsulating surface for the data OldTransparentSurface srcImage(*this, false); // TODO: Is the data really in the screen format? if (format.bytesPerPixel != 4) { warning("OldTransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8); return retSize; } if (pPartRect) { int xOffset = pPartRect->left; int yOffset = pPartRect->top; if (flipping & FLIP_V) { yOffset = srcImage.h - pPartRect->bottom; } if (flipping & FLIP_H) { xOffset = srcImage.w - pPartRect->right; } srcImage.setPixels(getBasePtr(xOffset, yOffset)); srcImage.w = pPartRect->width(); srcImage.h = pPartRect->height(); debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, pPartRect->left, pPartRect->top, pPartRect->width(), pPartRect->height(), color, width, height); } else { debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, 0, 0, srcImage.w, srcImage.h, color, width, height); } if (width == -1) { width = srcImage.w; } if (height == -1) { height = srcImage.h; } #ifdef SCALING_TESTING // Hardcode scaling to 66% to test scaling width = width * 2 / 3; height = height * 2 / 3; #endif Graphics::ManagedSurface *img = nullptr; Graphics::ManagedSurface *imgScaled = nullptr; byte *savedPixels = nullptr; if ((width != srcImage.w) || (height != srcImage.h)) { // Scale the image img = imgScaled = srcImage.scale(width, height); savedPixels = (byte *)img->getPixels(); } else { img = &srcImage; } // Handle off-screen clipping if (posY < 0) { img->h = MAX(0, (int)img->h - -posY); if (!(flipping & FLIP_V)) img->setPixels((byte *)img->getBasePtr(0, -posY)); posY = 0; } if (posX < 0) { img->w = MAX(0, (int)img->w - -posX); if (!(flipping & FLIP_H)) img->setPixels((byte *)img->getBasePtr(-posX, 0)); posX = 0; } if (img->w > target.w - posX) { if (flipping & FLIP_H) img->setPixels((byte *)img->getBasePtr(img->w - target.w + posX, 0)); img->w = CLIP((int)img->w, 0, (int)MAX((int)target.w - posX, 0)); } if (img->h > target.h - posY) { if (flipping & FLIP_V) img->setPixels((byte *)img->getBasePtr(0, img->h - target.h + posY)); img->h = CLIP((int)img->h, 0, (int)MAX((int)target.h - posY, 0)); } // Flip surface if ((img->w > 0) && (img->h > 0)) { int xp = 0, yp = 0; int inStep = 4; int inoStep = img->pitch; if (flipping & FLIP_H) { inStep = -inStep; xp = img->w - 1; } if (flipping & FLIP_V) { inoStep = -inoStep; yp = img->h - 1; } byte *ino = (byte *)img->getBasePtr(xp, yp); byte *outo = (byte *)target.getBasePtr(posX, posY); if (color == 0xFFFFFFFF && blendMode == BLEND_NORMAL && _alphaMode == ALPHA_OPAQUE) { doBlitOpaqueFast(ino, outo, img->w, img->h, target.pitch, inStep, inoStep); } else if (color == 0xFFFFFFFF && blendMode == BLEND_NORMAL && _alphaMode == ALPHA_BINARY) { doBlitBinaryFast(ino, outo, img->w, img->h, target.pitch, inStep, inoStep); } else { if (blendMode == BLEND_ADDITIVE) { doBlitAdditiveBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } else if (blendMode == BLEND_SUBTRACTIVE) { doBlitSubtractiveBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } else if (blendMode == BLEND_MULTIPLY) { doBlitMultiplyBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } else { assert(blendMode == BLEND_NORMAL); doBlitAlphaBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } } } retSize.setWidth(img->w); retSize.setHeight(img->h); if (imgScaled) { imgScaled->setPixels(savedPixels); imgScaled->free(); delete imgScaled; } return retSize; } Common::Rect OldTransparentSurface::blitClip(Graphics::ManagedSurface &target, Common::Rect clippingArea, int posX, int posY, int flipping, Common::Rect *pPartRect, uint color, int width, int height, TSpriteBlendMode blendMode) { Common::Rect retSize; retSize.top = 0; retSize.left = 0; retSize.setWidth(0); retSize.setHeight(0); // Check if we need to draw anything at all int ca = (color >> kAModShift) & 0xff; if (ca == 0) { return retSize; } // Create an encapsulating surface for the data OldTransparentSurface srcImage(*this, false); // TODO: Is the data really in the screen format? if (format.bytesPerPixel != 4) { warning("OldTransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8); return retSize; } if (pPartRect) { int xOffset = pPartRect->left; int yOffset = pPartRect->top; if (flipping & FLIP_V) { yOffset = srcImage.h - pPartRect->bottom; } if (flipping & FLIP_H) { xOffset = srcImage.w - pPartRect->right; } srcImage.setPixels(getBasePtr(xOffset, yOffset)); srcImage.w = pPartRect->width(); srcImage.h = pPartRect->height(); debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, pPartRect->left, pPartRect->top, pPartRect->width(), pPartRect->height(), color, width, height); } else { debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, 0, 0, srcImage.w, srcImage.h, color, width, height); } if (width == -1) { width = srcImage.w; } if (height == -1) { height = srcImage.h; } #ifdef SCALING_TESTING // Hardcode scaling to 66% to test scaling width = width * 2 / 3; height = height * 2 / 3; #endif Graphics::ManagedSurface *img = nullptr; Graphics::ManagedSurface *imgScaled = nullptr; byte *savedPixels = nullptr; if ((width != srcImage.w) || (height != srcImage.h)) { // Scale the image img = imgScaled = srcImage.scale(width, height); savedPixels = (byte *)img->getPixels(); } else { img = &srcImage; } // Handle off-screen clipping if (posY < clippingArea.top) { img->h = MAX(0, (int)img->h - (clippingArea.top - posY)); if (!(flipping & FLIP_V)) img->setPixels((byte *)img->getBasePtr(0, clippingArea.top - posY)); posY = clippingArea.top; } if (posX < clippingArea.left) { img->w = MAX(0, (int)img->w - (clippingArea.left - posX)); if (!(flipping & FLIP_H)) img->setPixels((byte *)img->getBasePtr(clippingArea.left - posX, 0)); posX = clippingArea.left; } if (img->w > clippingArea.right - posX) { if (flipping & FLIP_H) img->setPixels((byte *)img->getBasePtr(img->w - clippingArea.right + posX, 0)); img->w = CLIP((int)img->w, 0, (int)MAX((int)clippingArea.right - posX, 0)); } if (img->h > clippingArea.bottom - posY) { if (flipping & FLIP_V) img->setPixels((byte *)img->getBasePtr(0, img->h - clippingArea.bottom + posY)); img->h = CLIP((int)img->h, 0, (int)MAX((int)clippingArea.bottom - posY, 0)); } // Flip surface if ((img->w > 0) && (img->h > 0)) { int xp = 0, yp = 0; int inStep = 4; int inoStep = img->pitch; if (flipping & FLIP_H) { inStep = -inStep; xp = img->w - 1; } if (flipping & FLIP_V) { inoStep = -inoStep; yp = img->h - 1; } byte *ino = (byte *)img->getBasePtr(xp, yp); byte *outo = (byte *)target.getBasePtr(posX, posY); if (color == 0xFFFFFFFF && blendMode == BLEND_NORMAL && _alphaMode == ALPHA_OPAQUE) { doBlitOpaqueFast(ino, outo, img->w, img->h, target.pitch, inStep, inoStep); } else if (color == 0xFFFFFFFF && blendMode == BLEND_NORMAL && _alphaMode == ALPHA_BINARY) { doBlitBinaryFast(ino, outo, img->w, img->h, target.pitch, inStep, inoStep); } else { if (blendMode == BLEND_ADDITIVE) { doBlitAdditiveBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } else if (blendMode == BLEND_SUBTRACTIVE) { doBlitSubtractiveBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } else if (blendMode == BLEND_MULTIPLY) { doBlitMultiplyBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } else { assert(blendMode == BLEND_NORMAL); doBlitAlphaBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); } } } retSize.setWidth(img->w); retSize.setHeight(img->h); if (imgScaled) { imgScaled->setPixels(savedPixels); imgScaled->free(); delete imgScaled; } return retSize; } OldTransparentSurface *OldTransparentSurface::scale(int16 newWidth, int16 newHeight, bool filtering) const { OldTransparentSurface *target = new OldTransparentSurface(); target->create(newWidth, newHeight, format); if (filtering) { scaleBlitBilinear((byte *)target->getPixels(), (const byte *)getPixels(), target->pitch, pitch, target->w, target->h, w, h, format); } else { scaleBlit((byte *)target->getPixels(), (const byte *)getPixels(), target->pitch, pitch, target->w, target->h, w, h, format); } return target; } } // namespace OldTransparentSurface #ifdef TEST_IMAGE_BLENDING_SAVE static int save_bitmap(const char *path, const Graphics::ManagedSurface *surf) { Common::FSNode fileNode(path); Common::SeekableWriteStream *out = fileNode.createWriteStream(); const Graphics::PixelFormat requiredFormat_3byte = Graphics::PixelFormat::createFormatBGR24(); Graphics::ManagedSurface surface(surf->w, surf->h, requiredFormat_3byte); // Copy from the source surface without alpha transparency Graphics::ManagedSurface temp = surf; temp.format.aLoss = 8; surface.rawBlitFrom(temp, Common::Rect(0, 0, surf->w, surf->h), Common::Point(0, 0)); // Write out the bitmap int dstPitch = surface.w * 3; int extraDataLength = (dstPitch % 4) ? 4 - (dstPitch % 4) : 0; int padding = 0; out->writeByte('B'); out->writeByte('M'); out->writeUint32LE(surface.h * dstPitch + 54); out->writeUint32LE(0); out->writeUint32LE(54); out->writeUint32LE(40); out->writeUint32LE(surface.w); out->writeUint32LE(surface.h); out->writeUint16LE(1); out->writeUint16LE(24); out->writeUint32LE(0); out->writeUint32LE(0); out->writeUint32LE(0); out->writeUint32LE(0); out->writeUint32LE(0); out->writeUint32LE(0); for (uint y = surface.h; y-- > 0;) { out->write((const void *)surface.getBasePtr(0, y), dstPitch); out->write(&padding, extraDataLength); } return true; } #endif static bool areSurfacesEqual(const Graphics::ManagedSurface *a, const Graphics::ManagedSurface *b) { if (a->w != b->w || a->h != b->h) return false; return memcmp(a->getPixels(), b->getPixels(), a->h * a->pitch) == 0; } class BlendBlitUnfilteredTestSuite : public CxxTest::TestSuite { public: void test_blend_speed() { #if BENCHMARK_TIME Common::install_null_g_system(); Graphics::BlendBlit::blitFunc = Graphics::BlendBlit::blitGeneric; #ifdef SCUMMVM_NEON Graphics::BlendBlit::blitFunc = Graphics::BlendBlit::blitNEON; #endif #ifdef SCUMMVM_SSE2 if (instrset_detect() >= 2) { Graphics::BlendBlit::blitFunc = Graphics::BlendBlit::blitSSE2; } #endif #ifdef SCUMMVM_AVX2 if (instrset_detect() >= 8) { Graphics::BlendBlit::blitFunc = Graphics::BlendBlit::blitAVX2; } #endif Graphics::ManagedSurface baseSurface, destSurface; baseSurface.create(103, 103, OldTransparentSurface::OldTransparentSurface::getSupportedPixelFormat()); destSurface.create(256, 256, OldTransparentSurface::OldTransparentSurface::getSupportedPixelFormat()); for (int y = 0; y < baseSurface.h; y++) { for (int x = 0; x < baseSurface.w; x++) { int i = x / 4 + y / 4; baseSurface.setPixel(x, y, baseSurface.format.ARGBToColor((i & 16) * 255, (i & 1) * 255, (i & 2) * 255, (i & 4) * 255)); } } OldTransparentSurface::OldTransparentSurface oldSurf(baseSurface, true); OldTransparentSurface::OldTransparentSurface oldSurfDest(destSurface, true); Graphics::ManagedSurface &managedSurf = baseSurface; Graphics::ManagedSurface &managedSurfDest = destSurface; int numIters = 0, numItersScaled = 0; double oldTime = 0.0, newTime = 0.0, genericTime = 0.0; double oldTimeScaled = 0.0, newTimeScaled = 0.0, genericTimeScaled = 0.0; #ifdef SLOW_TESTS const int iters = 2500; #else const int iters = 1; #endif for (int blendMode = 0; blendMode < Graphics::NUM_BLEND_MODES; blendMode++) { for (int alphaType = 0; alphaType <= Graphics::ALPHA_FULL; alphaType++) { for (int flipping = 0; flipping <= 3; flipping++) { for (uint32 color = 0xffffffff; color != 0; color = (color == 0xffffffff ? 0x7f7f7f7f : 0)) { oldSurfDest.fillRect(Common::Rect(0, 0, oldSurfDest.w, oldSurfDest.h), oldSurfDest.format.ARGBToColor(255, 255, 255, 255)); managedSurfDest.fillRect(Common::Rect(0, 0, managedSurfDest.w, managedSurfDest.h), managedSurfDest.format.ARGBToColor(255, 255, 255, 255)); oldSurf._alphaMode = (Graphics::AlphaType)alphaType; uint32 oldStart = g_system->getMillis(); for (int i = 0; i < iters; i++) { oldSurf.blit(oldSurfDest, 0, 0, flipping, nullptr, color, -1, -1, (Graphics::TSpriteBlendMode)blendMode); } oldTime += g_system->getMillis() - oldStart; uint32 newStart = g_system->getMillis(); for (int i = 0; i < iters; i++) { managedSurfDest.blendBlitFrom(managedSurf, flipping, color, (Graphics::TSpriteBlendMode)blendMode, (Graphics::AlphaType)alphaType); } newTime += g_system->getMillis() - newStart; managedSurfDest.fillRect(Common::Rect(0, 0, managedSurfDest.w, managedSurfDest.h), managedSurfDest.format.ARGBToColor(255, 255, 255, 255)); Graphics::BlendBlit::BlitFunc oldFunc = Graphics::BlendBlit::blitFunc; Graphics::BlendBlit::blitFunc = Graphics::BlendBlit::blitGeneric; uint32 genericStart = g_system->getMillis(); for (int i = 0; i < iters; i++) { managedSurfDest.blendBlitFrom(managedSurf, flipping, color, (Graphics::TSpriteBlendMode)blendMode, (Graphics::AlphaType)alphaType); } Graphics::BlendBlit::blitFunc = oldFunc; genericTime += g_system->getMillis() - genericStart; numIters ++; // scaled oldSurfDest.fillRect(Common::Rect(0, 0, oldSurfDest.w, oldSurfDest.h), oldSurfDest.format.ARGBToColor(255, 255, 255, 255)); managedSurfDest.fillRect(Common::Rect(0, 0, managedSurfDest.w, managedSurfDest.h), managedSurfDest.format.ARGBToColor(255, 255, 255, 255)); oldSurf._alphaMode = (Graphics::AlphaType)alphaType; oldStart = g_system->getMillis(); for (int i = 0; i < iters; i++) { oldSurf.blit(oldSurfDest, 0, 0, flipping, nullptr, color, oldSurfDest.w, oldSurfDest.h, (Graphics::TSpriteBlendMode)blendMode); } oldTimeScaled += g_system->getMillis() - oldStart; newStart = g_system->getMillis(); for (int i = 0; i < iters; i++) { managedSurfDest.blendBlitFrom(managedSurf, Common::Rect(managedSurf.w, managedSurf.h), Common::Rect(managedSurfDest.w, managedSurfDest.h), flipping, color, (Graphics::TSpriteBlendMode)blendMode, (Graphics::AlphaType)alphaType); } newTimeScaled += g_system->getMillis() - newStart; managedSurfDest.fillRect(Common::Rect(0, 0, managedSurfDest.w, managedSurfDest.h), managedSurfDest.format.ARGBToColor(255, 255, 255, 255)); Graphics::BlendBlit::blitFunc = Graphics::BlendBlit::blitGeneric; genericStart = g_system->getMillis(); for (int i = 0; i < iters; i++) { managedSurfDest.blendBlitFrom(managedSurf, Common::Rect(managedSurf.w, managedSurf.h), Common::Rect(managedSurfDest.w, managedSurfDest.h), flipping, color, (Graphics::TSpriteBlendMode)blendMode, (Graphics::AlphaType)alphaType); } Graphics::BlendBlit::blitFunc = oldFunc; genericTimeScaled += g_system->getMillis() - genericStart; numItersScaled++; } // color } // flipping } // alpha } // blend debug("Old TransparentSurface::blit avg time per %d iters (in milliseconds): %f\n", iters, oldTime / numIters); debug("New ManagedSurface::blendBlitFrom (non SIMD) avg time per %d iters (in milliseconds): %f\n", iters, genericTime / numIters); debug("New ManagedSurface::blendBlitFrom avg time per %d iters (in milliseconds): %f\n", iters, newTime / numIters); debug("Old SCALING TransparentSurface::blit avg time per %d iters (in milliseconds): %f\n", iters, oldTimeScaled / numItersScaled); debug("New SCALING ManagedSurface::blendBlitFrom (non SIMD) avg time per %d iters (in milliseconds): %f\n", iters, genericTimeScaled / numItersScaled); debug("New SCALING ManagedSurface::blendBlitFrom avg time per %d iters (in milliseconds): %f\n", iters, newTimeScaled / numItersScaled); baseSurface.free(); #endif } void test_blend_blit_unfiltered() { #ifdef SLOW_TESTS Common::Rect dsts[] = { Common::Rect(4, 4, 4+16, 4+16), // Case 0 (source clipping) Common::Rect(24, 20, 24+16, 20+16), // Case 1 (outside of destination) Common::Rect(0, 0, 32, 32), // Case 2 (stretching bigger) Common::Rect(3, 3, 3+8, 3+8), // Case 3 (stretching smaller) Common::Rect(8, 4, 8+32, 4+32), // Case 4 (stretching outside of destination) Common::Rect(-4, -4, -4+16, -4+16), // Case 5 (outside of destination 2) Common::Rect(-16, -16, 32+16, 32+16), // Case 6 (completely bigger) }, srcs[] = { Common::Rect(0, 0, 16, 16), // Case 0 (source clipping) Common::Rect(0, 0, 16, 16), // Case 1 (outside of destination) Common::Rect(0, 0, 16, 16), // Case 2 (stretching) Common::Rect(0, 0, 16, 16), // Case 3 (stretching smaller) Common::Rect(0, 0, 16, 16), // Case 4 (stretching outside of destination) Common::Rect(0, 0, 16, 16), // Case 5 (outside of destination 2) Common::Rect(0, 0, 16, 16), // Case 6 (completely bigger) }; Graphics::ManagedSurface baseSurface, destSurface; baseSurface.create(16, 16, OldTransparentSurface::OldTransparentSurface::getSupportedPixelFormat()); destSurface.create(32, 32, OldTransparentSurface::OldTransparentSurface::getSupportedPixelFormat()); for (int y = 0; y < baseSurface.h; y++) { for (int x = 0; x < baseSurface.w; x++) { int i = x / 4 + y / 4; baseSurface.setPixel(x, y, baseSurface.format.ARGBToColor((i & 16) * 255, (i & 1) * 255, (i & 2) * 255, (i & 4) * 255)); } } OldTransparentSurface::OldTransparentSurface oldSurf(baseSurface, true); OldTransparentSurface::OldTransparentSurface oldSurfDest(destSurface, true); Graphics::ManagedSurface &managedSurf = baseSurface; Graphics::ManagedSurface &managedSurfDest = destSurface; const char *blendModes[] = { "BLEND_NORMAL", "BLEND_ADDITIVE", "BLEND_SUBTRACTIVE", "BLEND_MULTIPLY", }, *alphaTypes[] = { "ALPHA_OPAQUE", "ALPHA_BINARY", "ALPHA_FULL", }, *flipNames[] = { "FLIP_NONE", "FLIP_H", "FLIP_V", "FLIP_HV", }, *rectNames[] = { "0 -> (source clipping)", "1 -> (outside of destination)", "2 -> (stretching bigger)", "3 -> (stretching smaller)", "4 -> (stretching outside of destination)", "5 -> (outside of destination)", "6 -> (completely bigger)" }; for (int blendMode = 0; blendMode < Graphics::NUM_BLEND_MODES; blendMode++) { for (int alphaType = 0; alphaType <= Graphics::ALPHA_FULL; alphaType++) { for (int ba = 255; ba >= 0; ba = (ba == 255 ? 128 : (ba == 128 ? 0 : -1))) { for (int br = 255; br >= 0; br = (br == 255 ? 128 : (br == 128 ? 0 : -1))) { for (int bg = 255; bg >= 0; bg = (bg == 255 ? 128 : (bg == 128 ? 0 : -1))) { for (int bb = 255; bb >= 0; bb = (bb == 255 ? 128 : (bb == 128 ? 0 : -1))) { for (int a = 255; a >= 0; a = (a == 255 ? 128 : (a == 128 ? 0 : -1))) { for (int r = 255; r >= 0; r = (r == 255 ? 128 : (r == 128 ? 0 : -1))) { for (int g = 255; g >= 0; g = (g == 255 ? 128 : (g == 128 ? 0 : -1))) { for (int b = 255; b >= 0; b = (b == 255 ? 128 : (b == 128 ? 0 : -1))) { for (int flipping = 0; flipping <= 3; flipping++) { for (int rect = 0; rect < (int)(sizeof(srcs)/sizeof(srcs[0])); rect++) { oldSurfDest.fillRect(Common::Rect(0, 0, oldSurfDest.w, oldSurfDest.h), oldSurfDest.format.ARGBToColor(ba, br, bg, bb)); oldSurf._alphaMode = (Graphics::AlphaType)alphaType; oldSurf.blit(oldSurfDest, dsts[rect].left, dsts[rect].top, flipping, &srcs[rect], MS_ARGB(a, r, g, b), dsts[rect].width(), dsts[rect].height(), (Graphics::TSpriteBlendMode)blendMode); managedSurfDest.fillRect(Common::Rect(0, 0, managedSurfDest.w, managedSurfDest.h), managedSurfDest.format.ARGBToColor(ba, br, bg, bb)); managedSurfDest.blendBlitFrom(managedSurf, srcs[rect], dsts[rect], flipping, MS_ARGB(a, r, g, b), (Graphics::TSpriteBlendMode)blendMode, (Graphics::AlphaType)alphaType); if (!areSurfacesEqual(&oldSurfDest, &managedSurfDest)) { warning("blendMode: %s, alphaType: %s, a: %d, r: %d, g: %d, b: %d, flipping: %s, test rect id: %s", blendModes[blendMode], alphaTypes[alphaType], a, r, g, b, flipNames[flipping], rectNames[rect]); #ifdef TEST_IMAGE_BLENDING_SAVE save_bitmap("sourceSurf.bmp", &newSurf); save_bitmap("oldSurfDest.bmp", &oldSurfDest); save_bitmap("newSurfDest.bmp", &newSurfDest); save_bitmap("managedSurfDest.bmp", managedSurfDest.surfacePtr()); #endif TS_FAIL("oldSurfDest and managedSurfDest are not equal!"); return; } } // rect } // flipping } // b } // g } // r } // a } // bb } // bg } // br } // ba } // alpha } // blend baseSurface.free(); #else // This kills warning about unused function (void)areSurfacesEqual; #endif } };