// // TextureCache.cpp // interface // // Created by Andrzej Kapolka on 8/6/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" #include #include #include #include #include #include #include "Application.h" #include "TextureCache.h" TextureCache::TextureCache() : _permutationNormalTextureID(0), _whiteTextureID(0), _blueTextureID(0), _primaryFramebufferObject(NULL), _secondaryFramebufferObject(NULL), _tertiaryFramebufferObject(NULL), _shadowFramebufferObject(NULL) { } TextureCache::~TextureCache() { if (_permutationNormalTextureID != 0) { glDeleteTextures(1, &_permutationNormalTextureID); } if (_whiteTextureID != 0) { glDeleteTextures(1, &_whiteTextureID); } foreach (GLuint id, _fileTextureIDs) { glDeleteTextures(1, &id); } if (_primaryFramebufferObject) { delete _primaryFramebufferObject; glDeleteTextures(1, &_primaryDepthTextureID); } if (_secondaryFramebufferObject) { delete _secondaryFramebufferObject; } if (_tertiaryFramebufferObject) { delete _tertiaryFramebufferObject; } } GLuint TextureCache::getPermutationNormalTextureID() { if (_permutationNormalTextureID == 0) { glGenTextures(1, &_permutationNormalTextureID); glBindTexture(GL_TEXTURE_2D, _permutationNormalTextureID); // the first line consists of random permutation offsets unsigned char data[256 * 2 * 3]; for (int i = 0; i < 256 * 3; i++) { data[i] = rand() % 256; } // the next, random unit normals for (int i = 256 * 3; i < 256 * 3 * 2; i += 3) { glm::vec3 randvec = glm::sphericalRand(1.0f); data[i] = ((randvec.x + 1.0f) / 2.0f) * 255.0f; data[i + 1] = ((randvec.y + 1.0f) / 2.0f) * 255.0f; data[i + 2] = ((randvec.z + 1.0f) / 2.0f) * 255.0f; } glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); } return _permutationNormalTextureID; } const unsigned char OPAQUE_WHITE[] = { 0xFF, 0xFF, 0xFF, 0xFF }; const unsigned char OPAQUE_BLUE[] = { 0x80, 0x80, 0xFF, 0xFF }; static void loadSingleColorTexture(const unsigned char* color) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, color); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } GLuint TextureCache::getWhiteTextureID() { if (_whiteTextureID == 0) { glGenTextures(1, &_whiteTextureID); glBindTexture(GL_TEXTURE_2D, _whiteTextureID); loadSingleColorTexture(OPAQUE_WHITE); glBindTexture(GL_TEXTURE_2D, 0); } return _whiteTextureID; } GLuint TextureCache::getBlueTextureID() { if (_blueTextureID == 0) { glGenTextures(1, &_blueTextureID); glBindTexture(GL_TEXTURE_2D, _blueTextureID); loadSingleColorTexture(OPAQUE_BLUE); glBindTexture(GL_TEXTURE_2D, 0); } return _blueTextureID; } GLuint TextureCache::getFileTextureID(const QString& filename) { GLuint id = _fileTextureIDs.value(filename); if (id == 0) { switchToResourcesParentIfRequired(); QImage image = QImage(filename).convertToFormat(QImage::Format_ARGB32); glGenTextures(1, &id); glBindTexture(GL_TEXTURE_2D, id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); _fileTextureIDs.insert(filename, id); } return id; } QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) { if (!dilatable) { return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast(); } QSharedPointer texture = _dilatableNetworkTextures.value(url); if (texture.isNull()) { texture = QSharedPointer(new DilatableNetworkTexture(url)); texture->setSelf(texture); _dilatableNetworkTextures.insert(url, texture); } return texture; } QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() { if (!_primaryFramebufferObject) { _primaryFramebufferObject = createFramebufferObject(); glGenTextures(1, &_primaryDepthTextureID); glBindTexture(GL_TEXTURE_2D, _primaryDepthTextureID); QSize size = Application::getInstance()->getGLWidget()->size(); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width(), size.height(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); _primaryFramebufferObject->bind(); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _primaryDepthTextureID, 0); _primaryFramebufferObject->release(); } return _primaryFramebufferObject; } GLuint TextureCache::getPrimaryDepthTextureID() { // ensure that the primary framebuffer object is initialized before returning the depth texture id getPrimaryFramebufferObject(); return _primaryDepthTextureID; } QOpenGLFramebufferObject* TextureCache::getSecondaryFramebufferObject() { if (!_secondaryFramebufferObject) { _secondaryFramebufferObject = createFramebufferObject(); } return _secondaryFramebufferObject; } QOpenGLFramebufferObject* TextureCache::getTertiaryFramebufferObject() { if (!_tertiaryFramebufferObject) { _tertiaryFramebufferObject = createFramebufferObject(); } return _tertiaryFramebufferObject; } QOpenGLFramebufferObject* TextureCache::getShadowFramebufferObject() { if (!_shadowFramebufferObject) { const int SHADOW_MAP_SIZE = 2048; _shadowFramebufferObject = new QOpenGLFramebufferObject(SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, QOpenGLFramebufferObject::NoAttachment, GL_TEXTURE_2D, GL_RGB); glGenTextures(1, &_shadowDepthTextureID); glBindTexture(GL_TEXTURE_2D, _shadowDepthTextureID); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); const float DISTANT_BORDER[] = { 1.0f, 1.0f, 1.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, DISTANT_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glBindTexture(GL_TEXTURE_2D, 0); _shadowFramebufferObject->bind(); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _shadowDepthTextureID, 0); _shadowFramebufferObject->release(); } return _shadowFramebufferObject; } GLuint TextureCache::getShadowDepthTextureID() { // ensure that the shadow framebuffer object is initialized before returning the depth texture id getShadowFramebufferObject(); return _shadowDepthTextureID; } bool TextureCache::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::Resize) { QSize size = static_cast(event)->size(); if (_primaryFramebufferObject && _primaryFramebufferObject->size() != size) { delete _primaryFramebufferObject; _primaryFramebufferObject = NULL; glDeleteTextures(1, &_primaryDepthTextureID); } if (_secondaryFramebufferObject && _secondaryFramebufferObject->size() != size) { delete _secondaryFramebufferObject; _secondaryFramebufferObject = NULL; } if (_tertiaryFramebufferObject && _tertiaryFramebufferObject->size() != size) { delete _tertiaryFramebufferObject; _tertiaryFramebufferObject = NULL; } } return false; } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { return QSharedPointer(new NetworkTexture(url, *(const bool*)extra)); } QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size()); Application::getInstance()->getGLWidget()->installEventFilter(this); glBindTexture(GL_TEXTURE_2D, fbo->texture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); return fbo; } Texture::Texture() { glGenTextures(1, &_id); } Texture::~Texture() { glDeleteTextures(1, &_id); } NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : Resource(url), _averageColor(1.0f, 1.0f, 1.0f, 1.0f), _translucent(false) { if (!url.isValid()) { _loaded = true; return; } // default to white/blue glBindTexture(GL_TEXTURE_2D, getID()); loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE); glBindTexture(GL_TEXTURE_2D, 0); } class ImageReader : public QRunnable { public: ImageReader(const QWeakPointer& texture, const QByteArray& data); virtual void run(); private: QWeakPointer _texture; QByteArray _data; }; ImageReader::ImageReader(const QWeakPointer& texture, const QByteArray& data) : _texture(texture), _data(data) { } void ImageReader::run() { QSharedPointer texture = _texture.toStrongRef(); if (texture.isNull()) { return; } QImage image = QImage::fromData(_data); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // sum up the colors for the average and check for translucency glm::vec4 accumulated; int translucentPixels = 0; const int EIGHT_BIT_MAXIMUM = 255; for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { QRgb pixel = image.pixel(x, y); accumulated.r += qRed(pixel); accumulated.g += qGreen(pixel); accumulated.b += qBlue(pixel); int alpha = qAlpha(pixel); if (alpha != 0 && alpha != EIGHT_BIT_MAXIMUM) { translucentPixels++; } accumulated.a += alpha; } } int imageArea = image.width() * image.height(); QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(const glm::vec4&, accumulated / (imageArea * EIGHT_BIT_MAXIMUM)), Q_ARG(bool, translucentPixels >= imageArea / 2)); } void NetworkTexture::downloadFinished(QNetworkReply* reply) { // send the reader off to the thread pool QThreadPool::globalInstance()->start(new ImageReader(_self, reply->readAll())); } void NetworkTexture::setImage(const QImage& image, const glm::vec4& averageColor, bool translucent) { _averageColor = averageColor; _translucent = translucent; finishedLoading(true); imageLoaded(image); glBindTexture(GL_TEXTURE_2D, getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); } void NetworkTexture::imageLoaded(const QImage& image) { // nothing by default } DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : NetworkTexture(url, false), _innerRadius(0), _outerRadius(0) { } void DilatableNetworkTexture::imageLoaded(const QImage& image) { _image = image; // scan out from the center to find inner and outer radii int halfWidth = image.width() / 2; int halfHeight = image.height() / 2; const int BLACK_THRESHOLD = 32; while (_innerRadius < halfWidth && qGray(image.pixel(halfWidth + _innerRadius, halfHeight)) < BLACK_THRESHOLD) { _innerRadius++; } _outerRadius = _innerRadius; const int TRANSPARENT_THRESHOLD = 32; while (_outerRadius < halfWidth && qAlpha(image.pixel(halfWidth + _outerRadius, halfHeight)) > TRANSPARENT_THRESHOLD) { _outerRadius++; } // clear out any textures we generated before loading _dilatedTextures.clear(); } QSharedPointer DilatableNetworkTexture::getDilatedTexture(float dilation) { QSharedPointer texture = _dilatedTextures.value(dilation); if (texture.isNull()) { texture = QSharedPointer(new Texture()); if (!_image.isNull()) { QImage dilatedImage = _image; QPainter painter; painter.begin(&dilatedImage); QPainterPath path; qreal radius = glm::mix(_innerRadius, _outerRadius, dilation); path.addEllipse(QPointF(_image.width() / 2.0, _image.height() / 2.0), radius, radius); painter.fillPath(path, Qt::black); painter.end(); glBindTexture(GL_TEXTURE_2D, texture->getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 1, GL_BGRA, GL_UNSIGNED_BYTE, dilatedImage.constBits()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); } _dilatedTextures.insert(dilation, texture); } return texture; }