Parcourir la source

Offloaded xbrz upscaling to background threads

Ivan Savenko il y a 9 mois
Parent
commit
c3fb76b56f

+ 5 - 0
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;

+ 108 - 15
client/renderSDL/SDLImage.cpp

@@ -21,8 +21,11 @@
 #include "../render/IScreenHandler.h"
 
 #include <tbb/parallel_for.h>
-#include <SDL_surface.h>
+#include <tbb/task_arena.h>
+
 #include <SDL_image.h>
+#include <SDL_surface.h>
+#include <SDL_version.h>
 
 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<const ISharedImage> 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<const ISharedImage> 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 result = std::make_shared<SDLImageShared>(this, factor, algorithm);
 
-	auto ret = std::make_shared<SDLImageShared>(scaled);
+	if (surf->format->palette)
+		SDL_SetSurfacePalette(surf, originalPalette);
 
-	ret->fullSize = scaler.getResultDimensions().dimensions();
-	ret->margins = scaler.getResultDimensions().topLeft();
+	return result;
+}
 
-	// erase our own reference
-	SDL_FreeSurface(scaled);
+SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm)
+{
+	static tbb::task_arena upscalingArena;
 
-	if (surf->format->palette)
-		SDL_SetSurfacePalette(surf, originalPalette);
+	upscalingInProgress = true;
 
-	return ret;
+	auto scaler = std::make_shared<SDLImageScaler>(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<const ISharedImage> 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<const ISharedImage> 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<const ISharedImage> SDLImageShared::horizontalFlip() const
 {
+	assert(upscalingInProgress == false);
 	if (!surf)
 		return shared_from_this();
 
@@ -275,6 +366,7 @@ std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const
 
 std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const
 {
+	assert(upscalingInProgress == false);
 	if (!surf)
 		return shared_from_this();
 
@@ -291,6 +383,7 @@ std::shared_ptr<const ISharedImage> 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;

+ 9 - 2
client/renderSDL/SDLImage.h

@@ -27,14 +27,16 @@ struct SDL_Palette;
 class SDLImageShared final : public ISharedImage, public std::enable_shared_from_this<SDLImageShared>, 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<const ISharedImage> horizontalFlip() const override;

+ 19 - 3
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
 		{