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.

462 lines
11KB

  1. #include <map>
  2. #include <queue>
  3. #include <thread>
  4. #include <stb_image_write.h>
  5. #include <osdialog.h>
  6. #include <window/Window.hpp>
  7. #include <asset.hpp>
  8. #include <widget/Widget.hpp>
  9. #include <app/Scene.hpp>
  10. #include <keyboard.hpp>
  11. #include <context.hpp>
  12. #include <patch.hpp>
  13. #include <settings.hpp>
  14. #include <plugin.hpp> // used in Window::screenshot
  15. #include <system.hpp> // used in Window::screenshot
  16. #include "DistrhoUI.hpp"
  17. namespace rack {
  18. namespace window {
  19. extern DISTRHO_NAMESPACE::UI* lastUI;
  20. static const math::Vec minWindowSize = math::Vec(640, 480);
  21. void Font::loadFile(const std::string& filename, NVGcontext* vg) {
  22. this->vg = vg;
  23. handle = nvgCreateFont(vg, filename.c_str(), filename.c_str());
  24. if (handle < 0)
  25. throw Exception("Failed to load font %s", filename.c_str());
  26. INFO("Loaded font %s", filename.c_str());
  27. }
  28. Font::~Font() {
  29. // There is no NanoVG deleteFont() function yet, so do nothing
  30. }
  31. std::shared_ptr<Font> Font::load(const std::string& filename) {
  32. return APP->window->loadFont(filename);
  33. }
  34. void Image::loadFile(const std::string& filename, NVGcontext* vg) {
  35. this->vg = vg;
  36. handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  37. if (handle <= 0)
  38. throw Exception("Failed to load image %s", filename.c_str());
  39. INFO("Loaded image %s", filename.c_str());
  40. }
  41. Image::~Image() {
  42. // TODO What if handle is invalid?
  43. if (handle >= 0)
  44. nvgDeleteImage(vg, handle);
  45. }
  46. std::shared_ptr<Image> Image::load(const std::string& filename) {
  47. return APP->window->loadImage(filename);
  48. }
  49. struct Window::Internal {
  50. DISTRHO_NAMESPACE::UI* ui;
  51. math::Vec size;
  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 frameTime = 0.0;
  62. double lastFrameDuration = 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. bool fbDirtyOnSubpixelChange = true;
  67. };
  68. Window::Window() {
  69. internal = new Internal;
  70. internal->ui = lastUI;
  71. internal->size = minWindowSize;
  72. int err;
  73. // Set up GLEW
  74. glewExperimental = GL_TRUE;
  75. err = glewInit();
  76. if (err != GLEW_OK) {
  77. 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.");
  78. exit(1);
  79. }
  80. const GLubyte* vendor = glGetString(GL_VENDOR);
  81. const GLubyte* renderer = glGetString(GL_RENDERER);
  82. const GLubyte* version = glGetString(GL_VERSION);
  83. INFO("Renderer: %s %s", vendor, renderer);
  84. INFO("OpenGL: %s", version);
  85. INFO("UI pointer: %p", lastUI);
  86. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  87. glGetError();
  88. // Set up NanoVG
  89. int nvgFlags = NVG_ANTIALIAS;
  90. #if defined NANOVG_GL2
  91. vg = nvgCreateGL2(nvgFlags);
  92. fbVg = nvgCreateSharedGL2(vg, nvgFlags);
  93. #elif defined NANOVG_GL3
  94. vg = nvgCreateGL3(nvgFlags);
  95. #elif defined NANOVG_GLES2
  96. vg = nvgCreateGLES2(nvgFlags);
  97. #endif
  98. if (!vg) {
  99. 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.");
  100. exit(1);
  101. }
  102. // Load default Blendish font
  103. uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf"));
  104. bndSetFont(uiFont->handle);
  105. if (APP->scene) {
  106. widget::Widget::ContextCreateEvent e;
  107. APP->scene->onContextCreate(e);
  108. }
  109. }
  110. Window::~Window() {
  111. if (APP->scene) {
  112. widget::Widget::ContextDestroyEvent e;
  113. APP->scene->onContextDestroy(e);
  114. }
  115. // Fonts and Images in the cache must be deleted before the NanoVG context is deleted
  116. internal->fontCache.clear();
  117. internal->imageCache.clear();
  118. // nvgDeleteClone(fbVg);
  119. #if defined NANOVG_GL2
  120. nvgDeleteGL2(vg);
  121. nvgDeleteGL2(fbVg);
  122. #elif defined NANOVG_GL3
  123. nvgDeleteGL3(vg);
  124. #elif defined NANOVG_GLES2
  125. nvgDeleteGLES2(vg);
  126. #endif
  127. delete internal;
  128. }
  129. math::Vec Window::getSize() {
  130. return internal->size;
  131. }
  132. void Window::setSize(math::Vec size) {
  133. internal->size = size.max(minWindowSize);
  134. }
  135. void Window::run() {
  136. internal->frame = 0;
  137. }
  138. void Window::step() {
  139. double frameTime = system::getTime();
  140. double lastFrameTime = internal->frameTime;
  141. internal->frameTime = frameTime;
  142. internal->lastFrameDuration = frameTime - lastFrameTime;
  143. // DEBUG("%.2lf Hz", 1.0 / internal->lastFrameDuration);
  144. double t1 = 0.0, t2 = 0.0, t3 = 0.0, t4 = 0.0, t5 = 0.0;
  145. // Make event handlers and step() have a clean NanoVG context
  146. nvgReset(vg);
  147. bndSetFont(uiFont->handle);
  148. // Poll events
  149. // Save and restore context because event handler set their own context based on which window they originate from.
  150. // Context* context = contextGet();
  151. // glfwPollEvents();
  152. // contextSet(context);
  153. // Set window title
  154. std::string windowTitle = APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION;
  155. if (APP->patch->path != "") {
  156. windowTitle += " - ";
  157. if (!APP->history->isSaved())
  158. windowTitle += "*";
  159. windowTitle += system::getFilename(APP->patch->path);
  160. }
  161. if (windowTitle != internal->lastWindowTitle) {
  162. internal->ui->getWindow().setTitle(windowTitle.c_str());
  163. internal->lastWindowTitle = windowTitle;
  164. }
  165. // Get desired pixel ratio
  166. float newPixelRatio = internal->ui->getScaleFactor();
  167. if (newPixelRatio != pixelRatio) {
  168. pixelRatio = newPixelRatio;
  169. APP->event->handleDirty();
  170. }
  171. // Get framebuffer/window ratio
  172. int winWidth = internal->ui->getWidth();
  173. int winHeight = internal->ui->getHeight();
  174. int fbWidth = winWidth;// * newPixelRatio;
  175. int fbHeight = winHeight;// * newPixelRatio;
  176. windowRatio = (float)fbWidth / winWidth;
  177. t1 = system::getTime();
  178. if (APP->scene) {
  179. // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth);
  180. // Resize scene
  181. APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(pixelRatio);
  182. // Step scene
  183. APP->scene->step();
  184. t2 = system::getTime();
  185. // Render scene
  186. bool visible = true;
  187. if (visible) {
  188. // Update and render
  189. nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio);
  190. nvgScale(vg, pixelRatio, pixelRatio);
  191. // Draw scene
  192. widget::Widget::DrawArgs args;
  193. args.vg = vg;
  194. args.clipBox = APP->scene->box.zeroPos();
  195. APP->scene->draw(args);
  196. t3 = system::getTime();
  197. glViewport(0, 0, fbWidth, fbHeight);
  198. glClearColor(0.0, 0.0, 0.0, 1.0);
  199. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  200. nvgEndFrame(vg);
  201. t4 = system::getTime();
  202. }
  203. }
  204. t5 = system::getTime();
  205. // DEBUG("pre-step %6.1f step %6.1f draw %6.1f nvgEndFrame %6.1f glfwSwapBuffers %6.1f total %6.1f",
  206. // (t1 - frameTime) * 1e3f,
  207. // (t2 - t1) * 1e3f,
  208. // (t3 - t2) * 1e3f,
  209. // (t4 - t2) * 1e3f,
  210. // (t5 - t4) * 1e3f,
  211. // (t5 - frameTime) * 1e3f
  212. // );
  213. internal->frame++;
  214. }
  215. void Window::screenshot(const std::string&) {
  216. }
  217. void Window::screenshotModules(const std::string&, float) {
  218. }
  219. void Window::close() {
  220. internal->ui->getWindow().close();
  221. }
  222. void Window::cursorLock() {
  223. if (!settings::allowCursorLock)
  224. return;
  225. internal->ignoreNextMouseDelta = true;
  226. }
  227. void Window::cursorUnlock() {
  228. if (!settings::allowCursorLock)
  229. return;
  230. internal->ignoreNextMouseDelta = true;
  231. }
  232. bool Window::isCursorLocked() {
  233. return false;
  234. }
  235. int Window::getMods() {
  236. int mods = 0;
  237. /*
  238. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  239. mods |= GLFW_MOD_SHIFT;
  240. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  241. mods |= GLFW_MOD_CONTROL;
  242. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  243. mods |= GLFW_MOD_ALT;
  244. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  245. mods |= GLFW_MOD_SUPER;
  246. */
  247. return mods;
  248. }
  249. void Window::setFullScreen(bool) {
  250. }
  251. bool Window::isFullScreen() {
  252. return false;
  253. }
  254. double Window::getMonitorRefreshRate() {
  255. return internal->monitorRefreshRate;
  256. }
  257. double Window::getFrameTime() {
  258. return internal->frameTime;
  259. }
  260. double Window::getLastFrameDuration() {
  261. return internal->lastFrameDuration;
  262. }
  263. double Window::getFrameDurationRemaining() {
  264. double frameDurationDesired = internal->frameSwapInterval / internal->monitorRefreshRate;
  265. return frameDurationDesired - (system::getTime() - internal->frameTime);
  266. }
  267. std::shared_ptr<Font> Window::loadFont(const std::string& filename) {
  268. const auto& pair = internal->fontCache.find(filename);
  269. if (pair != internal->fontCache.end())
  270. return pair->second;
  271. // Load font
  272. std::shared_ptr<Font> font;
  273. try {
  274. font = std::make_shared<Font>();
  275. font->loadFile(filename, vg);
  276. }
  277. catch (Exception& e) {
  278. WARN("%s", e.what());
  279. font = NULL;
  280. }
  281. internal->fontCache[filename] = font;
  282. return font;
  283. }
  284. std::shared_ptr<Image> Window::loadImage(const std::string& filename) {
  285. const auto& pair = internal->imageCache.find(filename);
  286. if (pair != internal->imageCache.end())
  287. return pair->second;
  288. // Load image
  289. std::shared_ptr<Image> image;
  290. try {
  291. image = std::make_shared<Image>();
  292. image->loadFile(filename, vg);
  293. }
  294. catch (Exception& e) {
  295. WARN("%s", e.what());
  296. image = NULL;
  297. }
  298. internal->imageCache[filename] = image;
  299. return image;
  300. }
  301. bool& Window::fbDirtyOnSubpixelChange() {
  302. return internal->fbDirtyOnSubpixelChange;
  303. }
  304. void mouseButtonCallback(Window* win, int button, int action, int mods) {
  305. /*
  306. #if defined ARCH_MAC
  307. // Remap Ctrl-left click to right click on Mac
  308. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  309. button = GLFW_MOUSE_BUTTON_RIGHT;
  310. mods &= ~GLFW_MOD_CONTROL;
  311. }
  312. // Remap Ctrl-shift-left click to middle click on Mac
  313. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  314. button = GLFW_MOUSE_BUTTON_MIDDLE;
  315. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  316. }
  317. #endif
  318. */
  319. APP->event->handleButton(win->internal->lastMousePos, button, action, mods);
  320. }
  321. void cursorPosCallback(Window* win, double xpos, double ypos) {
  322. math::Vec mousePos = math::Vec(xpos, ypos).div(win->pixelRatio / win->windowRatio).round();
  323. math::Vec mouseDelta = mousePos.minus(win->internal->lastMousePos);
  324. // Workaround for GLFW warping mouse to a different position when the cursor is locked or unlocked.
  325. if (win->internal->ignoreNextMouseDelta) {
  326. win->internal->ignoreNextMouseDelta = false;
  327. mouseDelta = math::Vec();
  328. }
  329. win->internal->lastMousePos = mousePos;
  330. APP->event->handleHover(mousePos, mouseDelta);
  331. // Keyboard/mouse MIDI driver
  332. math::Vec scaledPos(xpos / win->internal->ui->getWidth(), ypos / win->internal->ui->getHeight());
  333. keyboard::mouseMove(scaledPos);
  334. }
  335. void scrollCallback(Window* win, double x, double y) {
  336. math::Vec scrollDelta = math::Vec(x, y);
  337. #if defined ARCH_MAC
  338. scrollDelta = scrollDelta.mult(10.0);
  339. #else
  340. scrollDelta = scrollDelta.mult(50.0);
  341. #endif
  342. APP->event->handleScroll(win->internal->lastMousePos, scrollDelta);
  343. }
  344. void init() {
  345. }
  346. void destroy() {
  347. }
  348. } // namespace window
  349. } // namespace rack