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.

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