1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23
24#include "graphicalapplication.h"
25#include <framework/core/adaptiverenderer.h>
26#include <framework/core/clock.h>
27#include <framework/core/eventdispatcher.h>
28#include <framework/core/asyncdispatcher.h>
29#include <framework/platform/platformwindow.h>
30#include <framework/ui/uimanager.h>
31#include <framework/graphics/graph.h>
32#include <framework/graphics/graphics.h>
33#include <framework/graphics/texturemanager.h>
34#include <framework/graphics/painter.h>
35#include <framework/graphics/framebuffermanager.h>
36#include <framework/graphics/fontmanager.h>
37#include <framework/graphics/atlas.h>
38#include <framework/graphics/image.h>
39#include <framework/graphics/textrender.h>
40#include <framework/graphics/shadermanager.h>
41#include <framework/input/mouse.h>
42#include <framework/util/extras.h>
43#include <framework/util/stats.h>
44
45#ifdef FW_SOUND
46#include <framework/sound/soundmanager.h>
47#endif
48
49GraphicalApplication g_app;
50
51void GraphicalApplication::init(std::vector<std::string>& args)
52{
53 Application::init(args);
54
55 // setup platform window
56 g_window.init();
57 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
58 g_window.hide();
59 g_window.setOnResize(std::bind(&GraphicalApplication::resize, this, std::placeholders::_1));
60 g_window.setOnInputEvent(std::bind(&GraphicalApplication::inputEvent, this, std::placeholders::_1));
61 g_window.setOnClose(std::bind(&GraphicalApplication::close, this));
62 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
63
64 g_mouse.init();
65
66 // initialize ui
67 g_ui.init();
68
69 // initialize graphics
70 g_graphics.init();
71
72 // fire first resize event
73 resize(g_window.getSize());
74
75#ifdef FW_SOUND
76 // initialize sound
77 g_sounds.init();
78#endif
79}
80
81void GraphicalApplication::deinit()
82{
83 // hide the window because there is no render anymore
84 g_window.hide();
85 g_asyncDispatcher.terminate();
86
87 Application::deinit();
88}
89
90void GraphicalApplication::terminate()
91{
92 // destroy any remaining widget
93 g_ui.terminate();
94
95 Application::terminate();
96 m_terminated = false;
97
98#ifdef FW_SOUND
99 // terminate sound
100 g_sounds.terminate();
101#endif
102
103 g_mouse.terminate();
104
105 // terminate graphics
106 g_graphicsDispatcher.shutdown();
107 g_graphics.terminate();
108 g_window.terminate();
109
110 m_terminated = true;
111}
112
113void GraphicalApplication::run()
114{
115 m_running = true;
116 m_windowPollTimer.restart();
117
118 // first clock update
119 g_clock.update();
120
121 // run the first poll
122 poll();
123 pollGraphics();
124 g_clock.update();
125
126 // show window
127 g_window.show();
128
129 // run the second poll
130 poll();
131 pollGraphics();
132 g_clock.update();
133
134 g_lua.callGlobalField("g_app", "onRun");
135
136 m_framebuffer = g_framebuffers.createFrameBuffer();
137 m_framebuffer->resize(g_painter->getResolution());
138 m_mapFramebuffer = g_framebuffers.createFrameBuffer();
139 m_mapFramebuffer->resize(g_painter->getResolution());
140
141 ticks_t lastRender = stdext::micros();
142
143 std::shared_ptr<DrawQueue> drawQueue;
144 std::shared_ptr<DrawQueue> drawMapQueue;
145 std::shared_ptr<DrawQueue> drawMapForegroundQueue;
146 bool isOnline = false;
147 size_t totalFrames = 0;
148
149 std::mutex mutex;
150 std::thread worker([&] {
151 g_dispatcherThreadId = std::this_thread::get_id();
152 while (!m_stopping) {
153 m_processingFrames.addFrame();
154 {
155 g_clock.update();
156 poll();
157 g_clock.update();
158 }
159
160 mutex.lock();
161 if (drawQueue && drawMapQueue && m_maxFps > 0) { // old drawQueue not processed yet
162 mutex.unlock();
163 AutoStat s(STATS_MAIN, "Sleep");
164 stdext::millisleep(1);
165 continue;
166 }
167 mutex.unlock();
168
169 ticks_t renderStart = stdext::millis();
170 {
171 AutoStat s(STATS_MAIN, "DrawMapBackground");
172 g_drawQueue = std::make_shared<DrawQueue>();
173 g_ui.render(Fw::MapBackgroundPane);
174 }
175 std::shared_ptr<DrawQueue> mapBackgroundQueue = g_drawQueue;
176 {
177 AutoStat s(STATS_MAIN, "DrawMapForeground");
178 g_drawQueue = std::make_shared<DrawQueue>();
179 g_ui.render(Fw::MapForegroundPane);
180 }
181
182 mutex.lock();
183 drawMapQueue = mapBackgroundQueue;
184 drawMapForegroundQueue = g_drawQueue;
185 mutex.unlock();
186
187 {
188 AutoStat s(STATS_MAIN, "DrawForeground");
189 g_drawQueue = std::make_shared<DrawQueue>();
190 g_ui.render(Fw::ForegroundPane);
191 }
192
193 mutex.lock();
194 drawQueue = g_drawQueue;
195 g_drawQueue = nullptr;
196 mutex.unlock();
197
198 g_graphs[GRAPH_CPU_FRAME_TIME].addValue(stdext::millis() - renderStart);
199
200 if (m_maxFps > 0 || g_window.hasVerticalSync()) {
201 AutoStat s(STATS_MAIN, "Sleep");
202 stdext::millisleep(1);
203 }
204 }
205 g_dispatcher.poll(); // last poll
206 g_dispatcherThreadId = g_mainThreadId;
207 });
208
209 std::shared_ptr<DrawQueue> toDrawQueue, toDrawMapQueue, toDrawMapForegroundQueue;
210 ticks_t lastFrame = stdext::millis();
211 while (!m_stopping) {
212 m_iteration += 1;
213
214 g_clock.update();
215 pollGraphics();
216
217 if (!g_window.isVisible()) {
218 AutoStat s(STATS_RENDER, "Sleep");
219 stdext::millisleep(1);
220 g_adaptiveRenderer.refresh();
221 continue;
222 }
223
224#ifdef FREE_VERSION_LIB
225 m_maxFps = 150;
226#endif
227 int frameDelay = m_maxFps <= 0 ? 0 : (1000000 / m_maxFps);
228 if (lastRender + frameDelay > stdext::micros() && !m_mustRepaint) {
229 AutoStat s(STATS_RENDER, "Sleep");
230 stdext::millisleep(1);
231 continue;
232 }
233
234 mutex.lock();
235 if ((!drawQueue && !toDrawQueue) ||
236 ((!drawMapQueue || !drawMapForegroundQueue) && isOnline) ||
237 (m_mustRepaint && !drawQueue)) {
238 mutex.unlock();
239 AutoStat s(STATS_RENDER, "Wait");
240 stdext::millisleep(1);
241 continue;
242 }
243 toDrawQueue = drawQueue ? drawQueue : toDrawQueue;
244 toDrawMapQueue = drawMapQueue;
245 toDrawMapForegroundQueue = drawMapForegroundQueue;
246 drawQueue = drawMapQueue = drawMapForegroundQueue = nullptr;
247 mutex.unlock();
248
249 g_adaptiveRenderer.newFrame();
250 m_graphicsFrames.addFrame();
251 m_mustRepaint = false;
252 lastRender = stdext::micros() > lastRender + frameDelay * 2 ? stdext::micros() : lastRender + frameDelay;
253
254 g_painter->resetDraws();
255 if (m_scaling > 1.0f) {
256 AutoStat s(STATS_RENDER, "SetupScaling");
257 g_painter->setResolution(g_graphics.getViewportSize() / m_scaling);
258 m_framebuffer->resize(g_painter->getResolution());
259 m_framebuffer->bind();
260 }
261
262 if (toDrawMapQueue && toDrawMapQueue->hasFrameBuffer()) {
263 AutoStat s(STATS_RENDER, "UpdateMap");
264 m_mapFramebuffer->resize(toDrawMapQueue->getFrameBufferSize());
265 m_mapFramebuffer->bind();
266 g_painter->clear(Color::black);
267 toDrawMapQueue->draw(DRAW_ALL);
268 m_mapFramebuffer->release();
269 }
270
271 {
272 AutoStat s(STATS_RENDER, "Clear");
273 g_painter->clear(Color::alpha);
274 }
275
276 {
277 AutoStat s(STATS_RENDER, "DrawFirstForeground");
278 if (toDrawQueue)
279 toDrawQueue->draw(DRAW_BEFORE_MAP);
280 }
281
282 if(toDrawMapQueue) {
283 isOnline = toDrawMapQueue->hasFrameBuffer();
284 if(isOnline) {
285 AutoStat s(STATS_RENDER, "DrawMapBackground");
286 PainterShaderProgramPtr shader = nullptr;
287 if (!toDrawMapQueue->getShader().empty()) {
288 shader = g_shaders.getShader(toDrawMapQueue->getShader());
289 }
290 if (shader) {
291 g_painter->setShaderProgram(shader);
292 shader->bindMultiTextures();
293 shader->setCenter(toDrawMapQueue->getFrameBufferDest().center());
294 shader->setOffset(toDrawMapQueue->getFrameBufferSrc().topLeft());
295 }
296 m_mapFramebuffer->draw(toDrawMapQueue->getFrameBufferDest(), toDrawMapQueue->getFrameBufferSrc());
297 if (shader) {
298 g_painter->resetShaderProgram();
299 }
300 }
301 if(toDrawMapForegroundQueue) {
302 AutoStat s(STATS_RENDER, "DrawMapForeground");
303 toDrawMapForegroundQueue->draw();
304 }
305 }
306
307 {
308 AutoStat s(STATS_RENDER, "DrawSecondForeground");
309 toDrawQueue->draw(DRAW_AFTER_MAP);
310 }
311
312 {
313 if (g_extras.debugRender) {
314 AutoStat s(STATS_RENDER, "DrawGraphs");
315 for (int i = 0, x = 60, y = 30; i <= GRAPH_LAST; ++i) {
316 g_graphs[i].draw(Rect(x, y, Size(200, 60)));
317 y += 70;
318 if (y + 70 > g_painter->getResolution().height()) {
319 x += 220;
320 y = 30;
321 }
322 }
323 }
324 }
325
326 if (m_scaling > 1.0f) {
327 AutoStat s(STATS_RENDER, "DrawScaled");
328 m_framebuffer->release();
329 g_painter->setResolution(g_graphics.getViewportSize());
330 g_painter->clear(Color::alpha);
331 m_framebuffer->draw(Rect(0, 0, g_painter->getResolution()));
332 }
333
334 g_graphs[GRAPH_GPU_CALLS].addValue(g_painter->calls());
335 g_graphs[GRAPH_GPU_DRAWS].addValue(g_painter->draws());
336
337 AutoStat s(STATS_RENDER, "SwapBuffers");
338 g_window.swapBuffers();
339 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
340 g_graphs[GRAPH_TOTAL_FRAME_TIME].addValue(stdext::millis() - lastFrame);
341 lastFrame = stdext::millis();
342 totalFrames += 1;
343
344#ifdef FREE_VERSION_LIB
345 if (totalFrames > m_maxFps * 3600 * 1.2 || stdext::millis() > 3600'000) {
346 g_logger.info("Free version of OTCv8 is limited to 1 hour of runtime. Exiting...");
347 g_app.exit();
348 }
349 if (totalFrames > m_maxFps * 3600 * 1.3 || stdext::millis() > 3900'000) {
350 // force exit
351 g_logger.fatal("Free version of OTCv8 is limited to 1 hour of runtime. Exiting...");
352 std::exit(0);
353 }
354 if (totalFrames > m_maxFps * 3600 * 1.4) {
355 // can't exit, crash app
356 m_framebuffer = nullptr;
357 m_mapFramebuffer = nullptr;
358 g_drawQueue = nullptr;
359 }
360#endif
361 }
362
363 worker.join();
364 g_graphicsDispatcher.poll();
365
366 m_framebuffer = nullptr;
367 m_mapFramebuffer = nullptr;
368 g_drawQueue = nullptr;
369 m_stopping = false;
370 m_running = false;
371}
372
373void GraphicalApplication::poll() {
374 ticks_t start = stdext::millis();
375#ifdef FW_SOUND
376 g_sounds.poll();
377#endif
378 Application::poll();
379 g_graphs[GRAPH_PROCESSING_POLL].addValue(stdext::millis() - start, true);
380}
381
382void GraphicalApplication::pollGraphics()
383{
384 ticks_t start = stdext::millis();
385 g_graphicsDispatcher.poll();
386 g_text.poll();
387 if (m_windowPollTimer.elapsed_millis() > 10) {
388 g_window.poll();
389 m_windowPollTimer.restart();
390 }
391 g_graphs[GRAPH_GRAPHICS_POLL].addValue(stdext::millis() - start, true);
392}
393
394void GraphicalApplication::close()
395{
396 VALIDATE(std::this_thread::get_id() == g_dispatcherThreadId);
397 m_onInputEvent = true;
398 Application::close();
399 m_onInputEvent = false;
400}
401
402void GraphicalApplication::resize(const Size& size)
403{
404 VALIDATE(std::this_thread::get_id() == g_mainThreadId);
405 g_graphics.resize(size); // uses painter
406 scale(m_scaling); // thread safe
407}
408
409void GraphicalApplication::inputEvent(InputEvent event)
410{
411 VALIDATE(std::this_thread::get_id() == g_dispatcherThreadId);
412 m_onInputEvent = true;
413 g_ui.inputEvent(event);
414 m_onInputEvent = false;
415}
416
417void GraphicalApplication::doScreenshot(std::string file)
418{
419 if (g_mainThreadId != std::this_thread::get_id()) {
420 g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::doScreenshot, this, file));
421 return;
422 }
423
424 if (file.empty()) {
425 file = "screenshot.png";
426 }
427 auto resolution = g_graphics.getViewportSize();
428 int width = resolution.width();
429 int height = resolution.height();
430 auto pixels = std::make_shared<std::vector<uint8_t>>(width * height * 4 * sizeof(GLubyte), 0);
431 glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (GLubyte*)(pixels->data()));
432
433 g_asyncDispatcher.dispatch([resolution, pixels, file] {
434 for (int line = 0, h = resolution.height(), w = resolution.width(); line != h / 2; ++line) {
435 std::swap_ranges(
436 pixels->begin() + 4 * w * line,
437 pixels->begin() + 4 * w * (line + 1),
438 pixels->begin() + 4 * w * (h - line - 1));
439 }
440 try {
441 Image image(resolution, 4, pixels->data());
442 image.savePNG(file);
443 } catch (stdext::exception& e) {
444 g_logger.error(std::string("Can't do screenshot: ") + e.what());
445 }
446 });
447}
448
449void GraphicalApplication::scaleUp()
450{
451 if (g_mainThreadId != std::this_thread::get_id()) {
452 g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::scaleUp, this));
453 return;
454 }
455 scale(m_scaling + 0.5);
456}
457
458void GraphicalApplication::scaleDown()
459{
460 if (g_mainThreadId != std::this_thread::get_id()) {
461 g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::scaleDown, this));
462 return;
463 }
464 scale(m_scaling - 0.5);
465}
466
467void GraphicalApplication::scale(float value)
468{
469 if (g_mainThreadId != std::this_thread::get_id()) {
470 g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::scale, this, value));
471 return;
472 }
473
474 float maxScale = std::min<float>((g_graphics.getViewportSize().height() / 180),
475 g_graphics.getViewportSize().width() / 280);
476 if (maxScale < 2.0)
477 maxScale = 2.0;
478 maxScale /= 2;
479
480 if (m_scaling == value) {
481 value = m_lastScaling;
482 } else {
483 m_lastScaling = std::max<float>(1.0, std::min<float>(maxScale, value));
484 }
485
486 m_scaling = std::max<float>(1.0, std::min<float>(maxScale, value));
487 g_window.setScaling(m_scaling);
488
489 g_dispatcher.addEvent([&] {
490 m_onInputEvent = true;
491 g_ui.resize(g_graphics.getViewportSize() / m_scaling);
492 m_onInputEvent = false;
493 m_mustRepaint = true;
494 });
495}
496
497void GraphicalApplication::setSmooth(bool value)
498{
499 if (!m_mapFramebuffer) return;
500
501 m_mapFramebuffer->setSmooth(value);
502}
503
504void GraphicalApplication::doMapScreenshot(std::string fileName)
505{
506 if (!m_mapFramebuffer) return;
507
508 m_mapFramebuffer->doScreenshot(fileName);
509}