1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qeglfscursor_p.h" |
5 | #include "qeglfsintegration_p.h" |
6 | #include "qeglfsscreen_p.h" |
7 | #include "qeglfscontext_p.h" |
8 | |
9 | #include <qpa/qwindowsysteminterface.h> |
10 | #include <QtGui/QOpenGLContext> |
11 | #include <QtGui/QOpenGLFunctions> |
12 | #include <QtCore/QFile> |
13 | #include <QtCore/QJsonDocument> |
14 | #include <QtCore/QJsonArray> |
15 | #include <QtCore/QJsonObject> |
16 | |
17 | #include <QtGui/private/qguiapplication_p.h> |
18 | #include <QtOpenGL/private/qopenglvertexarrayobject_p.h> |
19 | |
20 | #ifndef GL_VERTEX_ARRAY_BINDING |
21 | #define GL_VERTEX_ARRAY_BINDING 0x85B5 |
22 | #endif |
23 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | using namespace Qt::StringLiterals; |
27 | |
28 | QEglFSCursor::QEglFSCursor(QPlatformScreen *screen) |
29 | : m_visible(true), |
30 | m_screen(static_cast<QEglFSScreen *>(screen)), |
31 | m_activeScreen(nullptr), |
32 | m_deviceListener(nullptr), |
33 | m_updateRequested(false) |
34 | { |
35 | QByteArray hideCursorVal = qgetenv(varName: "QT_QPA_EGLFS_HIDECURSOR" ); |
36 | if (!hideCursorVal.isEmpty()) |
37 | m_visible = hideCursorVal.toInt() == 0; |
38 | if (!m_visible) |
39 | return; |
40 | |
41 | int rotation = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_ROTATION" ); |
42 | if (rotation) |
43 | m_rotationMatrix.rotate(angle: rotation, x: 0, y: 0, z: 1); |
44 | |
45 | // Try to load the cursor atlas. If this fails, m_visible is set to false and |
46 | // paintOnScreen() and setCurrentCursor() become no-ops. |
47 | initCursorAtlas(); |
48 | |
49 | // initialize the cursor |
50 | #ifndef QT_NO_CURSOR |
51 | QCursor cursor(Qt::ArrowCursor); |
52 | setCurrentCursor(&cursor); |
53 | #endif |
54 | |
55 | m_deviceListener = new QEglFSCursorDeviceListener(this); |
56 | connect(sender: QGuiApplicationPrivate::inputDeviceManager(), signal: &QInputDeviceManager::deviceListChanged, |
57 | context: m_deviceListener, slot: &QEglFSCursorDeviceListener::onDeviceListChanged); |
58 | updateMouseStatus(); |
59 | } |
60 | |
61 | QEglFSCursor::~QEglFSCursor() |
62 | { |
63 | resetResources(); |
64 | delete m_deviceListener; |
65 | } |
66 | |
67 | void QEglFSCursor::updateMouseStatus() |
68 | { |
69 | m_visible = m_deviceListener->hasMouse(); |
70 | } |
71 | |
72 | bool QEglFSCursorDeviceListener::hasMouse() const |
73 | { |
74 | return QGuiApplicationPrivate::inputDeviceManager()->deviceCount(type: QInputDeviceManager::DeviceTypePointer) > 0; |
75 | } |
76 | |
77 | void QEglFSCursorDeviceListener::onDeviceListChanged(QInputDeviceManager::DeviceType type) |
78 | { |
79 | if (type == QInputDeviceManager::DeviceTypePointer) |
80 | m_cursor->updateMouseStatus(); |
81 | } |
82 | |
83 | void QEglFSCursor::resetResources() |
84 | { |
85 | m_cursor.customCursorPending = !m_cursor.customCursorImage.isNull(); |
86 | } |
87 | |
88 | void QEglFSCursor::createShaderPrograms() |
89 | { |
90 | static const char *textureVertexProgram = |
91 | "attribute highp vec2 vertexCoordEntry;\n" |
92 | "attribute highp vec2 textureCoordEntry;\n" |
93 | "varying highp vec2 textureCoord;\n" |
94 | "uniform highp mat4 mat;\n" |
95 | "void main() {\n" |
96 | " textureCoord = textureCoordEntry;\n" |
97 | " gl_Position = mat * vec4(vertexCoordEntry, 1.0, 1.0);\n" |
98 | "}\n" ; |
99 | |
100 | static const char *textureFragmentProgram = |
101 | "uniform sampler2D texture;\n" |
102 | "varying highp vec2 textureCoord;\n" |
103 | "void main() {\n" |
104 | " gl_FragColor = texture2D(texture, textureCoord).bgra;\n" |
105 | "}\n" ; |
106 | |
107 | QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData; |
108 | gfx.program.reset(other: new QOpenGLShaderProgram); |
109 | gfx.program->addCacheableShaderFromSourceCode(type: QOpenGLShader::Vertex, source: textureVertexProgram); |
110 | gfx.program->addCacheableShaderFromSourceCode(type: QOpenGLShader::Fragment, source: textureFragmentProgram); |
111 | gfx.program->bindAttributeLocation(name: "vertexCoordEntry" , location: 0); |
112 | gfx.program->bindAttributeLocation(name: "textureCoordEntry" , location: 1); |
113 | gfx.program->link(); |
114 | |
115 | gfx.textureEntry = gfx.program->uniformLocation(name: "texture" ); |
116 | gfx.matEntry = gfx.program->uniformLocation(name: "mat" ); |
117 | } |
118 | |
119 | void QEglFSCursor::createCursorTexture(uint *texture, const QImage &image) |
120 | { |
121 | Q_ASSERT(QOpenGLContext::currentContext()); |
122 | QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); |
123 | if (!*texture) |
124 | f->glGenTextures(n: 1, textures: texture); |
125 | f->glBindTexture(GL_TEXTURE_2D, texture: *texture); |
126 | f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
127 | f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
128 | f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
129 | f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
130 | |
131 | f->glTexImage2D(GL_TEXTURE_2D, level: 0 /* level */, GL_RGBA, width: image.width(), height: image.height(), border: 0 /* border */, |
132 | GL_RGBA, GL_UNSIGNED_BYTE, pixels: image.constBits()); |
133 | } |
134 | |
135 | void QEglFSCursor::initCursorAtlas() |
136 | { |
137 | static QByteArray json = qgetenv(varName: "QT_QPA_EGLFS_CURSOR" ); |
138 | if (json.isEmpty()) |
139 | json = ":/cursor.json" ; |
140 | |
141 | QFile file(QString::fromUtf8(ba: json)); |
142 | if (!file.open(flags: QFile::ReadOnly)) { |
143 | m_visible = false; |
144 | return; |
145 | } |
146 | |
147 | QJsonDocument doc = QJsonDocument::fromJson(json: file.readAll()); |
148 | QJsonObject object = doc.object(); |
149 | |
150 | QString atlas = object.value(key: "image"_L1 ).toString(); |
151 | Q_ASSERT(!atlas.isEmpty()); |
152 | |
153 | const int cursorsPerRow = object.value(key: "cursorsPerRow"_L1 ).toDouble(); |
154 | Q_ASSERT(cursorsPerRow); |
155 | m_cursorAtlas.cursorsPerRow = cursorsPerRow; |
156 | |
157 | const QJsonArray hotSpots = object.value(key: "hotSpots"_L1 ).toArray(); |
158 | Q_ASSERT(hotSpots.count() == Qt::LastCursor + 1); |
159 | for (int i = 0; i < hotSpots.count(); i++) { |
160 | QPoint hotSpot(hotSpots[i].toArray()[0].toDouble(), hotSpots[i].toArray()[1].toDouble()); |
161 | m_cursorAtlas.hotSpots << hotSpot; |
162 | } |
163 | |
164 | QImage image = QImage(atlas).convertToFormat(f: QImage::Format_ARGB32_Premultiplied); |
165 | m_cursorAtlas.cursorWidth = image.width() / m_cursorAtlas.cursorsPerRow; |
166 | m_cursorAtlas.cursorHeight = image.height() / ((Qt::LastCursor + cursorsPerRow) / cursorsPerRow); |
167 | m_cursorAtlas.width = image.width(); |
168 | m_cursorAtlas.height = image.height(); |
169 | m_cursorAtlas.image = image; |
170 | } |
171 | |
172 | #ifndef QT_NO_CURSOR |
173 | void QEglFSCursor::changeCursor(QCursor *cursor, QWindow *window) |
174 | { |
175 | Q_UNUSED(window); |
176 | const QRect oldCursorRect = cursorRect(); |
177 | if (setCurrentCursor(cursor)) |
178 | update(rect: oldCursorRect | cursorRect(), allScreens: false); |
179 | } |
180 | |
181 | bool QEglFSCursor::setCurrentCursor(QCursor *cursor) |
182 | { |
183 | if (!m_visible) |
184 | return false; |
185 | |
186 | const Qt::CursorShape newShape = cursor ? cursor->shape() : Qt::ArrowCursor; |
187 | if (m_cursor.shape == newShape && newShape != Qt::BitmapCursor) |
188 | return false; |
189 | |
190 | if (m_cursor.shape == Qt::BitmapCursor) { |
191 | m_cursor.customCursorImage = QImage(); |
192 | m_cursor.customCursorPending = false; |
193 | } |
194 | m_cursor.shape = newShape; |
195 | if (newShape != Qt::BitmapCursor) { // standard cursor |
196 | const float ws = (float)m_cursorAtlas.cursorWidth / m_cursorAtlas.width, |
197 | hs = (float)m_cursorAtlas.cursorHeight / m_cursorAtlas.height; |
198 | m_cursor.textureRect = QRectF(ws * (m_cursor.shape % m_cursorAtlas.cursorsPerRow), |
199 | hs * (m_cursor.shape / m_cursorAtlas.cursorsPerRow), |
200 | ws, hs); |
201 | m_cursor.hotSpot = m_cursorAtlas.hotSpots[m_cursor.shape]; |
202 | m_cursor.useCustomCursor = false; |
203 | m_cursor.size = QSize(m_cursorAtlas.cursorWidth, m_cursorAtlas.cursorHeight); |
204 | } else { |
205 | QImage image = cursor->pixmap().toImage(); |
206 | m_cursor.textureRect = QRectF(0, 0, 1, 1); |
207 | m_cursor.hotSpot = cursor->hotSpot(); |
208 | m_cursor.useCustomCursor = false; // will get updated in the next render() |
209 | m_cursor.size = image.size(); |
210 | m_cursor.customCursorImage = image; |
211 | m_cursor.customCursorPending = true; |
212 | m_cursor.customCursorKey = m_cursor.customCursorImage.cacheKey(); |
213 | } |
214 | |
215 | return true; |
216 | } |
217 | #endif |
218 | |
219 | class CursorUpdateEvent : public QEvent |
220 | { |
221 | public: |
222 | CursorUpdateEvent(const QPoint &pos, const QRect &rect, bool allScreens) |
223 | : QEvent(QEvent::Type(QEvent::User + 1)), |
224 | m_pos(pos), |
225 | m_rect(rect), |
226 | m_allScreens(allScreens) |
227 | { } |
228 | QPoint pos() const { return m_pos; } |
229 | QRegion rect() const { return m_rect; } |
230 | bool allScreens() const { return m_allScreens; } |
231 | |
232 | private: |
233 | QPoint m_pos; |
234 | QRect m_rect; |
235 | bool m_allScreens; |
236 | }; |
237 | |
238 | bool QEglFSCursor::event(QEvent *e) |
239 | { |
240 | if (e->type() == QEvent::User + 1) { |
241 | CursorUpdateEvent *ev = static_cast<CursorUpdateEvent *>(e); |
242 | m_updateRequested = false; |
243 | if (!ev->allScreens()) { |
244 | QWindow *w = m_screen->topLevelAt(point: ev->pos()); // works for the entire virtual desktop, no need to loop |
245 | if (w) { |
246 | QWindowSystemInterface::handleExposeEvent(window: w, region: ev->rect()); |
247 | QWindowSystemInterface::flushWindowSystemEvents(flags: QEventLoop::ExcludeUserInputEvents); |
248 | } |
249 | } else { |
250 | for (QWindow *w : qGuiApp->topLevelWindows()) |
251 | QWindowSystemInterface::handleExposeEvent(window: w, region: w->geometry()); |
252 | QWindowSystemInterface::flushWindowSystemEvents(flags: QEventLoop::ExcludeUserInputEvents); |
253 | } |
254 | return true; |
255 | } |
256 | return QPlatformCursor::event(event: e); |
257 | } |
258 | |
259 | void QEglFSCursor::update(const QRect &rect, bool allScreens) |
260 | { |
261 | if (!m_updateRequested) { |
262 | // Must not flush the window system events directly from here since we are likely to |
263 | // be a called directly from QGuiApplication's processMouseEvents. Flushing events |
264 | // could cause reentering by dispatching more queued mouse events. |
265 | m_updateRequested = true; |
266 | QCoreApplication::postEvent(receiver: this, event: new CursorUpdateEvent(m_cursor.pos, rect, allScreens)); |
267 | } |
268 | } |
269 | |
270 | QRect QEglFSCursor::cursorRect() const |
271 | { |
272 | return QRect(m_cursor.pos - m_cursor.hotSpot, m_cursor.size); |
273 | } |
274 | |
275 | QPoint QEglFSCursor::pos() const |
276 | { |
277 | return m_cursor.pos; |
278 | } |
279 | |
280 | void QEglFSCursor::setPos(const QPoint &pos) |
281 | { |
282 | QGuiApplicationPrivate::inputDeviceManager()->setCursorPos(pos); |
283 | const QRect oldCursorRect = cursorRect(); |
284 | m_cursor.pos = pos; |
285 | update(rect: oldCursorRect | cursorRect(), allScreens: false); |
286 | for (QPlatformScreen *screen : m_screen->virtualSiblings()) |
287 | static_cast<QEglFSScreen *>(screen)->handleCursorMove(pos: m_cursor.pos); |
288 | } |
289 | |
290 | void QEglFSCursor::pointerEvent(const QMouseEvent &event) |
291 | { |
292 | if (event.type() != QEvent::MouseMove) |
293 | return; |
294 | const QRect oldCursorRect = cursorRect(); |
295 | m_cursor.pos = event.globalPosition().toPoint(); |
296 | update(rect: oldCursorRect | cursorRect(), allScreens: false); |
297 | for (QPlatformScreen *screen : m_screen->virtualSiblings()) |
298 | static_cast<QEglFSScreen *>(screen)->handleCursorMove(pos: m_cursor.pos); |
299 | } |
300 | |
301 | void QEglFSCursor::paintOnScreen() |
302 | { |
303 | if (!m_visible) |
304 | return; |
305 | |
306 | // cr must be a QRectF, otherwise cr.right() and bottom() would be off by |
307 | // one in the calculations below. |
308 | QRectF cr = cursorRect(); // hotspot included |
309 | |
310 | // Support virtual desktop too. Backends with multi-screen support (e.g. all |
311 | // variants of KMS/DRM) will enable this by default. In this case all |
312 | // screens are siblings of each other. When not enabled, the sibling list |
313 | // only contains m_screen itself. |
314 | for (QPlatformScreen *screen : m_screen->virtualSiblings()) { |
315 | if (screen->geometry().contains(p: cr.topLeft().toPoint() + m_cursor.hotSpot)) |
316 | { |
317 | cr.translate(p: -screen->geometry().topLeft()); |
318 | const QSize screenSize = screen->geometry().size(); |
319 | const GLfloat x1 = 2 * (cr.left() / GLfloat(screenSize.width())) - 1; |
320 | const GLfloat x2 = 2 * (cr.right() / GLfloat(screenSize.width())) - 1; |
321 | const GLfloat y1 = 1 - (cr.top() / GLfloat(screenSize.height())) * 2; |
322 | const GLfloat y2 = 1 - (cr.bottom() / GLfloat(screenSize.height())) * 2; |
323 | QRectF r(QPointF(x1, y1), QPointF(x2, y2)); |
324 | |
325 | draw(rect: r); |
326 | |
327 | if (screen != m_activeScreen) { |
328 | m_activeScreen = screen; |
329 | // Do not want a leftover cursor on the screen the cursor just left. |
330 | update(rect: cursorRect(), allScreens: true); |
331 | } |
332 | |
333 | break; |
334 | } |
335 | } |
336 | } |
337 | |
338 | // In order to prevent breaking code doing custom OpenGL rendering while |
339 | // expecting the state in the context unchanged, save and restore all the state |
340 | // we touch. The exception is Qt Quick where the scenegraph is known to be able |
341 | // to deal with the changes we make. |
342 | struct StateSaver |
343 | { |
344 | StateSaver(QOpenGLFunctions* func) { |
345 | f = func; |
346 | vaoHelper = QOpenGLVertexArrayObjectHelper::vertexArrayObjectHelperForContext(context: QOpenGLContext::currentContext()); |
347 | |
348 | static bool windowsChecked = false; |
349 | static bool shouldSave = true; |
350 | if (!windowsChecked) { |
351 | windowsChecked = true; |
352 | QWindowList windows = QGuiApplication::allWindows(); |
353 | if (!windows.isEmpty() && windows[0]->inherits(classname: "QQuickWindow" )) |
354 | shouldSave = false; |
355 | } |
356 | saved = shouldSave; |
357 | if (!shouldSave) |
358 | return; |
359 | |
360 | f->glGetIntegerv(GL_CURRENT_PROGRAM, params: &program); |
361 | f->glGetIntegerv(GL_TEXTURE_BINDING_2D, params: &texture); |
362 | f->glGetIntegerv(GL_ACTIVE_TEXTURE, params: &activeTexture); |
363 | f->glGetIntegerv(GL_FRONT_FACE, params: &frontFace); |
364 | cull = f->glIsEnabled(GL_CULL_FACE); |
365 | depthTest = f->glIsEnabled(GL_DEPTH_TEST); |
366 | blend = f->glIsEnabled(GL_BLEND); |
367 | f->glGetIntegerv(GL_BLEND_SRC_RGB, params: blendFunc); |
368 | f->glGetIntegerv(GL_BLEND_SRC_ALPHA, params: blendFunc + 1); |
369 | f->glGetIntegerv(GL_BLEND_DST_RGB, params: blendFunc + 2); |
370 | f->glGetIntegerv(GL_BLEND_DST_ALPHA, params: blendFunc + 3); |
371 | scissor = f->glIsEnabled(GL_SCISSOR_TEST); |
372 | stencil = f->glIsEnabled(GL_STENCIL_TEST); |
373 | f->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, params: &arrayBuf); |
374 | if (vaoHelper->isValid()) |
375 | f->glGetIntegerv(GL_VERTEX_ARRAY_BINDING, params: &vao); |
376 | else |
377 | vao = 0; |
378 | for (int i = 0; i < 2; ++i) { |
379 | f->glGetVertexAttribiv(index: i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, params: &va[i].enabled); |
380 | f->glGetVertexAttribiv(index: i, GL_VERTEX_ATTRIB_ARRAY_SIZE, params: &va[i].size); |
381 | f->glGetVertexAttribiv(index: i, GL_VERTEX_ATTRIB_ARRAY_TYPE, params: &va[i].type); |
382 | f->glGetVertexAttribiv(index: i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, params: &va[i].normalized); |
383 | f->glGetVertexAttribiv(index: i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, params: &va[i].stride); |
384 | f->glGetVertexAttribiv(index: i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, params: &va[i].buffer); |
385 | f->glGetVertexAttribPointerv(index: i, GL_VERTEX_ATTRIB_ARRAY_POINTER, pointer: &va[i].pointer); |
386 | } |
387 | } |
388 | ~StateSaver() { |
389 | if (saved) { |
390 | f->glUseProgram(program); |
391 | f->glBindTexture(GL_TEXTURE_2D, texture); |
392 | f->glActiveTexture(texture: activeTexture); |
393 | f->glFrontFace(mode: frontFace); |
394 | if (cull) |
395 | f->glEnable(GL_CULL_FACE); |
396 | if (depthTest) |
397 | f->glEnable(GL_DEPTH_TEST); |
398 | if (!blend) |
399 | f->glDisable(GL_BLEND); |
400 | f->glBlendFuncSeparate(srcRGB: blendFunc[0], dstRGB: blendFunc[1], srcAlpha: blendFunc[2], dstAlpha: blendFunc[3]); |
401 | if (scissor) |
402 | f->glEnable(GL_SCISSOR_TEST); |
403 | if (stencil) |
404 | f->glEnable(GL_STENCIL_TEST); |
405 | f->glBindBuffer(GL_ARRAY_BUFFER, buffer: arrayBuf); |
406 | if (vaoHelper->isValid()) |
407 | vaoHelper->glBindVertexArray(array: vao); |
408 | for (int i = 0; i < 2; ++i) { |
409 | if (va[i].enabled) |
410 | f->glEnableVertexAttribArray(index: i); |
411 | else |
412 | f->glDisableVertexAttribArray(index: i); |
413 | f->glBindBuffer(GL_ARRAY_BUFFER, buffer: va[i].buffer); |
414 | f->glVertexAttribPointer(indx: i, size: va[i].size, type: va[i].type, normalized: va[i].normalized, stride: va[i].stride, ptr: va[i].pointer); |
415 | } |
416 | } |
417 | } |
418 | QOpenGLFunctions *f; |
419 | QOpenGLVertexArrayObjectHelper *vaoHelper; |
420 | bool saved; |
421 | GLint program; |
422 | GLint texture; |
423 | GLint activeTexture; |
424 | GLint frontFace; |
425 | bool cull; |
426 | bool depthTest; |
427 | bool blend; |
428 | GLint blendFunc[4]; |
429 | bool scissor; |
430 | bool stencil; |
431 | GLint vao; |
432 | GLint arrayBuf; |
433 | struct { GLint enabled, type, size, normalized, stride, buffer; GLvoid *pointer; } va[2]; |
434 | }; |
435 | |
436 | void QEglFSCursor::draw(const QRectF &r) |
437 | { |
438 | Q_ASSERT(QOpenGLContext::currentContext()); |
439 | QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); |
440 | StateSaver stateSaver(f); |
441 | |
442 | QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData; |
443 | if (!gfx.program) { |
444 | createShaderPrograms(); |
445 | |
446 | if (!gfx.atlasTexture) { |
447 | createCursorTexture(texture: &gfx.atlasTexture, image: m_cursorAtlas.image); |
448 | |
449 | if (m_cursor.shape != Qt::BitmapCursor) |
450 | m_cursor.useCustomCursor = false; |
451 | } |
452 | } |
453 | |
454 | if (m_cursor.shape == Qt::BitmapCursor && (m_cursor.customCursorPending || m_cursor.customCursorKey != gfx.customCursorKey)) { |
455 | // upload the custom cursor |
456 | createCursorTexture(texture: &gfx.customCursorTexture, image: m_cursor.customCursorImage); |
457 | m_cursor.useCustomCursor = true; |
458 | m_cursor.customCursorPending = false; |
459 | gfx.customCursorKey = m_cursor.customCursorKey; |
460 | } |
461 | |
462 | GLuint cursorTexture = !m_cursor.useCustomCursor ? gfx.atlasTexture : gfx.customCursorTexture; |
463 | Q_ASSERT(cursorTexture); |
464 | |
465 | gfx.program->bind(); |
466 | |
467 | const GLfloat x1 = r.left(); |
468 | const GLfloat x2 = r.right(); |
469 | const GLfloat y1 = r.top(); |
470 | const GLfloat y2 = r.bottom(); |
471 | const GLfloat cursorCoordinates[] = { |
472 | x1, y2, |
473 | x2, y2, |
474 | x1, y1, |
475 | x2, y1 |
476 | }; |
477 | |
478 | const GLfloat s1 = m_cursor.textureRect.left(); |
479 | const GLfloat s2 = m_cursor.textureRect.right(); |
480 | const GLfloat t1 = m_cursor.textureRect.top(); |
481 | const GLfloat t2 = m_cursor.textureRect.bottom(); |
482 | const GLfloat textureCoordinates[] = { |
483 | s1, t2, |
484 | s2, t2, |
485 | s1, t1, |
486 | s2, t1 |
487 | }; |
488 | |
489 | f->glActiveTexture(GL_TEXTURE0); |
490 | f->glBindTexture(GL_TEXTURE_2D, texture: cursorTexture); |
491 | |
492 | if (stateSaver.vaoHelper->isValid()) |
493 | stateSaver.vaoHelper->glBindVertexArray(array: 0); |
494 | |
495 | f->glBindBuffer(GL_ARRAY_BUFFER, buffer: 0); |
496 | |
497 | gfx.program->enableAttributeArray(location: 0); |
498 | gfx.program->enableAttributeArray(location: 1); |
499 | gfx.program->setAttributeArray(location: 0, values: cursorCoordinates, tupleSize: 2); |
500 | gfx.program->setAttributeArray(location: 1, values: textureCoordinates, tupleSize: 2); |
501 | |
502 | gfx.program->setUniformValue(location: gfx.textureEntry, value: 0); |
503 | gfx.program->setUniformValue(location: gfx.matEntry, value: m_rotationMatrix); |
504 | |
505 | f->glDisable(GL_CULL_FACE); |
506 | f->glFrontFace(GL_CCW); |
507 | f->glEnable(GL_BLEND); |
508 | f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
509 | f->glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top |
510 | f->glDisable(GL_SCISSOR_TEST); |
511 | f->glDisable(GL_STENCIL_TEST); |
512 | |
513 | f->glDrawArrays(GL_TRIANGLE_STRIP, first: 0, count: 4); |
514 | |
515 | gfx.program->disableAttributeArray(location: 0); |
516 | gfx.program->disableAttributeArray(location: 1); |
517 | gfx.program->release(); |
518 | } |
519 | |
520 | QT_END_NAMESPACE |
521 | |
522 | #include "moc_qeglfscursor_p.cpp" |
523 | |