memononen / nanovg

Antialiased 2D vector drawing library on top of OpenGL for UI and visualizations.
zlib License
5.15k stars 771 forks source link

Getting filled rectangle when trying to render an arc #539

Open SkyAphid opened 5 years ago

SkyAphid commented 5 years ago

I'm using code from this issue and I'm getting the same error as the OP: https://github.com/memononen/nanovg/issues/250

Code:

nvgBeginPath(vg);

nvgMoveTo(vg, centerX, centerY);
nvgArc(vg, centerX, centerY, radius, NVG_PI / 4, NVG_PI / 2 * 3, NVG_CW);
nvgLineTo(vg, centerX, centerY);

nvgFillColor(vg, fill);
nvgFill(vg);

nvgStrokeColor(vg, strokeFill);
nvgStroke(vg);

nvgClosePath(vg);

Here's what it looks like: Untitled

Can someone please help me render the arc correctly? I don't really have any idea what's wrong here.

memononen commented 5 years ago

You need to setup OpenGL with stencil buffer. The first shape renders fine because it os convex, the second is concave which needs stencil buffer to render.

kallaballa commented 1 year ago

I have the exact same problem though (I think) I have setup the stencil buffer correctly. My setup is rather complex because my use case is putting nanovg on top of OpenCL/OpenGL and OpenCL/VAAPI interop: https://github.com/opencv/opencv/issues/22607 For the moment I am concentrating on OpenGL interop and removed all VAAPI related code:

#define CL_TARGET_OPENCL_VERSION 220

constexpr unsigned long WIDTH = 1920;
constexpr unsigned long HEIGHT = 1080;

#include "../common/subsystems.hpp"
#include <csignal>

using std::cerr;
using std::endl;

bool done = false;
static void finish(int ignore) {
    std::cerr << endl;
    done = true;
}

int main(int argc, char **argv) {
    done = false;
    signal(SIGINT, finish);

    using namespace kb;

    x11::init();
    egl::init(true);
    gl::init();
    nvg::init(true);

    cerr << "EGL Version: " << egl::get_info() << endl;
    cerr << "OpenGL Version: " << gl::get_info() << endl;
    cerr << "OpenCL Platforms: " << endl << cl::get_info() << endl;

    cv::UMat frameBuffer(HEIGHT, WIDTH, CV_8UC4, cv::Scalar::all(0));
    cv::UMat videoFrame;

    while (!done) {
        //Activate the OpenCL context for OpenGL
        gl::bind();

        nvg::begin(WIDTH, HEIGHT, 1.0);

            using kb::nvg::vg;
            nvgBeginPath(vg);

            nvgMoveTo(vg, WIDTH/2, HEIGHT/2);
            nvgArc(vg, WIDTH/2, HEIGHT/2, WIDTH/8, NVG_PI / 4, NVG_PI / 2 * 3, NVG_CW);
            nvgLineTo(vg, WIDTH/2, HEIGHT/2);

            nvgFillColor(vg, nvgRGBA(255, 0, 255, 255));
            nvgFill(vg);

            nvgStrokeColor(vg, nvgRGBA(255, 255, 0, 255));
            nvgStroke(vg);

            nvgClosePath(vg);

        nvg::end();

        //Aquire frame buffer from OpenGL
        gl::acquire_from_gl(frameBuffer);
        //Color-conversions using OpenCV/OpenCL.
        cv::cvtColor(frameBuffer, frameBuffer, cv::COLOR_BGRA2GRAY);
        cv::cvtColor(frameBuffer, frameBuffer, cv::COLOR_GRAY2BGRA);
        //Transfer buffer ownership back to OpenGL
        gl::release_to_gl(frameBuffer);

        if (x11::is_initialized()) {
            //Yet again activate the OpenCL context for OpenGL
            gl::bind();
            //Blit the framebuffer we have been working on to the screen
            gl::blit_frame_buffer_to_screen();

            //Check if the x11 window was closed
            if (x11::window_closed()) {
                finish(0);
                break;
            }
            //Transfer the back buffer (which we have been using as frame buffer) to the native window
            gl::swap_buffers();
        }
    }

    return 0;
}

subsystems.hpp:

#ifndef SRC_SUBSYSTEMS_HPP_
#define SRC_SUBSYSTEMS_HPP_

#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <filesystem>
#include <opencv2/opencv.hpp>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <GL/glew.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include "nanovg.h"
#define NANOVG_GL3_IMPLEMENTATION
#include "nanovg_gl.h"
#include <CL/cl.h>
#include <CL/cl_gl.h>
#include <opencv2/core/ocl.hpp>
#include <opencv2/core/opengl.hpp>

using std::cout;
using std::cerr;
using std::endl;

namespace kb {

void glCheckError(const std::filesystem::path &file, unsigned int line, const char *expression) {
    GLint errorCode = glGetError();

    if (errorCode != GL_NO_ERROR) {
        cerr << "GL failed in " << file.filename() << " (" << line << ") : " << "\nExpression:\n   " << expression << "\nError code:\n   " << errorCode << "\n   " << endl;
        assert(false);
    }
}
#define glCheck(expr)                            \
    expr;                                        \
    kb::glCheckError(__FILE__, __LINE__, #expr);

void eglCheckError(const std::filesystem::path &file, unsigned int line, const char *expression) {
    EGLint errorCode = eglGetError();

    if (errorCode != EGL_SUCCESS) {
        cerr << "EGL failed in " << file.filename() << " (" << line << ") : " << "\nExpression:\n   " << expression << "\nError code:\n   " << errorCode << "\n   " << endl;
        assert(false);
    }
}
#define eglCheck(expr)                                 \
        expr;                                          \
        kb::eglCheckError(__FILE__, __LINE__, #expr);

namespace x11 {
Display *xdisplay;
Window xroot;
Window xwin;
Atom wmDeleteMessage;

bool initialized = false;

std::pair<unsigned int, unsigned int> get_window_size() {
    std::pair<unsigned int, unsigned int> ret;
    int x, y;
    unsigned int border, depth;
    XGetGeometry(xdisplay, xwin, &xroot, &x, &y, &ret.first, &ret.second, &border, &depth);
    return ret;
}

Display* get_x11_display() {
    return xdisplay;
}

Window get_x11_window() {
    return xwin;
}

bool is_initialized() {
    return initialized;
}

bool window_closed() {
    if (XPending(xdisplay) == 0)
        return false;

    XEvent event;
    XNextEvent(xdisplay, &event);

    switch (event.type) {
    case ClientMessage:
        if (event.xclient.data.l[0] == static_cast<long int>(wmDeleteMessage))
            return true;
        break;

    default:
        break;
    }
    return false;
}

void init() {
    xdisplay = XOpenDisplay(nullptr);
    if (xdisplay == nullptr) {
        cerr << "Unable to open X11 display" << endl;
        exit(3);
    }

    xroot = DefaultRootWindow(xdisplay);
    XSetWindowAttributes swa;
    swa.event_mask = ClientMessage;
    xwin = XCreateWindow(xdisplay, xroot, 0, 0, WIDTH, HEIGHT, 0,
    CopyFromParent, InputOutput, CopyFromParent, CWEventMask, &swa);

    XSetWindowAttributes xattr;
    xattr.override_redirect = False;
    XChangeWindowAttributes(xdisplay, xwin, CWOverrideRedirect, &xattr);

    int one = 1;
    XChangeProperty(xdisplay, xwin, XInternAtom(xdisplay, "_HILDON_NON_COMPOSITED_WINDOW", False),
    XA_INTEGER, 32, PropModeReplace, (unsigned char*) &one, 1);

    XWMHints hints;
    hints.input = True;
    hints.flags = InputHint;
    XSetWMHints(xdisplay, xwin, &hints);

    XMapWindow(xdisplay, xwin);
    XStoreName(xdisplay, xwin, "nanovg-demo");
    wmDeleteMessage = XInternAtom(xdisplay, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(xdisplay, xwin, &wmDeleteMessage, 1);

    initialized = true;
}
}

namespace egl {
//code in the kb::egl namespace deals with setting up EGL
EGLDisplay display;
EGLSurface surface;
EGLContext context;

void debugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *msg, const void *data) {
    std::string _source;
    std::string _type;
    std::string _severity;

    switch (source) {
        case GL_DEBUG_SOURCE_API:
        _source = "API";
        break;

        case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
        _source = "WINDOW SYSTEM";
        break;

        case GL_DEBUG_SOURCE_SHADER_COMPILER:
        _source = "SHADER COMPILER";
        break;

        case GL_DEBUG_SOURCE_THIRD_PARTY:
        _source = "THIRD PARTY";
        break;

        case GL_DEBUG_SOURCE_APPLICATION:
        _source = "APPLICATION";
        break;

        case GL_DEBUG_SOURCE_OTHER:
        _source = "UNKNOWN";
        break;

        default:
        _source = "UNKNOWN";
        break;
    }

    switch (type) {
        case GL_DEBUG_TYPE_ERROR:
        _type = "ERROR";
        break;

        case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
        _type = "DEPRECATED BEHAVIOR";
        break;

        case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
        _type = "UDEFINED BEHAVIOR";
        break;

        case GL_DEBUG_TYPE_PORTABILITY:
        _type = "PORTABILITY";
        break;

        case GL_DEBUG_TYPE_PERFORMANCE:
        _type = "PERFORMANCE";
        break;

        case GL_DEBUG_TYPE_OTHER:
        _type = "OTHER";
        break;

        case GL_DEBUG_TYPE_MARKER:
        _type = "MARKER";
        break;

        default:
        _type = "UNKNOWN";
        break;
    }

    switch (severity) {
        case GL_DEBUG_SEVERITY_HIGH:
        _severity = "HIGH";
        break;

        case GL_DEBUG_SEVERITY_MEDIUM:
        _severity = "MEDIUM";
        break;

        case GL_DEBUG_SEVERITY_LOW:
        _severity = "LOW";
        break;

        case GL_DEBUG_SEVERITY_NOTIFICATION:
        _severity = "NOTIFICATION";
        break;

        default:
        _severity = "UNKNOWN";
        break;
    }

    fprintf(stderr, "%d: %s of %s severity, raised from %s: %s\n", id, _type.c_str(), _severity.c_str(), _source.c_str(), msg);

    if(type == GL_DEBUG_TYPE_ERROR)
        exit(2);
}

EGLBoolean swap_buffers() {
    return eglCheck(eglSwapBuffers(display, surface));
}

void init(bool debug = false) {
    bool offscreen = !x11::is_initialized();

    eglCheck(eglBindAPI(EGL_OPENGL_API));
    if (offscreen) {
        eglCheck(display = eglGetDisplay(EGL_DEFAULT_DISPLAY));
    } else {
        eglCheck(display = eglGetDisplay(x11::get_x11_display()));
    }
    eglCheck(eglInitialize(display, nullptr, nullptr));

    const EGLint egl_config_constraints[] = {
    EGL_STENCIL_SIZE, static_cast<EGLint>(8),
    EGL_DEPTH_SIZE, static_cast<EGLint>(16),
    EGL_BUFFER_SIZE, static_cast<EGLint>(32),
    EGL_RED_SIZE, static_cast<EGLint>(8),
    EGL_GREEN_SIZE, static_cast<EGLint>(8),
    EGL_BLUE_SIZE, static_cast<EGLint>(8),
    EGL_ALPHA_SIZE, static_cast<EGLint>(8),
    EGL_SAMPLE_BUFFERS, EGL_TRUE,
    EGL_SAMPLES, 4,
    EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
    EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
    EGL_CONFORMANT, EGL_OPENGL_BIT,
    EGL_CONFIG_CAVEAT, EGL_NONE,
    EGL_NONE };

    EGLint configCount;
    EGLConfig configs[1];
    eglCheck(eglChooseConfig(display, egl_config_constraints, configs, 1, &configCount));

    EGLint stencilSize;
    eglGetConfigAttrib(display,
        configs[0],
        EGL_STENCIL_SIZE,
        &stencilSize);

    cerr << "STENCIL SIZE: " << stencilSize << endl;

    if (!offscreen) {
        eglCheck(surface = eglCreateWindowSurface(display, configs[0], x11::get_x11_window(), nullptr));
    } else {
        EGLint pbuffer_attrib_list[] = {
        EGL_WIDTH, WIDTH,
        EGL_HEIGHT, HEIGHT,
        EGL_NONE };
        eglCheck(surface = eglCreatePbufferSurface(display, configs[0], pbuffer_attrib_list));
    }

    const EGLint contextVersion[] = {
    EGL_CONTEXT_MAJOR_VERSION, 4,
    EGL_CONTEXT_MINOR_VERSION, 6,
    EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
            EGL_CONTEXT_OPENGL_DEBUG, debug ? EGL_TRUE : EGL_FALSE,
            EGL_NONE };
    eglCheck(context = eglCreateContext(display, configs[0], EGL_NO_CONTEXT, contextVersion));
    eglCheck(eglMakeCurrent(display, surface, surface, context));
    eglCheck(eglSwapInterval(display, 1));

    if (debug) {
        glCheck(glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS));
        auto glDebugMessageCallback = (void (*)(void*, void*)) eglGetProcAddress("glDebugMessageCallback");
        assert(glDebugMessageCallback);
        glCheck(glDebugMessageCallback(reinterpret_cast<void*>(debugMessageCallback), nullptr));
    }
}

std::string get_info() {
    return eglQueryString(display, EGL_VERSION);
}
} //namespace egl

namespace gl {
//code in the kb::gl namespace deals with OpenGL (and OpenCV/GL) internals
cv::ogl::Texture2D *frame_buf_tex;
GLuint frame_buf;
cv::ocl::OpenCLExecutionContext context;

void bind(){
    context.bind();
}

void init() {
    glewExperimental = true;
    glewInit();

    cv::ogl::ocl::initializeContextFromGL();

    frame_buf = 0;
    glCheck(glGenFramebuffers(1, &frame_buf));
    glCheck(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_buf));
    frame_buf_tex = new cv::ogl::Texture2D(cv::Size(WIDTH, HEIGHT), cv::ogl::Texture2D::RGBA, false);
    frame_buf_tex->bind();
    glCheck(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frame_buf_tex->texId(), 0));
    assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

    /*
     * The OpenCLExecutionContext for OpenGL needs to be copied right after gl::init().
     * Now everytime you want to do OpenGL interop first bind the context.
     */
    gl::context = cv::ocl::OpenCLExecutionContext::getCurrent();
}

void swap_buffers() {
    kb::egl::swap_buffers();
}

std::string get_info() {
    return reinterpret_cast<const char*>(glGetString(GL_VERSION));
}

void acquire_from_gl(cv::UMat &m) {
    glCheck(cv::ogl::convertFromGLTexture2D(*gl::frame_buf_tex, m));
}

void release_to_gl(cv::UMat &m) {
    glCheck(cv::ogl::convertToGLTexture2D(m, *gl::frame_buf_tex));
}

void blit_frame_buffer_to_screen() {
    glBindFramebuffer(GL_READ_FRAMEBUFFER, kb::gl::frame_buf);
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glBlitFramebuffer(0, 0, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);

}
} // namespace gl

namespace cl {

std::string get_info() {
    std::stringstream ss;
    std::vector<cv::ocl::PlatformInfo> plt_info;
    cv::ocl::getPlatfomsInfo(plt_info);
    const cv::ocl::Device &device = cv::ocl::Device::getDefault();
    for (const auto &info : plt_info) {
        ss << "\t* " << info.version() << " = " << info.name() << endl;
    }

    ss << "\t  GL sharing: " << (device.isExtensionSupported("cl_khr_gl_sharing") ? "true" : "false") << endl;
    ss << "\t  GL MSAA sharing: " << (device.isExtensionSupported("cl_khr_gl_msaa_sharing") ? "true" : "false") << endl;
    ss << "\t  VAAPI media sharing: " << (device.isExtensionSupported("cl_intel_va_api_media_sharing") ? "true" : "false") << endl;
    return ss.str();
}
} //namespace cl

namespace nvg {
NVGcontext *vg;

void reset() {
    glCheck(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, kb::gl::frame_buf));
    glCheck(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
    glCheck(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
}

void push() {
    glCheck(glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS));
    glCheck(glPushAttrib(GL_ALL_ATTRIB_BITS));
    glCheck(glMatrixMode(GL_MODELVIEW));
    glCheck(glPushMatrix());
    glCheck(glMatrixMode(GL_PROJECTION));
    glCheck(glPushMatrix());
    glCheck(glMatrixMode(GL_TEXTURE));
    glCheck(glPushMatrix());

    reset();
}

void pop() {
    glCheck(glMatrixMode(GL_TEXTURE));
    glCheck(glPopMatrix());
    glCheck(glMatrixMode(GL_PROJECTION));
    glCheck(glPopMatrix());
    glCheck(glMatrixMode(GL_MODELVIEW));
    glCheck(glPopMatrix());
    glCheck(glPopClientAttrib());
    glCheck(glPopAttrib());
}

void begin(int w, int h, double pxRatio = 1) {
    push();
    nvgSave(vg);
    nvgBeginFrame(vg, w, h, pxRatio);
}

void end() {
    nvgEndFrame(vg);
    nvgRestore(vg);
    pop();
    glCheck(glFlush());
    glCheck(glFinish());
}

void init(bool debug = false) {
    push();

    glCheck(glViewport(0, 0, WIDTH, HEIGHT));
    glCheck(glEnable(GL_STENCIL_TEST));
    glCheck(glStencilMask(~0));
    glCheck(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
    glCheck(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));

    vg = nvgCreateGL3(NVG_STENCIL_STROKES | debug ? NVG_DEBUG : 0);
    if (vg == NULL) {
        cerr << "Couldn't init nanovg." << endl;
        exit(24);
    }

    pop();
}
} //namespace nvg
}

#endif /* SRC_SUBSYSTEMS_HPP_ */

Resulting in: bla

kallaballa commented 1 year ago

I found the problem. I had to create a GL_DEPTH_STENCIL_ATTACHMENT.

i changed gl::init() to:

void init() {
    glewExperimental = true;
    glewInit();

    cv::ogl::ocl::initializeContextFromGL();

    frame_buf = 0;
    glCheck(glGenFramebuffers(1, &frame_buf));
    glCheck(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_buf));

    frame_buf_tex = new cv::ogl::Texture2D(cv::Size(WIDTH, HEIGHT), cv::ogl::Texture2D::RGBA, false);
    frame_buf_tex->bind();

    // Create stencil render buffer (note that I create depth buffer the exact same way, and It works.
    GLuint sb;
    glGenRenderbuffers(1, &sb);
    glBindRenderbuffer(GL_RENDERBUFFER, sb);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, WIDTH, HEIGHT);

    glCheck(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frame_buf_tex->texId(), 0));
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, sb);
    assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

    /*
     * The OpenCLExecutionContext for OpenGL needs to be copied right after gl::init().
     * Now everytime you want to do OpenGL interop first bind the context.
     */
    gl::context = cv::ocl::OpenCLExecutionContext::getCurrent();
}

and now it works.