overte-JulianGro/interface/src/renderer/TextureCache.cpp
Andrzej Kapolka 7ecfeeaba3 Do the actual reading of the reply on the worker thread. Otherwise, when we
read from the cache, we're reading synchronously from a file.  Closes #2171.
2014-03-03 15:16:13 -08:00

402 lines
14 KiB
C++

//
// 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 <QGLWidget>
#include <QNetworkReply>
#include <QOpenGLFramebufferObject>
#include <QRunnable>
#include <QThreadPool>
#include <glm/gtc/random.hpp>
#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<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) {
if (!dilatable) {
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>();
}
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(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<QResizeEvent*>(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<Resource> TextureCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(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<Resource>& texture, QNetworkReply* reply);
virtual void run();
private:
QWeakPointer<Resource> _texture;
QNetworkReply* _reply;
};
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply) :
_texture(texture),
_reply(reply) {
}
void ImageReader::run() {
QSharedPointer<Resource> texture = _texture.toStrongRef();
if (texture.isNull()) {
_reply->deleteLater();
return;
}
QImage image = QImage::fromData(_reply->readAll());
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));
_reply->deleteLater();
}
void NetworkTexture::downloadFinished(QNetworkReply* reply) {
// send the reader off to the thread pool
QThreadPool::globalInstance()->start(new ImageReader(_self, reply));
}
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<Texture> DilatableNetworkTexture::getDilatedTexture(float dilation) {
QSharedPointer<Texture> texture = _dilatedTextures.value(dilation);
if (texture.isNull()) {
texture = QSharedPointer<Texture>(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;
}