Conflict between shaders and QPainter in paintGL() for rendering 2D text - qt

I have the following class inheriting from QOpenGLWidget and QOpenGLFunctions:
class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
OpenGLWidget();
virtual ~OpenGLWidget();
void initializeGL();
void paintGL()
{
QPainter painter(this);
painter.beginNativePainting();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Calls OpenGL draw functions with VBOs
m_viewport.render(m_shader, m_entities);
painter.endNativePainting();
painter.drawText(0, 0, width(), height(), Qt::AlignCenter, "Hello World!");
}
void resizeGL(int width, int height);
[...]
}
"Hello World" is drawn as intended, but the 3D scene is broken. I should have 3D axis in the center and in the top-right of the screen:
To me, it seems that the vertex and fragment shaders I'm using are the source of the problem. Otherwise, given the simplicity of the code and the examples I've found, it should work.
A good output would be:
with the "Hello World" at the center. This is what I get when I comment the QPainter calls.

It seems that your shader program is released when you use QPainter. Bind the shader program before OpenGL calls and release it afterwards. It should fix it.
painter.beginNativePainting();
// Bind shader program
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Calls OpenGL draw functions with VBOs
m_viewport.render(m_shader, m_entities);
// Release shader program
painter.endNativePainting();

Related

How to draw something with QPainter when the button is pushed

I am working on my project from programming and I need to draw, for example, a circle every time the pushButton is pressed using QPainter. This is the first problem, and the second one here is that I need some information to be sent to the drawing function too, for example, int vector, and being able to draw so many circles, as there are elements in the vector with radii of the elements itself. I have found some code based on signals and slots.
The sender:
public:
Listener(QObject *p = 0) : QObject(p) {
QTimer * t = new QTimer(this);
t->setInterval(200);
connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
t->start();
}
signals:
void dataAvaiable(int, int);
public slots:
void sendData() {
emit dataAvaiable(qrand() % 200, qrand() % 200);
}
The reciever:
void receiveData(int x, int y) {
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
QPoint point(x, y);
painter.drawEllipse(x, y, 100, 100);
data.append(point);
}
The connection itself in main.cpp
QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));
But the code doesn't suit for my exact task with buttons and doesn't even want to draw anythig, just any circle at all. Howewer, in debugger the code executes properly, and I am relatively new to Qt and C++ so I can't figure out by myself, where the problem is and how to solve my task.Can someone please do a minimal of code or simply explain to me, where exactly the problem is? Need to solve the problem as soon as possible. Thank you.
Upd: any possible solution with or without QPainter would be good now.
Qt Forum users gave me an answer.
Quote:
From the QPainter class description:
Warning: When the paintdevice is a widget, QPainter can only be used inside a
paintEvent() function or in a function called by paintEvent().
You can force calling paintEvent() by invoking update(), so you must connect the onclicked() signal of your button to the update() slot of the widget you're drawing on.
For your second problem, the data can be a member variable.
Here's an example:
// mywidget.h
#include <QVector>
#include <QPoint>
// other includes and the constructor...
protected:
virtual void paintEvent(QPaintEvent *event);
private slots:
void onButtonClicked();
private:
QPushButton* mButton;
QVector<QPoint> mCirclesData;
// mywidget.cpp
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
mButton = new QPushButton(this);
// customise your button...
connect(mButton, &QPushButton::clicked, this, &MyWidget::onButtonClicked);
}
//...
void MyWidget::onButtonClicked(){
int x = qrand() % 200, y = x;
mCirclesData << QPoint(x,y);
update(); // force calling paintEvent
}
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
painter.drawEllipse(mCirclesData.last().x(), mCirclesData.last().y(), 100, 100);
}
``

QOpenGLWidget and transparency

I have small problem with QOpenGLWidget and its background color.
When I want to create semi-transparent rect on my custom QOpenGLWidget using QPainter there are 2 different results:
If MyCustomWidget have parent - on every update rect's color multiplies (and after few repaints it is opaque, like previous painting result not cleaned)
If MyCustomWidget doesn't have parent - color doesn't repaints each time
Here is code example for QPainter:
class Widget : public QOpenGLWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0)
: QOpenGLWidget(parent)
{
resize(800, 600);
Test *test = new Test(this);
}
~Widget(){}
protected:
void paintEvent(QPaintEvent *) {}
protected:
void initializeGL() {
if(paintEngine()->type() != QPaintEngine::OpenGL &&
paintEngine()->type() != QPaintEngine::OpenGL2)
qDebug() << "ERROR. Type is: " << paintEngine()->type();
}
void resizeGL(int, int) {}
void paintGL() {
QPainter p;
p.begin(this);
{
p.fillRect(rect(), Qt::white);
}
p.end();
}
private:
class Test : public QOpenGLWidget
{
public:
Test(QWidget *parent = 0) : QOpenGLWidget(parent) {
resize(100, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.fillRect(rect(), QColor(125, 125, 125, 255/10));
}
};
};
Also by default it has black background (I don't know how to fix it. setAttribute(Qt::WA_TranslucentBackground) doesn't helps).
Also, when I'm trying to clear color using glClear it ignores alpha (both on QOpenGLWidget with parent and not). Here is Test class from previous code, but now it is using opengl to clear color:
class Test : public QOpenGLWidget
{
public:
Test(QWidget *parent = 0) : QOpenGLWidget(parent) {
resize(100, 100);
}
void initializeGL() {
QOpenGLFunctions *f = context()->functions();
f->glClearColor(0.0f, 1.0f, 0.0f, 0.1f);
}
void paintGL() {
QOpenGLFunctions *f = context()->functions();
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
};
How can I fix this problems?
I'm using Qt 5.5.0, Windows 10, MinGW 4.9.2
Xeed is correct when saying the QOpenGLWidget is painted first.
I'm not an expert but I think I found the solution. You need to set a widget attribute to always make the widget stacked on top (think of the widgets as layers on the window). Here is a link to where I got the following information:
P.S. As mentioned in the QQuickWidget post, there is a limitation regarding semi-transparency when using QQuickWidget or QOpenGLWidget as child widgets. For applications that absolutely need this, Qt 5.4 offers a workaround: the newly introduced Qt::WA_AlwaysStackOnTop widget attribute. This, at the expense of breaking the stacking order for other types of layouts, makes it possible to have a semi-transparent QQuickWidget or QOpenGLWidget with other widgets visible underneath. Of course, if the intention is only to make other applications on the desktop visible underneath, then the Qt::WA_TranslucentBackground attribute is sufficient
Solution in Python:
set attribute of OpenGL widget
setAttribute(Qt.WA_AlwaysStackOnTop)
Now the OpenGL widget is considered 'on top' in the window. Use 'glClearColor' function and specify the alpha channel to be zero (0.0).
glClearColor(0.0, 0.0, 0.0, 0.0)
I'm not sure how to write that in other languages but this worked for me. The OpenGL widget no longer has the default black background. It is transparent! Hope this helps.
As far as I know the QOpenGLWidget is always drawn first. Therefore you cannot show any widgets layered below. I'm currently looking into the same issue. I'll report back, when I find any solution.
I've had similar issue with QOpenGLWidget not repainting correctly in transparent areas and decided to switch to QOpenGLWindow wrapped inside QWidget::createWindowContainer()

How to look for equivalent functions of OpenGL in QGLWidget?

I'm new to OpenGL and Glut. There is a project implemented by Glut. I googled and found that there is an OpenGL implementation in Qt, called QGLWidget. However, it's hard for me converting the old Glut code to new Qt code since I don't know how to find equivalent function for Glut functions in Qt. Part of the code look like this:
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
glutInitWindowSize(gray1->width,gray1->height);
glutInitWindowPosition(100,100);
glutCreateWindow("hello");
init();
glutDisplayFunc(&display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMotionFunc(mouse_move);
glutMainLoop();
The glut* functions above don't exist in Qt's document. So my problem is how can I find equivalent glut functions in functions of QGLWidget?
You need to implement your own class inherited from QGLWidget, for example:
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
GLWidget(QWidget *parent = 0);
protected:
void initializeGL();
void resizeGL(int w, int h);
void paintGL();
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
};
You also need to override three important functions, initializeGL() where you're preparing your OpenGL. resizeGL() where you update the viewport and projection matrix if your panel is resized, and paintGL() the actual rendering.
The window initialization, of course, is handled by Qt.
For mouse events, there are three functions you can override: mousePressEvent(), mouseMoveEvent(), and mouseReleaseEvent()
void GLWidget::initializeGL()
{
glClearColor(0.5, 0.5, 0.5, 1.0);
}
void GLWidget::resizeGL(int width, int height)
{
glViewport(0, 0, width(), height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width(), 0, height());
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
// draw a red triangle
glColor3f(1,0,0);
glBegin(GL_POLYGON);
glVertex2f(10,10);
glVertex2f(10,600);
glVertex2f(300,10);
glEnd();
}
OK. Have you looked at the HelloGL sample?
So there you'll learn how to display a QGLWidget and process mouse input. I think this is what you are looking for.
Since Qt provides SIGNAL and SLOTS input processing is kind of different but also very intuitive. So you have to connect mouse SIGNALS to your SLOTS. Those SLOTS will then process the mouse event.
But look at the sample, it's quite intuitive.

Qt Beginner QPainter and QRect

How would I go about drawing a rectangle?
I have tried two different ways;
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Which works fine (even though the parameter is not named nor used), but I don't want to use the QPaintEvent * I have no use for it.
So I tried just renaming my function;
void MyWidget::draw()
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
This doesn't display anything (yet has no errors).
Why would it not work if I don't use QPaintEvent * ??
The paint event is the method that is called by the paint system when a widget needs to be redrawn. That is why simply naming your own method does not work. It is never called by the paint system.
You really should be using the QPaintEvent. It gives you the rect that needs to be drawn. This rect will be based upon the size of the widget, so instead of using an explicit rect in your paint event, set your widget to the right size. A paint event will be generated should your widget ever move, resize, etc.
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Now if you want to separate your paint logic into another method, that is fine. But you would need to have it called from the paint event:
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
draw(rect);
}
void MyWidget::draw(QRect &rect)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
If you want to completely bypass the paint events as you said, and only want to create a static rectangle to display, one way is to just draw it once to a pixmap and display it in a QLabel:
QPixMap pix(200,100);
QPainter painter(&pix);
// do paint operations
painter.end()
someLabel.setPixmap(pix)
Any data that your paintEvent() needs should be accessible as fields of the containing class, in your case, private fields of MyWidget. These private fields can be exposed to clients of MyWidget via "setters" which would set the data values before calling update() on MyWidget which will trigger a call to paintEvent().
This playlist contains the best Qt tutorials , starting tutorial 74 would be useful for you (Qpainter and QPen), tutorial 75 is how to draw rectangles using QRect.
As well #Mat told you: the "event" is the correct way to launch a painter.
QPainter can only be evoked after a QPaintEvent event, which carries the safe region where the object may be drawn.
So you must find another strategy to transport your data, to help
I will propose a method simple, which can be adjusted to many cases.
widget.cpp
#include <QtGui>
#include "widget.h"
#define MIN_DCX (0.1)
#define MAX_DCX (5.0)
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
dcx=MIN_DCX;
setFixedSize(170, 100);
}
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
pcx=dcx*2;
QRect rect = QRect(50-dcx,25-dcx,60+pcx,40+pcx);
painter.drawText(rect, Qt::AlignCenter,printData);
painter.drawRect(rect);
painter.end();
}
void Widget::setPrintData(QString value){
printData = value;
dcx=(dcx>MAX_DCX)?MIN_DCX:dcx+MIN_DCX;
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent);
void setPrintData(QString value);
protected:
void paintEvent(QPaintEvent *event);
private:
QString printData;
float dcx;
float pcx;
};
#endif
window.cpp
#include <QtGui>
#include "widget.h"
#include "window.h"
#define MAX_SDCX 20
Window::Window()
: QWidget()
{
gobject = new Widget(this);
textMode=1;
rectMode=1;
gobject->setPrintData(msgs[textMode]);
QGridLayout *layout = new QGridLayout;
layout->addWidget(gobject, 0, 0);
setLayout(layout);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(dataOnAir()));
timer->start(10);
setWindowTitle(tr("Rect Shaking"));
}
void Window::dataOnAir(){
if((++rectMode)>MAX_SDCX){
rectMode=0;
textMode^=1;
}
gobject->setPrintData(msgs[textMode]);
gobject->repaint();
}
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include "widget.h"
class Window : public QWidget
{
Q_OBJECT
public:
Window();
private slots:
void dataOnAir();
private:
Widget *gobject;
const QString msgs[2] = {"Hello","World"};
int textMode;
int rectMode;
};
#endif
main.cpp
#include <QApplication>
#include "window.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
As you can see in the code is executed a timer, outside the object "widget"
every 10ms sends a repaint the widget to redraw a "rect" with a different size and every 20 cycles (200ms) changes the text "hello" for "world"
In this example you can see that in any way need overwrite the QPainterDevice architecture.
You may also notice that the "event" within the "paintEvent" is silenced and not used directly, but it is essential to execute a sequence QPainter.
Overriding the paintEvent() function of a widget enables you to customize the widget and this function is called periodically to redraw the widget. Therefore any drawing should be made in this function. However overriding paintEvent() may cause some performance issues. I would prefer using a QGraphicsScene and QGraphicsView then I would add a rectangle to the scene which is the common way of doing this kind of drawing stuff. Please check the GraphicsView Framework
http://qt-project.org/doc/qt-4.8/graphicsview.html

How to use OpenGL functions on a QT OpenGL Widget?

I'm starting off QT 4.6's example "OpenGL - 2D painting"
It uses a subclass of QGLWidget, and makes painting operations with the class QPainter.
I'd like to know how to do drawing directly with OpenGL functions on the OpenGL Widget.
If you use the widget as described in its manual, you can just use the OpenGL functions as usual. For example (copied from the manual):
class MyGLDrawer : public QGLWidget
{
Q_OBJECT // must include this if you use Qt signals/slots
public:
MyGLDrawer(QWidget *parent)
: QGLWidget(parent) {}
protected:
void initializeGL()
{
// Set up the rendering context, define display lists etc.:
...
glClearColor(0.0, 0.0, 0.0, 0.0);
glEnable(GL_DEPTH_TEST);
...
}
void resizeGL(int w, int h)
{
// setup viewport, projection etc.:
glViewport(0, 0, (GLint)w, (GLint)h);
...
glFrustum(...);
...
}
void paintGL()
{
// draw the scene:
...
glRotatef(...);
glMaterialfv(...);
glBegin(GL_QUADS);
glVertex3f(...);
glVertex3f(...);
...
glEnd();
...
}
};

Resources