@@ -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++; | |||
} | |||
} | |||