You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

706 lines
19KB

  1. #include <map>
  2. #include <queue>
  3. #include <thread>
  4. #if defined ARCH_MAC
  5. // For CGAssociateMouseAndMouseCursorPosition
  6. #include <ApplicationServices/ApplicationServices.h>
  7. #endif
  8. #include <stb_image_write.h>
  9. #include <osdialog.h>
  10. #include <window.hpp>
  11. #include <asset.hpp>
  12. #include <widget/Widget.hpp>
  13. #include <app/Scene.hpp>
  14. #include <keyboard.hpp>
  15. #include <gamepad.hpp>
  16. #include <context.hpp>
  17. #include <patch.hpp>
  18. #include <settings.hpp>
  19. #include <plugin.hpp> // used in Window::screenshot
  20. #include <system.hpp> // used in Window::screenshot
  21. namespace rack {
  22. static const math::Vec minWindowSize = math::Vec(640, 480);
  23. void Font::loadFile(const std::string& filename, NVGcontext* vg) {
  24. this->vg = vg;
  25. handle = nvgCreateFont(vg, filename.c_str(), filename.c_str());
  26. if (handle < 0)
  27. throw Exception("Failed to load font %s", filename.c_str());
  28. INFO("Loaded font %s", filename.c_str());
  29. }
  30. Font::~Font() {
  31. // There is no NanoVG deleteFont() function yet, so do nothing
  32. }
  33. std::shared_ptr<Font> Font::load(const std::string& filename) {
  34. return APP->window->loadFont(filename);
  35. }
  36. void Image::loadFile(const std::string& filename, NVGcontext* vg) {
  37. this->vg = vg;
  38. handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  39. if (handle <= 0)
  40. throw Exception("Failed to load image %s", filename.c_str());
  41. INFO("Loaded image %s", filename.c_str());
  42. }
  43. Image::~Image() {
  44. // TODO What if handle is invalid?
  45. if (handle >= 0)
  46. nvgDeleteImage(vg, handle);
  47. }
  48. std::shared_ptr<Image> Image::load(const std::string& filename) {
  49. return APP->window->loadImage(filename);
  50. }
  51. struct Window::Internal {
  52. std::string lastWindowTitle;
  53. int lastWindowX = 0;
  54. int lastWindowY = 0;
  55. int lastWindowWidth = 0;
  56. int lastWindowHeight = 0;
  57. int frame = 0;
  58. bool ignoreNextMouseDelta = false;
  59. int frameSwapInterval = -1;
  60. double monitorRefreshRate = 0.0;
  61. double lastFrameDuration = 0.0;
  62. double lastFrameTime = 0.0;
  63. math::Vec lastMousePos;
  64. std::map<std::string, std::shared_ptr<Font>> fontCache;
  65. std::map<std::string, std::shared_ptr<Image>> imageCache;
  66. };
  67. static void windowPosCallback(GLFWwindow* win, int x, int y) {
  68. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED))
  69. return;
  70. if (glfwGetWindowMonitor(win))
  71. return;
  72. settings::windowPos = math::Vec(x, y);
  73. // DEBUG("windowPosCallback %d %d", x, y);
  74. }
  75. static void windowSizeCallback(GLFWwindow* win, int width, int height) {
  76. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED))
  77. return;
  78. if (glfwGetWindowMonitor(win))
  79. return;
  80. settings::windowSize = math::Vec(width, height);
  81. // DEBUG("windowSizeCallback %d %d", width, height);
  82. }
  83. static void windowMaximizeCallback(GLFWwindow* win, int maximized) {
  84. settings::windowMaximized = maximized;
  85. // DEBUG("windowMaximizeCallback %d", maximized);
  86. }
  87. static void mouseButtonCallback(GLFWwindow* win, int button, int action, int mods) {
  88. contextSet((Context*) glfwGetWindowUserPointer(win));
  89. #if defined ARCH_MAC
  90. // Remap Ctrl-left click to right click on Mac
  91. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  92. button = GLFW_MOUSE_BUTTON_RIGHT;
  93. mods &= ~GLFW_MOD_CONTROL;
  94. }
  95. // Remap Ctrl-shift-left click to middle click on Mac
  96. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  97. button = GLFW_MOUSE_BUTTON_MIDDLE;
  98. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  99. }
  100. #endif
  101. APP->event->handleButton(APP->window->internal->lastMousePos, button, action, mods);
  102. }
  103. static void cursorPosCallback(GLFWwindow* win, double xpos, double ypos) {
  104. contextSet((Context*) glfwGetWindowUserPointer(win));
  105. math::Vec mousePos = math::Vec(xpos, ypos).div(APP->window->pixelRatio / APP->window->windowRatio).round();
  106. math::Vec mouseDelta = mousePos.minus(APP->window->internal->lastMousePos);
  107. // Workaround for GLFW warping mouse to a different position when the cursor is locked or unlocked.
  108. if (APP->window->internal->ignoreNextMouseDelta) {
  109. APP->window->internal->ignoreNextMouseDelta = false;
  110. mouseDelta = math::Vec();
  111. }
  112. int cursorMode = glfwGetInputMode(win, GLFW_CURSOR);
  113. (void) cursorMode;
  114. #if defined ARCH_MAC
  115. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  116. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  117. if (cursorMode == GLFW_CURSOR_HIDDEN) {
  118. // CGSetLocalEventsSuppressionInterval(0.0);
  119. glfwSetCursorPos(win, APP->window->internal->lastMousePos.x, APP->window->internal->lastMousePos.y);
  120. CGAssociateMouseAndMouseCursorPosition(true);
  121. mousePos = APP->window->internal->lastMousePos;
  122. }
  123. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  124. glfwSetCursor(win, NULL);
  125. #endif
  126. APP->window->internal->lastMousePos = mousePos;
  127. APP->event->handleHover(mousePos, mouseDelta);
  128. // Keyboard/mouse MIDI driver
  129. int width, height;
  130. glfwGetWindowSize(win, &width, &height);
  131. math::Vec scaledPos(xpos / width, ypos / height);
  132. keyboard::mouseMove(scaledPos);
  133. }
  134. static void cursorEnterCallback(GLFWwindow* win, int entered) {
  135. contextSet((Context*) glfwGetWindowUserPointer(win));
  136. if (!entered) {
  137. APP->event->handleLeave();
  138. }
  139. }
  140. static void scrollCallback(GLFWwindow* win, double x, double y) {
  141. contextSet((Context*) glfwGetWindowUserPointer(win));
  142. math::Vec scrollDelta = math::Vec(x, y);
  143. #if defined ARCH_MAC
  144. scrollDelta = scrollDelta.mult(10.0);
  145. #else
  146. scrollDelta = scrollDelta.mult(50.0);
  147. #endif
  148. APP->event->handleScroll(APP->window->internal->lastMousePos, scrollDelta);
  149. }
  150. static void charCallback(GLFWwindow* win, unsigned int codepoint) {
  151. contextSet((Context*) glfwGetWindowUserPointer(win));
  152. if (APP->event->handleText(APP->window->internal->lastMousePos, codepoint))
  153. return;
  154. }
  155. static void keyCallback(GLFWwindow* win, int key, int scancode, int action, int mods) {
  156. contextSet((Context*) glfwGetWindowUserPointer(win));
  157. if (APP->event->handleKey(APP->window->internal->lastMousePos, key, scancode, action, mods))
  158. return;
  159. // Keyboard/mouse MIDI driver
  160. if (action == GLFW_PRESS && (mods & RACK_MOD_MASK) == 0) {
  161. keyboard::press(key);
  162. }
  163. if (action == GLFW_RELEASE) {
  164. keyboard::release(key);
  165. }
  166. }
  167. static void dropCallback(GLFWwindow* win, int count, const char** paths) {
  168. contextSet((Context*) glfwGetWindowUserPointer(win));
  169. std::vector<std::string> pathsVec;
  170. for (int i = 0; i < count; i++) {
  171. pathsVec.push_back(paths[i]);
  172. }
  173. APP->event->handleDrop(APP->window->internal->lastMousePos, pathsVec);
  174. }
  175. static void errorCallback(int error, const char* description) {
  176. WARN("GLFW error %d: %s", error, description);
  177. }
  178. Window::Window() {
  179. internal = new Internal;
  180. int err;
  181. // Set window hints
  182. #if defined NANOVG_GL2
  183. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  184. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  185. #elif defined NANOVG_GL3
  186. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  187. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  188. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  189. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  190. #endif
  191. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  192. glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
  193. #if defined ARCH_MAC
  194. glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
  195. #endif
  196. // Create window
  197. win = glfwCreateWindow(1024, 768, "", NULL, NULL);
  198. if (!win) {
  199. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not open GLFW window. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  200. exit(1);
  201. }
  202. float contentScale;
  203. glfwGetWindowContentScale(win, &contentScale, NULL);
  204. INFO("Window content scale: %f", contentScale);
  205. glfwSetWindowSizeLimits(win, minWindowSize.x, minWindowSize.y, GLFW_DONT_CARE, GLFW_DONT_CARE);
  206. glfwSetWindowSize(win, settings::windowSize.x, settings::windowSize.y);
  207. if (settings::windowPos.isFinite()) {
  208. glfwSetWindowPos(win, settings::windowPos.x, settings::windowPos.y);
  209. }
  210. if (settings::windowMaximized) {
  211. glfwMaximizeWindow(win);
  212. }
  213. glfwShowWindow(win);
  214. glfwSetWindowUserPointer(win, contextGet());
  215. glfwSetInputMode(win, GLFW_LOCK_KEY_MODS, 1);
  216. glfwMakeContextCurrent(win);
  217. glfwSwapInterval(1);
  218. const GLFWvidmode* monitorMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
  219. internal->monitorRefreshRate = monitorMode->refreshRate;
  220. // Set window callbacks
  221. glfwSetWindowPosCallback(win, windowPosCallback);
  222. glfwSetWindowSizeCallback(win, windowSizeCallback);
  223. glfwSetWindowMaximizeCallback(win, windowMaximizeCallback);
  224. glfwSetMouseButtonCallback(win, mouseButtonCallback);
  225. // Call this ourselves, but on every frame instead of only when the mouse moves
  226. // glfwSetCursorPosCallback(win, cursorPosCallback);
  227. glfwSetCursorEnterCallback(win, cursorEnterCallback);
  228. glfwSetScrollCallback(win, scrollCallback);
  229. glfwSetCharCallback(win, charCallback);
  230. glfwSetKeyCallback(win, keyCallback);
  231. glfwSetDropCallback(win, dropCallback);
  232. // Set up GLEW
  233. glewExperimental = GL_TRUE;
  234. err = glewInit();
  235. if (err != GLEW_OK) {
  236. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLEW. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  237. exit(1);
  238. }
  239. const GLubyte* renderer = glGetString(GL_RENDERER);
  240. const GLubyte* version = glGetString(GL_VERSION);
  241. INFO("Renderer: %s", renderer);
  242. INFO("OpenGL: %s", version);
  243. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  244. glGetError();
  245. // Set up NanoVG
  246. int nvgFlags = NVG_ANTIALIAS;
  247. #if defined NANOVG_GL2
  248. vg = nvgCreateGL2(nvgFlags);
  249. #elif defined NANOVG_GL3
  250. vg = nvgCreateGL3(nvgFlags);
  251. #elif defined NANOVG_GLES2
  252. vg = nvgCreateGLES2(nvgFlags);
  253. #endif
  254. if (!vg) {
  255. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize NanoVG. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  256. exit(1);
  257. }
  258. // Load default Blendish font
  259. uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf"));
  260. bndSetFont(uiFont->handle);
  261. if (APP->scene) {
  262. widget::Widget::ContextCreateEvent e;
  263. APP->scene->onContextCreate(e);
  264. }
  265. }
  266. Window::~Window() {
  267. if (APP->scene) {
  268. widget::Widget::ContextDestroyEvent e;
  269. APP->scene->onContextDestroy(e);
  270. }
  271. #if defined NANOVG_GL2
  272. nvgDeleteGL2(vg);
  273. #elif defined NANOVG_GL3
  274. nvgDeleteGL3(vg);
  275. #elif defined NANOVG_GLES2
  276. nvgDeleteGLES2(vg);
  277. #endif
  278. glfwDestroyWindow(win);
  279. delete internal;
  280. }
  281. math::Vec Window::getSize() {
  282. int width, height;
  283. glfwGetWindowSize(win, &width, &height);
  284. return math::Vec(width, height);
  285. }
  286. void Window::setSize(math::Vec size) {
  287. size = size.max(minWindowSize);
  288. glfwSetWindowSize(win, size.x, size.y);
  289. }
  290. void Window::run() {
  291. internal->frame = 0;
  292. while (!glfwWindowShouldClose(win)) {
  293. step();
  294. }
  295. }
  296. void Window::step() {
  297. double frameTime = glfwGetTime();
  298. internal->lastFrameDuration = frameTime - internal->lastFrameTime;
  299. // DEBUG("%.2lf Hz", 1.0 / internal->lastFrameDuration);
  300. internal->lastFrameTime = frameTime;
  301. // Make event handlers and step() have a clean nanovg context
  302. nvgReset(vg);
  303. bndSetFont(uiFont->handle);
  304. // Poll events
  305. // Save and restore context because event handler set their own context based on which window they originate from.
  306. Context* context = contextGet();
  307. glfwPollEvents();
  308. contextSet(context);
  309. // In case glfwPollEvents() sets another OpenGL context
  310. glfwMakeContextCurrent(win);
  311. if (settings::frameSwapInterval != internal->frameSwapInterval) {
  312. glfwSwapInterval(settings::frameSwapInterval);
  313. internal->frameSwapInterval = settings::frameSwapInterval;
  314. }
  315. // Call cursorPosCallback every frame, not just when the mouse moves
  316. {
  317. double xpos, ypos;
  318. glfwGetCursorPos(win, &xpos, &ypos);
  319. cursorPosCallback(win, xpos, ypos);
  320. }
  321. gamepad::step();
  322. // Set window title
  323. std::string windowTitle = APP_NAME + " " + APP_VARIANT + " " + APP_VERSION;
  324. if (APP->patch->path != "") {
  325. windowTitle += " - ";
  326. if (!APP->history->isSaved())
  327. windowTitle += "*";
  328. windowTitle += system::getFilename(APP->patch->path);
  329. }
  330. if (windowTitle != internal->lastWindowTitle) {
  331. glfwSetWindowTitle(win, windowTitle.c_str());
  332. internal->lastWindowTitle = windowTitle;
  333. }
  334. // Get desired scaling
  335. float newPixelRatio;
  336. glfwGetWindowContentScale(win, &newPixelRatio, NULL);
  337. newPixelRatio = std::floor(newPixelRatio + 0.5);
  338. if (newPixelRatio != pixelRatio) {
  339. APP->event->handleDirty();
  340. pixelRatio = newPixelRatio;
  341. }
  342. // Get framebuffer/window ratio
  343. int fbWidth, fbHeight;
  344. glfwGetFramebufferSize(win, &fbWidth, &fbHeight);
  345. int winWidth, winHeight;
  346. glfwGetWindowSize(win, &winWidth, &winHeight);
  347. windowRatio = (float)fbWidth / winWidth;
  348. if (APP->scene) {
  349. // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth);
  350. // Resize scene
  351. APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(pixelRatio);
  352. // Step scene
  353. APP->scene->step();
  354. // Render scene
  355. bool visible = glfwGetWindowAttrib(win, GLFW_VISIBLE) && !glfwGetWindowAttrib(win, GLFW_ICONIFIED);
  356. if (visible) {
  357. // Update and render
  358. nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio);
  359. nvgScale(vg, pixelRatio, pixelRatio);
  360. // Draw scene
  361. widget::Widget::DrawArgs args;
  362. args.vg = vg;
  363. args.clipBox = APP->scene->box.zeroPos();
  364. APP->scene->draw(args);
  365. glViewport(0, 0, fbWidth, fbHeight);
  366. glClearColor(0.0, 0.0, 0.0, 1.0);
  367. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  368. nvgEndFrame(vg);
  369. }
  370. }
  371. glfwSwapBuffers(win);
  372. internal->frame++;
  373. }
  374. static void flipBitmap(uint8_t* pixels, int width, int height, int depth) {
  375. for (int y = 0; y < height / 2; y++) {
  376. int flipY = height - y - 1;
  377. uint8_t tmp[width * depth];
  378. std::memcpy(tmp, &pixels[y * width * depth], width * depth);
  379. std::memcpy(&pixels[y * width * depth], &pixels[flipY * width * depth], width * depth);
  380. std::memcpy(&pixels[flipY * width * depth], tmp, width * depth);
  381. }
  382. }
  383. void Window::screenshot(const std::string& screenshotPath) {
  384. // Get window framebuffer size
  385. int width, height;
  386. glfwGetFramebufferSize(APP->window->win, &width, &height);
  387. // Allocate pixel color buffer
  388. uint8_t* pixels = new uint8_t[height * width * 4];
  389. // glReadPixels defaults to GL_BACK, but the back-buffer is unstable, so use the front buffer (what the user sees)
  390. glReadBuffer(GL_FRONT);
  391. glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
  392. // Write pixels to PNG
  393. flipBitmap(pixels, width, height, 4);
  394. stbi_write_png(screenshotPath.c_str(), width, height, 4, pixels, width * 4);
  395. delete[] pixels;
  396. }
  397. void Window::screenshotModules(const std::string& screenshotsDir, float zoom) {
  398. // Iterate plugins and create directories
  399. system::createDirectories(screenshotsDir);
  400. for (plugin::Plugin* p : plugin::plugins) {
  401. std::string dir = system::join(screenshotsDir, p->slug);
  402. system::createDirectory(dir);
  403. for (plugin::Model* model : p->models) {
  404. std::string filename = system::join(dir, model->slug + ".png");
  405. // Skip model if screenshot already exists
  406. if (system::isFile(filename))
  407. continue;
  408. INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str());
  409. // Create widgets
  410. widget::FramebufferWidget* fbw = new widget::FramebufferWidget;
  411. fbw->oversample = 2;
  412. fbw->setScale(math::Vec(zoom, zoom));
  413. app::ModuleWidget* mw = model->createModuleWidget(NULL);
  414. fbw->addChild(mw);
  415. // Hack the frame time so FramebufferWidgets are never overdue and therefore guaranteed to draw
  416. internal->lastFrameTime = INFINITY;
  417. // Draw to framebuffer
  418. fbw->step();
  419. // Read pixels
  420. nvgluBindFramebuffer(fbw->getFramebuffer());
  421. int width, height;
  422. nvgImageSize(vg, fbw->getImageHandle(), &width, &height);
  423. uint8_t* pixels = new uint8_t[height * width * 4];
  424. glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
  425. // Write pixels to PNG
  426. flipBitmap(pixels, width, height, 4);
  427. stbi_write_png(filename.c_str(), width, height, 4, pixels, width * 4);
  428. // Cleanup
  429. delete[] pixels;
  430. nvgluBindFramebuffer(NULL);
  431. delete fbw;
  432. }
  433. }
  434. }
  435. void Window::close() {
  436. glfwSetWindowShouldClose(win, GLFW_TRUE);
  437. }
  438. void Window::cursorLock() {
  439. if (!settings::allowCursorLock)
  440. return;
  441. #if defined ARCH_MAC
  442. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  443. #else
  444. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  445. #endif
  446. internal->ignoreNextMouseDelta = true;
  447. }
  448. void Window::cursorUnlock() {
  449. if (!settings::allowCursorLock)
  450. return;
  451. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  452. internal->ignoreNextMouseDelta = true;
  453. }
  454. bool Window::isCursorLocked() {
  455. return glfwGetInputMode(win, GLFW_CURSOR) != GLFW_CURSOR_NORMAL;
  456. }
  457. int Window::getMods() {
  458. int mods = 0;
  459. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  460. mods |= GLFW_MOD_SHIFT;
  461. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  462. mods |= GLFW_MOD_CONTROL;
  463. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  464. mods |= GLFW_MOD_ALT;
  465. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  466. mods |= GLFW_MOD_SUPER;
  467. return mods;
  468. }
  469. void Window::setFullScreen(bool fullScreen) {
  470. if (!fullScreen) {
  471. glfwSetWindowMonitor(win, NULL, internal->lastWindowX, internal->lastWindowY, internal->lastWindowWidth, internal->lastWindowHeight, GLFW_DONT_CARE);
  472. }
  473. else {
  474. glfwGetWindowPos(win, &internal->lastWindowX, &internal->lastWindowY);
  475. glfwGetWindowSize(win, &internal->lastWindowWidth, &internal->lastWindowHeight);
  476. GLFWmonitor* monitor = glfwGetPrimaryMonitor();
  477. const GLFWvidmode* mode = glfwGetVideoMode(monitor);
  478. glfwSetWindowMonitor(win, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
  479. }
  480. }
  481. bool Window::isFullScreen() {
  482. GLFWmonitor* monitor = glfwGetWindowMonitor(win);
  483. return monitor != NULL;
  484. }
  485. double Window::getMonitorRefreshRate() {
  486. return internal->monitorRefreshRate;
  487. }
  488. double Window::getLastFrameTime() {
  489. return internal->lastFrameTime;
  490. }
  491. double Window::getLastFrameDuration() {
  492. return internal->lastFrameDuration;
  493. }
  494. double Window::getFrameTimeOverdue() {
  495. double desiredFrameDuration = internal->frameSwapInterval / internal->monitorRefreshRate;
  496. double frameDuration = glfwGetTime() - internal->lastFrameTime;
  497. return frameDuration - desiredFrameDuration;
  498. }
  499. std::shared_ptr<Font> Window::loadFont(const std::string& filename) {
  500. const auto& pair = internal->fontCache.find(filename);
  501. if (pair != internal->fontCache.end())
  502. return pair->second;
  503. // Load font
  504. std::shared_ptr<Font> font;
  505. try {
  506. font = std::make_shared<Font>();
  507. font->loadFile(filename, vg);
  508. }
  509. catch (Exception& e) {
  510. WARN("%s", e.what());
  511. font = NULL;
  512. }
  513. internal->fontCache[filename] = font;
  514. return font;
  515. }
  516. std::shared_ptr<Image> Window::loadImage(const std::string& filename) {
  517. const auto& pair = internal->imageCache.find(filename);
  518. if (pair != internal->imageCache.end())
  519. return pair->second;
  520. // Load image
  521. std::shared_ptr<Image> image;
  522. try {
  523. image = std::make_shared<Image>();
  524. image->loadFile(filename, vg);
  525. }
  526. catch (Exception& e) {
  527. WARN("%s", e.what());
  528. image = NULL;
  529. }
  530. internal->imageCache[filename] = image;
  531. return image;
  532. }
  533. void windowInit() {
  534. int err;
  535. // Set up GLFW
  536. #if defined ARCH_MAC
  537. glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_TRUE);
  538. glfwInitHint(GLFW_COCOA_MENUBAR, GLFW_TRUE);
  539. #endif
  540. glfwSetErrorCallback(errorCallback);
  541. err = glfwInit();
  542. if (err != GLFW_TRUE) {
  543. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  544. exit(1);
  545. }
  546. }
  547. void windowDestroy() {
  548. glfwTerminate();
  549. }
  550. } // namespace rack