| @@ -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/). | |||
| @@ -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> 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> svg); | |||
| }; | |||
| @@ -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); | |||
| } | |||
| @@ -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 | |||
| @@ -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 { | |||
| @@ -7,6 +7,9 @@ | |||
| #include <GL/glew.h> | |||
| #include <GLFW/glfw3.h> | |||
| #include <nanovg.h> | |||
| #define NANOVG_GL2 | |||
| #include <nanovg_gl.h> | |||
| #include <nanovg_gl_utils.h> | |||
| #include <nanosvg.h> | |||
| @@ -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. | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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> 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 | |||
| @@ -2,18 +2,18 @@ | |||
| #include "system.hpp" | |||
| #include "plugin/Plugin.hpp" | |||
| #if ARCH_MAC | |||
| #if defined ARCH_MAC | |||
| #include <CoreFoundation/CoreFoundation.h> | |||
| #include <pwd.h> | |||
| #endif | |||
| #if ARCH_WIN | |||
| #if defined ARCH_WIN | |||
| #include <Windows.h> | |||
| #include <Shlobj.h> | |||
| #include <Shlwapi.h> | |||
| #endif | |||
| #if ARCH_LIN | |||
| #if defined ARCH_LIN | |||
| #include <unistd.h> | |||
| #include <sys/types.h> | |||
| #include <pwd.h> | |||
| @@ -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) { | |||
| @@ -4,7 +4,7 @@ | |||
| #include "dsp/ringbuffer.hpp" | |||
| #include <unistd.h> | |||
| #if ARCH_WIN | |||
| #if defined ARCH_WIN | |||
| #include <winsock2.h> | |||
| #else | |||
| #include <sys/socket.h> | |||
| @@ -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"); | |||
| @@ -0,0 +1,18 @@ | |||
| // This source file compiles those annoying implementation-in-header libraries | |||
| #define GLEW_STATIC | |||
| #include <GL/glew.h> | |||
| #include <nanovg.h> | |||
| #define NANOVG_GL2_IMPLEMENTATION | |||
| // #define NANOVG_GL3_IMPLEMENTATION | |||
| // #define NANOVG_GLES2_IMPLEMENTATION | |||
| // #define NANOVG_GLES3_IMPLEMENTATION | |||
| #include <nanovg_gl.h> | |||
| // Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported) | |||
| #define NANOVG_FBO_VALID | |||
| #include <nanovg_gl_utils.h> | |||
| #define BLENDISH_IMPLEMENTATION | |||
| #include <blendish.h> | |||
| #define NANOSVG_IMPLEMENTATION | |||
| #define NANOSVG_ALL_COLOR_KEYWORDS | |||
| #include <nanosvg.h> | |||
| @@ -19,7 +19,7 @@ | |||
| #include <zip.h> | |||
| #include <jansson.h> | |||
| #if ARCH_WIN | |||
| #if defined ARCH_WIN | |||
| #include <windows.h> | |||
| #include <direct.h> | |||
| #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 | |||
| @@ -2,7 +2,7 @@ | |||
| #include <dirent.h> | |||
| #include <sys/stat.h> | |||
| #if ARCH_WIN | |||
| #if defined ARCH_WIN | |||
| #include <windows.h> | |||
| #include <shellapi.h> | |||
| #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 | |||
| } | |||
| @@ -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); | |||
| } | |||
| @@ -1,34 +1,17 @@ | |||
| #include "widgets/FramebufferWidget.hpp" | |||
| #include "app.hpp" | |||
| #include <nanovg_gl.h> | |||
| #include <nanovg_gl_utils.h> | |||
| 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; | |||
| } | |||
| @@ -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) { | |||
| @@ -17,20 +17,6 @@ | |||
| #include <osdialog.h> | |||
| #define NANOVG_GL2_IMPLEMENTATION 1 | |||
| // #define NANOVG_GL3_IMPLEMENTATION 1 | |||
| // #define NANOVG_GLES2_IMPLEMENTATION 1 | |||
| // #define NANOVG_GLES3_IMPLEMENTATION 1 | |||
| #include <nanovg_gl.h> | |||
| // Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported) | |||
| #define NANOVG_FBO_VALID 1 | |||
| #include <nanovg_gl_utils.h> | |||
| #define BLENDISH_IMPLEMENTATION | |||
| #include <blendish.h> | |||
| #define NANOSVG_IMPLEMENTATION | |||
| #define NANOSVG_ALL_COLOR_KEYWORDS | |||
| #include <nanosvg.h> | |||
| namespace rack { | |||
| @@ -84,7 +70,7 @@ std::shared_ptr<Image> 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++; | |||
| } | |||
| } | |||