1 /** @file displaymode.cpp  Platform-independent display mode management.
2  * @ingroup gl
3  *
4  * @authors Copyright (c) 2012-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
5  *
6  * @par License
7  * LGPL: http://www.gnu.org/licenses/lgpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
15  * General Public License for more details. You should have received a copy of
16  * the GNU Lesser General Public License along with this program; if not, see:
17  * http://www.gnu.org/licenses</small>
18  */
19 
20 #include "de/gui/displaymode.h"
21 #include "de/gui/displaymode_native.h"
22 
23 #include <de/App>
24 #include <de/ArrayValue>
25 #include <de/DictionaryValue>
26 #include <de/FunctionValue>
27 #include <de/LogBuffer>
28 #include <de/NumberValue>
29 #include <de/Record>
30 #include <de/ScriptSystem>
31 #include <de/TextValue>
32 
33 #include <vector>
34 #include <set>
35 #include <algorithm>
36 #include <de/math.h>
37 #include <de/Log>
38 
39 namespace de {
40 
41 static bool inited = false;
42 static DisplayColorTransfer originalColorTransfer;
43 static de::Binder binder;
44 
45 static float differenceToOriginalHz(float hz);
46 
47 namespace internal {
48 
49 struct Mode : public DisplayMode
50 {
Modede::internal::Mode51     Mode()
52     {
53         de::zapPtr(static_cast<DisplayMode *>(this));
54     }
55 
Modede::internal::Mode56     Mode(const DisplayMode& dm)
57     {
58         memcpy(static_cast<DisplayMode *>(this), &dm, sizeof(dm));
59     }
60 
Modede::internal::Mode61     Mode(int i)
62     {
63         DisplayMode_Native_GetMode(i, this);
64         updateRatio();
65     }
66 
fromCurrentde::internal::Mode67     static Mode fromCurrent()
68     {
69         Mode m;
70         DisplayMode_Native_GetCurrentMode(&m);
71         m.updateRatio();
72         return m;
73     }
74 
operator ==de::internal::Mode75     bool operator == (const Mode& other) const
76     {
77         return width == other.width && height == other.height &&
78                depth == other.depth && refreshRate == other.refreshRate;
79     }
80 
operator !=de::internal::Mode81     bool operator != (const Mode& other) const
82     {
83         return !(*this == other);
84     }
85 
operator <de::internal::Mode86     bool operator < (const Mode& b) const
87     {
88         if (width == b.width)
89         {
90             if (height == b.height)
91             {
92                 if (depth == b.depth)
93                 {
94                     // The refresh rate that more closely matches the original is preferable.
95                     return differenceToOriginalHz(refreshRate) < differenceToOriginalHz(b.refreshRate);
96                 }
97                 return depth < b.depth;
98             }
99             return height < b.height;
100         }
101         return width < b.width;
102     }
103 
updateRatiode::internal::Mode104     void updateRatio()
105     {
106         ratioX = width;
107         ratioY = height;
108 
109         float fx;
110         float fy;
111         if (width > height)
112         {
113             fx = width/float(height);
114             fy = 1.f;
115         }
116         else
117         {
118             fx = 1.f;
119             fy = height/float(width);
120         }
121 
122         // Multiply until we arrive at a close enough integer ratio.
123         for (int mul = 2; mul < qMin(width, height); ++mul)
124         {
125             float rx = fx*mul;
126             float ry = fy*mul;
127             if (qAbs(rx - qRound(rx)) < .01f && qAbs(ry - qRound(ry)) < .01f)
128             {
129                 // This seems good.
130                 ratioX = qRound(rx);
131                 ratioY = qRound(ry);
132                 break;
133             }
134         }
135 
136         if (ratioX == 8 && ratioY == 5)
137         {
138             // This is commonly referred to as 16:10.
139             ratioX *= 2;
140             ratioY *= 2;
141         }
142     }
143 
debugPrintde::internal::Mode144     void debugPrint() const
145     {
146         LOG_GL_VERBOSE("size: %i x %i x %i, rate: %.1f Hz, ratio: %i:%i")
147                 << width << height << depth << refreshRate << ratioX << ratioY;
148     }
149 };
150 
151 } // namespace internal
152 
153 using namespace internal;
154 
155 typedef std::set<Mode> Modes; // note: no duplicates
156 
157 static Modes modes;
158 static Mode originalMode;
159 static bool captured;
160 
differenceToOriginalHz(float hz)161 static float differenceToOriginalHz(float hz)
162 {
163     return qAbs(hz - originalMode.refreshRate);
164 }
165 
Function_DisplayMode_OriginalMode(de::Context &,de::Function::ArgumentValues const &)166 static de::Value *Function_DisplayMode_OriginalMode(de::Context &, de::Function::ArgumentValues const &)
167 {
168     using de::NumberValue;
169     using de::TextValue;
170 
171     DisplayMode const *mode = DisplayMode_OriginalMode();
172 
173     de::DictionaryValue *dict = new de::DictionaryValue;
174     dict->add(new TextValue("width"),       new NumberValue(mode->width));
175     dict->add(new TextValue("height"),      new NumberValue(mode->height));
176     dict->add(new TextValue("depth"),       new NumberValue(mode->depth));
177     dict->add(new TextValue("refreshRate"), new NumberValue(mode->refreshRate));
178 
179     de::ArrayValue *ratio = new de::ArrayValue;
180     *ratio << NumberValue(mode->ratioX) << NumberValue(mode->ratioY);
181     dict->add(new TextValue("ratio"), ratio);
182 
183     return dict;
184 }
185 
186 } // namespace de
187 
188 using namespace de;
189 
DisplayMode_Init(void)190 int DisplayMode_Init(void)
191 {
192     if (inited) return true;
193 
194     captured = false;
195     DisplayMode_Native_Init();
196 #if defined(MACOSX) || defined(UNIX)
197     DisplayMode_SaveOriginalColorTransfer();
198 #endif
199 
200     // This is used for sorting the mode set (Hz).
201     originalMode = Mode::fromCurrent();
202 
203     for (int i = 0; i < DisplayMode_Native_Count(); ++i)
204     {
205         Mode mode(i);
206         if (mode.depth < 16 || mode.width < 320 || mode.height < 240)
207             continue; // This mode is not good.
208         modes.insert(mode);
209     }
210 
211     LOG_GL_VERBOSE("Current mode is:");
212     originalMode.debugPrint();
213 
214     LOG_GL_VERBOSE("All available modes:");
215     for (Modes::iterator i = modes.begin(); i != modes.end(); ++i)
216     {
217         i->debugPrint();
218     }
219 
220     // Script bindings.
221     binder.initNew() << DENG2_FUNC_NOARG(DisplayMode_OriginalMode, "originalMode");
222     de::App::scriptSystem().addNativeModule("DisplayMode", binder.module());
223     binder.module().addNumber("PIXEL_RATIO", 1.0);
224 
225     inited = true;
226     return true;
227 }
228 
DisplayMode_Shutdown(void)229 void DisplayMode_Shutdown(void)
230 {
231     if (!inited) return;
232 
233     binder.deinit();
234 
235     LOG_GL_NOTE("Restoring original display mode due to shutdown");
236 
237     // Back to the original mode.
238     DisplayMode_Change(&originalMode, false /*release captured*/);
239 
240     modes.clear();
241 
242     DisplayMode_Native_Shutdown();
243     captured = false;
244 
245     DisplayMode_Native_SetColorTransfer(&originalColorTransfer);
246 
247     inited = false;
248 }
249 
DisplayMode_SaveOriginalColorTransfer(void)250 void DisplayMode_SaveOriginalColorTransfer(void)
251 {
252     DisplayMode_Native_GetColorTransfer(&originalColorTransfer);
253 }
254 
DisplayMode_OriginalMode(void)255 DisplayMode const *DisplayMode_OriginalMode(void)
256 {
257     return &originalMode;
258 }
259 
DisplayMode_Current(void)260 DisplayMode const *DisplayMode_Current(void)
261 {
262     static Mode currentMode;
263     // Update it with current mode.
264     currentMode = Mode::fromCurrent();
265     return &currentMode;
266 }
267 
DisplayMode_Count(void)268 int DisplayMode_Count(void)
269 {
270     return (int) modes.size();
271 }
272 
DisplayMode_ByIndex(int index)273 DisplayMode const *DisplayMode_ByIndex(int index)
274 {
275     DENG2_ASSERT(index >= 0);
276     DENG2_ASSERT(index < (int) modes.size());
277 
278     int pos = 0;
279     for (Modes::iterator i = modes.begin(); i != modes.end(); ++i, ++pos)
280     {
281         if (pos == index)
282         {
283             return &*i;
284         }
285     }
286 
287     DENG2_ASSERT(false);
288     return 0; // unreachable
289 }
290 
DisplayMode_FindClosest(int width,int height,int depth,float freq)291 DisplayMode const *DisplayMode_FindClosest(int width, int height, int depth, float freq)
292 {
293     int bestScore = -1;
294     DisplayMode const *best = 0;
295 
296     for (Modes::iterator i = modes.begin(); i != modes.end(); ++i)
297     {
298         int score = de::squared(i->width  - width) +
299                     de::squared(i->height - height) +
300                     de::squared(i->depth  - depth);
301         if (freq >= 1)
302         {
303             score += de::squared(i->refreshRate - freq);
304         }
305 
306         // Note: The first mode to hit the lowest score wins; if there are many modes
307         // with the same score, the first one will be chosen. Particularly if the
308         // frequency has not been specified, the sort order of the modes defines which
309         // one is picked.
310         if (!best || score < bestScore)
311         {
312             bestScore = score;
313             best = &*i;
314         }
315     }
316     return best;
317 }
318 
DisplayMode_IsEqual(DisplayMode const * a,DisplayMode const * b)319 int DisplayMode_IsEqual(DisplayMode const *a, DisplayMode const *b)
320 {
321     if (!a || !b) return true; // Cannot compare against nothing.
322     return Mode(*a) == Mode(*b);
323 }
324 
DisplayMode_Change(DisplayMode const * mode,int shouldCapture)325 int DisplayMode_Change(DisplayMode const *mode, int shouldCapture)
326 {
327     if (Mode::fromCurrent() == *mode && !shouldCapture == !captured)
328     {
329         LOG_AS("DisplayMode");
330         LOGDEV_GL_XVERBOSE("Requested mode is the same as current, ignoring request", "");
331 
332         // Already in this mode.
333         return false;
334     }
335     captured = shouldCapture;
336     return DisplayMode_Native_Change(mode, shouldCapture || (originalMode != *mode));
337 }
338 
intensity8To16(de::duint8 b)339 static inline de::duint16 intensity8To16(de::duint8 b)
340 {
341     return (b << 8) | b; // 0xFF => 0xFFFF
342 }
343 
DisplayMode_GetColorTransfer(DisplayColorTransfer * colors)344 void DisplayMode_GetColorTransfer(DisplayColorTransfer *colors)
345 {
346     DisplayColorTransfer mapped;
347     DisplayMode_Native_GetColorTransfer(&mapped);
348 
349     // Factor out the original color transfer function, which may be set up
350     // specifically by the user.
351     for (int i = 0; i < 256; ++i)
352     {
353 #define LINEAR_UNMAP(i, c) ( (unsigned short) \
354     de::clamp(0.f, float(mapped.table[i]) / float(originalColorTransfer.table[i]) * intensity8To16(c), 65535.f) )
355         colors->table[i]       = LINEAR_UNMAP(i,       i);
356         colors->table[i + 256] = LINEAR_UNMAP(i + 256, i);
357         colors->table[i + 512] = LINEAR_UNMAP(i + 512, i);
358     }
359 }
360 
DisplayMode_SetColorTransfer(DisplayColorTransfer const * colors)361 void DisplayMode_SetColorTransfer(DisplayColorTransfer const *colors)
362 {
363     DisplayColorTransfer mapped;
364 
365     // Factor in the original color transfer function, which may be set up
366     // specifically by the user.
367     for (int i = 0; i < 256; ++i)
368     {
369 #define LINEAR_MAP(i, c) ( (unsigned short) \
370     de::clamp(0.f, float(colors->table[i]) / float(intensity8To16(c)) * originalColorTransfer.table[i], 65535.f) )
371         mapped.table[i]       = LINEAR_MAP(i,       i);
372         mapped.table[i + 256] = LINEAR_MAP(i + 256, i);
373         mapped.table[i + 512] = LINEAR_MAP(i + 512, i);
374     }
375 
376     DisplayMode_Native_SetColorTransfer(&mapped);
377 }
378