1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the QtOpenGL module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include "qpixmap.h" |
43 | #include "qglframebufferobject.h" |
44 | |
45 | #include <private/qpaintengine_raster_p.h> |
46 | |
47 | #include "qpixmapdata_gl_p.h" |
48 | |
49 | #include <private/qgl_p.h> |
50 | #include <private/qdrawhelper_p.h> |
51 | #include <private/qimage_p.h> |
52 | #include <private/qfont_p.h> |
53 | |
54 | #include <private/qpaintengineex_opengl2_p.h> |
55 | |
56 | #include <qdesktopwidget.h> |
57 | #include <qfile.h> |
58 | #include <qimagereader.h> |
59 | #include <qbuffer.h> |
60 | |
61 | QT_BEGIN_NAMESPACE |
62 | |
63 | Q_OPENGL_EXPORT extern const QGLContext* qt_gl_share_context(); |
64 | |
65 | /*! |
66 | \class QGLFramebufferObjectPool |
67 | \since 4.6 |
68 | |
69 | \brief The QGLFramebufferObject class provides a pool of framebuffer |
70 | objects for offscreen rendering purposes. |
71 | |
72 | When requesting an FBO of a given size and format, an FBO of the same |
73 | format and a size at least as big as the requested size will be returned. |
74 | |
75 | \internal |
76 | */ |
77 | |
78 | static inline int areaDiff(const QSize &size, const QGLFramebufferObject *fbo) |
79 | { |
80 | return qAbs(size.width() * size.height() - fbo->width() * fbo->height()); |
81 | } |
82 | |
83 | extern int qt_next_power_of_two(int v); |
84 | |
85 | static inline QSize maybeRoundToNextPowerOfTwo(const QSize &sz) |
86 | { |
87 | #ifdef QT_OPENGL_ES_2 |
88 | QSize rounded(qt_next_power_of_two(sz.width()), qt_next_power_of_two(sz.height())); |
89 | if (rounded.width() * rounded.height() < 1.20 * sz.width() * sz.height()) |
90 | return rounded; |
91 | #endif |
92 | return sz; |
93 | } |
94 | |
95 | |
96 | QGLFramebufferObject *QGLFramebufferObjectPool::acquire(const QSize &requestSize, const QGLFramebufferObjectFormat &requestFormat, bool strictSize) |
97 | { |
98 | QGLFramebufferObject *chosen = 0; |
99 | QGLFramebufferObject *candidate = 0; |
100 | for (int i = 0; !chosen && i < m_fbos.size(); ++i) { |
101 | QGLFramebufferObject *fbo = m_fbos.at(i); |
102 | |
103 | if (strictSize) { |
104 | if (fbo->size() == requestSize && fbo->format() == requestFormat) { |
105 | chosen = fbo; |
106 | break; |
107 | } else { |
108 | continue; |
109 | } |
110 | } |
111 | |
112 | if (fbo->format() == requestFormat) { |
113 | // choose the fbo with a matching format and the closest size |
114 | if (!candidate || areaDiff(requestSize, candidate) > areaDiff(requestSize, fbo)) |
115 | candidate = fbo; |
116 | } |
117 | |
118 | if (candidate) { |
119 | m_fbos.removeOne(candidate); |
120 | |
121 | const QSize fboSize = candidate->size(); |
122 | QSize sz = fboSize; |
123 | |
124 | if (sz.width() < requestSize.width()) |
125 | sz.setWidth(qMax(requestSize.width(), qRound(sz.width() * 1.5))); |
126 | if (sz.height() < requestSize.height()) |
127 | sz.setHeight(qMax(requestSize.height(), qRound(sz.height() * 1.5))); |
128 | |
129 | // wasting too much space? |
130 | if (sz.width() * sz.height() > requestSize.width() * requestSize.height() * 4) |
131 | sz = requestSize; |
132 | |
133 | if (sz != fboSize) { |
134 | delete candidate; |
135 | candidate = new QGLFramebufferObject(maybeRoundToNextPowerOfTwo(sz), requestFormat); |
136 | } |
137 | |
138 | chosen = candidate; |
139 | } |
140 | } |
141 | |
142 | if (!chosen) { |
143 | if (strictSize) |
144 | chosen = new QGLFramebufferObject(requestSize, requestFormat); |
145 | else |
146 | chosen = new QGLFramebufferObject(maybeRoundToNextPowerOfTwo(requestSize), requestFormat); |
147 | } |
148 | |
149 | if (!chosen->isValid()) { |
150 | delete chosen; |
151 | chosen = 0; |
152 | } |
153 | |
154 | return chosen; |
155 | } |
156 | |
157 | void QGLFramebufferObjectPool::release(QGLFramebufferObject *fbo) |
158 | { |
159 | if (fbo) |
160 | m_fbos << fbo; |
161 | } |
162 | |
163 | |
164 | QPaintEngine* QGLPixmapGLPaintDevice::paintEngine() const |
165 | { |
166 | return data->paintEngine(); |
167 | } |
168 | |
169 | void QGLPixmapGLPaintDevice::beginPaint() |
170 | { |
171 | if (!data->isValid()) |
172 | return; |
173 | |
174 | // QGLPaintDevice::beginPaint will store the current binding and replace |
175 | // it with m_thisFBO: |
176 | m_thisFBO = data->m_renderFbo->handle(); |
177 | QGLPaintDevice::beginPaint(); |
178 | |
179 | Q_ASSERT(data->paintEngine()->type() == QPaintEngine::OpenGL2); |
180 | |
181 | // QPixmap::fill() is deferred until now, where we actually need to do the fill: |
182 | if (data->needsFill()) { |
183 | const QColor &c = data->fillColor(); |
184 | float alpha = c.alphaF(); |
185 | glDisable(GL_SCISSOR_TEST); |
186 | glClearColor(c.redF() * alpha, c.greenF() * alpha, c.blueF() * alpha, alpha); |
187 | glClear(GL_COLOR_BUFFER_BIT); |
188 | } |
189 | else if (!data->isUninitialized()) { |
190 | // If the pixmap (GL Texture) has valid content (it has been |
191 | // uploaded from an image or rendered into before), we need to |
192 | // copy it from the texture to the render FBO. |
193 | |
194 | glDisable(GL_DEPTH_TEST); |
195 | glDisable(GL_SCISSOR_TEST); |
196 | glDisable(GL_BLEND); |
197 | |
198 | #if !defined(QT_OPENGL_ES_2) |
199 | glMatrixMode(GL_MODELVIEW); |
200 | glLoadIdentity(); |
201 | |
202 | glMatrixMode(GL_PROJECTION); |
203 | glLoadIdentity(); |
204 | glOrtho(0, data->width(), data->height(), 0, -999999, 999999); |
205 | #endif |
206 | |
207 | glViewport(0, 0, data->width(), data->height()); |
208 | |
209 | // Pass false to bind so it doesn't copy the FBO into the texture! |
210 | context()->drawTexture(QRect(0, 0, data->width(), data->height()), data->bind(false)); |
211 | } |
212 | } |
213 | |
214 | void QGLPixmapGLPaintDevice::endPaint() |
215 | { |
216 | if (!data->isValid()) |
217 | return; |
218 | |
219 | data->copyBackFromRenderFbo(false); |
220 | |
221 | // Base's endPaint will restore the previous FBO binding |
222 | QGLPaintDevice::endPaint(); |
223 | |
224 | qgl_fbo_pool()->release(data->m_renderFbo); |
225 | data->m_renderFbo = 0; |
226 | } |
227 | |
228 | QGLContext* QGLPixmapGLPaintDevice::context() const |
229 | { |
230 | data->ensureCreated(); |
231 | return data->m_ctx; |
232 | } |
233 | |
234 | QSize QGLPixmapGLPaintDevice::size() const |
235 | { |
236 | return data->size(); |
237 | } |
238 | |
239 | bool QGLPixmapGLPaintDevice::alphaRequested() const |
240 | { |
241 | return data->m_hasAlpha; |
242 | } |
243 | |
244 | void QGLPixmapGLPaintDevice::setPixmapData(QGLPixmapData* d) |
245 | { |
246 | data = d; |
247 | } |
248 | |
249 | static int qt_gl_pixmap_serial = 0; |
250 | |
251 | QGLPixmapData::QGLPixmapData(PixelType type) |
252 | : QPixmapData(type, OpenGLClass) |
253 | , m_renderFbo(0) |
254 | , m_engine(0) |
255 | , m_ctx(0) |
256 | , m_dirty(false) |
257 | , m_hasFillColor(false) |
258 | , m_hasAlpha(false) |
259 | { |
260 | setSerialNumber(++qt_gl_pixmap_serial); |
261 | m_glDevice.setPixmapData(this); |
262 | } |
263 | |
264 | QGLPixmapData::~QGLPixmapData() |
265 | { |
266 | const QGLContext *shareContext = qt_gl_share_context(); |
267 | if (!shareContext) |
268 | return; |
269 | |
270 | delete m_engine; |
271 | |
272 | if (m_texture.id) { |
273 | QGLShareContextScope ctx(shareContext); |
274 | glDeleteTextures(1, &m_texture.id); |
275 | } |
276 | } |
277 | |
278 | QPixmapData *QGLPixmapData::createCompatiblePixmapData() const |
279 | { |
280 | return new QGLPixmapData(pixelType()); |
281 | } |
282 | |
283 | bool QGLPixmapData::isValid() const |
284 | { |
285 | return w > 0 && h > 0; |
286 | } |
287 | |
288 | bool QGLPixmapData::isValidContext(const QGLContext *ctx) const |
289 | { |
290 | if (ctx == m_ctx) |
291 | return true; |
292 | |
293 | const QGLContext *share_ctx = qt_gl_share_context(); |
294 | return ctx == share_ctx || QGLContext::areSharing(ctx, share_ctx); |
295 | } |
296 | |
297 | void QGLPixmapData::resize(int width, int height) |
298 | { |
299 | if (width == w && height == h) |
300 | return; |
301 | |
302 | if (width <= 0 || height <= 0) { |
303 | width = 0; |
304 | height = 0; |
305 | } |
306 | |
307 | w = width; |
308 | h = height; |
309 | is_null = (w <= 0 || h <= 0); |
310 | d = pixelType() == QPixmapData::PixmapType ? 32 : 1; |
311 | |
312 | if (m_texture.id) { |
313 | QGLShareContextScope ctx(qt_gl_share_context()); |
314 | glDeleteTextures(1, &m_texture.id); |
315 | m_texture.id = 0; |
316 | } |
317 | |
318 | m_source = QImage(); |
319 | m_dirty = isValid(); |
320 | setSerialNumber(++qt_gl_pixmap_serial); |
321 | } |
322 | |
323 | void QGLPixmapData::ensureCreated() const |
324 | { |
325 | if (!m_dirty) |
326 | return; |
327 | |
328 | m_dirty = false; |
329 | |
330 | QGLShareContextScope ctx(qt_gl_share_context()); |
331 | m_ctx = ctx; |
332 | |
333 | const GLenum internal_format = m_hasAlpha ? GL_RGBA : GL_RGB; |
334 | #ifdef QT_OPENGL_ES_2 |
335 | const GLenum external_format = internal_format; |
336 | #else |
337 | const GLenum external_format = qt_gl_preferredTextureFormat(); |
338 | #endif |
339 | const GLenum target = GL_TEXTURE_2D; |
340 | |
341 | if (!m_texture.id) { |
342 | glGenTextures(1, &m_texture.id); |
343 | glBindTexture(target, m_texture.id); |
344 | glTexImage2D(target, 0, internal_format, w, h, 0, external_format, GL_UNSIGNED_BYTE, 0); |
345 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
346 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
347 | } |
348 | |
349 | if (!m_source.isNull()) { |
350 | if (external_format == GL_RGB) { |
351 | const QImage tx = m_source.convertToFormat(QImage::Format_RGB888).mirrored(false, true); |
352 | |
353 | glBindTexture(target, m_texture.id); |
354 | glTexSubImage2D(target, 0, 0, 0, w, h, external_format, |
355 | GL_UNSIGNED_BYTE, tx.bits()); |
356 | } else { |
357 | const QImage tx = ctx->d_func()->convertToGLFormat(m_source, true, external_format); |
358 | |
359 | glBindTexture(target, m_texture.id); |
360 | glTexSubImage2D(target, 0, 0, 0, w, h, external_format, |
361 | GL_UNSIGNED_BYTE, tx.bits()); |
362 | } |
363 | |
364 | if (useFramebufferObjects()) |
365 | m_source = QImage(); |
366 | } |
367 | |
368 | m_texture.options &= ~QGLContext::MemoryManagedBindOption; |
369 | } |
370 | |
371 | void QGLPixmapData::fromImage(const QImage &image, |
372 | Qt::ImageConversionFlags flags) |
373 | { |
374 | QImage img = image; |
375 | createPixmapForImage(img, flags, false); |
376 | } |
377 | |
378 | void QGLPixmapData::fromImageReader(QImageReader *imageReader, |
379 | Qt::ImageConversionFlags flags) |
380 | { |
381 | QImage image = imageReader->read(); |
382 | if (image.isNull()) |
383 | return; |
384 | |
385 | createPixmapForImage(image, flags, true); |
386 | } |
387 | |
388 | bool QGLPixmapData::fromFile(const QString &filename, const char *format, |
389 | Qt::ImageConversionFlags flags) |
390 | { |
391 | if (pixelType() == QPixmapData::BitmapType) |
392 | return QPixmapData::fromFile(filename, format, flags); |
393 | QFile file(filename); |
394 | if (file.open(QIODevice::ReadOnly)) { |
395 | QByteArray data = file.peek(64); |
396 | bool alpha; |
397 | if (m_texture.canBindCompressedTexture |
398 | (data.constData(), data.size(), format, &alpha)) { |
399 | resize(0, 0); |
400 | data = file.readAll(); |
401 | file.close(); |
402 | QGLShareContextScope ctx(qt_gl_share_context()); |
403 | QSize size = m_texture.bindCompressedTexture |
404 | (data.constData(), data.size(), format); |
405 | if (!size.isEmpty()) { |
406 | w = size.width(); |
407 | h = size.height(); |
408 | is_null = false; |
409 | d = 32; |
410 | m_hasAlpha = alpha; |
411 | m_source = QImage(); |
412 | m_dirty = isValid(); |
413 | return true; |
414 | } |
415 | return false; |
416 | } |
417 | } |
418 | |
419 | QImage image = QImageReader(filename, format).read(); |
420 | if (image.isNull()) |
421 | return false; |
422 | |
423 | createPixmapForImage(image, flags, true); |
424 | |
425 | return !isNull(); |
426 | } |
427 | |
428 | bool QGLPixmapData::fromData(const uchar *buffer, uint len, const char *format, |
429 | Qt::ImageConversionFlags flags) |
430 | { |
431 | bool alpha; |
432 | const char *buf = reinterpret_cast<const char *>(buffer); |
433 | if (m_texture.canBindCompressedTexture(buf, int(len), format, &alpha)) { |
434 | resize(0, 0); |
435 | QGLShareContextScope ctx(qt_gl_share_context()); |
436 | QSize size = m_texture.bindCompressedTexture(buf, int(len), format); |
437 | if (!size.isEmpty()) { |
438 | w = size.width(); |
439 | h = size.height(); |
440 | is_null = false; |
441 | d = 32; |
442 | m_hasAlpha = alpha; |
443 | m_source = QImage(); |
444 | m_dirty = isValid(); |
445 | return true; |
446 | } |
447 | } |
448 | |
449 | QByteArray a = QByteArray::fromRawData(reinterpret_cast<const char *>(buffer), len); |
450 | QBuffer b(&a); |
451 | b.open(QIODevice::ReadOnly); |
452 | QImage image = QImageReader(&b, format).read(); |
453 | if (image.isNull()) |
454 | return false; |
455 | |
456 | createPixmapForImage(image, flags, true); |
457 | |
458 | return !isNull(); |
459 | } |
460 | |
461 | /*! |
462 | out-of-place conversion (inPlace == false) will always detach() |
463 | */ |
464 | void QGLPixmapData::createPixmapForImage(QImage &image, Qt::ImageConversionFlags flags, bool inPlace) |
465 | { |
466 | if (image.size() == QSize(w, h)) |
467 | setSerialNumber(++qt_gl_pixmap_serial); |
468 | |
469 | resize(image.width(), image.height()); |
470 | |
471 | if (pixelType() == BitmapType) { |
472 | m_source = image.convertToFormat(QImage::Format_MonoLSB); |
473 | |
474 | } else { |
475 | QImage::Format format = QImage::Format_RGB32; |
476 | if (qApp->desktop()->depth() == 16) |
477 | format = QImage::Format_RGB16; |
478 | |
479 | if (image.hasAlphaChannel() |
480 | && ((flags & Qt::NoOpaqueDetection) |
481 | || const_cast<QImage &>(image).data_ptr()->checkForAlphaPixels())) |
482 | format = QImage::Format_ARGB32_Premultiplied;; |
483 | |
484 | if (inPlace && image.data_ptr()->convertInPlace(format, flags)) { |
485 | m_source = image; |
486 | } else { |
487 | m_source = image.convertToFormat(format); |
488 | |
489 | // convertToFormat won't detach the image if format stays the same. |
490 | if (image.format() == format) |
491 | m_source.detach(); |
492 | } |
493 | } |
494 | |
495 | m_dirty = true; |
496 | m_hasFillColor = false; |
497 | |
498 | m_hasAlpha = m_source.hasAlphaChannel(); |
499 | w = image.width(); |
500 | h = image.height(); |
501 | is_null = (w <= 0 || h <= 0); |
502 | d = m_source.depth(); |
503 | |
504 | if (m_texture.id) { |
505 | QGLShareContextScope ctx(qt_gl_share_context()); |
506 | glDeleteTextures(1, &m_texture.id); |
507 | m_texture.id = 0; |
508 | } |
509 | } |
510 | |
511 | bool QGLPixmapData::scroll(int dx, int dy, const QRect &rect) |
512 | { |
513 | Q_UNUSED(dx); |
514 | Q_UNUSED(dy); |
515 | Q_UNUSED(rect); |
516 | return false; |
517 | } |
518 | |
519 | void QGLPixmapData::copy(const QPixmapData *data, const QRect &rect) |
520 | { |
521 | if (data->classId() != QPixmapData::OpenGLClass || !static_cast<const QGLPixmapData *>(data)->useFramebufferObjects()) { |
522 | QPixmapData::copy(data, rect); |
523 | return; |
524 | } |
525 | |
526 | const QGLPixmapData *other = static_cast<const QGLPixmapData *>(data); |
527 | if (other->m_renderFbo) { |
528 | QGLShareContextScope ctx(qt_gl_share_context()); |
529 | |
530 | resize(rect.width(), rect.height()); |
531 | m_hasAlpha = other->m_hasAlpha; |
532 | ensureCreated(); |
533 | |
534 | if (!ctx->d_ptr->fbo) |
535 | glGenFramebuffers(1, &ctx->d_ptr->fbo); |
536 | |
537 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, ctx->d_ptr->fbo); |
538 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, |
539 | GL_TEXTURE_2D, m_texture.id, 0); |
540 | |
541 | if (!other->m_renderFbo->isBound()) |
542 | glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, other->m_renderFbo->handle()); |
543 | |
544 | glDisable(GL_SCISSOR_TEST); |
545 | if (ctx->d_ptr->active_engine && ctx->d_ptr->active_engine->type() == QPaintEngine::OpenGL2) |
546 | static_cast<QGL2PaintEngineEx *>(ctx->d_ptr->active_engine)->invalidateState(); |
547 | |
548 | glBlitFramebufferEXT(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + rect.height(), |
549 | 0, 0, w, h, |
550 | GL_COLOR_BUFFER_BIT, |
551 | GL_NEAREST); |
552 | |
553 | glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->current_fbo); |
554 | } else { |
555 | QPixmapData::copy(data, rect); |
556 | } |
557 | } |
558 | |
559 | void QGLPixmapData::fill(const QColor &color) |
560 | { |
561 | if (!isValid()) |
562 | return; |
563 | |
564 | bool hasAlpha = color.alpha() != 255; |
565 | if (hasAlpha && !m_hasAlpha) { |
566 | if (m_texture.id) { |
567 | glDeleteTextures(1, &m_texture.id); |
568 | m_texture.id = 0; |
569 | m_dirty = true; |
570 | } |
571 | m_hasAlpha = color.alpha() != 255; |
572 | } |
573 | |
574 | if (useFramebufferObjects()) { |
575 | m_source = QImage(); |
576 | m_hasFillColor = true; |
577 | m_fillColor = color; |
578 | } else { |
579 | |
580 | if (m_source.isNull()) { |
581 | m_fillColor = color; |
582 | m_hasFillColor = true; |
583 | |
584 | } else if (m_source.depth() == 32) { |
585 | m_source.fill(PREMUL(color.rgba())); |
586 | |
587 | } else if (m_source.depth() == 1) { |
588 | if (color == Qt::color1) |
589 | m_source.fill(1); |
590 | else |
591 | m_source.fill(0); |
592 | } |
593 | } |
594 | } |
595 | |
596 | bool QGLPixmapData::hasAlphaChannel() const |
597 | { |
598 | return m_hasAlpha; |
599 | } |
600 | |
601 | QImage QGLPixmapData::fillImage(const QColor &color) const |
602 | { |
603 | QImage img; |
604 | if (pixelType() == BitmapType) { |
605 | img = QImage(w, h, QImage::Format_MonoLSB); |
606 | |
607 | img.setColorCount(2); |
608 | img.setColor(0, QColor(Qt::color0).rgba()); |
609 | img.setColor(1, QColor(Qt::color1).rgba()); |
610 | |
611 | if (color == Qt::color1) |
612 | img.fill(1); |
613 | else |
614 | img.fill(0); |
615 | } else { |
616 | img = QImage(w, h, |
617 | m_hasAlpha |
618 | ? QImage::Format_ARGB32_Premultiplied |
619 | : QImage::Format_RGB32); |
620 | img.fill(PREMUL(color.rgba())); |
621 | } |
622 | return img; |
623 | } |
624 | |
625 | extern QImage qt_gl_read_texture(const QSize &size, bool alpha_format, bool include_alpha); |
626 | |
627 | QImage QGLPixmapData::toImage() const |
628 | { |
629 | if (!isValid()) |
630 | return QImage(); |
631 | |
632 | if (m_renderFbo) { |
633 | copyBackFromRenderFbo(true); |
634 | } else if (!m_source.isNull()) { |
635 | QImageData *data = const_cast<QImage &>(m_source).data_ptr(); |
636 | if (data->paintEngine && data->paintEngine->isActive() |
637 | && data->paintEngine->paintDevice() == &m_source) |
638 | { |
639 | return m_source.copy(); |
640 | } |
641 | return m_source; |
642 | } else if (m_dirty || m_hasFillColor) { |
643 | return fillImage(m_fillColor); |
644 | } else { |
645 | ensureCreated(); |
646 | } |
647 | |
648 | QGLShareContextScope ctx(qt_gl_share_context()); |
649 | glBindTexture(GL_TEXTURE_2D, m_texture.id); |
650 | return qt_gl_read_texture(QSize(w, h), true, true); |
651 | } |
652 | |
653 | struct TextureBuffer |
654 | { |
655 | QGLFramebufferObject *fbo; |
656 | QGL2PaintEngineEx *engine; |
657 | }; |
658 | |
659 | Q_GLOBAL_STATIC(QGLFramebufferObjectPool, _qgl_fbo_pool) |
660 | QGLFramebufferObjectPool* qgl_fbo_pool() |
661 | { |
662 | return _qgl_fbo_pool(); |
663 | } |
664 | |
665 | void QGLPixmapData::copyBackFromRenderFbo(bool keepCurrentFboBound) const |
666 | { |
667 | if (!isValid()) |
668 | return; |
669 | |
670 | m_hasFillColor = false; |
671 | |
672 | const QGLContext *share_ctx = qt_gl_share_context(); |
673 | QGLShareContextScope ctx(share_ctx); |
674 | |
675 | ensureCreated(); |
676 | |
677 | if (!ctx->d_ptr->fbo) |
678 | glGenFramebuffers(1, &ctx->d_ptr->fbo); |
679 | |
680 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, ctx->d_ptr->fbo); |
681 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, |
682 | GL_TEXTURE_2D, m_texture.id, 0); |
683 | |
684 | const int x0 = 0; |
685 | const int x1 = w; |
686 | const int y0 = 0; |
687 | const int y1 = h; |
688 | |
689 | if (!m_renderFbo->isBound()) |
690 | glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, m_renderFbo->handle()); |
691 | |
692 | glDisable(GL_SCISSOR_TEST); |
693 | |
694 | glBlitFramebufferEXT(x0, y0, x1, y1, |
695 | x0, y0, x1, y1, |
696 | GL_COLOR_BUFFER_BIT, |
697 | GL_NEAREST); |
698 | |
699 | if (keepCurrentFboBound) { |
700 | glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->current_fbo); |
701 | } else { |
702 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, m_renderFbo->handle()); |
703 | ctx->d_ptr->current_fbo = m_renderFbo->handle(); |
704 | } |
705 | } |
706 | |
707 | bool QGLPixmapData::useFramebufferObjects() const |
708 | { |
709 | return QGLFramebufferObject::hasOpenGLFramebufferObjects() |
710 | && QGLFramebufferObject::hasOpenGLFramebufferBlit() |
711 | && qt_gl_preferGL2Engine() |
712 | && (w * h > 32*32); // avoid overhead of FBOs for small pixmaps |
713 | } |
714 | |
715 | QPaintEngine* QGLPixmapData::paintEngine() const |
716 | { |
717 | if (!isValid()) |
718 | return 0; |
719 | |
720 | if (m_renderFbo) |
721 | return m_engine; |
722 | |
723 | if (useFramebufferObjects()) { |
724 | extern QGLWidget* qt_gl_share_widget(); |
725 | |
726 | if (!QGLContext::currentContext()) |
727 | const_cast<QGLContext *>(qt_gl_share_context())->makeCurrent(); |
728 | QGLShareContextScope ctx(qt_gl_share_context()); |
729 | |
730 | QGLFramebufferObjectFormat format; |
731 | format.setAttachment(QGLFramebufferObject::CombinedDepthStencil); |
732 | format.setSamples(4); |
733 | format.setInternalTextureFormat(GLenum(m_hasAlpha ? GL_RGBA : GL_RGB)); |
734 | |
735 | m_renderFbo = qgl_fbo_pool()->acquire(size(), format); |
736 | |
737 | if (m_renderFbo) { |
738 | if (!m_engine) |
739 | m_engine = new QGL2PaintEngineEx; |
740 | return m_engine; |
741 | } |
742 | |
743 | qWarning() << "Failed to create pixmap texture buffer of size " << size() << ", falling back to raster paint engine" ; |
744 | } |
745 | |
746 | m_dirty = true; |
747 | if (m_source.size() != size()) |
748 | m_source = QImage(size(), QImage::Format_ARGB32_Premultiplied); |
749 | if (m_hasFillColor) { |
750 | m_source.fill(PREMUL(m_fillColor.rgba())); |
751 | m_hasFillColor = false; |
752 | } |
753 | return m_source.paintEngine(); |
754 | } |
755 | |
756 | extern QRgb qt_gl_convertToGLFormat(QRgb src_pixel, GLenum texture_format); |
757 | |
758 | // If copyBack is true, bind will copy the contents of the render |
759 | // FBO to the texture (which is not bound to the texture, as it's |
760 | // a multisample FBO). |
761 | GLuint QGLPixmapData::bind(bool copyBack) const |
762 | { |
763 | if (m_renderFbo && copyBack) { |
764 | copyBackFromRenderFbo(true); |
765 | } else { |
766 | ensureCreated(); |
767 | } |
768 | |
769 | GLuint id = m_texture.id; |
770 | glBindTexture(GL_TEXTURE_2D, id); |
771 | |
772 | if (m_hasFillColor) { |
773 | if (!useFramebufferObjects()) { |
774 | m_source = QImage(w, h, QImage::Format_ARGB32_Premultiplied); |
775 | m_source.fill(PREMUL(m_fillColor.rgba())); |
776 | } |
777 | |
778 | m_hasFillColor = false; |
779 | |
780 | GLenum format = qt_gl_preferredTextureFormat(); |
781 | QImage tx(w, h, QImage::Format_ARGB32_Premultiplied); |
782 | tx.fill(qt_gl_convertToGLFormat(m_fillColor.rgba(), format)); |
783 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, format, GL_UNSIGNED_BYTE, tx.bits()); |
784 | } |
785 | |
786 | return id; |
787 | } |
788 | |
789 | QGLTexture* QGLPixmapData::texture() const |
790 | { |
791 | return &m_texture; |
792 | } |
793 | |
794 | int QGLPixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const |
795 | { |
796 | if (w == 0) |
797 | return 0; |
798 | |
799 | switch (metric) { |
800 | case QPaintDevice::PdmWidth: |
801 | return w; |
802 | case QPaintDevice::PdmHeight: |
803 | return h; |
804 | case QPaintDevice::PdmNumColors: |
805 | return 0; |
806 | case QPaintDevice::PdmDepth: |
807 | return d; |
808 | case QPaintDevice::PdmWidthMM: |
809 | return qRound(w * 25.4 / qt_defaultDpiX()); |
810 | case QPaintDevice::PdmHeightMM: |
811 | return qRound(h * 25.4 / qt_defaultDpiY()); |
812 | case QPaintDevice::PdmDpiX: |
813 | case QPaintDevice::PdmPhysicalDpiX: |
814 | return qt_defaultDpiX(); |
815 | case QPaintDevice::PdmDpiY: |
816 | case QPaintDevice::PdmPhysicalDpiY: |
817 | return qt_defaultDpiY(); |
818 | default: |
819 | qWarning("QGLPixmapData::metric(): Invalid metric" ); |
820 | return 0; |
821 | } |
822 | } |
823 | |
824 | QGLPaintDevice *QGLPixmapData::glDevice() const |
825 | { |
826 | return &m_glDevice; |
827 | } |
828 | |
829 | QT_END_NAMESPACE |
830 | |