StelQGLTextureBackend* StelQGLTextureBackend::fromFBO (StelQGLRenderer* renderer, QGLFramebufferObject* fbo) { renderer->makeGLContextCurrent(); const TextureParams params = TextureParams(); StelQGLTextureBackend* result = new StelQGLTextureBackend(renderer, QString(), params); result->startedLoading(); result->glTextureID = fbo->texture(); // Prevent the StelQGLTextureBackend destructor from destroying the texture. result->ownsTexture = false; // To simplify code, we assume 4 bytes per fixel (32bit RGBA) - this // is most common, but might not always be the case. result->pixelBytes = 4.0f; renderer->getStatistics()[ESTIMATED_TEXTURE_MEMORY] += fbo->size().width() * fbo->size().height() * result->pixelBytes; result->finishedLoading(fbo->size()); return result; }
void StelQGLRenderer::drawText(const TextParams& params) { statistics[TEXT_DRAWS] += 1.0; StelQGLTextureBackend* currentTexture = currentlyBoundTextures[0]; if(params.string_.length() == 0) { return; } viewport.enablePainting(); if(currentFontSet) { viewport.setFont(currentFont); } QPainter* painter = viewport.getPainter(); Q_ASSERT_X(NULL != painter, Q_FUNC_INFO, "Trying to draw text but painting is disabled"); QFontMetrics fontMetrics = painter->fontMetrics(); StelProjectorP projector = NULL == params.projector_ ? StelApp::getInstance().getCore()->getProjection2d() : params.projector_; Vec3f win; if(params.doNotProject_) { win = params.position_; } else if(!projector->project(params.position_, win)) { viewport.disablePainting(); return; } const int x = win[0]; const int y = win[1]; // Avoid drawing if outside viewport. // We do a worst-case approximation as getting exact text dimensions is expensive. // We also account for rotation by assuming the worst case in bot X and Y // (culling with a rotating rectangle would be expensive) const int cullDistance = std::max(fontMetrics.height(), params.string_.size() * fontMetrics.maxWidth()); const Vec4i viewXywh = projector->getViewportXywh(); const int viewMinX = viewXywh[0]; const int viewMinY = viewXywh[1]; const int viewMaxX = viewMinX + viewXywh[2]; const int viewMaxY = viewMinY + viewXywh[3]; if(y + cullDistance < viewMinY || y - cullDistance > viewMaxY || x + cullDistance < viewMinX || x - cullDistance > viewMaxX) { viewport.disablePainting(); return; } if(projector->useGravityLabels() && !params.noGravity_) { drawTextGravityHelper(params, *painter, x, y, projector); return; } const int pixelSize = painter->font().pixelSize(); // Strings drawn by drawText() can differ by text, font size, or the font itself. const QByteArray hash = params.string_.toUtf8() + QByteArray::number(pixelSize) + painter->font().family().toUtf8(); StelQGLTextureBackend* textTexture = textTextureCache.object(hash); // No texture in cache for this string, need to draw it. if (NULL == textTexture) { const QRect extents = fontMetrics.boundingRect(params.string_); // Width and height of the text. // Texture width/height is required to be at least equal to this. // // Both X and Y need to be at least 1 so we don't create an empty image // (doesn't work with textures) const int requiredWidth = std::max(1, extents.width() + 1 + static_cast<int>(0.02f * extents.width())); const int requiredHeight = std::max(1, extents.height()); // Create temporary image and render text into it // QImage is used solely to reuse existing QGLTextureBackend constructor // function. QPixmap could be used as well (not sure which is faster, // needs profiling) QImage image = areNonPowerOfTwoTexturesSupported() ? QImage(requiredWidth, requiredHeight, QImage::Format_ARGB32_Premultiplied) : QImage(StelUtils::smallestPowerOfTwoGreaterOrEqualTo(requiredWidth), StelUtils::smallestPowerOfTwoGreaterOrEqualTo(requiredHeight), QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter fontPainter(&image); fontPainter.setFont(painter->font()); fontPainter.setRenderHints(QPainter::TextAntialiasing, true); fontPainter.setPen(Qt::white); // The second argument ensures the text is positioned correctly even if // the image is enlarged to power-of-two. fontPainter.drawText(-extents.x(), image.height() - requiredHeight - extents.y(), params.string_); textTexture = StelQGLTextureBackend::constructFromImage (this, QString(), TextureParams().filtering(TextureFiltering_Linear), image); const QSize size = textTexture->getDimensions(); if(!textTexture->getStatus() == TextureStatus_Loaded) { qWarning() << "Texture error: " << textTexture->getErrorMessage(); Q_ASSERT_X(false, Q_FUNC_INFO, "Failed to construct a text texture"); } textTextureCache.insert(hash, textTexture, 4 * size.width() * size.height()); } // Even if NPOT textures are not supported, we always draw the full rectangle // of the texture. The extra space is fully transparent, so it's not an issue. // Shortcut variables to calculate the rectangle. const QSize size = textTexture->getDimensions(); const float w = size.width(); const float h = size.height(); const float xShift = params.xShift_; const float yShift = params.yShift_; const float angleDegrees = params.angleDegrees_ + (params.noGravity_ ? 0.0f : projector->getDefaultAngleForGravityText()); // Zero out very small angles. // // (this could also be used to optimize the case with zero angled // to avoid sin/cos if needed) const bool angled = std::fabs(angleDegrees) >= 1.0f * M_PI / 180.f; const float cosr = angled ? std::cos(angleDegrees * M_PI / 180.0) : 1.0f; const float sinr = angled ? std::sin(angleDegrees * M_PI / 180.0) : 0.0f; // Corners of the (possibly rotated) texture rectangle. const Vec2f ne(round(x + cosr * xShift - sinr * yShift), round(y + sinr * xShift + cosr * yShift)); const Vec2f nw(round(x + cosr * (w + xShift) - sinr * yShift), round(y + sinr * (w + xShift) + cosr * yShift)); const Vec2f se(round(x + cosr * xShift - sinr * (h + yShift)), round(y + sinr * xShift + cosr * (h + yShift))); const Vec2f sw(round(x + cosr * (w + xShift) - sinr * (h + yShift)), round(y + sinr * (w + xShift) + cosr * (h + yShift))); // Construct the text vertex buffer if it doesn't exist yet, otherwise clear it. if(NULL == textBuffer) { textBuffer = createVertexBuffer<TexturedVertex>(PrimitiveType_TriangleStrip); } else { textBuffer->unlock(); textBuffer->clear(); } textBuffer->addVertex(TexturedVertex(ne, Vec2f(0.0f, 0.0f))); textBuffer->addVertex(TexturedVertex(nw, Vec2f(1.0f, 0.0f))); textBuffer->addVertex(TexturedVertex(se, Vec2f(0.0f, 1.0f))); textBuffer->addVertex(TexturedVertex(sw, Vec2f(1.0f, 1.0f))); textBuffer->lock(); // Draw. const BlendMode oldBlendMode = blendMode; setBlendMode(BlendMode_Alpha); textTexture->bind(0); drawVertexBuffer(textBuffer); setBlendMode(oldBlendMode); // Reset user-bound texture. if(NULL != currentTexture) { currentTexture->bind(0); } viewport.disablePainting(); }
StelQGLTextureBackend* StelQGLTextureBackend::fromViewport (StelQGLRenderer* renderer, const QSize viewportSize, const QGLFormat& viewportFormat) { // This function should only be used as a fallback for when FBOs aren't supported. const int r = viewportFormat.redBufferSize(); const int g = viewportFormat.greenBufferSize(); const int b = viewportFormat.blueBufferSize(); const int a = viewportFormat.alpha() ? viewportFormat.alphaBufferSize() : 0; // Creating a texture from a dummy image. QImage dummyImage(64, 64, QImage::Format_Mono); const TextureParams params = TextureParams(); StelQGLTextureBackend* result = new StelQGLTextureBackend(renderer, QString(), params); // Get image and GL pixel format matching viewport format. int glFormat; if(r == 8 && g == 8 && b == 8 && a == 8) { glFormat = GL_RGBA8; result->pixelBytes = 4.0f; } else if(r == 8 && g == 8 && b == 8 && a == 0) { glFormat = GL_RGB8; result->pixelBytes = 3.0f; } else if(r == 5 && g == 6 && b == 5 && a == 0) { glFormat = GL_RGB5; result->pixelBytes = 2.0f; } else { // This is extremely unlikely, but we can't rule it out. Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown screen vertex format when getting texture from viewport. " "Switching to OpenGL2, disabling viewport effects or " "chaning video mode bit depth might help"); // Invalid value to avoid warnings. glFormat = -1; } QGLContext* context = result->prepareContextForLoading(); const GLuint glTextureID = context->bindTexture(dummyImage, GL_TEXTURE_2D, glFormat, getTextureBindOptions(params)); // Need a power-of-two texture (as this is used mainly with GL1) const QSize size = StelUtils::smallestPowerOfTwoSizeGreaterOrEqualTo(viewportSize); // Set viewport so it matches the size of the texture glViewport(0, 0, size.width(), size.height()); // Copy viewport to texture (this overrides texture size - we must use POT for GL1) glCopyTexImage2D(GL_TEXTURE_2D, 0, glFormat, 0, 0, size.width(), size.height(), 0); // Restore viewport glViewport(0, 0, viewportSize.width(), viewportSize.height()); // Will need change if different screen bit depths are ever supported renderer->getStatistics()[ESTIMATED_TEXTURE_MEMORY] += size.width() * size.height() * result->pixelBytes; result->startedLoading(); result->glTextureID = glTextureID; result->finishedLoading(size); return result; }