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 ¤tMode;
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