mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-05-29 10:59:55 +02:00
TextureCache
This commit is contained in:
parent
cfe14518a1
commit
c044daf7b2
4 changed files with 247 additions and 242 deletions
|
@ -1,4 +1,4 @@
|
||||||
set(TARGET_NAME model-networking)
|
set(TARGET_NAME model-networking)
|
||||||
setup_hifi_library()
|
setup_hifi_library()
|
||||||
link_hifi_libraries(shared networking model fbx)
|
link_hifi_libraries(shared networking model fbx ktx)
|
||||||
|
|
||||||
|
|
|
@ -18,27 +18,39 @@
|
||||||
#include <QRunnable>
|
#include <QRunnable>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
|
|
||||||
|
#if DEBUG_DUMP_TEXTURE_LOADS
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
#include <QtCore/QFileInfo>
|
#include <QtCore/QFileInfo>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/random.hpp>
|
#include <glm/gtc/random.hpp>
|
||||||
|
|
||||||
#include <gpu/Batch.h>
|
#include <gpu/Batch.h>
|
||||||
|
|
||||||
|
#include <ktx/KTX.h>
|
||||||
|
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <shared/NsightHelpers.h>
|
#include <shared/NsightHelpers.h>
|
||||||
|
|
||||||
#include <Finally.h>
|
#include <Finally.h>
|
||||||
#include <PathUtils.h>
|
#include <PathUtils.h>
|
||||||
|
#include <ServerPathUtils.h>
|
||||||
|
|
||||||
#include "ModelNetworkingLogging.h"
|
#include "ModelNetworkingLogging.h"
|
||||||
#include <Trace.h>
|
#include <Trace.h>
|
||||||
#include <StatTracker.h>
|
#include <StatTracker.h>
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image")
|
Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image")
|
||||||
|
Q_LOGGING_CATEGORY(trace_resource_parse_ktx, "trace.resource.parse.ktx")
|
||||||
|
Q_LOGGING_CATEGORY(trace_resource_cache_ktx, "trace.resource.cache.ktx")
|
||||||
|
|
||||||
TextureCache::TextureCache() {
|
const std::string TextureCache::KTX_DIRNAME { "ktx_cache" };
|
||||||
|
const std::string TextureCache::KTX_EXT { "ktx" };
|
||||||
|
|
||||||
|
TextureCache::TextureCache() :
|
||||||
|
_ktxCache(KTX_DIRNAME, KTX_EXT) {
|
||||||
setUnusedResourceCacheSize(0);
|
setUnusedResourceCacheSize(0);
|
||||||
setObjectName("TextureCache");
|
setObjectName("TextureCache");
|
||||||
|
|
||||||
|
@ -61,7 +73,7 @@ TextureCache::~TextureCache() {
|
||||||
// this list taken from Ken Perlin's Improved Noise reference implementation (orig. in Java) at
|
// this list taken from Ken Perlin's Improved Noise reference implementation (orig. in Java) at
|
||||||
// http://mrl.nyu.edu/~perlin/noise/
|
// http://mrl.nyu.edu/~perlin/noise/
|
||||||
|
|
||||||
const int permutation[256] =
|
const int permutation[256] =
|
||||||
{
|
{
|
||||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
|
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
|
||||||
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
|
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
|
||||||
|
@ -241,7 +253,7 @@ NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type t
|
||||||
return NetworkTexture::TextureLoaderFunc();
|
return NetworkTexture::TextureLoaderFunc();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Type::DEFAULT_TEXTURE:
|
case Type::DEFAULT_TEXTURE:
|
||||||
default: {
|
default: {
|
||||||
return model::TextureUsage::create2DTextureFromImage;
|
return model::TextureUsage::create2DTextureFromImage;
|
||||||
|
@ -259,12 +271,32 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type
|
||||||
|
|
||||||
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||||
const void* extra) {
|
const void* extra) {
|
||||||
|
KTXFilePointer file = _ktxCache.getFile(url);
|
||||||
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
|
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
|
||||||
auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE;
|
auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE;
|
||||||
auto content = textureExtra ? textureExtra->content : QByteArray();
|
|
||||||
auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS;
|
NetworkTexture* texture;
|
||||||
return QSharedPointer<Resource>(new NetworkTexture(url, type, content, maxNumPixels),
|
if (file) {
|
||||||
&Resource::deleter);
|
texture = new NetworkTexture(url, type, file);
|
||||||
|
} else {
|
||||||
|
auto content = textureExtra ? textureExtra->content : QByteArray();
|
||||||
|
auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS;
|
||||||
|
texture = new NetworkTexture(url, type, content, maxNumPixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QSharedPointer<Resource>(texture, &Resource::deleter);
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkTexture::NetworkTexture(const QUrl& url, Type type, const KTXFilePointer& file) :
|
||||||
|
Resource(url),
|
||||||
|
_type(type),
|
||||||
|
_file(file) {
|
||||||
|
_textureSource = std::make_shared<gpu::TextureSource>();
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
_startedLoading = true;
|
||||||
|
QMetaObject::invokeMethod(this, "loadFile", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels) :
|
NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels) :
|
||||||
|
@ -278,7 +310,6 @@ NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& con
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string theName = url.toString().toStdString();
|
|
||||||
// if we have content, load it after we have our self pointer
|
// if we have content, load it after we have our self pointer
|
||||||
if (!content.isEmpty()) {
|
if (!content.isEmpty()) {
|
||||||
_startedLoading = true;
|
_startedLoading = true;
|
||||||
|
@ -299,149 +330,6 @@ NetworkTexture::TextureLoaderFunc NetworkTexture::getTextureLoader() const {
|
||||||
return getTextureLoaderForType(_type);
|
return getTextureLoaderForType(_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ImageReader : public QRunnable {
|
|
||||||
public:
|
|
||||||
|
|
||||||
ImageReader(const QWeakPointer<Resource>& resource, const QByteArray& data,
|
|
||||||
const QUrl& url = QUrl(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
|
|
||||||
|
|
||||||
virtual void run() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void listSupportedImageFormats();
|
|
||||||
|
|
||||||
QWeakPointer<Resource> _resource;
|
|
||||||
QUrl _url;
|
|
||||||
QByteArray _content;
|
|
||||||
int _maxNumPixels;
|
|
||||||
};
|
|
||||||
|
|
||||||
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
|
||||||
// send the reader off to the thread pool
|
|
||||||
QThreadPool::globalInstance()->start(new ImageReader(_self, data, _url));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkTexture::loadContent(const QByteArray& content) {
|
|
||||||
QThreadPool::globalInstance()->start(new ImageReader(_self, content, _url, _maxNumPixels));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QByteArray& data,
|
|
||||||
const QUrl& url, int maxNumPixels) :
|
|
||||||
_resource(resource),
|
|
||||||
_url(url),
|
|
||||||
_content(data),
|
|
||||||
_maxNumPixels(maxNumPixels)
|
|
||||||
{
|
|
||||||
#if DEBUG_DUMP_TEXTURE_LOADS
|
|
||||||
static auto start = usecTimestampNow() / USECS_PER_MSEC;
|
|
||||||
auto now = usecTimestampNow() / USECS_PER_MSEC - start;
|
|
||||||
QString urlStr = _url.toString();
|
|
||||||
auto dot = urlStr.lastIndexOf(".");
|
|
||||||
QString outFileName = QString(QCryptographicHash::hash(urlStr.toLocal8Bit(), QCryptographicHash::Md5).toHex()) + urlStr.right(urlStr.length() - dot);
|
|
||||||
QFile loadRecord("h:/textures/loads.txt");
|
|
||||||
loadRecord.open(QFile::Text | QFile::Append | QFile::ReadWrite);
|
|
||||||
loadRecord.write(QString("%1 %2\n").arg(now).arg(outFileName).toLocal8Bit());
|
|
||||||
outFileName = "h:/textures/" + outFileName;
|
|
||||||
QFileInfo outInfo(outFileName);
|
|
||||||
if (!outInfo.exists()) {
|
|
||||||
QFile outFile(outFileName);
|
|
||||||
outFile.open(QFile::WriteOnly | QFile::Truncate);
|
|
||||||
outFile.write(data);
|
|
||||||
outFile.close();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageReader::listSupportedImageFormats() {
|
|
||||||
static std::once_flag once;
|
|
||||||
std::call_once(once, []{
|
|
||||||
auto supportedFormats = QImageReader::supportedImageFormats();
|
|
||||||
qCDebug(modelnetworking) << "List of supported Image formats:" << supportedFormats.join(", ");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageReader::run() {
|
|
||||||
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
|
|
||||||
|
|
||||||
CounterStat counter("Processing");
|
|
||||||
|
|
||||||
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
|
|
||||||
auto originalPriority = QThread::currentThread()->priority();
|
|
||||||
if (originalPriority == QThread::InheritPriority) {
|
|
||||||
originalPriority = QThread::NormalPriority;
|
|
||||||
}
|
|
||||||
QThread::currentThread()->setPriority(QThread::LowPriority);
|
|
||||||
Finally restorePriority([originalPriority]{
|
|
||||||
QThread::currentThread()->setPriority(originalPriority);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!_resource.data()) {
|
|
||||||
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
listSupportedImageFormats();
|
|
||||||
|
|
||||||
// Help the QImage loader by extracting the image file format from the url filename ext.
|
|
||||||
// Some tga are not created properly without it.
|
|
||||||
auto filename = _url.fileName().toStdString();
|
|
||||||
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
|
|
||||||
QImage image = QImage::fromData(_content, filenameExtension.c_str());
|
|
||||||
|
|
||||||
// Note that QImage.format is the pixel format which is different from the "format" of the image file...
|
|
||||||
auto imageFormat = image.format();
|
|
||||||
int imageWidth = image.width();
|
|
||||||
int imageHeight = image.height();
|
|
||||||
|
|
||||||
if (imageWidth == 0 || imageHeight == 0 || imageFormat == QImage::Format_Invalid) {
|
|
||||||
if (filenameExtension.empty()) {
|
|
||||||
qCDebug(modelnetworking) << "QImage failed to create from content, no file extension:" << _url;
|
|
||||||
} else {
|
|
||||||
qCDebug(modelnetworking) << "QImage failed to create from content" << _url;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageWidth * imageHeight > _maxNumPixels) {
|
|
||||||
float scaleFactor = sqrtf(_maxNumPixels / (float)(imageWidth * imageHeight));
|
|
||||||
int originalWidth = imageWidth;
|
|
||||||
int originalHeight = imageHeight;
|
|
||||||
imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
|
|
||||||
imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
|
|
||||||
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
||||||
image.swap(newImage);
|
|
||||||
qCDebug(modelnetworking) << "Downscale image" << _url
|
|
||||||
<< "from" << originalWidth << "x" << originalHeight
|
|
||||||
<< "to" << imageWidth << "x" << imageHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpu::TexturePointer texture = nullptr;
|
|
||||||
{
|
|
||||||
// Double-check the resource still exists between long operations.
|
|
||||||
auto resource = _resource.toStrongRef();
|
|
||||||
if (!resource) {
|
|
||||||
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto url = _url.toString().toStdString();
|
|
||||||
|
|
||||||
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffffff00, 0);
|
|
||||||
texture.reset(resource.dynamicCast<NetworkTexture>()->getTextureLoader()(image, url));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the resource has not been deleted
|
|
||||||
auto resource = _resource.toStrongRef();
|
|
||||||
if (!resource) {
|
|
||||||
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
|
|
||||||
} else {
|
|
||||||
QMetaObject::invokeMethod(resource.data(), "setImage",
|
|
||||||
Q_ARG(gpu::TexturePointer, texture),
|
|
||||||
Q_ARG(int, imageWidth), Q_ARG(int, imageHeight));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth,
|
void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth,
|
||||||
int originalHeight) {
|
int originalHeight) {
|
||||||
_originalWidth = originalWidth;
|
_originalWidth = originalWidth;
|
||||||
|
@ -464,3 +352,196 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth,
|
||||||
|
|
||||||
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));
|
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Reader : public QRunnable {
|
||||||
|
public:
|
||||||
|
Reader(const QWeakPointer<Resource>& resource, const QUrl& url) : _resource(resource), _url(url) {
|
||||||
|
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
|
||||||
|
}
|
||||||
|
void run() override final {
|
||||||
|
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
|
||||||
|
CounterStat counter("Processing");
|
||||||
|
|
||||||
|
// Run this with low priority, then restore thread priority
|
||||||
|
auto originalPriority = QThread::currentThread()->priority();
|
||||||
|
if (originalPriority == QThread::InheritPriority) {
|
||||||
|
originalPriority = QThread::NormalPriority;
|
||||||
|
}
|
||||||
|
QThread::currentThread()->setPriority(QThread::LowPriority);
|
||||||
|
Finally restorePriority([originalPriority]{
|
||||||
|
QThread::currentThread()->setPriority(originalPriority);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!_resource.lock()) { // to ensure the resource is still needed
|
||||||
|
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
virtual void read() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QWeakPointer<Resource> _resource;
|
||||||
|
QUrl _url;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileReader : public Reader {
|
||||||
|
public:
|
||||||
|
FileReader(const QWeakPointer<Resource>& resource, const QUrl& url) : Reader(resource, url) {}
|
||||||
|
void read() override final;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageReader : public Reader {
|
||||||
|
public:
|
||||||
|
ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url,
|
||||||
|
const QByteArray& data, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
|
||||||
|
void read() override final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void listSupportedImageFormats();
|
||||||
|
|
||||||
|
QByteArray _content;
|
||||||
|
int _maxNumPixels;
|
||||||
|
};
|
||||||
|
|
||||||
|
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
||||||
|
// send the reader off to the thread pool
|
||||||
|
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTexture::loadFile() {
|
||||||
|
QThreadPool::globalInstance()->start(new FileReader(_self, _url));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTexture::loadContent(const QByteArray& content) {
|
||||||
|
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url,
|
||||||
|
const QByteArray& data, int maxNumPixels) :
|
||||||
|
Reader(resource, url), _content(data), _maxNumPixels(maxNumPixels) {
|
||||||
|
listSupportedImageFormats();
|
||||||
|
#if DEBUG_DUMP_TEXTURE_LOADS
|
||||||
|
static auto start = usecTimestampNow() / USECS_PER_MSEC;
|
||||||
|
auto now = usecTimestampNow() / USECS_PER_MSEC - start;
|
||||||
|
QString urlStr = _url.toString();
|
||||||
|
auto dot = urlStr.lastIndexOf(".");
|
||||||
|
QString outFileName = QString(QCryptographicHash::hash(urlStr.toLocal8Bit(), QCryptographicHash::Md5).toHex()) + urlStr.right(urlStr.length() - dot);
|
||||||
|
QFile loadRecord("h:/textures/loads.txt");
|
||||||
|
loadRecord.open(QFile::Text | QFile::Append | QFile::ReadWrite);
|
||||||
|
loadRecord.write(QString("%1 %2\n").arg(now).arg(outFileName).toLocal8Bit());
|
||||||
|
outFileName = "h:/textures/" + outFileName;
|
||||||
|
QFileInfo outInfo(outFileName);
|
||||||
|
if (!outInfo.exists()) {
|
||||||
|
QFile outFile(outFileName);
|
||||||
|
outFile.open(QFile::WriteOnly | QFile::Truncate);
|
||||||
|
outFile.write(data);
|
||||||
|
outFile.close();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageReader::listSupportedImageFormats() {
|
||||||
|
static std::once_flag once;
|
||||||
|
std::call_once(once, []{
|
||||||
|
auto supportedFormats = QImageReader::supportedImageFormats();
|
||||||
|
qCDebug(modelnetworking) << "List of supported Image formats:" << supportedFormats.join(", ");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileReader::read() {
|
||||||
|
PROFILE_RANGE_EX(resource_parse_ktx, __FUNCTION__, 0xffff0000, 0);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// auto ktx = ktx::KTX::create();
|
||||||
|
// auto texture = gpu::Texture::unserialize(getUsage(), getUsageType(), ktx, getSampler());
|
||||||
|
// FIXME: do I need to set the file as a backing file here?
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageReader::read() {
|
||||||
|
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
|
||||||
|
|
||||||
|
// Help the QImage loader by extracting the image file format from the url filename ext.
|
||||||
|
// Some tga are not created properly without it.
|
||||||
|
auto filename = _url.fileName().toStdString();
|
||||||
|
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
|
||||||
|
QImage image = QImage::fromData(_content, filenameExtension.c_str());
|
||||||
|
int imageWidth = image.width();
|
||||||
|
int imageHeight = image.height();
|
||||||
|
|
||||||
|
// Validate that the image loaded
|
||||||
|
if (imageWidth == 0 || imageHeight == 0 || image.format() == QImage::Format_Invalid) {
|
||||||
|
QString reason(filenameExtension.empty() ? "" : "(no file extension)");
|
||||||
|
qCWarning(modelnetworking) << "Failed to load" << _url << reason;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the image is less than _maxNumPixels, and downscale if necessary
|
||||||
|
if (imageWidth * imageHeight > _maxNumPixels) {
|
||||||
|
float scaleFactor = sqrtf(_maxNumPixels / (float)(imageWidth * imageHeight));
|
||||||
|
int originalWidth = imageWidth;
|
||||||
|
int originalHeight = imageHeight;
|
||||||
|
imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
|
||||||
|
imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
|
||||||
|
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
|
image.swap(newImage);
|
||||||
|
qCDebug(modelnetworking).nospace() << "Downscaled " << _url << " (" <<
|
||||||
|
QSize(originalWidth, originalHeight) << " to " <<
|
||||||
|
QSize(imageWidth, imageHeight) << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the image into a gpu::Texture
|
||||||
|
gpu::TexturePointer texture = nullptr;
|
||||||
|
{
|
||||||
|
auto resource = _resource.lock(); // to ensure the resource is still needed
|
||||||
|
if (!resource) {
|
||||||
|
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto url = _url.toString().toStdString();
|
||||||
|
|
||||||
|
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0);
|
||||||
|
texture.reset(resource.dynamicCast<NetworkTexture>()->getTextureLoader()(image, url));
|
||||||
|
texture->setSource(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the source image to use as a filename for on-disk caching
|
||||||
|
std::string hash;
|
||||||
|
{
|
||||||
|
QCryptographicHash hasher(QCryptographicHash::Md5);
|
||||||
|
hasher.addData((const char*)image.bits(), image.byteCount() * sizeof(char));
|
||||||
|
hash = hasher.result().toHex().toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto resource = _resource.lock(); // to ensure the resource is still needed
|
||||||
|
if (!resource) {
|
||||||
|
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PROFILE_RANGE_EX(resource_cache_ktx, __FUNCTION__, 0xffffff00, 0);
|
||||||
|
auto ktx = gpu::Texture::serialize(*texture);
|
||||||
|
const char* data = reinterpret_cast<const char*>(ktx->_storage->data());
|
||||||
|
size_t length = ktx->_storage->size();
|
||||||
|
KTXFilePointer file;
|
||||||
|
auto& ktxCache = DependencyManager::get<TextureCache>()->_ktxCache;
|
||||||
|
if (!ktx || !(file = ktxCache.writeFile({ _url, hash, data, length }))) {
|
||||||
|
qCWarning(modelnetworking) << _url << "file cache failed";
|
||||||
|
} else {
|
||||||
|
resource.dynamicCast<NetworkTexture>()->_file = file;
|
||||||
|
// FIXME: do I need to set the file as a backing file here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resource = _resource.toStrongRef(); // to ensure the resource is still needed
|
||||||
|
if (resource) {
|
||||||
|
QMetaObject::invokeMethod(resource.data(), "setImage",
|
||||||
|
Q_ARG(gpu::TexturePointer, texture),
|
||||||
|
Q_ARG(int, imageWidth), Q_ARG(int, imageHeight));
|
||||||
|
} else {
|
||||||
|
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include <ResourceCache.h>
|
#include <ResourceCache.h>
|
||||||
#include <model/TextureMap.h>
|
#include <model/TextureMap.h>
|
||||||
|
|
||||||
|
#include "KTXCache.h"
|
||||||
|
|
||||||
const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192;
|
const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192;
|
||||||
|
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
@ -63,6 +65,7 @@ public:
|
||||||
typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName);
|
typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName);
|
||||||
using TextureLoaderFunc = std::function<TextureLoader>;
|
using TextureLoaderFunc = std::function<TextureLoader>;
|
||||||
|
|
||||||
|
NetworkTexture(const QUrl& url, Type type, const KTXFilePointer& file);
|
||||||
NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels);
|
NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels);
|
||||||
NetworkTexture(const QUrl& url, const TextureLoaderFunc& textureLoader, const QByteArray& content);
|
NetworkTexture(const QUrl& url, const TextureLoaderFunc& textureLoader, const QByteArray& content);
|
||||||
|
|
||||||
|
@ -80,17 +83,20 @@ signals:
|
||||||
void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
|
void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
virtual bool isCacheable() const override { return _loaded; }
|
virtual bool isCacheable() const override { return _loaded; }
|
||||||
|
|
||||||
virtual void downloadFinished(const QByteArray& data) override;
|
virtual void downloadFinished(const QByteArray& data) override;
|
||||||
|
|
||||||
Q_INVOKABLE void loadContent(const QByteArray& content);
|
Q_INVOKABLE void loadContent(const QByteArray& content);
|
||||||
|
Q_INVOKABLE void loadFile();
|
||||||
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
|
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class ImageReader;
|
||||||
|
|
||||||
Type _type;
|
Type _type;
|
||||||
TextureLoaderFunc _textureLoader { [](const QImage&, const std::string&){ return nullptr; } };
|
TextureLoaderFunc _textureLoader { [](const QImage&, const std::string&){ return nullptr; } };
|
||||||
|
KTXFilePointer _file;
|
||||||
int _originalWidth { 0 };
|
int _originalWidth { 0 };
|
||||||
int _originalHeight { 0 };
|
int _originalHeight { 0 };
|
||||||
int _width { 0 };
|
int _width { 0 };
|
||||||
|
@ -143,9 +149,15 @@ protected:
|
||||||
const void* extra) override;
|
const void* extra) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class ImageReader;
|
||||||
|
friend class DilatableNetworkTexture;
|
||||||
|
|
||||||
TextureCache();
|
TextureCache();
|
||||||
virtual ~TextureCache();
|
virtual ~TextureCache();
|
||||||
friend class DilatableNetworkTexture;
|
|
||||||
|
static const std::string KTX_DIRNAME;
|
||||||
|
static const std::string KTX_EXT;
|
||||||
|
KTXCache _ktxCache;
|
||||||
|
|
||||||
gpu::TexturePointer _permutationNormalTexture;
|
gpu::TexturePointer _permutationNormalTexture;
|
||||||
gpu::TexturePointer _whiteTexture;
|
gpu::TexturePointer _whiteTexture;
|
||||||
|
|
|
@ -85,87 +85,6 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) {
|
||||||
return srcImage;
|
return srcImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true) {
|
|
||||||
if (!srcTexture) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QString ktxCacheFolder;
|
|
||||||
static std::once_flag once;
|
|
||||||
std::call_once(once, [&] {
|
|
||||||
// Prepare cache directory
|
|
||||||
static const QString HIFI_KTX_FOLDER("hifi_ktx");
|
|
||||||
QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
|
||||||
ktxCacheFolder = docsLocation + "/" + HIFI_KTX_FOLDER + "/";
|
|
||||||
QFileInfo info(ktxCacheFolder);
|
|
||||||
if (!info.exists()) {
|
|
||||||
QDir(docsLocation).mkpath(HIFI_KTX_FOLDER);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
std::string cleanedName = QCryptographicHash::hash(QUrl::toPercentEncoding(name.c_str()), QCryptographicHash::Sha1).toHex().toStdString();
|
|
||||||
std::string cacheFilename(ktxCacheFolder.toStdString());
|
|
||||||
cacheFilename += "/";
|
|
||||||
cacheFilename += cleanedName;
|
|
||||||
cacheFilename += ".ktx";
|
|
||||||
|
|
||||||
gpu::Texture* returnedTexture = srcTexture;
|
|
||||||
{
|
|
||||||
if (write && !QFileInfo(cacheFilename.c_str()).exists()) {
|
|
||||||
auto ktxMemory = gpu::Texture::serialize(*srcTexture);
|
|
||||||
if (ktxMemory) {
|
|
||||||
const auto& ktxStorage = ktxMemory->getStorage();
|
|
||||||
auto header = ktxMemory->getHeader();
|
|
||||||
QFile outFile(cacheFilename.c_str());
|
|
||||||
if (!outFile.open(QFile::Truncate | QFile::ReadWrite)) {
|
|
||||||
throw std::runtime_error("Unable to open file");
|
|
||||||
}
|
|
||||||
//auto ktxSize = sizeof(ktx::Header); // ktxStorage->size()
|
|
||||||
auto ktxSize = ktxStorage->size();
|
|
||||||
outFile.resize(ktxSize);
|
|
||||||
auto dest = outFile.map(0, ktxSize);
|
|
||||||
memcpy(dest, ktxStorage->data(), ktxSize);
|
|
||||||
outFile.unmap(dest);
|
|
||||||
outFile.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read && QFileInfo(cacheFilename.c_str()).exists()) {
|
|
||||||
#define DEBUG_KTX_LOADING 0
|
|
||||||
#if DEBUG_KTX_LOADING
|
|
||||||
{
|
|
||||||
FILE* file = fopen(cacheFilename.c_str(), "rb");
|
|
||||||
if (file != nullptr) {
|
|
||||||
// obtain file size:
|
|
||||||
fseek (file , 0 , SEEK_END);
|
|
||||||
auto size = ftell(file);
|
|
||||||
rewind(file);
|
|
||||||
|
|
||||||
auto storage = std::make_shared<storage::MemoryStorage>(size);
|
|
||||||
fread(storage->data(), 1, storage->size(), file);
|
|
||||||
fclose (file);
|
|
||||||
|
|
||||||
//then create a new texture out of the ktx
|
|
||||||
auto theNewTexure = Texture::unserialize(srcTexture->getUsage(), srcTexture->getUsageType(),
|
|
||||||
ktx::KTX::create(std::static_pointer_cast<storage::Storage>(storage)), srcTexture->getSampler());
|
|
||||||
|
|
||||||
if (theNewTexure) {
|
|
||||||
returnedTexture = theNewTexure;
|
|
||||||
delete srcTexture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
ktx::StoragePointer storage = std::make_shared<storage::FileStorage>(cacheFilename.c_str());
|
|
||||||
auto ktxFile = ktx::KTX::create(storage);
|
|
||||||
returnedTexture->setKtxBacking(ktxFile);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return returnedTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextureMap::setTextureSource(TextureSourcePointer& textureSource) {
|
void TextureMap::setTextureSource(TextureSourcePointer& textureSource) {
|
||||||
_textureSource = textureSource;
|
_textureSource = textureSource;
|
||||||
}
|
}
|
||||||
|
@ -355,7 +274,6 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag
|
||||||
::generateMips(theTexture, image, false);
|
::generateMips(theTexture, image, false);
|
||||||
}
|
}
|
||||||
theTexture->setSource(srcImageName);
|
theTexture->setSource(srcImageName);
|
||||||
theTexture = cacheTexture(theTexture->source(), theTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return theTexture;
|
return theTexture;
|
||||||
|
@ -405,7 +323,6 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src
|
||||||
generateMips(theTexture, image, true);
|
generateMips(theTexture, image, true);
|
||||||
|
|
||||||
theTexture->setSource(srcImageName);
|
theTexture->setSource(srcImageName);
|
||||||
theTexture = cacheTexture(theTexture->source(), theTexture, true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return theTexture;
|
return theTexture;
|
||||||
|
@ -496,7 +413,6 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
|
||||||
generateMips(theTexture, image, true);
|
generateMips(theTexture, image, true);
|
||||||
|
|
||||||
theTexture->setSource(srcImageName);
|
theTexture->setSource(srcImageName);
|
||||||
theTexture = cacheTexture(theTexture->source(), theTexture, true, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return theTexture;
|
return theTexture;
|
||||||
|
@ -533,7 +449,6 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma
|
||||||
generateMips(theTexture, image, true);
|
generateMips(theTexture, image, true);
|
||||||
|
|
||||||
theTexture->setSource(srcImageName);
|
theTexture->setSource(srcImageName);
|
||||||
theTexture = cacheTexture(theTexture->source(), theTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return theTexture;
|
return theTexture;
|
||||||
|
@ -574,7 +489,6 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s
|
||||||
generateMips(theTexture, image, true);
|
generateMips(theTexture, image, true);
|
||||||
|
|
||||||
theTexture->setSource(srcImageName);
|
theTexture->setSource(srcImageName);
|
||||||
theTexture = cacheTexture(theTexture->source(), theTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return theTexture;
|
return theTexture;
|
||||||
|
@ -612,7 +526,6 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag
|
||||||
generateMips(theTexture, image, true);
|
generateMips(theTexture, image, true);
|
||||||
|
|
||||||
theTexture->setSource(srcImageName);
|
theTexture->setSource(srcImageName);
|
||||||
theTexture = cacheTexture(theTexture->source(), theTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return theTexture;
|
return theTexture;
|
||||||
|
@ -946,7 +859,6 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm
|
||||||
}
|
}
|
||||||
|
|
||||||
theTexture->setSource(srcImageName);
|
theTexture->setSource(srcImageName);
|
||||||
theTexture = cacheTexture(theTexture->source(), theTexture);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue