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.

794 lines
22KB

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