This should be simple: I'm using a QGLWidget to draw some openGL graphics and I want to be able to write something on the openGL graphics rendered, so I'm using overpainting as in the Qt demo with QPainter.
Here are my two working choices of structuring my program:
// This works but it's probably stupid
paintEvent()
{
makeCurrent();
glewInit();
loadShaders();
loadTextures();
loadBuffers();
... actually paint something with openGL ...
QPainter painter(this);
... overpainting ...
}
------------------------------------------------------------------------------------
// This works and may probably be better
paintEvent()
{
QGLWidget::paintEvent(event); // Base class call, this calls initializeGL ONCE and then paintGL each time it's needed
QPainter painter(this);
... overpainting ...
}
initializeGL()
{
glewInit();
}
paintGL()
{
loadShaders();
loadTextures();
loadBuffers();
... actually paint something with openGL ...
}
Considering that textures and shaders aren't going to be always the same, is any of these options acceptable (in performances and reasonably)?
If not: how would you structure the program?
Thank you for any help
load/compile/link shaders in the initializeGL() method, because that is relatively slow operation (specially if it is read from the disk)
load textures in the initializeGL() method
Not sure what are buffers, but sounds like it should be done in the initialization, since it is done only once.
Related
I have some code that uses QPainter to render to a QWidget, QImage or QPrinter, since each is derived from QPaintDevice, like this:
void RenderWithQPainter (QPaintDevice* device)
{
QPainter painter (device);
painter->doThis();
painter->doThat();
}
void RenderToImage (QImage* image)
{
RenderWithQPainter (image);
}
void RenderToWidget (QWidget* widget)
{
RenderWithQPainter (widget);
}
void RenderToPrinter (QPrinter* printer)
{
RenderWithQPainter (printer);
}
Now though, I want to use OpenGL. I know how to render into a QOpelGLWidget but I'd like my code to remain device-agnostic. So what can I render into that I can then copy to the various devices? Is there something I could derive from each of QWidget, QImage and QPrinter, not necessarily a Qt class, which I could render to using OpenGL commands instead of QPainter?
I have this function that should draw a rect based on where the user clicks and drags however nothing is drawn. I have tried using the updateGL() function however that returns an error. I've also tried to use QPainter but I was not sure how to work it with the code below. I've also tried using makeCurrent() and doneCurrent() but that makes the program "unexpectedly finish".
shape drawing functions:
void drawingArea::paintEvent(QPaintEvent *event)
{
paintGL();
}
void drawingArea::mousePressEvent(QMouseEvent *event)
{
Shape newShape;
int x = event->x(),y = event->y();
shapes.push_back(newShape);
Point newPoint(x,y);
shapes[0].addPoint(newPoint);
}
void drawingArea::mouseReleaseEvent(QMouseEvent *event)
{
QVector<int> startPoint = shapes[0].points[0].getPoint();
int vertices[]
{
event->x(),event->y(),0,
event->x(),startPoint[1],0,
event->x(),event->y(),0,
startPoint[0],event->y(),0
};
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_INT,0,vertices);
glDrawArrays(GL_QUADS,0,4);
QPainter painter(this);
glDisableClientState(GL_VERTEX_ARRAY);
update();
}
Also I do have this intializer function and maybe that the reason but when I try to run context->makeCurrent() it throws an error so I've commented it out but I'll drop the function incase it is that
drawingArea::drawingArea(QWidget *parent) : QOpenGLWidget(parent)
{
//initializeGL();
//paintGL();
QSurfaceFormat format;
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setVersion(2,1);
setFormat(format);
context = new QOpenGLContext;
context->setFormat(format);
context->create();
//context->makeCurrent(this->);
openGLFunctions = context->functions();
}
To execute an OpenGL instruction, an valid and current OpenGL Context is needed. QOpenGLWidget provides such an OpenGL context, when the event callback methods initializeGL(), paintGL() and resizeGL() a re executed.
If you want to use en OpenGL instruction in a mouse event callback, then you have to make the OpenGL context current by makeCurrent() and releases it by doneCurrent():
For instance:
void drawingArea::mouseReleaseEvent(QMouseEvent *event)
{
makeCurrent()
// [...]
doneCurrent()
}
Side note, never call the paintGL callback function directly. Use update() to schedules a paint even.
It seems that the paintEvent method of the QGLWidget is called before initializeGL, so where am I supposed to put my openGL initialization code?
I'm putting it into the paintEvent method like this:
void MyGLWidget::paintEvent(...)
{
makeCurrent();
..save modelview and projection matrices..
// This is initialization code
GLenum init = glewInit();
if (GLEW_OK != init)
{
/* Problem: glewInit failed, something is seriously wrong. */
qWarning() << glewGetErrorString(init);
}
// Dark blue background
glClearColor(0.2f, 0.0f, 0.5f, 0.0f);
// Enable depth test
glEnable(GL_DEPTH_TEST);
// End initialization code
... drawing code
QPainter painter(this);
...overpainting..
}
I really don't like the idea of having my glew library initialization function called every time a paintEvent is raised... although that's working.
Any suggestion?
You have to initialize OpenGL in initializeGL(), there is no other option.
But you also have to draw inside paintGL, not inside paintEvent, so that is where your mistake is.
Override initializeGL() function of QGLWidget. It is Created right for the purposes you want
From it's documentation:
This virtual function is called once before the first call to
paintGL() or resizeGL(), and then once whenever the widget has been
assigned a new QGLContext. Reimplement it in a subclass.
link to documentation: http://doc.qt.io/archives/qt-4.7/qglwidget.html#initializeGL
I'm trying to write a QML plugin that reads frames from a video (using a custom widget to do that task, NOT QtMultimedia/Phonon), and each frame is converted to a QImage RGB888, and then displayed on a QGLWidget (for performance reasons). Right now nothing is draw to the screen and the screen stays white all the time.
It's important to state that I already have all of this working without QGLWidget, so I know the issue is setting up and drawing on QGLWidget.
The plugin is being registered with:
qmlRegisterType<Video>(uri,1,0,"Video");
so Video is the main class of the plugin. On it's constructor we have:
Video::Video(QDeclarativeItem* parent)
: QDeclarativeItem(parent), d_ptr(new VideoPrivate(this))
{
setFlag(QGraphicsItem::ItemHasNoContents, false);
Q_D(Video);
QDeclarativeView* view = new QDeclarativeView;
view->setViewport(&d->canvas()); // canvas() returns a reference to my custom OpenGL Widget
}
Before I jump to the canvas object, let me say that I overloaded Video::paint() so it calls canvas.paint() while passing QImage as parameter, I don't know if this is the right way to do it so I would like some advice on this:
void Video::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(painter);
Q_UNUSED(widget);
Q_UNUSED(option);
Q_D(Video);
// I know for sure at this point "d->image()" is valid, but I'm hiding the code for clarity
d->canvas().paint(painter, option, d->image());
}
The canvas object is declared as GLWidget canvas; and the header of this class is defined as:
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
explicit GLWidget(QWidget* parent = NULL);
~GLWidget();
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QImage* image);
};
Seems pretty simple. Now, the implementation of QGLWidget is the following:
GLWidget::GLWidget(QWidget* parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
{
// Should I do something here?
// Maybe setAutoFillBackground(false); ???
}
GLWidget::~GLWidget()
{
}
And finally:
void GLWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QImage* image)
{
// I ignore painter because it comes from Video, so I create a new one:
QPainter gl_painter(this);
// Perform drawing as Qt::KeepAspectRatio
gl_painter.fillRect(QRectF(QPoint(0, 0), QSize(this->width(), this->height())), Qt::black);
QImage scaled_img = image->scaled(QSize(this->width(), this->height()), _ar, Qt::FastTransformation);
gl_painter.drawImage(qRound(this->width()/2) - qRound(scaled_img.size().width()/2),
qRound(this->height()/2) - qRound(scaled_img.size().height()/2),
scaled_img);
}
What am I missing?
I originally asked this question on Qt Forum but got no replies.
Solved. The problem was that I was trying to create a new GL context within my plugin when I should be retrieving the GL context from the application that loaded it.
This code was very helpful to understand how to accomplish that.
By the way, I discovered that the stuff was being draw inside view. It's just that I needed to execute view->show(), but that created another window which was not what I was looking for. The link I shared above has the answer.
I think that you have to draw on your glwidget using the opengl functions.
One possible way is to override your paintGL() method in GLWidget
and then draw your image with glDrawPixels().
glClear(GL_COLOR_BUFFER_BIT);
glDrawPixels(buffer.width(), buffer.height(), GL_RGBA, GL_UNSIGNED_BYTE, buffer.bits());
Where buffer is a QImage object that needs to be converted using QGLWidget::converrtToGLFormat() static method.
Look at this source for reference:
https://github.com/nayyden/ZVector/blob/master/src/GLDebugBufferWidget.cpp
So it appears that Qt4 doesn't let you draw on windows outside of a paint event. I have a lot of code that expects to be able to in order to draw rubber band lines (generic drawing code for a particular, proprietary interface that I then implement in the given UI). I've read about the pixmap method, it would be a lot of work and I don't think it's really what I want.
Is there a workaround that allows me to do what I want anyway? I just need to draw XOR bands on the screen.
Tried the WA_PaintOutsidePaintEvent flag. Then I saw the bit that says it doesn't work on Windows.
In modern compositing desktops window painting needs to be synchronized by the window manager so that the alpha blending and other effects can be applied, in order, to the correct back buffers - the result of which is then flipped onto the screen to allow tear-free window animations.
Invoking painting operations out-of-band of this process - while supported for legacy reasons on the underlying platforms - would subvert this process and cause a number of very non optimal code paths to be executed.
Basically, when you have painting to do on a window: Call the invalidate function to schedule the painting soon, and paint during the paint event.
Just paint to a QPixmap, and copy it to the real widget in the paintEvent. This is the only standard way. You shouldn't try to workaround it.
Seems like if you could get access to the Hwnd of the window in question, you could paint on that surface. Otherwise, I'm not sure. If by pixmap method you mean something like this, I don't think it's a bad solution:
m_composed_image = QImage(size, QImage::Format_ARGB32);
m_composed_image.setDotsPerMeterX(dpm);
m_composed_image.setDotsPerMeterY(dpm);
m_composed_image.fill(Qt::transparent);
//paint all image data onto new image
QPainter painter(&m_composed_image);
painter.drawImage(QPoint(0, 0), m_alignment_image);
As it's mentioned in one of the answers, The best way to do it will be to make a pixmap buffer. The painting works will be done in the buffer and when it's done, repaint() will be scheduled. And the paintEvent() function just paints the widget by copying the pixel buffer
I was trying to draw a circle on a widget area after user inputs values and pushes a button. This was my solution. connecting the drawCircle() slot to the clicked() signal.
class PaintHelper : public QWidget
{
Q_OBJECT
private:
QPixmap *buffer;
public:
explicit PaintHelper(QWidget *parent = 0) : QWidget(parent)
{
buffer=new QPixmap(350,250);// this is the fixe width of this widget so
buffer->fill(Qt::cyan);
}
signals:
public slots:
void drawCircle(int cx, int cy, int r){
QPainter painter(buffer);
painter.setBrush(QBrush(QColor(0,0,255)));
// A part of mid-point algorithm to draw 1/8 pacrt of circle
int x1=0,y1=r;
int p=1-r;
for(int i=0;y1>=x1;i++){
painter.drawPoint(x1+cx,y1+cy);
x1++;
if(p>0){
p+=3+x1;
}
else{
y1--;
p+=2*x1-2*y1;
p++;
}
}
this->repaint();
}
// QWidget interface
protected:
void paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(0,0,*buffer);
}
};