diff --git a/README.md b/README.md index 3da7e32b..9457ffcd 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,5 @@ -# VCV Rack +# Rack -*Rack* is the main application for the VCV open-source virtual modular synthesizer. +*Rack* is the engine for the VCV open-source virtual modular synthesizer. -This README includes instructions for building Rack from source. For information about the software, go to https://vcvrack.com/. - -## The [Issue Tracker](https://github.com/VCVRack/Rack/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) is the official developer's forum - -Bug reports, feature requests, questions, and discussions are welcome on the GitHub Issue Tracker for all repos under the VCVRack organization. -However, please search before posting to avoid duplicates, and limit to one issue per post. - -Please vote on feature requests by using the Thumbs Up/Down reaction on the first post. - -I rarely accept code contributions to Rack itself, so please notify me in advance if you wish to send a pull request. - -## Setting up your development environment - -Before building Rack, you must install build dependencies provided by your system's package manager. -Rack's own dependencies (GLEW, glfw, etc) do not need to be installed on your system, since specific versions are compiled locally during the build process. -However, you need proper tools to build Rack and these dependencies. - -### Mac - -Install [Xcode](https://developer.apple.com/xcode/). -Using [Homebrew](https://brew.sh/), install the build dependencies. -``` -brew install git wget cmake autoconf automake libtool -``` - -### Windows - -Install [MSYS2](http://www.msys2.org/) and launch the MinGW 64-bit shell (not the default MSYS shell). -``` -pacman -S git wget make tar unzip zip mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake autoconf automake mingw-w64-x86_64-libtool -``` - -### Linux - -On Ubuntu 16.04: -``` -sudo apt install git curl cmake libx11-dev libglu1-mesa-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev zlib1g-dev libasound2-dev libgtk2.0-dev libjack-jackd2-dev -``` - -On Arch Linux: -``` -pacman -S git wget gcc make cmake tar unzip zip curl -``` - -## Building - -*If the build fails for you, please report the issue with a detailed error message to help the portability of Rack.* - -Clone this repository with `git clone https://github.com/VCVRack/Rack.git` and `cd Rack`. -Make sure there are no spaces in your absolute path, as this breaks many build systems. - -Clone submodules. - - git submodule update --init --recursive - -Build dependencies locally. -You may add `-j$(nproc)` to your make commands to parallelize builds across all CPU cores. - - make dep - -Build Rack. - - make - -Run Rack. - - make run - -## Building plugins - -Be sure to check out and build the version of Rack you wish to build your plugins against. - -You must clone the plugin in Rack's `plugins/` directory, e.g. - - cd plugins - git clone https://github.com/VCVRack/Fundamental.git - -Clone submodules. - - cd Fundamental - git submodule update --init --recursive - -Build plugin. - - make dep - make - -## Licenses - -See [LICENSE.md](LICENSE.md) for a description of all licenses for VCV Rack. +For information about the software, go to the [VCV website](https://vcvrack.com/) or the [VCV Rack manual](https://vcvrack.com/manual/). diff --git a/include/app/SVGPanel.hpp b/include/app/SVGPanel.hpp index d13914d5..c543f9d4 100644 --- a/include/app/SVGPanel.hpp +++ b/include/app/SVGPanel.hpp @@ -10,38 +10,13 @@ namespace rack { struct PanelBorder : TransparentWidget { - void draw(NVGcontext *vg) override { - NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); - nvgBeginPath(vg); - nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0); - nvgStrokeColor(vg, borderColor); - nvgStrokeWidth(vg, 1.0); - nvgStroke(vg); - } + void draw(NVGcontext *vg) override; }; struct SVGPanel : FramebufferWidget { - void step() override { - if (math::isNear(app()->window->pixelRatio, 1.0)) { - // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer - oversample = 2.0; - } - FramebufferWidget::step(); - } - - void setBackground(std::shared_ptr svg) { - SVGWidget *sw = new SVGWidget; - sw->setSVG(svg); - addChild(sw); - - // Set size - box.size = sw->box.size.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE); - - PanelBorder *pb = new PanelBorder; - pb->box.size = box.size; - addChild(pb); - } + void step() override; + void setBackground(std::shared_ptr svg); }; diff --git a/include/app/common.hpp b/include/app/common.hpp index 8be21b6d..8677c909 100644 --- a/include/app/common.hpp +++ b/include/app/common.hpp @@ -11,26 +11,26 @@ extern const std::string APP_NAME; extern const std::string APP_VERSION; extern const std::string API_HOST; -static const float SVG_DPI = 75.0; +static const float APP_SVG_DPI = 75.0; static const float MM_PER_IN = 25.4; /** Converts inch measurements to pixels */ inline float in2px(float in) { - return in * SVG_DPI; + return in * APP_SVG_DPI; } inline math::Vec in2px(math::Vec in) { - return in.mult(SVG_DPI); + return in.mult(APP_SVG_DPI); } /** Converts millimeter measurements to pixels */ inline float mm2px(float mm) { - return mm * (SVG_DPI / MM_PER_IN); + return mm * (APP_SVG_DPI / MM_PER_IN); } inline math::Vec mm2px(math::Vec mm) { - return mm.mult(SVG_DPI / MM_PER_IN); + return mm.mult(APP_SVG_DPI / MM_PER_IN); } diff --git a/include/math.hpp b/include/math.hpp index 4eb2f1de..fc2515df 100644 --- a/include/math.hpp +++ b/include/math.hpp @@ -346,5 +346,12 @@ inline Vec Vec::clampSafe(Rect bound) const { } +/** Useful for debugging Vecs and Rects, e.g. + printf("%f %f %f %f", RECT_ARGS(r)); +*/ +#define VEC_ARGS(v) (v).x, (v).y +#define RECT_ARGS(r) (r).pos.x, (r).pos.y, (r).size.x, (r).size.y + + } // namespace math } // namespace rack diff --git a/include/widgets/FramebufferWidget.hpp b/include/widgets/FramebufferWidget.hpp index 403498ad..74a2ec30 100644 --- a/include/widgets/FramebufferWidget.hpp +++ b/include/widgets/FramebufferWidget.hpp @@ -12,19 +12,23 @@ Events are not passed to the underlying scene. struct FramebufferWidget : Widget { /** Set this to true to re-render the children to the framebuffer the next time it is drawn */ bool dirty = true; - /** A margin in pixels around the children in the framebuffer - This prevents cutting the rendered SVG off on the box edges. - */ float oversample; - /** The root object in the framebuffer scene - The FramebufferWidget owns the pointer + NVGLUframebuffer *fb = NULL; + /** Pixel dimensions of the allocated framebuffer */ + math::Vec fbSize; + /** Bounding box in world coordinates of where the framebuffer should be painted + Always has integer coordinates so that blitting framebuffers is pixel-perfect. */ - struct Internal; - Internal *internal; + math::Rect fbBox; + /** Local scale relative to the world scale */ + math::Vec fbScale; + /** Subpixel offset of fbBox in world coordinates */ + math::Vec fbOffset; FramebufferWidget(); ~FramebufferWidget(); void draw(NVGcontext *vg) override; + virtual void drawFramebuffer(NVGcontext *vg); int getImageHandle(); void onZoom(const event::Zoom &e) override { diff --git a/include/window.hpp b/include/window.hpp index 8642f3d3..d0c05e95 100644 --- a/include/window.hpp +++ b/include/window.hpp @@ -7,6 +7,9 @@ #include #include #include +#define NANOVG_GL2 +#include +#include #include @@ -60,7 +63,8 @@ struct SVG { struct Window { GLFWwindow *win = NULL; NVGcontext *vg = NULL; - NVGcontext *framebufferVg = NULL; + /** Secondary nanovg context for drawing to framebuffers */ + NVGcontext *fbVg = NULL; /** The scaling ratio */ float pixelRatio = 1.f; /* The ratio between the framebuffer size and the window size reported by the OS. diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index ebdeca23..a822eda6 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -181,12 +181,12 @@ void ModuleWidget::onHover(const event::Hover &e) { OpaqueWidget::onHover(e); // Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget. - if (glfwGetKey(app()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(app()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) { - if ((app()->window->getMods() & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - removeAction(); - e.consume(NULL); - return; - } + if ((glfwGetKey(app()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS + || glfwGetKey(app()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) + && (app()->window->getMods() & WINDOW_MOD_MASK) == 0) { + removeAction(); + e.consume(NULL); + return; } } diff --git a/src/app/SVGPanel.cpp b/src/app/SVGPanel.cpp new file mode 100644 index 00000000..586f1bf2 --- /dev/null +++ b/src/app/SVGPanel.cpp @@ -0,0 +1,39 @@ +#include "app/SVGPanel.hpp" + + +namespace rack { + + +void PanelBorder::draw(NVGcontext *vg) { + NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); + nvgBeginPath(vg); + nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0); + nvgStrokeColor(vg, borderColor); + nvgStrokeWidth(vg, 1.0); + nvgStroke(vg); +} + + +void SVGPanel::step() { + if (math::isNear(app()->window->pixelRatio, 1.0)) { + // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer + oversample = 2.0; + } + FramebufferWidget::step(); +} + +void SVGPanel::setBackground(std::shared_ptr svg) { + SVGWidget *sw = new SVGWidget; + sw->setSVG(svg); + addChild(sw); + + // Set size + box.size = sw->box.size.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE); + + PanelBorder *pb = new PanelBorder; + pb->box.size = box.size; + addChild(pb); +} + + +} // namespace rack diff --git a/src/asset.cpp b/src/asset.cpp index 401619b8..11d88ba8 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -2,18 +2,18 @@ #include "system.hpp" #include "plugin/Plugin.hpp" -#if ARCH_MAC +#if defined ARCH_MAC #include #include #endif -#if ARCH_WIN +#if defined ARCH_WIN #include #include #include #endif -#if ARCH_LIN +#if defined ARCH_LIN #include #include #include @@ -31,7 +31,7 @@ void init(bool devMode) { systemDir = "."; } else { -#if ARCH_MAC +#if defined ARCH_MAC CFBundleRef bundle = CFBundleGetMainBundle(); assert(bundle); CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(bundle); @@ -41,14 +41,14 @@ void init(bool devMode) { CFRelease(resourcesUrl); systemDir = resourcesBuf; #endif -#if ARCH_WIN +#if defined ARCH_WIN char moduleBuf[MAX_PATH]; DWORD length = GetModuleFileName(NULL, moduleBuf, sizeof(moduleBuf)); assert(length > 0); PathRemoveFileSpec(moduleBuf); systemDir = moduleBuf; #endif -#if ARCH_LIN +#if defined ARCH_LIN // TODO For now, users should launch Rack from their terminal in the system directory systemDir = "."; #endif @@ -61,7 +61,7 @@ void init(bool devMode) { userDir = "."; } else { -#if ARCH_WIN +#if defined ARCH_WIN // Get "My Documents" folder char documentsBuf[MAX_PATH]; HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, documentsBuf); @@ -69,14 +69,14 @@ void init(bool devMode) { userDir = documentsBuf; userDir += "/Rack"; #endif -#if ARCH_MAC +#if defined ARCH_MAC // Get home directory struct passwd *pw = getpwuid(getuid()); assert(pw); userDir = pw->pw_dir; userDir += "/Documents/Rack"; #endif -#if ARCH_LIN +#if defined ARCH_LIN // Get home directory const char *homeBuf = getenv("HOME"); if (!homeBuf) { diff --git a/src/bridge.cpp b/src/bridge.cpp index 764fab2f..403d8aad 100644 --- a/src/bridge.cpp +++ b/src/bridge.cpp @@ -4,7 +4,7 @@ #include "dsp/ringbuffer.hpp" #include -#if ARCH_WIN +#if defined ARCH_WIN #include #else #include @@ -87,7 +87,7 @@ struct BridgeClientConnection { if (length <= 0) return false; -#if ARCH_LIN +#if defined ARCH_LIN int flags = MSG_NOSIGNAL; #else int flags = 0; @@ -114,7 +114,7 @@ struct BridgeClientConnection { if (length <= 0) return false; -#if ARCH_LIN +#if defined ARCH_LIN int flags = MSG_NOSIGNAL; #else int flags = 0; @@ -282,7 +282,7 @@ struct BridgeClientConnection { static void clientRun(int client) { DEFER({ -#if ARCH_WIN +#if defined ARCH_WIN if (shutdown(client, SD_SEND)) { WARN("Bridge client shutdown() failed"); } @@ -296,7 +296,7 @@ static void clientRun(int client) { #endif }); -#if ARCH_MAC +#if defined ARCH_MAC // Avoid SIGPIPE int flag = 1; if (setsockopt(client, SOL_SOCKET, SO_NOSIGPIPE, &flag, sizeof(int))) { @@ -306,7 +306,7 @@ static void clientRun(int client) { #endif // Disable non-blocking -#if ARCH_WIN +#if defined ARCH_WIN unsigned long blockingMode = 0; if (ioctlsocket(client, FIONBIO, &blockingMode)) { WARN("Bridge client ioctlsocket() failed"); @@ -327,7 +327,7 @@ static void clientRun(int client) { static void serverConnect() { // Initialize sockets -#if ARCH_WIN +#if defined ARCH_WIN WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData)) { WARN("Bridge server WSAStartup() failed"); @@ -343,7 +343,7 @@ static void serverConnect() { memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(BRIDGE_PORT); -#if ARCH_WIN +#if defined ARCH_WIN addr.sin_addr.s_addr = inet_addr(BRIDGE_HOST); #else inet_pton(AF_INET, BRIDGE_HOST, &addr.sin_addr); @@ -363,7 +363,7 @@ static void serverConnect() { INFO("Bridge server closed"); }); -#if ARCH_MAC || ARCH_LIN +#if defined ARCH_MAC || defined ARCH_LIN int reuseAddrFlag = 1; setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &reuseAddrFlag, sizeof(reuseAddrFlag)); #endif @@ -382,7 +382,7 @@ static void serverConnect() { INFO("Bridge server started"); // Enable non-blocking -#if ARCH_WIN +#if defined ARCH_WIN unsigned long blockingMode = 1; if (ioctlsocket(server, FIONBIO, &blockingMode)) { WARN("Bridge server ioctlsocket() failed"); diff --git a/src/dep.cpp b/src/dep.cpp new file mode 100644 index 00000000..2979dcc6 --- /dev/null +++ b/src/dep.cpp @@ -0,0 +1,18 @@ +// This source file compiles those annoying implementation-in-header libraries + +#define GLEW_STATIC +#include +#include +#define NANOVG_GL2_IMPLEMENTATION +// #define NANOVG_GL3_IMPLEMENTATION +// #define NANOVG_GLES2_IMPLEMENTATION +// #define NANOVG_GLES3_IMPLEMENTATION +#include +// Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported) +#define NANOVG_FBO_VALID +#include +#define BLENDISH_IMPLEMENTATION +#include +#define NANOSVG_IMPLEMENTATION +#define NANOSVG_ALL_COLOR_KEYWORDS +#include diff --git a/src/plugin.cpp b/src/plugin.cpp index 0ee3d1d0..8866159e 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -19,7 +19,7 @@ #include #include -#if ARCH_WIN +#if defined ARCH_WIN #include #include #define mkdir(_dir, _perms) _mkdir(_dir) @@ -62,9 +62,9 @@ static bool loadPlugin(std::string path) { // Load plugin library std::string libraryFilename; -#if ARCH_LIN +#if defined ARCH_LIN libraryFilename = path + "/" + "plugin.so"; -#elif ARCH_WIN +#elif defined ARCH_WIN libraryFilename = path + "/" + "plugin.dll"; #elif ARCH_MAC libraryFilename = path + "/" + "plugin.dylib"; @@ -77,7 +77,7 @@ static bool loadPlugin(std::string path) { } // Load dynamic/shared library -#if ARCH_WIN +#if defined ARCH_WIN SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); HINSTANCE handle = LoadLibrary(libraryFilename.c_str()); SetErrorMode(0); @@ -97,7 +97,7 @@ static bool loadPlugin(std::string path) { // Call plugin's init() function typedef void (*InitCallback)(Plugin *); InitCallback initCallback; -#if ARCH_WIN +#if defined ARCH_WIN initCallback = (InitCallback) GetProcAddress(handle, "init"); #else initCallback = (InitCallback) dlsym(handle, "init"); @@ -170,11 +170,11 @@ static bool syncPlugin(std::string slug, json_t *manifestJ, bool dryRun) { name = slug; } -#if ARCH_WIN +#if defined ARCH_WIN std::string arch = "win"; #elif ARCH_MAC std::string arch = "mac"; -#elif ARCH_LIN +#elif defined ARCH_LIN std::string arch = "lin"; #endif @@ -359,7 +359,7 @@ void init(bool devMode) { void destroy() { for (Plugin *plugin : plugins) { // Free library handle -#if ARCH_WIN +#if defined ARCH_WIN if (plugin->handle) FreeLibrary((HINSTANCE) plugin->handle); #else diff --git a/src/system.cpp b/src/system.cpp index e19615e7..2b0f6182 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -2,7 +2,7 @@ #include #include -#if ARCH_WIN +#if defined ARCH_WIN #include #include #endif @@ -70,7 +70,7 @@ void copyFile(const std::string &srcPath, const std::string &destPath) { } void createDirectory(const std::string &path) { -#if ARCH_WIN +#if defined ARCH_WIN CreateDirectory(path.c_str(), NULL); #else mkdir(path.c_str(), 0755); @@ -78,15 +78,15 @@ void createDirectory(const std::string &path) { } void openBrowser(const std::string &url) { -#if ARCH_LIN +#if defined ARCH_LIN std::string command = "xdg-open " + url; (void) std::system(command.c_str()); #endif -#if ARCH_MAC +#if defined ARCH_MAC std::string command = "open " + url; std::system(command.c_str()); #endif -#if ARCH_WIN +#if defined ARCH_WIN ShellExecute(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); #endif } diff --git a/src/ui/ScrollWidget.cpp b/src/ui/ScrollWidget.cpp index 8f181fdd..7431561e 100644 --- a/src/ui/ScrollWidget.cpp +++ b/src/ui/ScrollWidget.cpp @@ -73,7 +73,12 @@ void ScrollWidget::onHover(const event::Hover &e) { } void ScrollWidget::onHoverScroll(const event::HoverScroll &e) { - offset = offset.minus(e.scrollDelta); + math::Vec scrollDelta = e.scrollDelta; + // Flip coordinates if shift is held + if ((app()->window->getMods() & WINDOW_MOD_MASK) == GLFW_MOD_SHIFT) + scrollDelta = scrollDelta.flip(); + + offset = offset.minus(scrollDelta); e.consume(this); } diff --git a/src/widgets/FramebufferWidget.cpp b/src/widgets/FramebufferWidget.cpp index f324c01a..8e7d62ca 100644 --- a/src/widgets/FramebufferWidget.cpp +++ b/src/widgets/FramebufferWidget.cpp @@ -1,34 +1,17 @@ #include "widgets/FramebufferWidget.hpp" #include "app.hpp" -#include -#include namespace rack { -struct FramebufferWidget::Internal { - NVGLUframebuffer *fb = NULL; - math::Rect box; - - ~Internal() { - setFramebuffer(NULL); - } - void setFramebuffer(NVGLUframebuffer *fb) { - if (this->fb) - nvgluDeleteFramebuffer(this->fb); - this->fb = fb; - } -}; - - FramebufferWidget::FramebufferWidget() { oversample = 1.0; - internal = new Internal; } FramebufferWidget::~FramebufferWidget() { - delete internal; + if (fb) + nvgluDeleteFramebuffer(fb); } void FramebufferWidget::draw(NVGcontext *vg) { @@ -40,83 +23,106 @@ void FramebufferWidget::draw(NVGcontext *vg) { float xform[6]; nvgCurrentTransform(vg, xform); // Skew and rotate is not supported - assert(std::abs(xform[1]) < 1e-6); - assert(std::abs(xform[2]) < 1e-6); - math::Vec s = math::Vec(xform[0], xform[3]); - math::Vec b = math::Vec(xform[4], xform[5]); - math::Vec bi = b.floor(); - math::Vec bf = b.minus(bi); + assert(math::isNear(xform[1], 0.f)); + assert(math::isNear(xform[2], 0.f)); + // Extract scale and offset from world transform + math::Vec scale = math::Vec(xform[0], xform[3]); + math::Vec offset = math::Vec(xform[4], xform[5]); + math::Vec offsetI = offset.floor(); // Render to framebuffer if (dirty) { dirty = false; - internal->box = getChildrenBoundingBox(); - internal->box.pos = internal->box.pos.mult(s).floor(); - internal->box.size = internal->box.size.mult(s).ceil().plus(math::Vec(1, 1)); - - math::Vec fbSize = internal->box.size.mult(app()->window->pixelRatio * oversample); - - if (!fbSize.isFinite()) - return; - if (fbSize.isZero()) - return; + fbScale = scale; + // World coordinates, in range [0, 1) + fbOffset = offset.minus(offsetI); + + math::Rect localBox; + if (children.empty()) { + localBox = box.zeroPos(); + } + else { + localBox = getChildrenBoundingBox(); + } + + // DEBUG("%g %g %g %g, %g %g, %g %g", RECT_ARGS(localBox), VEC_ARGS(fbOffset), VEC_ARGS(scale)); + // Transform to world coordinates, then expand to nearest integer coordinates + math::Vec min = localBox.getTopLeft().mult(scale).plus(fbOffset).floor(); + math::Vec max = localBox.getBottomRight().mult(scale).plus(fbOffset).ceil(); + fbBox = math::Rect::fromMinMax(min, max); + // DEBUG("%g %g %g %g", RECT_ARGS(fbBox)); + + math::Vec newFbSize = fbBox.size.mult(app()->window->pixelRatio * oversample); + + if (!fb || !newFbSize.isEqual(fbSize)) { + fbSize = newFbSize; + // Delete old framebuffer + if (fb) + nvgluDeleteFramebuffer(fb); + // Create a framebuffer from the main nanovg context. We will draw to this in the secondary nanovg context. + if (fbSize.isFinite() && !fbSize.isZero()) + fb = nvgluCreateFramebuffer(vg, fbSize.x, fbSize.y, 0); + } - // INFO("rendering framebuffer %f %f", fbSize.x, fbSize.y); - // Delete old one first to free up GPU memory - internal->setFramebuffer(NULL); - // Create a framebuffer from the main nanovg context. We will draw to this in the secondary nanovg context. - NVGLUframebuffer *fb = nvgluCreateFramebuffer(app()->window->vg, fbSize.x, fbSize.y, 0); if (!fb) return; - internal->setFramebuffer(fb); nvgluBindFramebuffer(fb); - glViewport(0.0, 0.0, fbSize.x, fbSize.y); - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - NVGcontext *framebufferVg = app()->window->framebufferVg; - nvgBeginFrame(framebufferVg, fbSize.x, fbSize.y, app()->window->pixelRatio * oversample); - - nvgScale(framebufferVg, app()->window->pixelRatio * oversample, app()->window->pixelRatio * oversample); - // Use local scaling - nvgTranslate(framebufferVg, bf.x, bf.y); - nvgTranslate(framebufferVg, -internal->box.pos.x, -internal->box.pos.y); - nvgScale(framebufferVg, s.x, s.y); - Widget::draw(framebufferVg); - - nvgEndFrame(framebufferVg); + drawFramebuffer(app()->window->fbVg); nvgluBindFramebuffer(NULL); } - if (!internal->fb) { + if (!fb) return; - } // Draw framebuffer image, using world coordinates nvgSave(vg); nvgResetTransform(vg); - nvgTranslate(vg, bi.x, bi.y); nvgBeginPath(vg); - nvgRect(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y); - NVGpaint paint = nvgImagePattern(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y, 0.0, internal->fb->image, 1.0); + nvgRect(vg, + offsetI.x + fbBox.pos.x, + offsetI.y + fbBox.pos.y, + fbBox.size.x, fbBox.size.y); + NVGpaint paint = nvgImagePattern(vg, + offsetI.x + fbBox.pos.x, + offsetI.y + fbBox.pos.y, + fbBox.size.x, fbBox.size.y, + 0.0, fb->image, 1.0); nvgFillPaint(vg, paint); nvgFill(vg); // For debugging the bounding box of the framebuffer // nvgStrokeWidth(vg, 2.0); - // nvgStrokeColor(vg, nvgRGBA(255, 0, 0, 128)); + // nvgStrokeColor(vg, nvgRGBAf(1, 1, 0, 0.5)); // nvgStroke(vg); nvgRestore(vg); } +void FramebufferWidget::drawFramebuffer(NVGcontext *vg) { + float pixelRatio = fbSize.x / fbBox.size.x; + nvgBeginFrame(vg, fbBox.size.x, fbBox.size.y, pixelRatio); + + // Use local scaling + nvgTranslate(vg, -fbBox.pos.x, -fbBox.pos.y); + nvgTranslate(vg, fbOffset.x, fbOffset.y); + nvgScale(vg, fbScale.x, fbScale.y); + + Widget::draw(vg); + + glViewport(0.0, 0.0, fbSize.x, fbSize.y); + glClearColor(0.0, 0.0, 0.0, 0.0); + // glClearColor(0.0, 1.0, 1.0, 0.5); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + nvgEndFrame(vg); +} + int FramebufferWidget::getImageHandle() { - if (!internal->fb) + if (!fb) return -1; - return internal->fb->image; + return fb->image; } diff --git a/src/widgets/Widget.cpp b/src/widgets/Widget.cpp index 0b3c24c0..dd781778 100644 --- a/src/widgets/Widget.cpp +++ b/src/widgets/Widget.cpp @@ -14,16 +14,13 @@ Widget::~Widget() { } math::Rect Widget::getChildrenBoundingBox() { - math::Rect bound; + math::Vec min = math::Vec(INFINITY, INFINITY); + math::Vec max = math::Vec(-INFINITY, -INFINITY); for (Widget *child : children) { - if (child == children.front()) { - bound = child->box; - } - else { - bound = bound.expand(child->box); - } + min = min.min(child->box.getTopLeft()); + max = max.max(child->box.getBottomRight()); } - return bound; + return math::Rect::fromMinMax(min, max); } math::Vec Widget::getRelativeOffset(math::Vec v, Widget *relative) { diff --git a/src/window.cpp b/src/window.cpp index e5923fd5..88376184 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -17,20 +17,6 @@ #include -#define NANOVG_GL2_IMPLEMENTATION 1 -// #define NANOVG_GL3_IMPLEMENTATION 1 -// #define NANOVG_GLES2_IMPLEMENTATION 1 -// #define NANOVG_GLES3_IMPLEMENTATION 1 -#include -// Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported) -#define NANOVG_FBO_VALID 1 -#include -#define BLENDISH_IMPLEMENTATION -#include -#define NANOSVG_IMPLEMENTATION -#define NANOSVG_ALL_COLOR_KEYWORDS -#include - namespace rack { @@ -84,7 +70,7 @@ std::shared_ptr Image::load(const std::string &filename) { } SVG::SVG(const std::string &filename) { - handle = nsvgParseFromFile(filename.c_str(), "px", SVG_DPI); + handle = nsvgParseFromFile(filename.c_str(), "px", APP_SVG_DPI); if (handle) { INFO("Loaded SVG %s", filename.c_str()); } @@ -171,10 +157,6 @@ static void scrollCallback(GLFWwindow *win, double x, double y) { math::Vec scrollDelta = math::Vec(x, y); scrollDelta = scrollDelta.mult(50.0); - // Flip coordinates if shift is held - if ((window->getMods() & WINDOW_MOD_MASK) == GLFW_MOD_SHIFT) - scrollDelta = scrollDelta.flip(); - app()->event->handleScroll(window->mousePos, scrollDelta); } @@ -215,10 +197,10 @@ Window::Window() { internal = new Internal; int err; -#if NANOVG_GL2 +#if defined NANOVG_GL2 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); -#elif NANOVG_GL3 +#elif defined NANOVG_GL3 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); @@ -275,13 +257,13 @@ Window::Window() { assert(vg); #if defined NANOVG_GL2 - framebufferVg = nvgCreateGL2(nvgFlags); + fbVg = nvgCreateGL2(nvgFlags); #elif defined NANOVG_GL3 - framebufferVg = nvgCreateGL3(nvgFlags); + fbVg = nvgCreateGL3(nvgFlags); #elif defined NANOVG_GLES2 - framebufferVg = nvgCreateGLES2(nvgFlags); + fbVg = nvgCreateGLES2(nvgFlags); #endif - assert(framebufferVg); + assert(fbVg); } Window::~Window() { @@ -294,11 +276,11 @@ Window::~Window() { #endif #if defined NANOVG_GL2 - nvgDeleteGL2(framebufferVg); + nvgDeleteGL2(fbVg); #elif defined NANOVG_GL3 - nvgDeleteGL3(framebufferVg); + nvgDeleteGL3(fbVg); #elif defined NANOVG_GLES2 - nvgDeleteGLES2(framebufferVg); + nvgDeleteGLES2(fbVg); #endif glfwDestroyWindow(win); @@ -311,10 +293,11 @@ void Window::run() { frame = 0; while(!glfwWindowShouldClose(win)) { double startTime = glfwGetTime(); - frame++; // Poll events glfwPollEvents(); + // In case glfwPollEvents() set another OpenGL context + glfwMakeContextCurrent(win); // Call cursorPosCallback every frame, not just when the mouse moves { double xpos, ypos; @@ -364,12 +347,6 @@ void Window::run() { // Render bool visible = glfwGetWindowAttrib(win, GLFW_VISIBLE) && !glfwGetWindowAttrib(win, GLFW_ICONIFIED); if (visible) { - // In case glfwPollEvents() worked with another OpenGL context - glfwMakeContextCurrent(win); - glViewport(0, 0, fbWidth, fbHeight); - glClearColor(0.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - // Update and render nvgBeginFrame(vg, winWidth, winHeight, pixelRatio); @@ -378,7 +355,11 @@ void Window::run() { app()->event->rootWidget->draw(vg); + glViewport(0, 0, fbWidth, fbHeight); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); nvgEndFrame(vg); + glfwSwapBuffers(win); } @@ -391,6 +372,7 @@ void Window::run() { } endTime = glfwGetTime(); // INFO("%lf fps", 1.0 / (endTime - startTime)); + frame++; } }