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