From c3fb76b56fc5bc4363b17f5bd3bcf541a9aaa964 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 21 Jan 2025 21:14:49 +0000 Subject: [PATCH] Offloaded xbrz upscaling to background threads --- client/render/IImage.h | 5 ++ client/renderSDL/SDLImage.cpp | 125 +++++++++++++++++++++++++---- client/renderSDL/SDLImage.h | 11 ++- client/renderSDL/ScalableImage.cpp | 22 ++++- 4 files changed, 142 insertions(+), 21 deletions(-) diff --git a/client/render/IImage.h b/client/render/IImage.h index fdf103b02..ec27276a6 100644 --- a/client/render/IImage.h +++ b/client/render/IImage.h @@ -116,8 +116,13 @@ public: virtual void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const = 0; virtual bool isTransparent(const Point & coords) const = 0; virtual Rect contentRect() const = 0; + + virtual void scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaling, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0; virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0; + /// Returns true if this image is still loading and can't be used + virtual bool isLoading() const = 0; + virtual ~ISharedImage() = default; virtual const SDL_Palette * getPalette() const = 0; diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 97175271b..d7633358c 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -21,8 +21,11 @@ #include "../render/IScreenHandler.h" #include -#include +#include + #include +#include +#include class SDLImageLoader; @@ -88,8 +91,70 @@ SDLImageShared::SDLImageShared(const ImagePath & filename) } } +void SDLImageShared::scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaleTo, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const +{ + assert(upscalingInProgress == false); + if (!surf) + return; + + Rect sourceRect(0, 0, surf->w, surf->h); + Point destShift(0, 0); + Point destScale = Point(surf->w, surf->h) * scaleTo / dimensions(); + Point marginsScaled = margins * scaleTo / dimensions(); + + if(src) + { + Rect srcUnscaled(Point(src->topLeft() * dimensions() / scaleTo), Point(src->dimensions() * dimensions() / scaleTo)); + + if(srcUnscaled.x < margins.x) + destShift.x += marginsScaled.x - src->x; + + if(srcUnscaled.y < margins.y) + destShift.y += marginsScaled.y - src->y; + + sourceRect = Rect(srcUnscaled).intersect(Rect(margins.x, margins.y, surf->w, surf->h)); + + destScale.x = std::min(destScale.x, sourceRect.w * scaleTo.x / dimensions().x); + destScale.y = std::min(destScale.y, sourceRect.h * scaleTo.y / dimensions().y); + + sourceRect -= margins; + } + else + destShift = marginsScaled; + + destShift += dest; + + SDL_SetSurfaceColorMod(surf, colorMultiplier.r, colorMultiplier.g, colorMultiplier.b); + SDL_SetSurfaceAlphaMod(surf, alpha); + + if (alpha != SDL_ALPHA_OPAQUE || (mode != EImageBlitMode::OPAQUE && surf->format->Amask != 0)) + SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND); + else + SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); + + if (palette && surf->format->palette) + SDL_SetSurfacePalette(surf, palette); + + SDL_Rect srcRect = CSDL_Ext::toSDL(sourceRect); + SDL_Rect dstRect = CSDL_Ext::toSDL(Rect(destShift, destScale)); + + if (sourceRect.dimensions() * scaleTo / dimensions() != destScale) + logGlobal->info("???"); + + SDL_Surface * tempSurface = SDL_ConvertSurface(surf, where->format, 0); + int result = SDL_BlitScaled(tempSurface, &srcRect, where, &dstRect); + + SDL_FreeSurface(tempSurface); + if (result != 0) + logGlobal->error("SDL_BlitScaled failed! %s", SDL_GetError()); + + if (surf->format->palette) + SDL_SetSurfacePalette(surf, originalPalette); +} + void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const { + assert(upscalingInProgress == false); if (!surf) return; @@ -140,6 +205,7 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin void SDLImageShared::optimizeSurface() { + assert(upscalingInProgress == false); if (!surf) return; @@ -155,6 +221,7 @@ void SDLImageShared::optimizeSurface() std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const { + assert(upscalingInProgress == false); if (factor <= 0) throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor)); @@ -164,32 +231,50 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL if (palette && surf->format->palette) SDL_SetSurfacePalette(surf, palette); - SDLImageScaler scaler(surf, Rect(margins, fullSize)); - - // dump heuristics to differentiate tileable UI elements from map object / combat assets + // simple heuristics to differentiate tileable UI elements from map object / combat assets + EScalingAlgorithm algorithm; if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE) - scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), EScalingAlgorithm::XBRZ_OPAQUE); + algorithm = EScalingAlgorithm::XBRZ_OPAQUE; else - scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), EScalingAlgorithm::XBRZ_ALPHA); + algorithm = EScalingAlgorithm::XBRZ_ALPHA; - SDL_Surface * scaled = scaler.acquireResultSurface(); - - auto ret = std::make_shared(scaled); - - ret->fullSize = scaler.getResultDimensions().dimensions(); - ret->margins = scaler.getResultDimensions().topLeft(); - - // erase our own reference - SDL_FreeSurface(scaled); + auto result = std::make_shared(this, factor, algorithm); if (surf->format->palette) SDL_SetSurfacePalette(surf, originalPalette); - return ret; + return result; +} + +SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm) +{ + static tbb::task_arena upscalingArena; + + upscalingInProgress = true; + + auto scaler = std::make_shared(from->surf, Rect(from->margins, from->fullSize)); + + const auto & scalingTask = [this, algorithm, scaler]() + { + scaler->scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm); + surf = scaler->acquireResultSurface(); + fullSize = scaler->getResultDimensions().dimensions(); + margins = scaler->getResultDimensions().topLeft(); + + upscalingInProgress = false; + }; + + upscalingArena.enqueue(scalingTask); +} + +bool SDLImageShared::isLoading() const +{ + return upscalingInProgress; } std::shared_ptr SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const { + assert(upscalingInProgress == false); if (palette && surf->format->palette) SDL_SetSurfacePalette(surf, palette); @@ -221,6 +306,7 @@ std::shared_ptr SDLImageShared::scaleTo(const Point & size, void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const { + assert(upscalingInProgress == false); if (!surf) return; @@ -233,6 +319,7 @@ void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palet bool SDLImageShared::isTransparent(const Point & coords) const { + assert(upscalingInProgress == false); if (surf) return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y - margins.y); else @@ -241,6 +328,7 @@ bool SDLImageShared::isTransparent(const Point & coords) const Rect SDLImageShared::contentRect() const { + assert(upscalingInProgress == false); auto tmpMargins = margins; auto tmpSize = Point(surf->w, surf->h); return Rect(tmpMargins, tmpSize); @@ -248,6 +336,7 @@ Rect SDLImageShared::contentRect() const const SDL_Palette * SDLImageShared::getPalette() const { + assert(upscalingInProgress == false); if (!surf) return nullptr; return surf->format->palette; @@ -255,11 +344,13 @@ const SDL_Palette * SDLImageShared::getPalette() const Point SDLImageShared::dimensions() const { + assert(upscalingInProgress == false); return fullSize; } std::shared_ptr SDLImageShared::horizontalFlip() const { + assert(upscalingInProgress == false); if (!surf) return shared_from_this(); @@ -275,6 +366,7 @@ std::shared_ptr SDLImageShared::horizontalFlip() const std::shared_ptr SDLImageShared::verticalFlip() const { + assert(upscalingInProgress == false); if (!surf) return shared_from_this(); @@ -291,6 +383,7 @@ std::shared_ptr SDLImageShared::verticalFlip() const // Keep the original palette, in order to do color switching operation void SDLImageShared::savePalette() { + assert(upscalingInProgress == false); // For some images that don't have palette, skip this if(surf->format->palette == nullptr) return; diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index aa7d7efe6..2ad465c67 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -27,14 +27,16 @@ struct SDL_Palette; class SDLImageShared final : public ISharedImage, public std::enable_shared_from_this, boost::noncopyable { //Surface without empty borders - SDL_Surface * surf; + SDL_Surface * surf = nullptr; - SDL_Palette * originalPalette; + SDL_Palette * originalPalette = nullptr; //size of left and top borders Point margins; //total size including borders Point fullSize; + std::atomic_bool upscalingInProgress = false; + // Keep the original palette, in order to do color switching operation void savePalette(); @@ -47,8 +49,11 @@ public: SDLImageShared(const ImagePath & filename); //Create using existing surface, extraRef will increase refcount on SDL_Surface SDLImageShared(SDL_Surface * from); + /// Creates image at specified scaling factor from source image + SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm); ~SDLImageShared(); + void scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaling, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override; void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override; void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override; @@ -56,6 +61,8 @@ public: bool isTransparent(const Point & coords) const override; Rect contentRect() const override; + bool isLoading() const override; + const SDL_Palette * getPalette() const override; [[nodiscard]] std::shared_ptr horizontalFlip() const override; diff --git a/client/renderSDL/ScalableImage.cpp b/client/renderSDL/ScalableImage.cpp index 2c76c254d..dd22b41cf 100644 --- a/client/renderSDL/ScalableImage.cpp +++ b/client/renderSDL/ScalableImage.cpp @@ -230,7 +230,7 @@ Rect ScalableImageShared::contentRect() const void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Rect * src, const ScalableImageParameters & parameters, int scalingFactor) { - const auto & flipAndDraw = [&](FlippedImages & images, const ColorRGBA & colorMultiplier, uint8_t alphaValue){ + const auto & getFlippedImage = [&](FlippedImages & images){ int index = 0; if (parameters.flipVertical) { @@ -248,7 +248,12 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re index |= 2; } - images[index]->draw(where, parameters.palette, dest, src, colorMultiplier, alphaValue, locator.layer); + return images[index]; + }; + + const auto & flipAndDraw = [&](FlippedImages & images, const ColorRGBA & colorMultiplier, uint8_t alphaValue){ + + getFlippedImage(images)->draw(where, parameters.palette, dest, src, colorMultiplier, alphaValue, locator.layer); }; if (scalingFactor == 1) @@ -257,12 +262,23 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re } else { + bool shadowLoading = scaled.at(scalingFactor).shadow.at(0) && scaled.at(scalingFactor).shadow.at(0)->isLoading(); + bool bodyLoading = scaled.at(scalingFactor).body.at(0) && scaled.at(scalingFactor).body.at(0)->isLoading(); + bool overlayLoading = scaled.at(scalingFactor).overlay.at(0) && scaled.at(scalingFactor).overlay.at(0)->isLoading(); + bool playerLoading = parameters.player.isValidPlayer() && scaled.at(scalingFactor).playerColored.at(parameters.player.getNum()) && scaled.at(scalingFactor).playerColored.at(parameters.player.getNum())->isLoading(); + + if (shadowLoading || bodyLoading || overlayLoading || playerLoading) + { + getFlippedImage(base)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer); + return; + } + if (scaled.at(scalingFactor).shadow.at(0)) flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue); if (parameters.player.isValidPlayer()) { - scaled.at(scalingFactor).playerColored[parameters.player.getNum()]->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer); + scaled.at(scalingFactor).playerColored.at(parameters.player.getNum())->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer); } else {