Demo Plugin

This section provides an example of a demo plugin for VRED. The demo plugin demonstrates how to create a user plugin by inheriting from the VREDUserPluginInterface class and implementing its methods.

Implementation

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <vector>
#include <array>
#include <thread>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif

#include "vrUserPluginDemo.h"

// *********************************
// OpenGL related stuff
// *********************************

#define GL_BGR              0x80E0
#define GL_PACK_ALIGNMENT   0x0D05
#define GL_UNSIGNED_BYTE    0x1401

using GLenum = unsigned int;
using GLint = int;
using GLsizei = int;
using GLvoid = void;

using GLReadPixelsFunc = void (*)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid *);
static GLReadPixelsFunc glReadPixels = nullptr;

using GLPixelStoreiFunc = void (*)(GLenum, GLint);
static GLPixelStoreiFunc glPixelStorei = nullptr;

static void initGL()
{
#ifdef _WIN32
    auto glLibrary = LoadLibrary("opengl32.dll");
    if(glLibrary == nullptr)
        return;

    glReadPixels = reinterpret_cast<GLReadPixelsFunc>(GetProcAddress(glLibrary, "glReadPixels"));
    glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(GetProcAddress(glLibrary, "glPixelStorei"));

#else

    auto glLibrary = dlopen("/usr/lib64/libGL.so.1", RTLD_LAZY);
    if(glLibrary == nullptr)
        return;

    glReadPixels = reinterpret_cast<GLReadPixelsFunc>(dlsym(glLibrary, "glReadPixels"));
    glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(dlsym(glLibrary, "glPixelStorei"));

#endif
}

static bool writeBMP(const std::string &filename, std::uint32_t w, std::uint32_t h, const char *rgb)
{
    std::ofstream out(filename, std::ios_base::out | std::ios_base::binary);
    if(!out)
        return false;

    std::uint32_t pad = w * -3 & 3;
    std::uint32_t total = 54 + 3*w*h + pad*h;
    std::array<uint32_t, 13> head = {total, 0u, 54u, 40u, w, h, (24u<<16u)|1u};

    out.write("BM", 2);
    out.write(reinterpret_cast<const char *>(head.data()), 52);
    for(uint32_t i=0 ; i<h ; i++)
    {   out.write(rgb + (3 * w * i), 3 * w);
        out.write(reinterpret_cast<const char *>(&pad), pad);
    }

    return true;
}

static void writeFrameBuffer(std::uint32_t fboId, std::uint32_t width, std::uint32_t height)
{
    if(glReadPixels == nullptr || glPixelStorei == nullptr)
        return;

    std::vector<char> img;
    img.resize(width * height * 3);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glReadPixels(0, 0, width, height, GL_BGR, GL_UNSIGNED_BYTE, img.data());

#ifdef _WIN32
    static int count = 0;
    std::string path = "image" + std::to_string(count++) + ".bmp";
    auto appdata = getenv("APPDATA");
    if(appdata != nullptr)
        path = std::string(appdata) + "\\" + path;
#else
    std::string path = "/tmp/image.bmp";
#endif

    if(writeBMP(path, width, height, img.data()))
        std::cout << "*** vrUserPluginDemo::writeFrameBuffer: wrote image to: " << path << std::endl;
}

// *********************************
// Plugin implementation
// *********************************
vrUserPluginDemo::vrUserPluginDemo(VREDUserKernelInterface *kernelInterface) :
    m_ki(kernelInterface),
    m_readyToRender(false),
    m_updateCount(0)
{
    std::cout << "*** vrUserPluginDemo::vrUserPluginDemo" << std::endl;
    m_ki->registerUpdate([this]()
    {
        update();
    });

    m_ki->registerMessage([this](const VREDMessage &msg)
    {
        message(msg);
    });

    m_ki->registerFrameBufferChanged([this](const VREDGLBuffer &context) -> bool
    {
        return frameBufferChanged(context);
    });
}

vrUserPluginDemo::~vrUserPluginDemo()
{
    std::cout << "*** vrUserPluginDemo::~vrUserPluginDemo" << std::endl;
}

void vrUserPluginDemo::init()
{
    initGL();
    std::string cmd = "42 * 12";
    auto r = m_ki->runPythonR(cmd.c_str());
    std::cout << "*** vrUserPluginDemo::init: runPython: '" << cmd << "' returns: " << r << std::endl;
    //m_ki->setFrameSize(1920, 1080);
    std::thread t([this]()
    {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        std::string cmd = "42 * 12";
        auto r = m_ki->runPythonR(cmd.c_str());
        std::cout << "*** vrUserPluginDemo::init: runPython from a thread: '" << cmd << "' returns: " << r << std::endl;
    });
    t.detach();
}

void vrUserPluginDemo::update()
{
    // just print the first 10 times
    if(m_updateCount < 10)
    {
        std::cout << "*** vrUserPluginDemo::update" << std::endl;
        ++m_updateCount;
    }
}

void vrUserPluginDemo::message(const VREDMessage &msg)
{
    //std::cout << "*** vrUserPluginDemo::message: " << msg.type << std::endl;
    if(msg.type == VREDMessageTypes::NewScene)
    {
        m_readyToRender = false;
    }
    else if(msg.type == VREDMessageTypes::ProjectReadyToRender)
    {
        std::cout << "*** vrUserPluginDemo::message: ProjectReadyToRender" << std::endl;
        m_readyToRender = true;
    }
}

bool vrUserPluginDemo::frameBufferChanged(const VREDGLBuffer &context)
{
    if(!m_readyToRender)
        return false;

    writeFrameBuffer(context.fboId, context.width, context.height);
    // return true means don't blit to the vred render window.
    return false;
}

VRED_REGISTER_USER_PLUGIN(vrUserPluginDemo)

Viewer Plugin

The implementation of the vrUserPluginViewer class includes methods for initialization, updating, handling messages, and handling frame buffer changes. These methods are used to manage the display and interaction with images within VRED. The viewer plugin opens a view of the render result from VRED, allowing for mouse navigation and keyboard interaction.

Header

#pragma once

#include <VREDUserPluginInterface.h>

class vrImageWidget;

class vrUserPluginViewer : public VREDUserPluginInterface
{
public:

    vrUserPluginViewer(VREDUserKernelInterface *kernelInterface);
    ~vrUserPluginViewer();

    void init() override;

    bool frameBufferChanged(const VREDGLBuffer &context);

private:

    VREDUserKernelInterface *m_ki;
    bool m_enabled;
    vrImageWidget *m_viewer;
};

Implementation

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <vector>
#include <array>
#include <format>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif

#include <QWidget>
#include <QPainter>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QWheelEvent>
#include <QTimer>

#include "vrUserPluginViewer.h"

// *********************************
// OpenGL related stuff
// *********************************

#define GL_BGR              0x80E0
#define GL_PACK_ALIGNMENT   0x0D05
#define GL_UNSIGNED_BYTE    0x1401

using GLenum = unsigned int;
using GLint = int;
using GLsizei = int;
using GLvoid = void;

using GLReadPixelsFunc = void (*)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid *);
static GLReadPixelsFunc glReadPixels = nullptr;

using GLPixelStoreiFunc = void (*)(GLenum, GLint);
static GLPixelStoreiFunc glPixelStorei = nullptr;

static void initGL()
{
#ifdef _WIN32
    auto glLibrary = LoadLibrary("opengl32.dll");
    if(glLibrary == nullptr)
        return;

    glReadPixels = reinterpret_cast<GLReadPixelsFunc>(GetProcAddress(glLibrary, "glReadPixels"));
    glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(GetProcAddress(glLibrary, "glPixelStorei"));

#else

    auto glLibrary = dlopen("/usr/lib64/libGL.so.1", RTLD_LAZY);
    if(glLibrary == nullptr)
        return;

    glReadPixels = reinterpret_cast<GLReadPixelsFunc>(dlsym(glLibrary, "glReadPixels"));
    glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(dlsym(glLibrary, "glPixelStorei"));

#endif
}

// *******

class vrImageWidget : public QWidget
{
public:
    vrImageWidget(VREDUserKernelInterface *ki, QWidget *parent = nullptr) :
        QWidget(parent),
        m_ki(ki),
        m_initScript(),
        m_image()
    {
        setAttribute(Qt::WA_AcceptTouchEvents);

        // execute python code to get the render window
        // we actually have two widgets with the name "Render". One is a wrapper and the other one is the actual render window
        // the wrapper has the real render window as a child.
        // with VRED 2024.2 you can use directly the new pre-defined python variable VREDVREDRenderWindow
        // a new scene in VRED will delete all user variables and imports so we need to call this script every time we want to use Qt APIs
        m_initScript =
R"(
try:
    viewerScriptInitialized
except NameError:
    from PySide6 import QtCore, QtGui, QtWidgets
    viewerScriptInitialized = True
try:
    VREDRenderWindow
except NameError:
    VREDRenderWindowTmp = VREDMainWindow.findChild(QtWidgets.QWidget, "Render")
    VREDRenderWindow = VREDRenderWindowTmp.findChild(QtWidgets.QWidget, "Render")
    if VREDRenderWindow is None:
    VREDRenderWindow = VREDRenderWindowTmp
    del VREDRenderWindowTmp)";

        m_ki->runPython(m_initScript);
    }

    void setImage(const QImage &image)
    {
        m_image = image;
        update();
    }

protected:

    void paintEvent(QPaintEvent *event) override
    {
        QPainter p(this);
        p.drawImage(rect(), m_image);
    }

    void wheelEvent(QWheelEvent *we) override
    {
        QWidget::wheelEvent(we);
        m_ki->runPython(m_initScript);
        m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QWheelEvent(QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({}), QtCore.Qt.ScrollPhase({}), {}))",
                        we->position().x(), we->position().y(), we->globalPosition().x(), we->globalPosition().y(), we->pixelDelta().x(), we->pixelDelta().y(),
                        we->angleDelta().x(), we->angleDelta().y(), static_cast<int>(we->buttons()), static_cast<int>(we->modifiers()), static_cast<int>(we->phase()), we->inverted() ? "True" : "False"));
    }

#if 0
QMouseEvent(QEvent::Type type, const QPointF &localPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, const QPointingDevice *device = QPointingDevice::primaryPointingDevice())

QMouseEvent(QEvent::Type type, const QPointF &localPos, const QPointF &globalPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, const QPointingDevice *device = QPointingDevice::primaryPointingDevice())
#endif
    void mousePressEvent(QMouseEvent *me) override
    {
        QWidget::mousePressEvent(me);
        m_ki->runPython(m_initScript);
        m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButton({}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({})))", me->position().x(), me->position().y(), me->globalPosition().x(), me->globalPosition().y(), static_cast<int>(me->button()), static_cast<int>(me->buttons()), static_cast<int>(me->modifiers())));
    }

    void mouseReleaseEvent(QMouseEvent *me) override
    {
        QWidget::mouseReleaseEvent(me);
        m_ki->runPython(m_initScript);
        m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButton({}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({})))", me->position().x(), me->position().y(), me->globalPosition().x(), me->globalPosition().y(), static_cast<int>(me->button()), static_cast<int>(me->buttons()), static_cast<int>(me->modifiers())));
    }

    void mouseMoveEvent(QMouseEvent *me) override
    {
        QWidget::mouseMoveEvent(me);
        m_ki->runPython(m_initScript);
        m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QMouseEvent(QtCore.QEvent.MouseMove, QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButton({}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({})))", me->position().x(), me->position().y(), me->globalPosition().x(), me->globalPosition().y(), static_cast<int>(me->button()), static_cast<int>(me->buttons()), static_cast<int>(me->modifiers())));
    }

    void keyPressEvent(QKeyEvent *ke) override
    {
        QWidget::keyPressEvent(ke);
        m_ki->runPython(m_initScript);
        m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QKeyEvent(QtCore.QEvent.KeyPress, {}, QtCore.Qt.KeyboardModifiers({})))", ke->key(), static_cast<int>(ke->modifiers())));
    }

    void keyReleaseEvent(QKeyEvent *ke) override
    {
        QWidget::keyReleaseEvent(ke);
        m_ki->runPython(m_initScript);
        m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QKeyEvent(QtCore.QEvent.KeyRelease, {}, QtCore.Qt.KeyboardModifiers({})))", ke->key(), static_cast<int>(ke->modifiers())));
    }

private:

    VREDUserKernelInterface *m_ki;
    std::string m_initScript;
    QImage m_image;
};

// *********************************
// Plugin implementation
// *********************************
vrUserPluginViewer::vrUserPluginViewer(VREDUserKernelInterface *kernelInterface) :
    m_ki(kernelInterface),
    m_enabled(false),
    m_viewer(nullptr)
{
    std::cout << "*** vrUserPluginViewer::vrUserPluginViewer" << std::endl;
    m_ki->registerFrameBufferChanged([this](const VREDGLBuffer &context) -> bool
    {
        return frameBufferChanged(context);
    });
}

vrUserPluginViewer::~vrUserPluginViewer()
{
    std::cout << "*** vrUserPluginViewer::~vrUserPluginViewer" << std::endl;
}

void vrUserPluginViewer::init()
{
    m_enabled = (qgetenv("VRED_PLUGIN_VIEWER").toInt() == 1);

    if(!m_enabled)
        return;

    initGL();

    m_viewer = new vrImageWidget(m_ki);
    m_viewer->setWindowTitle("Plugin Viewer");

    QTimer::singleShot(1000, [this] { m_viewer->show(); m_viewer->raise(); });
}

bool vrUserPluginViewer::frameBufferChanged(const VREDGLBuffer &context)
{
    if(!m_enabled)
        return false;

    if(glReadPixels == nullptr || glPixelStorei == nullptr)
        return false;

    const auto w = context.width;
    const auto h = context.height;

    QImage img(w, h, QImage::Format_BGR888);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glReadPixels(0, 0, w, h, GL_BGR, GL_UNSIGNED_BYTE, img.bits());

    m_viewer->resize(w, h);
    m_viewer->setImage(img.mirrored());

    // return true means don't blit to the vred render window.
    return false;
}

VRED_REGISTER_USER_PLUGIN(vrUserPluginViewer)