diff --git a/.gitignore b/.gitignore index ed0bca5fed..cbbe22f3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ Windows/ipch # For ppsspp.ini, etc. ppsspp.ini +PPSSPPControls.dat # Qt Linguist files *.qm diff --git a/Core/Config.cpp b/Core/Config.cpp index e211fe5873..7f5ec88649 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -100,7 +100,6 @@ void Config::Load(const char *iniFileName) graphics->Get("SSAA", &SSAntiAliasing, 0); graphics->Get("VBO", &bUseVBO, false); graphics->Get("FrameSkip", &iFrameSkip, 0); - graphics->Get("XBRZTexScalingLevel", &iXBRZTexScalingLevel, 1); graphics->Get("UseMediaEngine", &bUseMediaEngine, true); #ifdef USING_GLES2 graphics->Get("AnisotropyLevel", &iAnisotropyLevel, 0); @@ -116,6 +115,8 @@ void Config::Load(const char *iniFileName) #else graphics->Get("MipMap", &bMipMap, false); #endif + graphics->Get("TexScalingLevel", &iTexScalingLevel, 1); + graphics->Get("TexScalingType", &iTexScalingType, 0); IniFile::Section *sound = iniFile.GetOrCreateSection("Sound"); sound->Get("Enable", &bEnableSound, true); @@ -190,7 +191,6 @@ void Config::Save() graphics->Set("SSAA", SSAntiAliasing); graphics->Set("VBO", bUseVBO); graphics->Set("FrameSkip", iFrameSkip); - graphics->Set("XBRZTexScalingLevel", iXBRZTexScalingLevel); graphics->Set("UseMediaEngine", bUseMediaEngine); graphics->Set("AnisotropyLevel", iAnisotropyLevel); graphics->Set("VertexCache", bVertexCache); @@ -198,7 +198,8 @@ void Config::Save() graphics->Set("StretchToDisplay", bStretchToDisplay); graphics->Set("TrueColor", bTrueColor); graphics->Set("MipMap", bMipMap); - graphics->Set("XBRZTexScalingLevel", iXBRZTexScalingLevel); + graphics->Set("TexScalingLevel", iTexScalingLevel); + graphics->Set("TexScalingType", iTexScalingType); IniFile::Section *sound = iniFile.GetOrCreateSection("Sound"); sound->Set("Enable", bEnableSound); diff --git a/Core/Config.h b/Core/Config.h index 6ff1fc31d4..8058bf51e2 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -72,7 +72,8 @@ public: int iAnisotropyLevel; bool bTrueColor; bool bMipMap; - int iXBRZTexScalingLevel; + int iTexScalingLevel; // 1 = off, 2 = 2x, ..., 5 = 5x + int iTexScalingType; // 0 = xBRZ, 1 = Hybrid // Sound bool bEnableSound; diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 5ecd00a734..11c017fc20 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -1221,7 +1221,7 @@ void TextureCache::LoadTextureLevel(TexCacheEntry &entry, int level) { u32* pixelData = (u32*)finalBuf; - int scaleFactor = g_Config.iXBRZTexScalingLevel; + int scaleFactor = g_Config.iTexScalingLevel; // Don't scale the PPGe texture. if (entry.addr > 0x05000000 && entry.addr < 0x08800000) diff --git a/GPU/GLES/TextureScaler.cpp b/GPU/GLES/TextureScaler.cpp index ff32acb861..870f44b112 100644 --- a/GPU/GLES/TextureScaler.cpp +++ b/GPU/GLES/TextureScaler.cpp @@ -41,10 +41,11 @@ namespace p = std::placeholders; #endif namespace { + // convert 4444 image to 8888, parallelizable void convert4444(u16* data, u32* out, int width, int l, int u) { for(int y = l; y < u; ++y) { for(int x = 0; x < width; ++x) { - u32 val = ((u16*)data)[y*width + x]; + u32 val = data[y*width + x]; u32 r = ((val>>12) & 0xF) * 17; u32 g = ((val>> 8) & 0xF) * 17; u32 b = ((val>> 4) & 0xF) * 17; @@ -54,10 +55,11 @@ namespace { } } + // convert 565 image to 8888, parallelizable void convert565(u16* data, u32* out, int width, int l, int u) { for(int y = l; y < u; ++y) { for(int x = 0; x < width; ++x) { - u32 val = ((u16*)data)[y*width + x]; + u32 val = data[y*width + x]; u32 r = Convert5To8((val>>11) & 0x1F); u32 g = Convert6To8((val>> 5) & 0x3F); u32 b = Convert5To8((val ) & 0x1F); @@ -66,10 +68,11 @@ namespace { } } + // convert 5551 image to 8888, parallelizable void convert5551(u16* data, u32* out, int width, int l, int u) { for(int y = l; y < u; ++y) { for(int x = 0; x < width; ++x) { - u32 val = ((u16*)data)[y*width + x]; + u32 val = data[y*width + x]; u32 r = Convert5To8((val>>11) & 0x1F); u32 g = Convert5To8((val>> 6) & 0x1F); u32 b = Convert5To8((val>> 1) & 0x1F); @@ -78,6 +81,174 @@ namespace { } } } + + // 3x3 convolution with Neumann boundary conditions, parallelizable + // quite slow, could be sped up a lot + // especially handling of separable kernels + void convolve3x3(u32* data, u32* out, const int kernel[3][3], int width, int height, int l, int u) { + for(int y = l; y < u; ++y) { + for(int x = 0; x < width; ++x) { + int val = 0; + for(int yoff = -1; yoff <= 1; ++yoff) { + int yy = std::max(std::min(y+yoff, height-1), 0); + for(int xoff = -1; xoff <= 1; ++xoff) { + int xx = std::max(std::min(x+xoff, width-1), 0); + val += data[yy*width + xx] * kernel[yoff+1][xoff+1]; + } + } + out[y*width + x] = abs(val); + } + } + } + + #define R(_col) ((_col>> 0)&0xFF) + #define G(_col) ((_col>> 8)&0xFF) + #define B(_col) ((_col>>16)&0xFF) + #define A(_col) ((_col>>24)&0xFF) + + #define DISTANCE(_p1,_p2) ( abs((int)((int)(R(_p1))-R(_p2))) + abs((int)((int)(G(_p1))-G(_p2))) \ + + abs((int)((int)(B(_p1)-B(_p2)))) + abs((int)((int)(A(_p1)-A(_p2)))) ) + + void generateDistanceMask(u32* data, u32* out, int width, int height, int l, int u) { + for(int y = l; y < u; ++y) { + for(int x = 0; x < width; ++x) { + out[y*width + x] = 0; + u32 center = data[y*width + x]; + for(int yoff = -1; yoff <= 1; ++yoff) { + int yy = y+yoff; + if(yy == height || yy == -1) { + out[y*width + x] += 1200; // assume distance at borders, usually makes for better result + continue; + } + for(int xoff = -1; xoff <= 1; ++xoff) { + if(yoff == 0 && xoff == 0) continue; + int xx = x+xoff; + if(xx == width || xx == -1) { + out[y*width + x] += 400; // assume distance at borders, usually makes for better result + continue; + } + out[y*width + x] += DISTANCE(data[yy*width + xx], center); + } + } + } + } + } + + // this is sadly much faster than an inline function with a loop, at least in VC10 + #define MIX_PIXELS(p0, p1, p2, factors) \ + ((R(p0)*factors[0] + R(p1)*factors[1] + R(p2)*factors[2])/255 << 0 ) | \ + ((G(p0)*factors[0] + G(p1)*factors[1] + G(p2)*factors[2])/255 << 8 ) | \ + ((B(p0)*factors[0] + B(p1)*factors[1] + B(p2)*factors[2])/255 << 16 ) | \ + ((A(p0)*factors[0] + A(p1)*factors[1] + A(p2)*factors[2])/255 << 24 ) + + void mix(u32* data, u32* source, u32* mask, u32 maskmax, int width, int l, int u) { + for(int y = l; y < u; ++y) { + for(int x = 0; x < width; ++x) { + int pos = y*width + x; + u8 mixFactors[3] = {0, (std::min(mask[pos], maskmax)*255)/maskmax, 0 }; + mixFactors[0] = 255-mixFactors[1]; + data[pos] = MIX_PIXELS(data[pos], source[pos], 0, mixFactors); + if(A(source[pos]) == 0) data[pos] = data[pos] & 0x00FFFFFF; // xBRZ always does a better job with hard alpha + } + } + } + + const static u8 BILINEAR_FACTORS[4][5][3] = { + { { 76,179, 0}, { 0,179, 76}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0} }, // x2 + { { 85,170, 0}, { 0,255, 0}, { 0,170, 85}, { 0, 0, 0}, { 0, 0, 0} }, // x3 + { {102,153, 0}, { 51,204, 0}, { 0,204, 51}, { 0,153,102}, { 0, 0, 0} }, // x4 + { {102,153, 0}, { 51,204, 0}, { 0,255, 0}, { 0,204, 51}, { 0,153,102} }, // x5 + }; + // integral bilinear upscaling by factor f, horizontal part + template + void bilinearHt(u32* data, u32* out, int w, int l, int u) { + static_assert(f>1 && f<=5, "Bilinear scaling only implemented for factors 2 to 5"); + int outw = w*f; + for(int y = l; y < u; ++y) { + for(int x = 0; x < w; ++x) { + int inpos = y*w + x; + u32 left = data[inpos - (x==0 ?0:1)]; + u32 center = data[inpos]; + u32 right = data[inpos + (x==w-1?0:1)]; + for(int i=0; i(data, out, w, l, u); break; + case 3: bilinearHt<3>(data, out, w, l, u); break; + case 4: bilinearHt<4>(data, out, w, l, u); break; + case 5: bilinearHt<5>(data, out, w, l, u); break; + default: ERROR_LOG(G3D, "Bilinear upsampling only implemented for factors 2 to 5"); + } + } + // integral bilinear upscaling by factor f, vertical part + // gl/gu == global lower and upper bound + template + void bilinearVt(u32* data, u32* out, int w, int gl, int gu, int l, int u) { + static_assert(f>1 && f<=5, "Bilinear scaling only implemented for 2x, 3x, 4x, and 5x"); + int outw = w*f; + for(int y = l; y < u; ++y) { + for(int x = 0; x < outw; ++x) { + u32 upper = data[(y - (y==gl ?0:1)) * outw + x]; + u32 center = data[y * outw + x]; + u32 lower = data[(y + (y==gu-1?0:1)) * outw + x]; + for(int i=0; i(data, out, w, gl, gu, l, u); break; + case 3: bilinearVt<3>(data, out, w, gl, gu, l, u); break; + case 4: bilinearVt<4>(data, out, w, gl, gu, l, u); break; + case 5: bilinearVt<5>(data, out, w, gl, gu, l, u); break; + default: ERROR_LOG(G3D, "Bilinear upsampling only implemented for factors 2 to 5"); + } + } + + #undef MIX_PIXELS + #undef DISTANCE + #undef R + #undef G + #undef B + #undef A + + // used for debugging texture scaling (writing textures to files) + static int g_imgCount = 0; + void dbgPPM(int w, int h, u8* pixels, const char* prefix = "dbg") { // 3 component RGB + char fn[32]; + snprintf(fn, 32, "%s%04d.ppm", prefix, g_imgCount++); + FILE *fp = fopen(fn, "wb"); + fprintf(fp, "P6\n%d %d\n255\n", w, h); + for(int j = 0; j < h; ++j) { + for(int i = 0; i < w; ++i) { + static unsigned char color[3]; + color[0] = pixels[(j*w+i)*4+0]; /* red */ + color[1] = pixels[(j*w+i)*4+1]; /* green */ + color[2] = pixels[(j*w+i)*4+2]; /* blue */ + fwrite(color, 1, 3, fp); + } + } + fclose(fp); + } + void dbgPGM(int w, int h, u32* pixels, const char* prefix = "dbg") { // 1 component + char fn[32]; + snprintf(fn, 32, "%s%04d.pgm", prefix, g_imgCount++); + FILE *fp = fopen(fn, "wb"); + fprintf(fp, "P5\n%d %d\n65536\n", w, h); + for(int j = 0; j < h; ++j) { + for(int i = 0; i < w; ++i) { + fwrite((pixels+(j*w+i)), 1, 2, fp); + } + } + fclose(fp); + } } @@ -89,40 +260,28 @@ void TextureScaler::Scale(u32* &data, GLenum &dstFmt, int &width, int &height, i double t_start = real_time_now(); #endif - // depending on the factor and texture sizes, these can be pretty large (25 MB for a 512 by 512 texture with scaling factor 5) bufInput.resize(width*height); // used to store the input image image if it needs to be reformatted bufOutput.resize(width*height*factor*factor); // used to store the upscaled image - u32 *xbrzInputBuf = bufInput.data(); - u32 *xbrzBuf = bufOutput.data(); + u32 *inputBuf = bufInput.data(); + u32 *outputBuf = bufOutput.data(); - // convert texture to correct format for xBRZ - switch(dstFmt) { - case GL_UNSIGNED_BYTE: - xbrzInputBuf = data; // already fine - break; - - case GL_UNSIGNED_SHORT_4_4_4_4: - GlobalThreadPool::Loop(bind(&convert4444, (u16*)data, xbrzInputBuf, width, p::_1, p::_2), 0, height); - break; - - case GL_UNSIGNED_SHORT_5_6_5: - GlobalThreadPool::Loop(bind(&convert565, (u16*)data, xbrzInputBuf, width, p::_1, p::_2), 0, height); - break; - - case GL_UNSIGNED_SHORT_5_5_5_1: - GlobalThreadPool::Loop(bind(&convert5551, (u16*)data, xbrzInputBuf, width, p::_1, p::_2), 0, height); - break; - - default: - ERROR_LOG(G3D, "iXBRZTexScaling: unsupported texture format"); - } + // convert texture to correct format for scaling + ConvertTo8888(dstFmt, data, inputBuf, width, height); // scale - xbrz::ScalerCfg cfg; - GlobalThreadPool::Loop(bind(&xbrz::scale, factor, xbrzInputBuf, xbrzBuf, width, height, cfg, p::_1, p::_2), 0, height); + switch(g_Config.iTexScalingType) { + case XBRZ: + ScaleXBRZ(factor, inputBuf, outputBuf, width, height); + break; + case HYBRID: + ScaleHybrid(factor, inputBuf, outputBuf, width, height); + break; + default: + ERROR_LOG(G3D, "Unknown scaling type: %d", g_Config.iTexScalingType); + } // update values accordingly - data = xbrzBuf; + data = outputBuf; dstFmt = GL_UNSIGNED_BYTE; width *= factor; height *= factor; @@ -135,3 +294,68 @@ void TextureScaler::Scale(u32* &data, GLenum &dstFmt, int &width, int &height, i } #endif } + +void TextureScaler::ScaleXBRZ(int factor, u32* source, u32* dest, int width, int height) { + xbrz::ScalerCfg cfg; + GlobalThreadPool::Loop(std::bind(&xbrz::scale, factor, source, dest, width, height, cfg, p::_1, p::_2), 0, height); +} + +void TextureScaler::ScaleBilinear(int factor, u32* source, u32* dest, int width, int height) { + bufTmp1.resize(width*height*factor); + u32 *tmpBuf = bufTmp1.data(); + GlobalThreadPool::Loop(std::bind(&bilinearH, factor, source, tmpBuf, width, p::_1, p::_2), 0, height); + GlobalThreadPool::Loop(std::bind(&bilinearV, factor, tmpBuf, dest, width, 0, height, p::_1, p::_2), 0, height); +} + +void TextureScaler::ScaleHybrid(int factor, u32* source, u32* dest, int width, int height) { + // Basic algorithm: + // 1) determine a feature mask C based on a sobel-ish filter + splatting, and upscale that mask bilinearly + // 2) generate 2 scaled images: A - using Bilinear filtering, B - using xBRZ + // 3) output = A*C + B*(1-C) + + const static int KERNEL_SPLAT[3][3] = { + { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } + }; + + bufTmp1.resize(width*height); + bufTmp2.resize(width*height*factor*factor); + bufTmp3.resize(width*height*factor*factor); + GlobalThreadPool::Loop(std::bind(&generateDistanceMask, source, bufTmp1.data(), width, height, p::_1, p::_2), 0, height); + GlobalThreadPool::Loop(std::bind(&convolve3x3, bufTmp1.data(), bufTmp2.data(), KERNEL_SPLAT, width, height, p::_1, p::_2), 0, height); + ScaleBilinear(factor, bufTmp2.data(), bufTmp3.data(), width, height); + // mask C is now in bufTmp3 + + ScaleXBRZ(factor, source, bufTmp2.data(), width, height); + // xBRZ upscaled source is in bufTmp2 + + ScaleBilinear(factor, source, dest, width, height); + // Bilinear upscaled source is in dest + + // Now we can mix it all together + // The factor 8192 was found through practical testing on a variety of textures + GlobalThreadPool::Loop(std::bind(&mix, dest, bufTmp2.data(), bufTmp3.data(), 8192, width*factor, p::_1, p::_2), 0, height*factor); +} + +void TextureScaler::ConvertTo8888(GLenum format, u32* source, u32* &dest, int width, int height) { + switch(format) { + case GL_UNSIGNED_BYTE: + dest = source; // already fine + break; + + case GL_UNSIGNED_SHORT_4_4_4_4: + GlobalThreadPool::Loop(std::bind(&convert4444, (u16*)source, dest, width, p::_1, p::_2), 0, height); + break; + + case GL_UNSIGNED_SHORT_5_6_5: + GlobalThreadPool::Loop(std::bind(&convert565, (u16*)source, dest, width, p::_1, p::_2), 0, height); + break; + + case GL_UNSIGNED_SHORT_5_5_5_1: + GlobalThreadPool::Loop(std::bind(&convert5551, (u16*)source, dest, width, p::_1, p::_2), 0, height); + break; + + default: + dest = source; + ERROR_LOG(G3D, "iXBRZTexScaling: unsupported texture format"); + } +} diff --git a/GPU/GLES/TextureScaler.h b/GPU/GLES/TextureScaler.h index becb0dcaef..11b69fa966 100644 --- a/GPU/GLES/TextureScaler.h +++ b/GPU/GLES/TextureScaler.h @@ -31,7 +31,16 @@ public: void Scale(u32* &data, GLenum &dstfmt, int &width, int &height, int factor); + enum { XBRZ= 0, HYBRID = 1 }; + private: - SimpleBuf bufInput; - SimpleBuf bufOutput; + void ScaleXBRZ(int factor, u32* source, u32* dest, int width, int height); + void ScaleBilinear(int factor, u32* source, u32* dest, int width, int height); + void ScaleHybrid(int factor, u32* source, u32* dest, int width, int height); + void ConvertTo8888(GLenum format, u32* source, u32* &dest, int width, int height); + + // depending on the factor and texture sizes, these can get pretty large + // maximum is (100 MB total for a 512 by 512 texture with scaling factor 5 and hybrid scaling) + // of course, scaling factor 5 is totally silly anyway + SimpleBuf bufInput, bufOutput, bufTmp1, bufTmp2, bufTmp3; }; diff --git a/UI/MenuScreens.cpp b/UI/MenuScreens.cpp index 6011e0df13..81c45326bd 100644 --- a/UI/MenuScreens.cpp +++ b/UI/MenuScreens.cpp @@ -756,20 +756,20 @@ void GraphicsScreenP2::render() { g_Config.iAnisotropyLevel = 0; } - bool XBRZTexScaling = g_Config.iXBRZTexScalingLevel > 1; - UICheckBox(GEN_ID, x, y += stride, gs->T("xBRZ Texture Scaling"), ALIGN_TOPLEFT, &XBRZTexScaling); - if (XBRZTexScaling) { - if (g_Config.iXBRZTexScalingLevel <= 1) - g_Config.iXBRZTexScalingLevel = 2; + bool TexScaling = g_Config.iTexScalingLevel > 1; + UICheckBox(GEN_ID, x, y += stride, gs->T("xBRZ Texture Scaling"), ALIGN_TOPLEFT, &TexScaling); + if (TexScaling) { + if (g_Config.iTexScalingLevel <= 1) + g_Config.iTexScalingLevel = 2; ui_draw2d.DrawText(UBUNTU24, gs->T("Level :"), x + 60, y += stride + 10, 0xFFFFFFFF, ALIGN_LEFT); HLinear hlinear1(x + 160 , y + 5, 20); if (UIButton(GEN_ID, hlinear1, 45, 0, "2x", ALIGN_LEFT)) - g_Config.iXBRZTexScalingLevel = 2; + g_Config.iTexScalingLevel = 2; if (UIButton(GEN_ID, hlinear1, 45, 0, "3x", ALIGN_LEFT)) - g_Config.iXBRZTexScalingLevel = 3; + g_Config.iTexScalingLevel = 3; } else { - g_Config.iXBRZTexScalingLevel = 1; + g_Config.iTexScalingLevel = 1; } UIEnd(); } diff --git a/Windows/WndMainWindow.cpp b/Windows/WndMainWindow.cpp index c3825652a3..acf24b7732 100644 --- a/Windows/WndMainWindow.cpp +++ b/Windows/WndMainWindow.cpp @@ -34,6 +34,7 @@ #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" #include "native/image/png_load.h" +#include "GPU/GLES/TextureScaler.h" #ifdef THEMES #include "XPTheme.h" @@ -175,8 +176,12 @@ namespace MainWindow ResizeDisplay(); } - void setXbrzTexScaling(int num) { - g_Config.iXBRZTexScalingLevel = num; + void setTexScalingLevel(int num) { + g_Config.iTexScalingLevel = num; + if(gpu) gpu->ClearCacheNextFrame(); + } + void setTexScalingType(int num) { + g_Config.iTexScalingType = num; if(gpu) gpu->ClearCacheNextFrame(); } @@ -501,19 +506,26 @@ namespace MainWindow break; case ID_TEXTURESCALING_OFF: - setXbrzTexScaling(1); + setTexScalingLevel(1); break; - case ID_TEXTURESCALING_2XBRZ: - setXbrzTexScaling(2); + case ID_TEXTURESCALING_2X: + setTexScalingLevel(2); break; - case ID_TEXTURESCALING_3XBRZ: - setXbrzTexScaling(3); + case ID_TEXTURESCALING_3X: + setTexScalingLevel(3); break; - case ID_TEXTURESCALING_4XBRZ: - setXbrzTexScaling(4); + case ID_TEXTURESCALING_4X: + setTexScalingLevel(4); break; - case ID_TEXTURESCALING_5XBRZ: - setXbrzTexScaling(5); + case ID_TEXTURESCALING_5X: + setTexScalingLevel(5); + break; + + case ID_TEXTURESCALING_XBRZ: + setTexScalingType(TextureScaler::XBRZ); + break; + case ID_TEXTURESCALING_HYBRID: + setTexScalingType(TextureScaler::HYBRID); break; case ID_OPTIONS_BUFFEREDRENDERING: @@ -824,13 +836,21 @@ namespace MainWindow static const int texscalingitems[] = { ID_TEXTURESCALING_OFF, - ID_TEXTURESCALING_2XBRZ, - ID_TEXTURESCALING_3XBRZ, - ID_TEXTURESCALING_4XBRZ, - ID_TEXTURESCALING_5XBRZ, + ID_TEXTURESCALING_2X, + ID_TEXTURESCALING_3X, + ID_TEXTURESCALING_4X, + ID_TEXTURESCALING_5X, }; for (int i = 0; i < 5; i++) { - CheckMenuItem(menu, texscalingitems[i], MF_BYCOMMAND | ((i == g_Config.iXBRZTexScalingLevel-1) ? MF_CHECKED : MF_UNCHECKED)); + CheckMenuItem(menu, texscalingitems[i], MF_BYCOMMAND | ((i == g_Config.iTexScalingLevel-1) ? MF_CHECKED : MF_UNCHECKED)); + } + + static const int texscalingtypeitems[] = { + ID_TEXTURESCALING_XBRZ, + ID_TEXTURESCALING_HYBRID, + }; + for (int i = 0; i < 3; i++) { + CheckMenuItem(menu, texscalingtypeitems[i], MF_BYCOMMAND | ((i == g_Config.iTexScalingType) ? MF_CHECKED : MF_UNCHECKED)); } } diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 36fcd25ac4..58592b51f5 100644 Binary files a/Windows/ppsspp.rc and b/Windows/ppsspp.rc differ diff --git a/Windows/resource.h b/Windows/resource.h index 444cd77718..d74c24156c 100644 Binary files a/Windows/resource.h and b/Windows/resource.h differ diff --git a/ext/xbrz/xbrz.cpp b/ext/xbrz/xbrz.cpp index a03720683f..48ea561121 100644 --- a/ext/xbrz/xbrz.cpp +++ b/ext/xbrz/xbrz.cpp @@ -26,9 +26,10 @@ unsigned char getByte(uint32_t val) { return static_cast((val >> // adjusted for RGBA // - Durante -inline unsigned char getRed (uint32_t val) { return getByte<3>(val); } -inline unsigned char getGreen(uint32_t val) { return getByte<2>(val); } -inline unsigned char getBlue (uint32_t val) { return getByte<1>(val); } +inline unsigned char getRed (uint32_t val) { return getByte<0>(val); } +inline unsigned char getGreen(uint32_t val) { return getByte<1>(val); } +inline unsigned char getBlue (uint32_t val) { return getByte<2>(val); } +inline unsigned char getAlpha(uint32_t val) { return getByte<3>(val); } template inline T abs(T value) @@ -396,6 +397,31 @@ double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight) return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r)); } +// distance function taking alpha distance into account +inline +double distYCbCrA(uint32_t pix1, uint32_t pix2, double lumaWeight) +{ + //http://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion + //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first! + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //we may delay division by 255 to after matrix multiplication + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); // + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double! + + const double k_b = 0.0722; //ITU-R BT.709 conversion + const double k_r = 0.2126; // + const double k_g = 1 - k_b - k_r; + + 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); + + //we skip division by 255 to have similar range like other distance functions + return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r)+ square(static_cast(getAlpha(pix1)) - getAlpha(pix2))); +} + inline double distYUV(uint32_t pix1, uint32_t pix2, double luminanceWeight) @@ -443,8 +469,8 @@ double colorDist(uint32_t pix1, uint32_t pix2, double luminanceWeight) //return distLAB(pix1, pix2); //return distNonLinearRGB(pix1, pix2); //return distYUV(pix1, pix2, luminanceWeight); - - return distYCbCr(pix1, pix2, luminanceWeight); + //return distYCbCr(pix1, pix2, luminanceWeight); + return distYCbCrA(pix1, pix2, luminanceWeight); }