1 /*************************************************************************** 2 * Copyright (C) 2007 by Sindre Aamås * 3 * aamas@stud.ntnu.no * 4 * * 5 * This program is free software; you can redistribute it and/or modify * 6 * it under the terms of the GNU General Public License version 2 as * 7 * published by the Free Software Foundation. * 8 * * 9 * This program is distributed in the hope that it will be useful, * 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 * GNU General Public License version 2 for more details. * 13 * * 14 * You should have received a copy of the GNU General Public License * 15 * version 2 along with this program; if not, write to the * 16 * Free Software Foundation, Inc., * 17 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 18 ***************************************************************************/ 19 #include "video.h" 20 #include "savestate.h" 21 #include <cstring> 22 #include <algorithm> 23 #include <cmath> 24 25 /* GBC colour correction factors */ 26 #define GBC_CC_LUM 0.94f 27 #define GBC_CC_R 0.82f 28 #define GBC_CC_G 0.665f 29 #define GBC_CC_B 0.73f 30 #define GBC_CC_RG 0.125f 31 #define GBC_CC_RB 0.195f 32 #define GBC_CC_GR 0.24f 33 #define GBC_CC_GB 0.075f 34 #define GBC_CC_BR -0.06f 35 #define GBC_CC_BG 0.21f 36 37 namespace gambatte 38 { setDmgPaletteColor(const unsigned index,const video_pixel_t rgb32)39 void LCD::setDmgPaletteColor(const unsigned index, const video_pixel_t rgb32) 40 { 41 dmgColorsRgb32_[index] = rgb32; 42 } 43 setDmgPalette(video_pixel_t * const palette,const video_pixel_t * const dmgColors,const unsigned data)44 void LCD::setDmgPalette(video_pixel_t *const palette, const video_pixel_t *const dmgColors, const unsigned data) 45 { 46 palette[0] = dmgColors[data & 3]; 47 palette[1] = dmgColors[data >> 2 & 3]; 48 palette[2] = dmgColors[data >> 4 & 3]; 49 palette[3] = dmgColors[data >> 6 & 3]; 50 } 51 setColorCorrection(bool colorCorrection_)52 void LCD::setColorCorrection(bool colorCorrection_) 53 { 54 colorCorrection = colorCorrection_; 55 refreshPalettes(); 56 } 57 setColorCorrectionMode(unsigned colorCorrectionMode_)58 void LCD::setColorCorrectionMode(unsigned colorCorrectionMode_) 59 { 60 // Note: We only have two modes, so could make 'colorCorrectionMode' 61 // a bool... but may want to add other modes in the future 62 // (e.g. a special GBA colour correction mode for when the GBA 63 // flag is set in Shantae/Zelda: Oracle/etc.) 64 colorCorrectionMode = colorCorrectionMode_; 65 refreshPalettes(); 66 } 67 setColorCorrectionBrightness(float colorCorrectionBrightness_)68 void LCD::setColorCorrectionBrightness(float colorCorrectionBrightness_) 69 { 70 colorCorrectionBrightness = colorCorrectionBrightness_; 71 refreshPalettes(); 72 } 73 setDarkFilterLevel(unsigned darkFilterLevel_)74 void LCD::setDarkFilterLevel(unsigned darkFilterLevel_) 75 { 76 darkFilterLevel = darkFilterLevel_; 77 refreshPalettes(); 78 } 79 LCD(const unsigned char * const oamram,const unsigned char * const vram,const VideoInterruptRequester memEventRequester)80 LCD::LCD(const unsigned char *const oamram, const unsigned char *const vram, const VideoInterruptRequester memEventRequester) : 81 ppu_(nextM0Time_, oamram, vram), 82 eventTimes_(memEventRequester), 83 statReg_(0), 84 m2IrqStatReg_(0), 85 m1IrqStatReg_(0) 86 { 87 std::memset( bgpData_, 0, sizeof bgpData_); 88 std::memset(objpData_, 0, sizeof objpData_); 89 90 for (std::size_t i = 0; i < sizeof(dmgColorsRgb32_) / sizeof(dmgColorsRgb32_[0]); ++i) 91 { 92 #ifdef VIDEO_RGB565 93 uint16_t dmgColors[4]={0xFFFF, //11111 111111 11111 94 0xAD55, //10101 101010 10101 95 0x52AA, //01010 010101 01010 96 0x0000};//00000 000000 00000 97 setDmgPaletteColor(i, dmgColors[i&3]); 98 #elif defined(VIDEO_ABGR1555) 99 uint16_t dmgColors[4]={0xEFFF, //11111 111111 11111 100 0x56B5, //10101 10101 10101 101 0x294A, //01010 01010 01010 102 0x0000};//00000 000000 00000 103 setDmgPaletteColor(i, dmgColors[i&3]); 104 #else 105 setDmgPaletteColor(i, (3 - (i & 3)) * 85 * 0x010101); 106 #endif 107 } 108 109 reset(oamram, vram, false); 110 setVideoBuffer(0, 160); 111 112 setColorCorrection(true); 113 } 114 doCgbColorChange(unsigned char * const pdata,video_pixel_t * const palette,unsigned index,const unsigned data)115 void LCD::doCgbColorChange(unsigned char *const pdata, 116 video_pixel_t *const palette, unsigned index, const unsigned data) 117 { 118 pdata[index] = data; 119 index >>= 1; 120 palette[index] = gbcToRgb32(pdata[index << 1] | pdata[(index << 1) + 1] << 8); 121 } 122 setVideoBuffer(video_pixel_t * const videoBuf,const int pitch)123 void LCD::setVideoBuffer(video_pixel_t *const videoBuf, const int pitch) 124 { 125 ppu_.setFrameBuf(videoBuf, pitch); 126 } 127 clear(video_pixel_t * buf,const unsigned long color,const int dpitch)128 static void clear(video_pixel_t *buf, const unsigned long color, const int dpitch) 129 { 130 unsigned lines = 144; 131 132 while (lines--) 133 { 134 std::fill_n(buf, 160, color); 135 buf += dpitch; 136 } 137 } 138 setDmgPaletteColor(const unsigned palNum,const unsigned colorNum,const video_pixel_t rgb32)139 void LCD::setDmgPaletteColor(const unsigned palNum, const unsigned colorNum, const video_pixel_t rgb32) 140 { 141 if (palNum > 2 || colorNum > 3) 142 return; 143 144 setDmgPaletteColor(palNum * 4 | colorNum, rgb32); 145 refreshPalettes(); 146 } 147 updateScreen(const bool blanklcd,const unsigned long cycleCounter)148 void LCD::updateScreen(const bool blanklcd, const unsigned long cycleCounter) 149 { 150 update(cycleCounter); 151 152 if (blanklcd && ppu_.frameBuf().fb()) 153 { 154 const video_pixel_t color = ppu_.cgb() ? gbcToRgb32(0xFFFF) : dmgColorsRgb32_[0]; 155 clear(ppu_.frameBuf().fb(), color, ppu_.frameBuf().pitch()); 156 } 157 } 158 159 // RGB range: [0,1] darkenRgb(float & r,float & g,float & b)160 void LCD::darkenRgb(float &r, float &g, float &b) 161 { 162 // Note: This is *very* approximate... 163 // - Should be done in linear colour space. It isn't. 164 // - Should alter brightness by performing an RGB->HSL->RGB 165 // conversion. We just do simple linear scaling instead. 166 // Basically, this is intended for use on devices that are 167 // too weak to run shaders (i.e. why would you want a 'dark filter' 168 // if your device supports proper LCD shaders?). We therefore 169 // cut corners for the sake of performance... 170 // 171 // Constants 172 // > Luminosity factors: photometric/digital ITU BT.709 173 static const float lumaR = 0.2126f; 174 static const float lumaG = 0.7152f; 175 static const float lumaB = 0.0722f; 176 // Calculate luminosity 177 float luma = (lumaR * r) + (lumaG * g) + (lumaB * b); 178 // Get 'darkness' scaling factor 179 // > User set 'dark filter' level scaled by current luminosity 180 // (i.e. lighter colours affected more than darker colours) 181 float darkFactor = 1.0f - ((static_cast<float>(darkFilterLevel) * 0.01f) * luma); 182 darkFactor = darkFactor < 0.0f ? 0.0f : darkFactor; 183 // Perform scaling... 184 r = r * darkFactor; 185 g = g * darkFactor; 186 b = b * darkFactor; 187 } 188 gbcToRgb32(const unsigned bgr15)189 video_pixel_t LCD::gbcToRgb32(const unsigned bgr15) 190 { 191 const unsigned r = bgr15 & 0x1F; 192 const unsigned g = bgr15 >> 5 & 0x1F; 193 const unsigned b = bgr15 >> 10 & 0x1F; 194 195 unsigned rFinal = 0; 196 unsigned gFinal = 0; 197 unsigned bFinal = 0; 198 199 // Constants 200 // (Don't know whether floating-point rounding modes can be changed 201 // dynamically at run time, so can't rely on constant folding of 202 // inline [float / float] expressions - just play it safe...) 203 static const float rgbMax = 31.0; 204 static const float rgbMaxInv = 1.0 / rgbMax; 205 206 bool isDark = false; 207 208 if (colorCorrection) 209 { 210 if (colorCorrectionMode == 1) 211 { 212 // Use fast (inaccurate) Gambatte default method 213 rFinal = ((r * 13) + (g * 2) + b) >> 4; 214 gFinal = ((g * 3) + b) >> 2; 215 bFinal = ((r * 3) + (g * 2) + (b * 11)) >> 4; 216 } 217 else 218 { 219 // Use Pokefan531's "gold standard" GBC colour correction 220 // (https://forums.libretro.com/t/real-gba-and-ds-phat-colors/1540/190) 221 // NB: The results produced by this implementation are ever so slightly 222 // different from the output of the gbc-colour shader. This is due to the 223 // fact that we have to tolerate rounding errors here that are simply not 224 // an issue when tweaking the final image with a post-processing shader. 225 // *However:* the difference is so tiny small that 99.9% of users will 226 // never notice, and the result is still 100x better than the 'fast' 227 // colour correction method. 228 // 229 // Constants 230 static const float targetGamma = 2.2; 231 static const float displayGammaInv = 1.0 / targetGamma; 232 // Perform gamma expansion 233 float adjustedGamma = targetGamma - colorCorrectionBrightness; 234 float rFloat = std::pow(static_cast<float>(r) * rgbMaxInv, adjustedGamma); 235 float gFloat = std::pow(static_cast<float>(g) * rgbMaxInv, adjustedGamma); 236 float bFloat = std::pow(static_cast<float>(b) * rgbMaxInv, adjustedGamma); 237 // Perform colour mangling 238 float rCorrect = GBC_CC_LUM * ((GBC_CC_R * rFloat) + (GBC_CC_GR * gFloat) + (GBC_CC_BR * bFloat)); 239 float gCorrect = GBC_CC_LUM * ((GBC_CC_RG * rFloat) + (GBC_CC_G * gFloat) + (GBC_CC_BG * bFloat)); 240 float bCorrect = GBC_CC_LUM * ((GBC_CC_RB * rFloat) + (GBC_CC_GB * gFloat) + (GBC_CC_B * bFloat)); 241 // Range check... 242 rCorrect = rCorrect > 0.0f ? rCorrect : 0.0f; 243 gCorrect = gCorrect > 0.0f ? gCorrect : 0.0f; 244 bCorrect = bCorrect > 0.0f ? bCorrect : 0.0f; 245 // Perform gamma compression 246 rCorrect = std::pow(rCorrect, displayGammaInv); 247 gCorrect = std::pow(gCorrect, displayGammaInv); 248 bCorrect = std::pow(bCorrect, displayGammaInv); 249 // Range check... 250 rCorrect = rCorrect > 1.0f ? 1.0f : rCorrect; 251 gCorrect = gCorrect > 1.0f ? 1.0f : gCorrect; 252 bCorrect = bCorrect > 1.0f ? 1.0f : bCorrect; 253 // Perform image darkening, if required 254 if (darkFilterLevel > 0) 255 { 256 darkenRgb(rCorrect, gCorrect, bCorrect); 257 isDark = true; 258 } 259 // Convert back to 5bit unsigned 260 rFinal = static_cast<unsigned>((rCorrect * rgbMax) + 0.5) & 0x1F; 261 gFinal = static_cast<unsigned>((gCorrect * rgbMax) + 0.5) & 0x1F; 262 bFinal = static_cast<unsigned>((bCorrect * rgbMax) + 0.5) & 0x1F; 263 } 264 } 265 else 266 { 267 rFinal = r; 268 gFinal = g; 269 bFinal = b; 270 } 271 272 // Perform image darkening, if required and we haven't 273 // already done it during colour correction 274 if (darkFilterLevel > 0 && !isDark) 275 { 276 // Convert colour range from [0,0x1F] to [0,1] 277 float rDark = static_cast<float>(rFinal) * rgbMaxInv; 278 float gDark = static_cast<float>(gFinal) * rgbMaxInv; 279 float bDark = static_cast<float>(bFinal) * rgbMaxInv; 280 // Perform image darkening 281 darkenRgb(rDark, gDark, bDark); 282 // Convert back to 5bit unsigned 283 rFinal = static_cast<unsigned>((rDark * rgbMax) + 0.5) & 0x1F; 284 gFinal = static_cast<unsigned>((gDark * rgbMax) + 0.5) & 0x1F; 285 bFinal = static_cast<unsigned>((bDark * rgbMax) + 0.5) & 0x1F; 286 } 287 288 #ifdef VIDEO_RGB565 289 return rFinal << 11 | gFinal << 6 | bFinal; 290 #elif defined(VIDEO_ABGR1555) 291 return bFinal << 10 | gFinal << 5 | rFinal; 292 #else 293 return rFinal << 16 | gFinal << 8 | bFinal; 294 #endif 295 } 296 297 } 298