Update to xBRZ 1.6

This commit is contained in:
OV2 2018-02-27 21:39:50 +01:00
parent a13ac31400
commit 146ab1bd5f
6 changed files with 520 additions and 211 deletions

View File

@ -1,13 +1,14 @@
// **************************************************************************** // ****************************************************************************
// * This file is part of the HqMAME project. It is distributed under * // * This file is part of the xBRZ project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// * * // * *
// * Additionally and as a special exception, the author gives permission * // * Additionally and as a special exception, the author gives permission *
// * to link the code of this program with the MAME library (or with modified * // * to link the code of this program with the following libraries *
// * versions of MAME that use the same license as MAME), and distribute * // * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two. You must obey the GNU General * // * linked combinations including the two: MAME, FreeFileSync, Snes9x *
// * Public License in all respects for all of the code used other than MAME. * // * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x. *
// * If you modify this file, you may extend this exception to your version * // * If you modify this file, you may extend this exception to your version *
// * of the file, but you are not obligated to do so. If you do not wish to * // * of the file, but you are not obligated to do so. If you do not wish to *
// * do so, delete this exception statement from your version. * // * do so, delete this exception statement from your version. *
@ -15,27 +16,16 @@
#include "xbrz.h" #include "xbrz.h"
#include <cassert> #include <cassert>
#include <algorithm>
#include <vector> #include <vector>
#include <algorithm>
#include <cmath> //std::sqrt
#include "xbrz_tools.h"
using namespace xbrz;
#ifndef WIN32
#include <cmath>
#endif
namespace namespace
{ {
template <uint32_t N> inline
unsigned char getByte(uint32_t val) { return static_cast<unsigned char>((val >> (8 * N)) & 0xff); }
inline unsigned char getAlpha(uint32_t pix) { return getByte<3>(pix); }
inline unsigned char getRed (uint32_t pix) { return getByte<2>(pix); }
inline unsigned char getGreen(uint32_t pix) { return getByte<1>(pix); }
inline unsigned char getBlue (uint32_t pix) { return getByte<0>(pix); }
inline uint32_t makePixel( unsigned char r, unsigned char g, unsigned char b) { return (r << 16) | (g << 8) | b; }
inline uint32_t makePixel(unsigned char a, unsigned char r, unsigned char g, unsigned char b) { return (a << 24) | (r << 16) | (g << 8) | b; }
template <unsigned int M, unsigned int N> inline template <unsigned int M, unsigned int N> inline
uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color with opacity M / N over opaque background: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color with opacity M / N over opaque background: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
{ {
@ -84,26 +74,6 @@ uint32_t gradientARGB(uint32_t pixFront, uint32_t pixBack) //find intermediate c
// //
uint32_t* byteAdvance( uint32_t* ptr, int bytes) { return reinterpret_cast< uint32_t*>(reinterpret_cast< char*>(ptr) + bytes); }
const uint32_t* byteAdvance(const uint32_t* ptr, int bytes) { return reinterpret_cast<const uint32_t*>(reinterpret_cast<const char*>(ptr) + bytes); }
//fill block with the given color
inline
void fillBlock(uint32_t* trg, int pitch, uint32_t col, int blockWidth, int blockHeight)
{
//for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch))
// std::fill(trg, trg + blockWidth, col);
for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch))
for (int x = 0; x < blockWidth; ++x)
trg[x] = col;
}
inline
void fillBlock(uint32_t* trg, int pitch, uint32_t col, int n) { fillBlock(trg, pitch, col, n, n); }
#ifdef _MSC_VER #ifdef _MSC_VER
#define FORCE_INLINE __forceinline #define FORCE_INLINE __forceinline
#elif defined __GNUC__ #elif defined __GNUC__
@ -166,7 +136,7 @@ template <class T> inline
T square(T value) { return value * value; } T square(T value) { return value * value; }
#if 0
inline inline
double distRGB(uint32_t pix1, uint32_t pix2) double distRGB(uint32_t pix1, uint32_t pix2)
{ {
@ -177,6 +147,7 @@ double distRGB(uint32_t pix1, uint32_t pix2)
//euklidean RGB distance //euklidean RGB distance
return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff)); return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff));
} }
#endif
inline inline
@ -206,26 +177,20 @@ double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight)
} }
struct DistYCbCrBuffer //30% perf boost compared to distYCbCr()! inline
double distYCbCrBuffered(uint32_t pix1, uint32_t pix2)
{ {
public: //30% perf boost compared to plain distYCbCr()!
static double dist(uint32_t pix1, uint32_t pix2) //consumes 64 MB memory; using double is only 2% faster, but takes 128 MB
static const std::vector<float> diffToDist = []
{ {
#if defined _MSC_VER && _MSC_VER < 1900 std::vector<float> tmp;
#error function scope static initialization is not yet thread-safe!
#endif
static const DistYCbCrBuffer inst;
return inst.distImpl(pix1, pix2);
}
private:
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) 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 r_diff = getByte<2>(i) * 2 - 0xFF;
const int g_diff = getByte<1>(i) * 2 - 255; const int g_diff = getByte<1>(i) * 2 - 0xFF;
const int b_diff = getByte<0>(i) * 2 - 255; const int b_diff = getByte<0>(i) * 2 - 0xFF;
const double k_b = 0.0593; //ITU-R BT.2020 conversion const double k_b = 0.0593; //ITU-R BT.2020 conversion
const double k_r = 0.2627; // const double k_r = 0.2627; //
@ -238,28 +203,31 @@ private:
const double c_b = scale_b * (b_diff - y); const double c_b = scale_b * (b_diff - y);
const double c_r = scale_r * (r_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))); tmp.push_back(static_cast<float>(std::sqrt(square(y) + square(c_b) + square(c_r))));
}
} }
return tmp;
}();
double distImpl(uint32_t pix1, uint32_t pix2) const
{
//if (pix1 == pix2) -> 8% perf degradation! //if (pix1 == pix2) -> 8% perf degradation!
// return 0; // return 0;
//if (pix1 > pix2) //if (pix1 < pix2)
// std::swap(pix1, pix2); -> 30% perf degradation!!! // std::swap(pix1, pix2); -> 30% perf degradation!!!
#if 1
const int r_diff = static_cast<int>(getRed (pix1)) - getRed (pix2); const int r_diff = static_cast<int>(getRed (pix1)) - getRed (pix2);
const int g_diff = static_cast<int>(getGreen(pix1)) - getGreen(pix2); const int g_diff = static_cast<int>(getGreen(pix1)) - getGreen(pix2);
const int b_diff = static_cast<int>(getBlue (pix1)) - getBlue (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 return diffToDist[(((r_diff + 0xFF) / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte
(((g_diff + 255) / 2) << 8) | (((g_diff + 0xFF) / 2) << 8) |
(( b_diff + 255) / 2)]; (( b_diff + 0xFF) / 2)];
} #else //not noticeably faster:
const int r_diff_tmp = ((pix1 & 0xFF0000) + 0xFF0000 - (pix2 & 0xFF0000)) / 2;
const int g_diff_tmp = ((pix1 & 0x00FF00) + 0x00FF00 - (pix2 & 0x00FF00)) / 2; //slightly reduce precision (division by 2) to squeeze value into single byte
const int b_diff_tmp = ((pix1 & 0x0000FF) + 0x0000FF - (pix2 & 0x0000FF)) / 2;
std::vector<float> buffer; //consumes 64 MB memory; using double is only 2% faster, but takes 128 MB return diffToDist[(r_diff_tmp & 0xFF0000) | (g_diff_tmp & 0x00FF00) | (b_diff_tmp & 0x0000FF)];
}; #endif
}
enum BlendType enum BlendType
@ -373,7 +341,7 @@ DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g)
//compress four blend types into a single byte //compress four blend types into a single byte
inline BlendType getTopL (unsigned char b) { return static_cast<BlendType>(0x3 & b); } //inline BlendType getTopL (unsigned char b) { return static_cast<BlendType>(0x3 & b); }
inline BlendType getTopR (unsigned char b) { return static_cast<BlendType>(0x3 & (b >> 2)); } inline BlendType getTopR (unsigned char b) { return static_cast<BlendType>(0x3 & (b >> 2)); }
inline BlendType getBottomR(unsigned char b) { return static_cast<BlendType>(0x3 & (b >> 4)); } inline BlendType getBottomR(unsigned char b) { return static_cast<BlendType>(0x3 & (b >> 4)); }
inline BlendType getBottomL(unsigned char b) { return static_cast<BlendType>(0x3 & (b >> 6)); } inline BlendType getBottomL(unsigned char b) { return static_cast<BlendType>(0x3 & (b >> 6)); }
@ -446,13 +414,13 @@ void blendPixel(const Kernel_3x3& ker,
return true; return true;
//make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes
if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90° corners if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90° corners
return false; return false;
if (getBottomL(blend) != BLEND_NONE && !eq(e, c)) if (getBottomL(blend) != BLEND_NONE && !eq(e, c))
return false; return false;
//no full blending for L-shapes; blend corner only (handles "mario mushroom eyes") //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes")
if (!eq(e, i) && eq(g, h) && eq(h , i) && eq(i, f) && eq(f, c)) if (!eq(e, i) && eq(g, h) && eq(h, i) && eq(i, f) && eq(f, c))
return false; return false;
return true; return true;
@ -482,7 +450,7 @@ void blendPixel(const Kernel_3x3& ker,
if (haveSteepLine) if (haveSteepLine)
Scaler::blendLineSteep(px, out); Scaler::blendLineSteep(px, out);
else else
Scaler::blendLineDiagonal(px,out); Scaler::blendLineDiagonal(px, out);
} }
} }
else else
@ -515,7 +483,7 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
//"sizeof(uint32_t) * srcWidth * (yLast - yFirst)" bytes without risk of accidental overwriting before accessing //"sizeof(uint32_t) * srcWidth * (yLast - yFirst)" bytes without risk of accidental overwriting before accessing
const int bufferSize = srcWidth; const int bufferSize = srcWidth;
unsigned char* preProcBuffer = reinterpret_cast<unsigned char*>(trg + yLast * Scaler::scale * trgWidth) - bufferSize; unsigned char* preProcBuffer = reinterpret_cast<unsigned char*>(trg + yLast * Scaler::scale * trgWidth) - bufferSize;
std::fill(preProcBuffer, preProcBuffer + bufferSize, 0); std::fill(preProcBuffer, preProcBuffer + bufferSize, '\0');
static_assert(BLEND_NONE == 0, ""); static_assert(BLEND_NONE == 0, "");
//initialize preprocessing buffer for first row of current stripe: detect upper left and right corner blending //initialize preprocessing buffer for first row of current stripe: detect upper left and right corner blending
@ -644,7 +612,8 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
} }
//fill block of size scale * scale with the given color //fill block of size scale * scale with the given color
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! fillBlock(out, trgWidth * sizeof(uint32_t), ker4.f, Scaler::scale, Scaler::scale);
//place *after* preprocessing step, to not overwrite the results while processing the the last pixel!
//blend four corners of current pixel //blend four corners of current pixel
if (blendingNeeded(blend_xy)) //good 5% perf-improvement if (blendingNeeded(blend_xy)) //good 5% perf-improvement
@ -1039,7 +1008,7 @@ struct ColorDistanceRGB
{ {
static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight)
{ {
return DistYCbCrBuffer::dist(pix1, pix2); return distYCbCrBuffered(pix1, pix2);
//if (pix1 == pix2) //about 4% perf boost //if (pix1 == pix2) //about 4% perf boost
// return 0; // return 0;
@ -1061,15 +1030,31 @@ struct ColorDistanceARGB
3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr()
*/ */
//return std::min(a1, a2) * DistYCbCrBuffer::dist(pix1, pix2) + 255 * abs(a1 - a2); //return std::min(a1, a2) * distYCbCrBuffered(pix1, pix2) + 255 * abs(a1 - a2);
//=> following code is 15% faster: //=> following code is 15% faster:
const double d = DistYCbCrBuffer::dist(pix1, pix2); const double d = distYCbCrBuffered(pix1, pix2);
if (a1 < a2) if (a1 < a2)
return a1 * d + 255 * (a2 - a1); return a1 * d + 255 * (a2 - a1);
else else
return a2 * d + 255 * (a1 - a2); return a2 * d + 255 * (a1 - a2);
//alternative? return std::sqrt(a1 * a2 * square(DistYCbCrBuffer::dist(pix1, pix2)) + square(255 * (a1 - a2))); //alternative? return std::sqrt(a1 * a2 * square(distYCbCrBuffered(pix1, pix2)) + square(255 * (a1 - a2)));
}
};
struct ColorDistanceUnbufferedARGB
{
static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight)
{
const double a1 = getAlpha(pix1) / 255.0 ;
const double a2 = getAlpha(pix2) / 255.0 ;
const double d = distYCbCr(pix1, pix2, luminanceWeight);
if (a1 < a2)
return a1 * d + 255 * (a2 - a1);
else
return a2 * d + 255 * (a1 - a2);
} }
}; };
@ -1096,8 +1081,25 @@ struct ColorGradientARGB
void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, ColorFormat colFmt, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, ColorFormat colFmt, const xbrz::ScalerCfg& cfg, int yFirst, int yLast)
{ {
static_assert(SCALE_FACTOR_MAX == 6, "");
switch (colFmt) switch (colFmt)
{ {
case ColorFormat::RGB:
switch (factor)
{
case 2:
return scaleImage<Scaler2x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 3:
return scaleImage<Scaler3x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 4:
return scaleImage<Scaler4x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 5:
return scaleImage<Scaler5x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 6:
return scaleImage<Scaler6x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
}
break;
case ColorFormat::ARGB: case ColorFormat::ARGB:
switch (factor) switch (factor)
{ {
@ -1114,19 +1116,19 @@ void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth
} }
break; break;
case ColorFormat::RGB: case ColorFormat::ARGB_UNBUFFERED:
switch (factor) switch (factor)
{ {
case 2: case 2:
return scaleImage<Scaler2x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); return scaleImage<Scaler2x<ColorGradientARGB>, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 3: case 3:
return scaleImage<Scaler3x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); return scaleImage<Scaler3x<ColorGradientARGB>, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 4: case 4:
return scaleImage<Scaler4x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); return scaleImage<Scaler4x<ColorGradientARGB>, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 5: case 5:
return scaleImage<Scaler5x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); return scaleImage<Scaler5x<ColorGradientARGB>, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
case 6: case 6:
return scaleImage<Scaler6x<ColorGradientRGB>, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); return scaleImage<Scaler6x<ColorGradientARGB>, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
} }
break; break;
} }
@ -1138,84 +1140,133 @@ bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, doub
{ {
switch (colFmt) switch (colFmt)
{ {
case ColorFormat::ARGB:
return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
case ColorFormat::RGB: case ColorFormat::RGB:
return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
case ColorFormat::ARGB:
return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
case ColorFormat::ARGB_UNBUFFERED:
return ColorDistanceUnbufferedARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
} }
assert(false); assert(false);
return false; return false;
} }
void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, void xbrz::bilinearScale(const uint32_t* src, int srcWidth, int srcHeight,
uint32_t* trg, int trgWidth, int trgHeight, int trgPitch, /**/ uint32_t* trg, int trgWidth, int trgHeight)
SliceType st, int yFirst, int yLast)
{ {
if (srcPitch < srcWidth * static_cast<int>(sizeof(uint32_t)) || bilinearScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t),
trgPitch < trgWidth * static_cast<int>(sizeof(uint32_t))) trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t),
{ 0, trgHeight, [](uint32_t pix) { return pix; });
assert(false);
return;
}
switch (st)
{
case NN_SCALE_SLICE_SOURCE:
//nearest-neighbor (going over source image - fast for upscaling, since source is read only once
yFirst = std::max(yFirst, 0);
yLast = std::min(yLast, srcHeight);
if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return;
for (int y = yFirst; y < yLast; ++y)
{
//mathematically: ySrc = floor(srcHeight * yTrg / trgHeight)
// => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight
//keep within for loop to support MT input slices!
const int yTrg_first = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight)
const int yTrg_last = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight)
const int blockHeight = yTrg_last - yTrg_first;
if (blockHeight > 0)
{
const uint32_t* srcLine = byteAdvance(src, y * srcPitch);
uint32_t* trgLine = byteAdvance(trg, yTrg_first * trgPitch);
int xTrg_first = 0;
for (int x = 0; x < srcWidth; ++x)
{
int xTrg_last = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth;
const int blockWidth = xTrg_last - xTrg_first;
if (blockWidth > 0)
{
xTrg_first = xTrg_last;
fillBlock(trgLine, trgPitch, srcLine[x], blockWidth, blockHeight);
trgLine += blockWidth;
}
}
}
}
break;
case NN_SCALE_SLICE_TARGET:
//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!)
yFirst = std::max(yFirst, 0);
yLast = std::min(yLast, trgHeight);
if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return;
for (int y = yFirst; y < yLast; ++y)
{
uint32_t* trgLine = byteAdvance(trg, y * trgPitch);
const int ySrc = srcHeight * y / trgHeight;
const uint32_t* srcLine = byteAdvance(src, ySrc * srcPitch);
for (int x = 0; x < trgWidth; ++x)
{
const int xSrc = srcWidth * x / trgWidth;
trgLine[x] = srcLine[xSrc];
}
}
break;
}
} }
void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight,
/**/ uint32_t* trg, int trgWidth, int trgHeight)
{
nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t),
trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t),
0, trgHeight, [](uint32_t pix) { return pix; });
}
#if 0
//#include <ppl.h>
void bilinearScaleCpu(const uint32_t* src, int srcWidth, int srcHeight,
/**/ uint32_t* trg, int trgWidth, int trgHeight)
{
const int TASK_GRANULARITY = 16;
concurrency::task_group tg;
for (int i = 0; i < trgHeight; i += TASK_GRANULARITY)
tg.run([=]
{
const int iLast = std::min(i + TASK_GRANULARITY, trgHeight);
xbrz::bilinearScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t),
trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t),
i, iLast, [](uint32_t pix) { return pix; });
});
tg.wait();
}
//Perf: AMP vs CPU: merely ~10% shorter runtime (scaling 1280x800 -> 1920x1080)
//#include <amp.h>
void bilinearScaleAmp(const uint32_t* src, int srcWidth, int srcHeight, //throw concurrency::runtime_exception
/**/ uint32_t* trg, int trgWidth, int trgHeight)
{
//C++ AMP reference: https://msdn.microsoft.com/en-us/library/hh289390.aspx
//introduction to C++ AMP: https://msdn.microsoft.com/en-us/magazine/hh882446.aspx
using namespace concurrency;
//TODO: pitch
if (srcHeight <= 0 || srcWidth <= 0) return;
const float scaleX = static_cast<float>(trgWidth ) / srcWidth;
const float scaleY = static_cast<float>(trgHeight) / srcHeight;
array_view<const uint32_t, 2> srcView(srcHeight, srcWidth, src);
array_view< uint32_t, 2> trgView(trgHeight, trgWidth, trg);
trgView.discard_data();
parallel_for_each(trgView.extent, [=](index<2> idx) restrict(amp) //throw ?
{
const int y = idx[0];
const int x = idx[1];
//Perf notes:
// -> float-based calculation is (almost 2x) faster than double!
// -> no noticeable improvement via tiling: https://msdn.microsoft.com/en-us/magazine/hh882447.aspx
// -> no noticeable improvement with restrict(amp,cpu)
// -> iterating over y-axis only is significantly slower!
// -> pre-calculating x,y-dependent variables in a buffer + array_view<> is ~ 20 % slower!
const int y1 = srcHeight * y / trgHeight;
int y2 = y1 + 1;
if (y2 == srcHeight) --y2;
const float yy1 = y / scaleY - y1;
const float y2y = 1 - yy1;
//-------------------------------------
const int x1 = srcWidth * x / trgWidth;
int x2 = x1 + 1;
if (x2 == srcWidth) --x2;
const float xx1 = x / scaleX - x1;
const float x2x = 1 - xx1;
//-------------------------------------
const float x2xy2y = x2x * y2y;
const float xx1y2y = xx1 * y2y;
const float x2xyy1 = x2x * yy1;
const float xx1yy1 = xx1 * yy1;
auto interpolate = [=](int offset)
{
/*
https://en.wikipedia.org/wiki/Bilinear_interpolation
(c11(x2 - x) + c21(x - x1)) * (y2 - y ) +
(c12(x2 - x) + c22(x - x1)) * (y - y1)
*/
const auto c11 = (srcView(y1, x1) >> (8 * offset)) & 0xff;
const auto c21 = (srcView(y1, x2) >> (8 * offset)) & 0xff;
const auto c12 = (srcView(y2, x1) >> (8 * offset)) & 0xff;
const auto c22 = (srcView(y2, x2) >> (8 * offset)) & 0xff;
return c11 * x2xy2y + c21 * xx1y2y +
c12 * x2xyy1 + c22 * xx1yy1;
};
const float bi = interpolate(0);
const float gi = interpolate(1);
const float ri = interpolate(2);
const float ai = interpolate(3);
const auto b = static_cast<uint32_t>(bi + 0.5f);
const auto g = static_cast<uint32_t>(gi + 0.5f);
const auto r = static_cast<uint32_t>(ri + 0.5f);
const auto a = static_cast<uint32_t>(ai + 0.5f);
trgView(y, x) = (a << 24) | (r << 16) | (g << 8) | b;
});
trgView.synchronize(); //throw ?
}
#endif

View File

@ -1,13 +1,14 @@
// **************************************************************************** // ****************************************************************************
// * This file is part of the HqMAME project. It is distributed under * // * This file is part of the xBRZ project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// * * // * *
// * Additionally and as a special exception, the author gives permission * // * Additionally and as a special exception, the author gives permission *
// * to link the code of this program with the MAME library (or with modified * // * to link the code of this program with the following libraries *
// * versions of MAME that use the same license as MAME), and distribute * // * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two. You must obey the GNU General * // * linked combinations including the two: MAME, FreeFileSync, Snes9x *
// * Public License in all respects for all of the code used other than MAME. * // * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x. *
// * If you modify this file, you may extend this exception to your version * // * If you modify this file, you may extend this exception to your version *
// * of the file, but you are not obligated to do so. If you do not wish to * // * of the file, but you are not obligated to do so. If you do not wish to *
// * do so, delete this exception statement from your version. * // * do so, delete this exception statement from your version. *
@ -20,7 +21,7 @@
//#include <cstddef> //size_t //#include <cstddef> //size_t
//#include <cstdint> //uint32_t //#include <cstdint> //uint32_t
#include <limits> #include <limits>
#include "xbrz-config.h" #include "xbrz_config.h"
namespace xbrz namespace xbrz
{ {
@ -43,53 +44,37 @@ enum class ColorFormat //from high bits -> low bits, 8 bit per channel
{ {
RGB, //8 bit for each red, green, blue, upper 8 bits unused RGB, //8 bit for each red, green, blue, upper 8 bits unused
ARGB, //including alpha channel, BGRA byte order on little-endian machines ARGB, //including alpha channel, BGRA byte order on little-endian machines
ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time
}; };
const int SCALE_FACTOR_MAX = 6;
/* /*
-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only -> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only
-> support for source/target pitch in bytes! -> 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: -> 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) 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)
Caveat: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition CAVEAT: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition
in the target image data if you are using multiple threads for processing each enlarged slice! 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! 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; suggestion: process 8-16 rows at least - there is a minor inefficiency for the first row of a slice, so avoid processing single rows only; suggestion: process at least 8-16 rows
*/ */
void scale(size_t factor, //valid range: 2 - 6 void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX
const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
ColorFormat colFmt, ColorFormat colFmt,
const ScalerCfg& cfg = ScalerCfg(), const ScalerCfg& cfg = ScalerCfg(),
int yFirst = 0, int yLast = std::numeric_limits<int>::max()); //slice of source image int yFirst = 0, int yLast = std::numeric_limits<int>::max()); //slice of source image
void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight,
uint32_t* trg, int trgWidth, int trgHeight); /**/ uint32_t* trg, int trgWidth, int trgHeight);
void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight,
/**/ uint32_t* trg, int trgWidth, int trgHeight);
enum SliceType
{
NN_SCALE_SLICE_SOURCE,
NN_SCALE_SLICE_TARGET,
};
void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, //pitch in bytes!
uint32_t* trg, int trgWidth, int trgHeight, int trgPitch,
SliceType st, int yFirst, int yLast);
//parameter tuning //parameter tuning
bool equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance); bool equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance);
//########################### implementation ###########################
inline
void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight,
uint32_t* trg, int trgWidth, int trgHeight)
{
nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t),
trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t),
NN_SCALE_SLICE_TARGET, 0, trgHeight);
}
} }
#endif #endif

View File

@ -1,13 +1,14 @@
// **************************************************************************** // ****************************************************************************
// * This file is part of the HqMAME project. It is distributed under * // * This file is part of the xBRZ project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// * * // * *
// * Additionally and as a special exception, the author gives permission * // * Additionally and as a special exception, the author gives permission *
// * to link the code of this program with the MAME library (or with modified * // * to link the code of this program with the following libraries *
// * versions of MAME that use the same license as MAME), and distribute * // * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two. You must obey the GNU General * // * linked combinations including the two: MAME, FreeFileSync, Snes9x *
// * Public License in all respects for all of the code used other than MAME. * // * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x. *
// * If you modify this file, you may extend this exception to your version * // * If you modify this file, you may extend this exception to your version *
// * of the file, but you are not obligated to do so. If you do not wish to * // * of the file, but you are not obligated to do so. If you do not wish to *
// * do so, delete this exception statement from your version. * // * do so, delete this exception statement from your version. *

268
filter/xbrz_tools.h Normal file
View File

@ -0,0 +1,268 @@
// ****************************************************************************
// * This file is part of the xBRZ project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// * *
// * Additionally and as a special exception, the author gives permission *
// * to link the code of this program with the following libraries *
// * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two: MAME, FreeFileSync, Snes9x *
// * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x. *
// * If you modify this file, you may extend this exception to your version *
// * of the file, but you are not obligated to do so. If you do not wish to *
// * do so, delete this exception statement from your version. *
// ****************************************************************************
#ifndef XBRZ_TOOLS_H_825480175091875
#define XBRZ_TOOLS_H_825480175091875
#include <cassert>
#include <algorithm>
#include <type_traits>
namespace xbrz
{
template <uint32_t N> inline
unsigned char getByte(uint32_t val) { return static_cast<unsigned char>((val >> (8 * N)) & 0xff); }
inline unsigned char getAlpha(uint32_t pix) { return getByte<3>(pix); }
inline unsigned char getRed (uint32_t pix) { return getByte<2>(pix); }
inline unsigned char getGreen(uint32_t pix) { return getByte<1>(pix); }
inline unsigned char getBlue (uint32_t pix) { return getByte<0>(pix); }
inline uint32_t makePixel(unsigned char a, unsigned char r, unsigned char g, unsigned char b) { return (a << 24) | (r << 16) | (g << 8) | b; }
inline uint32_t makePixel( unsigned char r, unsigned char g, unsigned char b) { return (r << 16) | (g << 8) | b; }
inline uint32_t rgb555to888(uint16_t pix) { return ((pix & 0x7C00) << 9) | ((pix & 0x03E0) << 6) | ((pix & 0x001F) << 3); }
inline uint32_t rgb565to888(uint16_t pix) { return ((pix & 0xF800) << 8) | ((pix & 0x07E0) << 5) | ((pix & 0x001F) << 3); }
inline uint16_t rgb888to555(uint32_t pix) { return static_cast<uint16_t>(((pix & 0xF80000) >> 9) | ((pix & 0x00F800) >> 6) | ((pix & 0x0000F8) >> 3)); }
inline uint16_t rgb888to565(uint32_t pix) { return static_cast<uint16_t>(((pix & 0xF80000) >> 8) | ((pix & 0x00FC00) >> 5) | ((pix & 0x0000F8) >> 3)); }
template <class Pix> inline
Pix* byteAdvance(Pix* ptr, int bytes)
{
using PixNonConst = typename std::remove_cv<Pix>::type;
using PixByte = typename std::conditional<std::is_same<Pix, PixNonConst>::value, char, const char>::type;
static_assert(std::is_integral<PixNonConst>::value, "Pix* is expected to be cast-able to char*");
return reinterpret_cast<Pix*>(reinterpret_cast<PixByte*>(ptr) + bytes);
}
//fill block with the given color
template <class Pix> inline
void fillBlock(Pix* trg, int pitch, Pix col, int blockWidth, int blockHeight)
{
//for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch))
// std::fill(trg, trg + blockWidth, col);
for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch))
for (int x = 0; x < blockWidth; ++x)
trg[x] = col;
}
//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!)
template <class PixSrc, class PixTrg, class PixConverter>
void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch,
/**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch,
int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
{
static_assert(std::is_integral<PixSrc>::value, "PixSrc* is expected to be cast-able to char*");
static_assert(std::is_integral<PixTrg>::value, "PixTrg* is expected to be cast-able to char*");
static_assert(std::is_same<decltype(pixCvrt(PixSrc())), PixTrg>::value, "PixConverter returning wrong pixel format");
if (srcPitch < srcWidth * static_cast<int>(sizeof(PixSrc)) ||
trgPitch < trgWidth * static_cast<int>(sizeof(PixTrg)))
{
assert(false);
return;
}
yFirst = std::max(yFirst, 0);
yLast = std::min(yLast, trgHeight);
if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return;
for (int y = yFirst; y < yLast; ++y)
{
const int ySrc = srcHeight * y / trgHeight;
const PixSrc* const srcLine = byteAdvance(src, ySrc * srcPitch);
PixTrg* const trgLine = byteAdvance(trg, y * trgPitch);
for (int x = 0; x < trgWidth; ++x)
{
const int xSrc = srcWidth * x / trgWidth;
trgLine[x] = pixCvrt(srcLine[xSrc]);
}
}
}
//nearest-neighbor (going over source image - fast for upscaling, since source is read only once
template <class PixSrc, class PixTrg, class PixConverter>
void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch,
/**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch,
int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
{
static_assert(std::is_integral<PixSrc>::value, "PixSrc* is expected to be cast-able to char*");
static_assert(std::is_integral<PixTrg>::value, "PixTrg* is expected to be cast-able to char*");
static_assert(std::is_same<decltype(pixCvrt(PixSrc())), PixTrg>::value, "PixConverter returning wrong pixel format");
if (srcPitch < srcWidth * static_cast<int>(sizeof(PixSrc)) ||
trgPitch < trgWidth * static_cast<int>(sizeof(PixTrg)))
{
assert(false);
return;
}
yFirst = std::max(yFirst, 0);
yLast = std::min(yLast, srcHeight);
if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return;
for (int y = yFirst; y < yLast; ++y)
{
//mathematically: ySrc = floor(srcHeight * yTrg / trgHeight)
// => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight
//keep within for loop to support MT input slices!
const int yTrgFirst = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight)
const int yTrgLast = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight)
const int blockHeight = yTrgLast - yTrgFirst;
if (blockHeight > 0)
{
const PixSrc* srcLine = byteAdvance(src, y * srcPitch);
/**/ PixTrg* trgLine = byteAdvance(trg, yTrgFirst * trgPitch);
int xTrgFirst = 0;
for (int x = 0; x < srcWidth; ++x)
{
const int xTrgLast = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth;
const int blockWidth = xTrgLast - xTrgFirst;
if (blockWidth > 0)
{
xTrgFirst = xTrgLast;
const auto trgPix = pixCvrt(srcLine[x]);
fillBlock(trgLine, trgPitch, trgPix, blockWidth, blockHeight);
trgLine += blockWidth;
}
}
}
}
}
template <class PixTrg, class PixConverter>
void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch,
/**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch,
int yFirst, int yLast, PixConverter pixCvrt /*convert uint32_t to PixTrg*/)
{
static_assert(std::is_integral<PixTrg>::value, "PixTrg* is expected to be cast-able to char*");
static_assert(std::is_same<decltype(pixCvrt(uint32_t())), PixTrg>::value, "PixConverter returning wrong pixel format");
if (srcPitch < srcWidth * static_cast<int>(sizeof(uint32_t)) ||
trgPitch < trgWidth * static_cast<int>(sizeof(PixTrg)))
{
assert(false);
return;
}
yFirst = std::max(yFirst, 0);
yLast = std::min(yLast, trgHeight);
if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return;
const double scaleX = static_cast<double>(trgWidth ) / srcWidth;
const double scaleY = static_cast<double>(trgHeight) / srcHeight;
//perf notes:
// -> double-based calculation is (slightly) faster than float
// -> precalculation gives significant boost; std::vector<> memory allocation is negligible!
struct CoeffsX
{
int x1 = 0;
int x2 = 0;
double xx1 = 0;
double x2x = 0;
};
std::vector<CoeffsX> buf(trgWidth);
for (int x = 0; x < trgWidth; ++x)
{
const int x1 = srcWidth * x / trgWidth;
int x2 = x1 + 1;
if (x2 == srcWidth) --x2;
const double xx1 = x / scaleX - x1;
const double x2x = 1 - xx1;
buf[x] = { x1, x2, xx1, x2x };
}
for (int y = yFirst; y < yLast; ++y)
{
const int y1 = srcHeight * y / trgHeight;
int y2 = y1 + 1;
if (y2 == srcHeight) --y2;
const double yy1 = y / scaleY - y1;
const double y2y = 1 - yy1;
const uint32_t* const srcLine = byteAdvance(src, y1 * srcPitch);
const uint32_t* const srcLineNext = byteAdvance(src, y2 * srcPitch);
PixTrg* const trgLine = byteAdvance(trg, y * trgPitch);
for (int x = 0; x < trgWidth; ++x)
{
//perf: do NOT "simplify" the variable layout without measurement!
const int x1 = buf[x].x1;
const int x2 = buf[x].x2;
const double xx1 = buf[x].xx1;
const double x2x = buf[x].x2x;
const double x2xy2y = x2x * y2y;
const double xx1y2y = xx1 * y2y;
const double x2xyy1 = x2x * yy1;
const double xx1yy1 = xx1 * yy1;
auto interpolate = [=](int offset)
{
/*
https://en.wikipedia.org/wiki/Bilinear_interpolation
(c11(x2 - x) + c21(x - x1)) * (y2 - y ) +
(c12(x2 - x) + c22(x - x1)) * (y - y1)
*/
const auto c11 = (srcLine [x1] >> (8 * offset)) & 0xff;
const auto c21 = (srcLine [x2] >> (8 * offset)) & 0xff;
const auto c12 = (srcLineNext[x1] >> (8 * offset)) & 0xff;
const auto c22 = (srcLineNext[x2] >> (8 * offset)) & 0xff;
return c11 * x2xy2y + c21 * xx1y2y +
c12 * x2xyy1 + c22 * xx1yy1;
};
const double bi = interpolate(0);
const double gi = interpolate(1);
const double ri = interpolate(2);
const double ai = interpolate(3);
const auto b = static_cast<uint32_t>(bi + 0.5);
const auto g = static_cast<uint32_t>(gi + 0.5);
const auto r = static_cast<uint32_t>(ri + 0.5);
const auto a = static_cast<uint32_t>(ai + 0.5);
const uint32_t trgPix = (a << 24) | (r << 16) | (g << 8) | b;
trgLine[x] = pixCvrt(trgPix);
}
}
}
}
#endif //XBRZ_TOOLS_H_825480175091875

View File

@ -321,13 +321,14 @@
<ClInclude Include="..\filter\blit.h" /> <ClInclude Include="..\filter\blit.h" />
<ClInclude Include="..\filter\epx.h" /> <ClInclude Include="..\filter\epx.h" />
<CustomBuild Include="..\filter\hq2x.h" /> <CustomBuild Include="..\filter\hq2x.h" />
<ClInclude Include="..\filter\xbrz-config.h" />
<ClInclude Include="..\filter\xbrz.h" /> <ClInclude Include="..\filter\xbrz.h" />
<CustomBuild Include="..\font.h" /> <CustomBuild Include="..\font.h" />
<CustomBuild Include="..\fxemu.h" /> <CustomBuild Include="..\fxemu.h" />
<CustomBuild Include="..\fxinst.h" /> <CustomBuild Include="..\fxinst.h" />
<CustomBuild Include="..\getset.h" /> <CustomBuild Include="..\getset.h" />
<CustomBuild Include="..\gfx.h" /> <CustomBuild Include="..\gfx.h" />
<ClInclude Include="..\filter\xbrz_config.h" />
<ClInclude Include="..\filter\xbrz_tools.h" />
<ClInclude Include="..\jma\7z.h" /> <ClInclude Include="..\jma\7z.h" />
<ClInclude Include="..\jma\aribitcd.h" /> <ClInclude Include="..\jma\aribitcd.h" />
<ClInclude Include="..\jma\ariconst.h" /> <ClInclude Include="..\jma\ariconst.h" />

View File

@ -231,9 +231,6 @@
<ClInclude Include="snes_ntsc_impl.h"> <ClInclude Include="snes_ntsc_impl.h">
<Filter>Filter</Filter> <Filter>Filter</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\filter\xbrz-config.h">
<Filter>Filter</Filter>
</ClInclude>
<ClInclude Include="..\filter\xbrz.h"> <ClInclude Include="..\filter\xbrz.h">
<Filter>Filter</Filter> <Filter>Filter</Filter>
</ClInclude> </ClInclude>
@ -246,6 +243,12 @@
<ClInclude Include="cgMini.h"> <ClInclude Include="cgMini.h">
<Filter>GUI\VideoDriver</Filter> <Filter>GUI\VideoDriver</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\filter\xbrz_config.h">
<Filter>Filter</Filter>
</ClInclude>
<ClInclude Include="..\filter\xbrz_tools.h">
<Filter>Filter</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\bsx.cpp"> <ClCompile Include="..\bsx.cpp">