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.

677 lines
18KB

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