mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-02-26 08:55:58 +00:00
Merge pull request #7449 from unknownbrackets/xbrz-update
Update to xBRZ 1.2, with its perf improvements
This commit is contained in:
commit
723d7c224d
@ -538,6 +538,10 @@ TextureScalerDX9::TextureScalerDX9() {
|
||||
initBicubicWeights();
|
||||
}
|
||||
|
||||
TextureScalerDX9::~TextureScalerDX9() {
|
||||
xbrz::shutdown();
|
||||
}
|
||||
|
||||
bool TextureScalerDX9::IsEmptyOrFlat(u32* data, int pixels, u32 fmt) {
|
||||
int pixelsPerWord = (fmt == D3DFMT_A8R8G8B8) ? 1 : 2;
|
||||
u32 ref = data[0];
|
||||
@ -609,6 +613,7 @@ void TextureScalerDX9::Scale(u32* &data, u32 &dstFmt, int &width, int &height, i
|
||||
|
||||
void TextureScalerDX9::ScaleXBRZ(int factor, u32* source, u32* dest, int width, int height) {
|
||||
xbrz::ScalerCfg cfg;
|
||||
xbrz::init();
|
||||
GlobalThreadPool::Loop(std::bind(&xbrz::scale, factor, source, dest, width, height, xbrz::ColorFormat::ARGB, cfg, placeholder::_1, placeholder::_2), 0, height);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ namespace DX9 {
|
||||
class TextureScalerDX9 {
|
||||
public:
|
||||
TextureScalerDX9();
|
||||
~TextureScalerDX9();
|
||||
|
||||
void Scale(u32* &data, u32 &dstfmt, int &width, int &height, int factor);
|
||||
|
||||
|
@ -530,6 +530,10 @@ TextureScaler::TextureScaler() {
|
||||
initBicubicWeights();
|
||||
}
|
||||
|
||||
TextureScaler::~TextureScaler() {
|
||||
xbrz::shutdown();
|
||||
}
|
||||
|
||||
bool TextureScaler::IsEmptyOrFlat(u32* data, int pixels, GLenum fmt) {
|
||||
int pixelsPerWord = (fmt == GL_UNSIGNED_BYTE) ? 1 : 2;
|
||||
u32 ref = data[0];
|
||||
@ -601,6 +605,7 @@ void TextureScaler::Scale(u32* &data, GLenum &dstFmt, int &width, int &height, i
|
||||
|
||||
void TextureScaler::ScaleXBRZ(int factor, u32* source, u32* dest, int width, int height) {
|
||||
xbrz::ScalerCfg cfg;
|
||||
xbrz::init();
|
||||
GlobalThreadPool::Loop(std::bind(&xbrz::scale, factor, source, dest, width, height, xbrz::ColorFormat::ARGB, cfg, placeholder::_1, placeholder::_2), 0, height);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
class TextureScaler {
|
||||
public:
|
||||
TextureScaler();
|
||||
~TextureScaler();
|
||||
|
||||
void Scale(u32* &data, GLenum &dstfmt, int &width, int &height, int factor);
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -393,21 +394,52 @@ double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight)
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
double distYCbCrAlpha(uint32_t pix1, uint32_t pix2, double lumaWeight)
|
||||
struct DistYCbCrBuffer //30% perf boost compared to distYCbCr()!
|
||||
{
|
||||
const double a1 = getAlpha(pix1) / 255.0 ;
|
||||
const double a2 = getAlpha(pix2) / 255.0 ;
|
||||
public:
|
||||
DistYCbCrBuffer() : buffer(256 * 256 * 256)
|
||||
{
|
||||
for (uint32_t i = 0; i < 256 * 256 * 256; ++i) //startup time: 114 ms on Intel Core i5 (four cores)
|
||||
{
|
||||
const int r_diff = getByte<2>(i) * 2 - 255;
|
||||
const int g_diff = getByte<1>(i) * 2 - 255;
|
||||
const int b_diff = getByte<0>(i) * 2 - 255;
|
||||
|
||||
/*
|
||||
Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1]
|
||||
const double k_b = 0.0593; //ITU-R BT.2020 conversion
|
||||
const double k_r = 0.2627; //
|
||||
const double k_g = 1 - k_b - k_r;
|
||||
|
||||
1. if a1 = a2, distance should be: a1 * distYCbCr()
|
||||
2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255
|
||||
3. if a1 = 1, distance should be: 255 * (1 - a2) + a2 * distYCbCr()
|
||||
*/
|
||||
return std::min(a1, a2) * distYCbCr(pix1, pix2, lumaWeight) + 255 * abs(a1 - a2);
|
||||
}
|
||||
const double scale_b = 0.5 / (1 - k_b);
|
||||
const double scale_r = 0.5 / (1 - k_r);
|
||||
|
||||
const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr!
|
||||
const double c_b = scale_b * (b_diff - y);
|
||||
const double c_r = scale_r * (r_diff - y);
|
||||
|
||||
buffer[i] = static_cast<float>(std::sqrt(square(y) + square(c_b) + square(c_r)));
|
||||
}
|
||||
}
|
||||
|
||||
double dist(uint32_t pix1, uint32_t pix2) const
|
||||
{
|
||||
//if (pix1 == pix2) -> 8% perf degradation!
|
||||
// return 0;
|
||||
//if (pix1 > pix2)
|
||||
// std::swap(pix1, pix2); -> 30% perf degradation!!!
|
||||
|
||||
const int r_diff = static_cast<int>(getRed (pix1)) - getRed (pix2);
|
||||
const int g_diff = static_cast<int>(getGreen(pix1)) - getGreen(pix2);
|
||||
const int b_diff = static_cast<int>(getBlue (pix1)) - getBlue (pix2);
|
||||
|
||||
return buffer[(((r_diff + 255) / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte
|
||||
(((g_diff + 255) / 2) << 8) |
|
||||
(( b_diff + 255) / 2)];
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<float> buffer; //consumes 64 MB memory; using double is 2% faster, but takes 128 MB
|
||||
};
|
||||
DistYCbCrBuffer *distYCbCrBuffer = nullptr;
|
||||
|
||||
|
||||
inline
|
||||
@ -475,7 +507,7 @@ input kernel area naming convention:
|
||||
-----------------
|
||||
| A | B | C | D |
|
||||
----|---|---|---|
|
||||
| E | F | G | H | //evalute the four corners between F, G, J, K
|
||||
| E | F | G | H | //evaluate the four corners between F, G, J, K
|
||||
----|---|---|---| //input pixel is at position F
|
||||
| I | J | K | L |
|
||||
----|---|---|---|
|
||||
@ -494,7 +526,7 @@ BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz::ScalerCfg& cfg)
|
||||
ker.g == ker.k))
|
||||
return result;
|
||||
|
||||
auto dist = [&](uint32_t col1, uint32_t col2) { return ColorDistance::dist(col1, col2, cfg.luminanceWeight_); };
|
||||
auto dist = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight_); };
|
||||
|
||||
const int weight = 4;
|
||||
double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + weight * dist(ker.j, ker.g);
|
||||
@ -613,8 +645,8 @@ void scalePixel(const Kernel_3x3& ker,
|
||||
|
||||
if (getBottomR(blend) >= BLEND_NORMAL)
|
||||
{
|
||||
auto eq = [&](uint32_t col1, uint32_t col2) { return ColorDistance::dist(col1, col2, cfg.luminanceWeight_) < cfg.equalColorTolerance_; };
|
||||
auto dist = [&](uint32_t col1, uint32_t col2) { return ColorDistance::dist(col1, col2, cfg.luminanceWeight_); };
|
||||
auto eq = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight_) < cfg.equalColorTolerance_; };
|
||||
auto dist = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight_); };
|
||||
|
||||
const bool doLineBlend = [&]() -> bool
|
||||
{
|
||||
@ -628,7 +660,7 @@ void scalePixel(const Kernel_3x3& ker,
|
||||
return false;
|
||||
|
||||
//no full blending for L-shapes; blend corner only (handles "mario mushroom eyes")
|
||||
if (eq(g, h) && eq(h , i) && eq(i, f) && eq(f, c) && !eq(e, i))
|
||||
if (!eq(e, i) && eq(g, h) && eq(h , i) && eq(i, f) && eq(f, c))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@ -743,7 +775,7 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
|
||||
*/
|
||||
setTopR(preProcBuffer[x], res.blend_j);
|
||||
|
||||
if (x + 1 < srcWidth)
|
||||
if (x + 1 < bufferSize)
|
||||
setTopL(preProcBuffer[x + 1], res.blend_k);
|
||||
}
|
||||
}
|
||||
@ -770,31 +802,32 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
|
||||
const int x_p1 = std::min(x + 1, srcWidth - 1);
|
||||
const int x_p2 = std::min(x + 2, srcWidth - 1);
|
||||
|
||||
Kernel_4x4 ker4 = {}; //perf: initialization is negligible
|
||||
|
||||
ker4.a = s_m1[x_m1]; //read sequentially from memory as far as possible
|
||||
ker4.b = s_m1[x];
|
||||
ker4.c = s_m1[x_p1];
|
||||
ker4.d = s_m1[x_p2];
|
||||
|
||||
ker4.e = s_0[x_m1];
|
||||
ker4.f = s_0[x];
|
||||
ker4.g = s_0[x_p1];
|
||||
ker4.h = s_0[x_p2];
|
||||
|
||||
ker4.i = s_p1[x_m1];
|
||||
ker4.j = s_p1[x];
|
||||
ker4.k = s_p1[x_p1];
|
||||
ker4.l = s_p1[x_p2];
|
||||
|
||||
ker4.m = s_p2[x_m1];
|
||||
ker4.n = s_p2[x];
|
||||
ker4.o = s_p2[x_p1];
|
||||
ker4.p = s_p2[x_p2];
|
||||
|
||||
//evaluate the four corners on bottom-right of current pixel
|
||||
unsigned char blend_xy = 0; //for current (x, y) position
|
||||
{
|
||||
Kernel_4x4 ker = {}; //perf: initialization is negligible
|
||||
ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible
|
||||
ker.b = s_m1[x];
|
||||
ker.c = s_m1[x_p1];
|
||||
ker.d = s_m1[x_p2];
|
||||
|
||||
ker.e = s_0[x_m1];
|
||||
ker.f = s_0[x];
|
||||
ker.g = s_0[x_p1];
|
||||
ker.h = s_0[x_p2];
|
||||
|
||||
ker.i = s_p1[x_m1];
|
||||
ker.j = s_p1[x];
|
||||
ker.k = s_p1[x_p1];
|
||||
ker.l = s_p1[x_p2];
|
||||
|
||||
ker.m = s_p2[x_m1];
|
||||
ker.n = s_p2[x];
|
||||
ker.o = s_p2[x_p1];
|
||||
ker.p = s_p2[x_p2];
|
||||
|
||||
const BlendResult res = preProcessCorners<ColorDistance>(ker, cfg);
|
||||
const BlendResult res = preProcessCorners<ColorDistance>(ker4, cfg);
|
||||
/*
|
||||
preprocessing blend result:
|
||||
---------
|
||||
@ -812,34 +845,34 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
|
||||
blend_xy1 = 0;
|
||||
setTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column
|
||||
|
||||
if (x + 1 < srcWidth) //set 3rd known corner for (x + 1, y)
|
||||
if (x + 1 < bufferSize) //set 3rd known corner for (x + 1, y)
|
||||
setBottomL(preProcBuffer[x + 1], res.blend_g);
|
||||
}
|
||||
|
||||
//fill block of size scale * scale with the given color
|
||||
fillBlock(out, trgWidth * sizeof(uint32_t), s_0[x], Scaler::scale); //place *after* preprocessing step, to not overwrite the results while processing the the last pixel!
|
||||
fillBlock(out, trgWidth * sizeof(uint32_t), ker4.f, Scaler::scale); //place *after* preprocessing step, to not overwrite the results while processing the the last pixel!
|
||||
|
||||
//blend four corners of current pixel
|
||||
if (blendingNeeded(blend_xy)) //good 20% perf-improvement
|
||||
{
|
||||
Kernel_3x3 ker = {}; //perf: initialization is negligible
|
||||
Kernel_3x3 ker3 = {}; //perf: initialization is negligible
|
||||
|
||||
ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible
|
||||
ker.b = s_m1[x];
|
||||
ker.c = s_m1[x_p1];
|
||||
ker3.a = ker4.a;
|
||||
ker3.b = ker4.b;
|
||||
ker3.c = ker4.c;
|
||||
|
||||
ker.d = s_0[x_m1];
|
||||
ker.e = s_0[x];
|
||||
ker.f = s_0[x_p1];
|
||||
ker3.d = ker4.e;
|
||||
ker3.e = ker4.f;
|
||||
ker3.f = ker4.g;
|
||||
|
||||
ker.g = s_p1[x_m1];
|
||||
ker.h = s_p1[x];
|
||||
ker.i = s_p1[x_p1];
|
||||
ker3.g = ker4.i;
|
||||
ker3.h = ker4.j;
|
||||
ker3.i = ker4.k;
|
||||
|
||||
scalePixel<Scaler, ColorDistance, ROT_0 >(ker, out, trgWidth, blend_xy, cfg);
|
||||
scalePixel<Scaler, ColorDistance, ROT_90 >(ker, out, trgWidth, blend_xy, cfg);
|
||||
scalePixel<Scaler, ColorDistance, ROT_180>(ker, out, trgWidth, blend_xy, cfg);
|
||||
scalePixel<Scaler, ColorDistance, ROT_270>(ker, out, trgWidth, blend_xy, cfg);
|
||||
scalePixel<Scaler, ColorDistance, ROT_0 >(ker3, out, trgWidth, blend_xy, cfg);
|
||||
scalePixel<Scaler, ColorDistance, ROT_90 >(ker3, out, trgWidth, blend_xy, cfg);
|
||||
scalePixel<Scaler, ColorDistance, ROT_180>(ker3, out, trgWidth, blend_xy, cfg);
|
||||
scalePixel<Scaler, ColorDistance, ROT_270>(ker3, out, trgWidth, blend_xy, cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1090,9 +1123,11 @@ struct ColorDistanceRGB
|
||||
{
|
||||
static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight)
|
||||
{
|
||||
if (pix1 == pix2) //about 8% perf boost
|
||||
return 0;
|
||||
return distYCbCr(pix1, pix2, luminanceWeight);
|
||||
return distYCbCrBuffer->dist(pix1, pix2);
|
||||
|
||||
//if (pix1 == pix2) //about 4% perf boost
|
||||
// return 0;
|
||||
//return distYCbCr(pix1, pix2, luminanceWeight);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1100,9 +1135,25 @@ struct ColorDistanceARGB
|
||||
{
|
||||
static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight)
|
||||
{
|
||||
if (pix1 == pix2)
|
||||
return 0;
|
||||
return distYCbCrAlpha(pix1, pix2, luminanceWeight);
|
||||
const double a1 = getAlpha(pix1) / 255.0 ;
|
||||
const double a2 = getAlpha(pix2) / 255.0 ;
|
||||
/*
|
||||
Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1]
|
||||
|
||||
1. if a1 = a2, distance should be: a1 * distYCbCr()
|
||||
2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255
|
||||
3. if a1 = 1, distance should be: 255 * (1 - a2) + a2 * distYCbCr()
|
||||
*/
|
||||
|
||||
const double d = distYCbCrBuffer->dist(pix1, pix2);
|
||||
if (a1 > a2)
|
||||
return a2 * d + 255 * (a1 - a2);
|
||||
else
|
||||
return a1 * d + 255 * (a2 - a1);
|
||||
|
||||
//if (pix1 == pix2)
|
||||
// return 0;
|
||||
//return std::min(a1, a2) * distYCbCr(pix1, pix2, luminanceWeight) + 255 * abs(a1 - a2);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1141,6 +1192,20 @@ void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth
|
||||
}
|
||||
|
||||
|
||||
void xbrz::init()
|
||||
{
|
||||
if (distYCbCrBuffer == nullptr)
|
||||
distYCbCrBuffer = new DistYCbCrBuffer();
|
||||
}
|
||||
|
||||
|
||||
void xbrz::shutdown()
|
||||
{
|
||||
delete distYCbCrBuffer;
|
||||
distYCbCrBuffer = nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance)
|
||||
{
|
||||
switch (colFmt)
|
||||
|
@ -37,9 +37,10 @@ namespace xbrz
|
||||
using a modified approach of xBR:
|
||||
http://board.byuu.org/viewtopic.php?f=10&t=2248
|
||||
- new rule set preserving small image features
|
||||
- highly optimized for performance
|
||||
- support alpha channel
|
||||
- support multithreading
|
||||
- support 64 bit architectures
|
||||
- support 64-bit architectures
|
||||
- support processing image slices
|
||||
*/
|
||||
|
||||
@ -51,7 +52,6 @@ enum class ColorFormat //from high bits -> low bits, 8 bit per channel
|
||||
|
||||
/*
|
||||
-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only
|
||||
-> color format: ARGB (BGRA byte order), alpha channel unused
|
||||
-> support for source/target pitch in bytes!
|
||||
-> if your emulator changes only a few image slices during each cycle (e.g. DOSBox) then there's no need to run xBRZ on the complete image:
|
||||
Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis)
|
||||
@ -59,7 +59,7 @@ enum class ColorFormat //from high bits -> low bits, 8 bit per channel
|
||||
in the target image data if you are using multiple threads for processing each enlarged slice!
|
||||
|
||||
THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap!
|
||||
- there is a minor inefficiency for the first row of a slice, so avoid processing single rows only
|
||||
- there is a minor inefficiency for the first row of a slice, so avoid processing single rows only; suggestion: process 6 rows at least
|
||||
*/
|
||||
void scale(size_t factor, //valid range: 2 - 5
|
||||
const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
|
||||
@ -67,6 +67,10 @@ void scale(size_t factor, //valid range: 2 - 5
|
||||
const ScalerCfg& cfg = ScalerCfg(),
|
||||
int yFirst = 0, int yLast = std::numeric_limits<int>::max()); //slice of source image
|
||||
|
||||
void init();
|
||||
|
||||
void shutdown();
|
||||
|
||||
void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight,
|
||||
uint32_t* trg, int trgWidth, int trgHeight);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user