Merge pull request #1050 from HifiExperiments/imageAspectFix

Image Entities: fix keepAspectRatio, implement naturalDimensions
This commit is contained in:
Kalila 2021-03-27 15:24:43 -04:00 committed by GitHub
commit 409cc5835c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 169 additions and 128 deletions

View file

@ -168,20 +168,20 @@ void TextureBaker::processTexture() {
gpu::BackendTarget::GLES32 gpu::BackendTarget::GLES32
}}; }};
for (auto target : BACKEND_TARGETS) { for (auto target : BACKEND_TARGETS) {
auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), image::ColorChannel::NONE, auto processedTextureAndSize = image::processImage(buffer, _textureURL.toString().toStdString(), image::ColorChannel::NONE,
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true,
target, _abortProcessing); target, _abortProcessing);
if (!processedTexture) { if (!processedTextureAndSize.first) {
handleError("Could not process texture " + _textureURL.toString()); handleError("Could not process texture " + _textureURL.toString());
return; return;
} }
processedTexture->setSourceHash(hash); processedTextureAndSize.first->setSourceHash(hash);
if (shouldStop()) { if (shouldStop()) {
return; return;
} }
auto memKTX = gpu::Texture::serialize(*processedTexture); auto memKTX = gpu::Texture::serialize(*processedTextureAndSize.first, processedTextureAndSize.second);
if (!memKTX) { if (!memKTX) {
handleError("Could not serialize " + _textureURL.toString() + " to KTX"); handleError("Could not serialize " + _textureURL.toString() + " to KTX");
return; return;
@ -211,19 +211,19 @@ void TextureBaker::processTexture() {
// Uncompressed KTX // Uncompressed KTX
if (_textureType == image::TextureUsage::Type::SKY_TEXTURE || _textureType == image::TextureUsage::Type::AMBIENT_TEXTURE) { if (_textureType == image::TextureUsage::Type::SKY_TEXTURE || _textureType == image::TextureUsage::Type::AMBIENT_TEXTURE) {
buffer->reset(); buffer->reset();
auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), image::ColorChannel::NONE, auto processedTextureAndSize = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), image::ColorChannel::NONE,
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, gpu::BackendTarget::GL45, _abortProcessing); ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, gpu::BackendTarget::GL45, _abortProcessing);
if (!processedTexture) { if (!processedTextureAndSize.first) {
handleError("Could not process texture " + _textureURL.toString()); handleError("Could not process texture " + _textureURL.toString());
return; return;
} }
processedTexture->setSourceHash(hash); processedTextureAndSize.first->setSourceHash(hash);
if (shouldStop()) { if (shouldStop()) {
return; return;
} }
auto memKTX = gpu::Texture::serialize(*processedTexture); auto memKTX = gpu::Texture::serialize(*processedTextureAndSize.first, processedTextureAndSize.second);
if (!memKTX) { if (!memKTX) {
handleError("Could not serialize " + _textureURL.toString() + " to KTX"); handleError("Could not serialize " + _textureURL.toString() + " to KTX");
return; return;

View file

@ -59,10 +59,24 @@ void ImageEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint
_alpha = entity->getAlpha(); _alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties(); _pulseProperties = entity->getPulseProperties();
bool nextTextureLoaded = _texture && (_texture->isLoaded() || _texture->isFailed());
if (!_textureIsLoaded) { if (!_textureIsLoaded) {
emit requestRenderUpdate(); emit requestRenderUpdate();
if (nextTextureLoaded) {
float width = _texture->getOriginalWidth();
float height = _texture->getOriginalHeight();
glm::vec3 naturalDimensions = glm::vec3(1.0f, 1.0f, 0.01f);
if (width < height) {
naturalDimensions.x = width / height;
} else {
naturalDimensions.y = height / width;
} }
_textureIsLoaded = _texture && (_texture->isLoaded() || _texture->isFailed()); // Unlike Models (where the Renderer also doubles as the EntityItem), Images need to
// convey this information back to the game object from the Renderer
entity->setNaturalDimension(naturalDimensions);
}
}
_textureIsLoaded = nextTextureLoaded;
} }
ShapeKey ImageEntityRenderer::getShapeKey() { ShapeKey ImageEntityRenderer::getShapeKey() {
@ -100,18 +114,19 @@ void ImageEntityRenderer::doRender(RenderArgs* args) {
transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
batch->setModelTransform(transform);
batch->setResourceTexture(0, _texture->getGPUTexture()); batch->setResourceTexture(0, _texture->getGPUTexture());
float imageWidth = _texture->getWidth(); float imageWidth = _texture->getWidth();
float imageHeight = _texture->getHeight(); float imageHeight = _texture->getHeight();
float originalWidth = _texture->getOriginalWidth();
float originalHeight = _texture->getOriginalHeight();
QRect fromImage; QRect fromImage;
if (_subImage.width() <= 0) { if (_subImage.width() <= 0) {
fromImage.setX(0); fromImage.setX(0);
fromImage.setWidth(imageWidth); fromImage.setWidth(imageWidth);
} else { } else {
float scaleX = imageWidth / _texture->getOriginalWidth(); float scaleX = imageWidth / originalWidth;
fromImage.setX(scaleX * _subImage.x()); fromImage.setX(scaleX * _subImage.x());
fromImage.setWidth(scaleX * _subImage.width()); fromImage.setWidth(scaleX * _subImage.width());
} }
@ -120,20 +135,30 @@ void ImageEntityRenderer::doRender(RenderArgs* args) {
fromImage.setY(0); fromImage.setY(0);
fromImage.setHeight(imageHeight); fromImage.setHeight(imageHeight);
} else { } else {
float scaleY = imageHeight / _texture->getOriginalHeight(); float scaleY = imageHeight / originalHeight;
fromImage.setY(scaleY * _subImage.y()); fromImage.setY(scaleY * _subImage.y());
fromImage.setHeight(scaleY * _subImage.height()); fromImage.setHeight(scaleY * _subImage.height());
} }
float maxSize = glm::max(fromImage.width(), fromImage.height());
float x = _keepAspectRatio ? fromImage.width() / (2.0f * maxSize) : 0.5f;
float y = _keepAspectRatio ? fromImage.height() / (2.0f * maxSize) : 0.5f;
glm::vec2 texCoordBottomLeft((fromImage.x() + 0.5f) / imageWidth, (fromImage.y() + fromImage.height() - 0.5f) / imageHeight); glm::vec2 texCoordBottomLeft((fromImage.x() + 0.5f) / imageWidth, (fromImage.y() + fromImage.height() - 0.5f) / imageHeight);
glm::vec2 texCoordTopRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth, (fromImage.y() + 0.5f) / imageHeight); glm::vec2 texCoordTopRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth, (fromImage.y() + 0.5f) / imageHeight);
if (_keepAspectRatio) {
glm::vec3 scale = transform.getScale();
float targetAspectRatio = originalWidth / originalHeight;
float currentAspectRatio = scale.x / scale.y;
if (targetAspectRatio < currentAspectRatio) {
scale.x *= targetAspectRatio / currentAspectRatio;
} else {
scale.y /= targetAspectRatio / currentAspectRatio;
}
transform.setScale(scale);
}
batch->setModelTransform(transform);
DependencyManager::get<GeometryCache>()->renderQuad( DependencyManager::get<GeometryCache>()->renderQuad(
*batch, glm::vec2(-x, -y), glm::vec2(x, y), texCoordBottomLeft, texCoordTopRight, *batch, glm::vec2(-0.5f), glm::vec2(0.5f), texCoordBottomLeft, texCoordTopRight,
color, _geometryId color, _geometryId
); );

View file

@ -723,7 +723,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* *
* @property {Vec3} naturalPosition=0,0,0 - The center of the entity's unscaled mesh model if it has one, otherwise * @property {Vec3} naturalPosition=0,0,0 - The center of the entity's unscaled mesh model if it has one, otherwise
* {@link Vec3(0)|Vec3.ZERO}. <em>Read-only.</em> * {@link Vec3(0)|Vec3.ZERO}. <em>Read-only.</em>
* @property {Vec3} naturalDimensions - The dimensions of the entity's unscaled mesh model if it has one, otherwise * @property {Vec3} naturalDimensions - The dimensions of the entity's unscaled mesh model or image if it has one, otherwise
* {@link Vec3(0)|Vec3.ONE}. <em>Read-only.</em> * {@link Vec3(0)|Vec3.ONE}. <em>Read-only.</em>
* *
* @property {Vec3} velocity=0,0,0 - The linear velocity of the entity in m/s with respect to world coordinates. * @property {Vec3} velocity=0,0,0 - The linear velocity of the entity in m/s with respect to world coordinates.

View file

@ -34,6 +34,7 @@ EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& d
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
withReadLock([&] { withReadLock([&] {
_pulseProperties.getProperties(properties); _pulseProperties.getProperties(properties);
properties.setNaturalDimensions(_naturalDimensions);
}); });
COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL);
@ -218,3 +219,9 @@ PulsePropertyGroup ImageEntityItem::getPulseProperties() const {
return _pulseProperties; return _pulseProperties;
}); });
} }
void ImageEntityItem::setNaturalDimension(const glm::vec3& naturalDimensions) const {
withWriteLock([&] {
_naturalDimensions = naturalDimensions;
});
}

View file

@ -63,6 +63,8 @@ public:
PulsePropertyGroup getPulseProperties() const; PulsePropertyGroup getPulseProperties() const;
void setNaturalDimension(const glm::vec3& naturalDimensions) const;
protected: protected:
glm::u8vec3 _color; glm::u8vec3 _color;
float _alpha; float _alpha;
@ -72,6 +74,8 @@ protected:
bool _emissive { false }; bool _emissive { false };
bool _keepAspectRatio { true }; bool _keepAspectRatio { true };
QRect _subImage; QRect _subImage;
mutable glm::vec3 _naturalDimensions;
}; };
#endif // hifi_ImageEntityItem_h #endif // hifi_ImageEntityItem_h

View file

@ -579,11 +579,11 @@ public:
ExternalUpdates getUpdates() const; ExternalUpdates getUpdates() const;
// Serialize a texture into a KTX file // Serialize a texture into a KTX file
static ktx::KTXUniquePointer serialize(const Texture& texture); static ktx::KTXUniquePointer serialize(const Texture& texture, const glm::ivec2& originalSize);
static TexturePointer build(const ktx::KTXDescriptor& descriptor); static std::pair<TexturePointer, glm::ivec2> build(const ktx::KTXDescriptor& descriptor);
static TexturePointer unserialize(const std::string& ktxFile); static std::pair<TexturePointer, glm::ivec2> unserialize(const std::string& ktxFile);
static TexturePointer unserialize(const cache::FilePointer& cacheEntry, const std::string& source = std::string()); static std::pair<TexturePointer, glm::ivec2> unserialize(const cache::FilePointer& cacheEntry, const std::string& source = std::string());
static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header); static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat); static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat);

View file

@ -13,6 +13,7 @@
#include "Texture.h" #include "Texture.h"
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <glm/gtc/type_ptr.hpp>
#include <ktx/KTX.h> #include <ktx/KTX.h>
@ -30,15 +31,16 @@ struct GPUKTXPayload {
using Version = uint8; using Version = uint8;
static const std::string KEY; static const std::string KEY;
static const Version CURRENT_VERSION { 1 }; static const Version CURRENT_VERSION { 2 };
static const size_t PADDING { 2 }; static const size_t PADDING { 2 };
static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint32) + sizeof(TextureUsageType) + PADDING }; static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint32) + sizeof(TextureUsageType) + sizeof(glm::ivec2) + PADDING };
static_assert(GPUKTXPayload::SIZE == 36, "Packing size may differ between platforms"); static_assert(GPUKTXPayload::SIZE == 44, "Packing size may differ between platforms");
static_assert(GPUKTXPayload::SIZE % 4 == 0, "GPUKTXPayload is not 4 bytes aligned"); static_assert(GPUKTXPayload::SIZE % 4 == 0, "GPUKTXPayload is not 4 bytes aligned");
Sampler::Desc _samplerDesc; Sampler::Desc _samplerDesc;
Texture::Usage _usage; Texture::Usage _usage;
TextureUsageType _usageType; TextureUsageType _usageType;
glm::ivec2 _originalSize;
Byte* serialize(Byte* data) const { Byte* serialize(Byte* data) const {
*(Version*)data = CURRENT_VERSION; *(Version*)data = CURRENT_VERSION;
@ -56,6 +58,9 @@ struct GPUKTXPayload {
memcpy(data, &_usageType, sizeof(TextureUsageType)); memcpy(data, &_usageType, sizeof(TextureUsageType));
data += sizeof(TextureUsageType); data += sizeof(TextureUsageType);
memcpy(data, glm::value_ptr(_originalSize), sizeof(glm::ivec2));
data += sizeof(glm::ivec2);
return data + PADDING; return data + PADDING;
} }
@ -66,14 +71,8 @@ struct GPUKTXPayload {
Version version = *(const Version*)data; Version version = *(const Version*)data;
if (version != CURRENT_VERSION) { if (version != CURRENT_VERSION) {
glm::vec4 borderColor(1.0f);
if (memcmp(&borderColor, data, sizeof(glm::vec4)) == 0) {
memcpy(this, data, sizeof(GPUKTXPayload));
return true;
} else {
return false; return false;
} }
}
data += sizeof(Version); data += sizeof(Version);
memcpy(&_samplerDesc, data, sizeof(Sampler::Desc)); memcpy(&_samplerDesc, data, sizeof(Sampler::Desc));
@ -87,6 +86,10 @@ struct GPUKTXPayload {
data += sizeof(uint32); data += sizeof(uint32);
memcpy(&_usageType, data, sizeof(TextureUsageType)); memcpy(&_usageType, data, sizeof(TextureUsageType));
data += sizeof(TextureUsageType);
memcpy(&_originalSize, data, sizeof(glm::ivec2));
data += sizeof(glm::ivec2);
return true; return true;
} }
@ -382,7 +385,7 @@ void Texture::setKtxBacking(const cache::FilePointer& cacheEntry) {
} }
ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { ktx::KTXUniquePointer Texture::serialize(const Texture& texture, const glm::ivec2& originalSize) {
ktx::Header header; ktx::Header header;
// From texture format to ktx format description // From texture format to ktx format description
@ -459,6 +462,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
gpuKeyval._samplerDesc = texture.getSampler().getDesc(); gpuKeyval._samplerDesc = texture.getSampler().getDesc();
gpuKeyval._usage = texture.getUsage(); gpuKeyval._usage = texture.getUsage();
gpuKeyval._usageType = texture.getUsageType(); gpuKeyval._usageType = texture.getUsageType();
gpuKeyval._originalSize = originalSize;
Byte keyvalPayload[GPUKTXPayload::SIZE]; Byte keyvalPayload[GPUKTXPayload::SIZE];
gpuKeyval.serialize(keyvalPayload); gpuKeyval.serialize(keyvalPayload);
@ -514,19 +518,19 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
return ktxBuffer; return ktxBuffer;
} }
TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { std::pair<TexturePointer, glm::ivec2> Texture::build(const ktx::KTXDescriptor& descriptor) {
Format mipFormat = Format::COLOR_BGRA_32; Format mipFormat = Format::COLOR_BGRA_32;
Format texelFormat = Format::COLOR_SRGBA_32; Format texelFormat = Format::COLOR_SRGBA_32;
const auto& header = descriptor.header; const auto& header = descriptor.header;
if (!Texture::evalTextureFormat(header, mipFormat, texelFormat)) { if (!Texture::evalTextureFormat(header, mipFormat, texelFormat)) {
return nullptr; return { nullptr, { 0, 0 } };
} }
// Find Texture Type based on dimensions // Find Texture Type based on dimensions
Type type = TEX_1D; Type type = TEX_1D;
if (header.pixelWidth == 0) { if (header.pixelWidth == 0) {
return nullptr; return { nullptr, { 0, 0 } };
} else if (header.pixelHeight == 0) { } else if (header.pixelHeight == 0) {
type = TEX_1D; type = TEX_1D;
} else if (header.pixelDepth == 0) { } else if (header.pixelDepth == 0) {
@ -569,39 +573,39 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) {
texture->overrideIrradiance(std::make_shared<SphericalHarmonics>(irradianceKtxKeyValue._irradianceSH)); texture->overrideIrradiance(std::make_shared<SphericalHarmonics>(irradianceKtxKeyValue._irradianceSH));
} }
return texture; return { texture, gpuktxKeyValue._originalSize };
} }
TexturePointer Texture::unserialize(const cache::FilePointer& cacheEntry, const std::string& source) { std::pair<TexturePointer, glm::ivec2> Texture::unserialize(const cache::FilePointer& cacheEntry, const std::string& source) {
std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(std::make_shared<storage::FileStorage>(cacheEntry->getFilepath().c_str())); std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(std::make_shared<storage::FileStorage>(cacheEntry->getFilepath().c_str()));
if (!ktxPointer) { if (!ktxPointer) {
return nullptr; return { nullptr, { 0, 0 } };
} }
auto texture = build(ktxPointer->toDescriptor()); auto textureAndSize = build(ktxPointer->toDescriptor());
if (texture) { if (textureAndSize.first) {
texture->setKtxBacking(cacheEntry); textureAndSize.first->setKtxBacking(cacheEntry);
if (texture->source().empty()) { if (textureAndSize.first->source().empty()) {
texture->setSource(source); textureAndSize.first->setSource(source);
} }
} }
return texture; return { textureAndSize.first, textureAndSize.second };
} }
TexturePointer Texture::unserialize(const std::string& ktxfile) { std::pair<TexturePointer, glm::ivec2> Texture::unserialize(const std::string& ktxfile) {
std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(std::make_shared<storage::FileStorage>(ktxfile.c_str())); std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(std::make_shared<storage::FileStorage>(ktxfile.c_str()));
if (!ktxPointer) { if (!ktxPointer) {
return nullptr; return { nullptr, { 0, 0 } };
} }
auto texture = build(ktxPointer->toDescriptor()); auto textureAndSize = build(ktxPointer->toDescriptor());
if (texture) { if (textureAndSize.first) {
texture->setKtxBacking(ktxfile); textureAndSize.first->setKtxBacking(ktxfile);
texture->setSource(ktxfile); textureAndSize.first->setSource(ktxfile);
} }
return texture; return { textureAndSize.first, textureAndSize.second };
} }
bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header) { bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header) {

View file

@ -338,7 +338,7 @@ void mapToRedChannel(Image& image, ColorChannel sourceChannel) {
} }
} }
gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& filename, ColorChannel sourceChannel, std::pair<gpu::TexturePointer, glm::ivec2> processImage(std::shared_ptr<QIODevice> content, const std::string& filename, ColorChannel sourceChannel,
int maxNumPixels, TextureUsage::Type textureType, int maxNumPixels, TextureUsage::Type textureType,
bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) { bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
@ -354,7 +354,7 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
if (imageWidth == 0 || imageHeight == 0 || image.getFormat() == Image::Format_Invalid) { if (imageWidth == 0 || imageHeight == 0 || image.getFormat() == Image::Format_Invalid) {
QString reason(image.getFormat() == Image::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)"); QString reason(image.getFormat() == Image::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)");
qCWarning(imagelogging) << "Failed to load:" << qPrintable(reason); qCWarning(imagelogging) << "Failed to load:" << qPrintable(reason);
return nullptr; return { nullptr, { imageWidth, imageHeight } };
} }
// Validate the image is less than _maxNumPixels, and downscale if necessary // Validate the image is less than _maxNumPixels, and downscale if necessary
@ -378,7 +378,7 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
auto loader = TextureUsage::getTextureLoaderForType(textureType); auto loader = TextureUsage::getTextureLoaderForType(textureType);
auto texture = loader(std::move(image), filename, compress, target, abortProcessing); auto texture = loader(std::move(image), filename, compress, target, abortProcessing);
return texture; return { texture, { imageWidth, imageHeight } };
} }
Image processSourceImage(Image&& srcImage, bool cubemap, BackendTarget target) { Image processSourceImage(Image&& srcImage, bool cubemap, BackendTarget target) {

View file

@ -121,7 +121,7 @@ gpu::TexturePointer processCubeTextureColorFromImage(Image&& srcImage, const std
const QStringList getSupportedFormats(); const QStringList getSupportedFormats();
gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url, ColorChannel sourceChannel, std::pair<gpu::TexturePointer, glm::ivec2> processImage(std::shared_ptr<QIODevice> content, const std::string& url, ColorChannel sourceChannel,
int maxNumPixels, TextureUsage::Type textureType, int maxNumPixels, TextureUsage::Type textureType,
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false); bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);

View file

@ -19,7 +19,7 @@ using FilePointer = cache::FilePointer;
// Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible, // Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible,
// this value should be incremented. This will force the KTX cache to be wiped // this value should be incremented. This will force the KTX cache to be wiped
const int KTXCache::CURRENT_VERSION = 0x01; const int KTXCache::CURRENT_VERSION = 0x02;
const int KTXCache::INVALID_VERSION = 0x00; const int KTXCache::INVALID_VERSION = 0x00;
const char* KTXCache::SETTING_VERSION_NAME = "hifi.ktx.cache_version"; const char* KTXCache::SETTING_VERSION_NAME = "hifi.ktx.cache_version";

View file

@ -266,23 +266,24 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUs
return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash<TextureExtra>()(extra)).staticCast<NetworkTexture>(); return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash<TextureExtra>()(extra)).staticCast<NetworkTexture>();
} }
gpu::TexturePointer TextureCache::getTextureByHash(const std::string& hash) { std::pair<gpu::TexturePointer, glm::ivec2> TextureCache::getTextureByHash(const std::string& hash) {
std::weak_ptr<gpu::Texture> weakPointer; std::pair<gpu::TextureWeakPointer, glm::ivec2> weakPointer;
{ {
std::unique_lock<std::mutex> lock(_texturesByHashesMutex); std::unique_lock<std::mutex> lock(_texturesByHashesMutex);
weakPointer = _texturesByHashes[hash]; weakPointer = _texturesByHashes[hash];
} }
return weakPointer.lock(); return { weakPointer.first.lock(), weakPointer.second };
} }
gpu::TexturePointer TextureCache::cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture) { std::pair<gpu::TexturePointer, glm::ivec2> TextureCache::cacheTextureByHash(const std::string& hash, const std::pair<gpu::TexturePointer, glm::ivec2>& textureAndSize) {
gpu::TexturePointer result; std::pair<gpu::TexturePointer, glm::ivec2> result;
{ {
std::unique_lock<std::mutex> lock(_texturesByHashesMutex); std::unique_lock<std::mutex> lock(_texturesByHashesMutex);
result = _texturesByHashes[hash].lock(); auto& value = _texturesByHashes[hash];
if (!result) { result = { value.first.lock(), value.second };
_texturesByHashes[hash] = texture; if (!result.first) {
result = texture; _texturesByHashes[hash] = textureAndSize;
result = textureAndSize;
} }
} }
return result; return result;
@ -616,7 +617,7 @@ void NetworkTexture::makeLocalRequest() {
ktxDescriptor = std::make_shared<ktx::KTXDescriptor>(ktxFile->toDescriptor()); ktxDescriptor = std::make_shared<ktx::KTXDescriptor>(ktxFile->toDescriptor());
} }
gpu::TexturePointer texture; std::pair<gpu::TexturePointer, glm::ivec2> textureAndSize;
if (ktxDescriptor) { if (ktxDescriptor) {
std::string hash; std::string hash;
// Create bare ktx in memory // Create bare ktx in memory
@ -634,18 +635,18 @@ void NetworkTexture::makeLocalRequest() {
} }
auto textureCache = DependencyManager::get<TextureCache>(); auto textureCache = DependencyManager::get<TextureCache>();
texture = textureCache->getTextureByHash(hash); textureAndSize = textureCache->getTextureByHash(hash);
if (!texture) { if (!textureAndSize.first) {
texture = gpu::Texture::build(*ktxDescriptor); textureAndSize = gpu::Texture::build(*ktxDescriptor);
if (texture) { if (textureAndSize.first) {
texture->setKtxBacking(path.toStdString()); textureAndSize.first->setKtxBacking(path.toStdString());
texture->setSource(path.toStdString()); textureAndSize.first->setSource(path.toStdString());
texture = textureCache->cacheTextureByHash(hash, texture); textureAndSize = textureCache->cacheTextureByHash(hash, textureAndSize);
} }
} }
} }
if (!texture) { if (!textureAndSize.first) {
qCDebug(networking).noquote() << "Failed load local KTX from" << path; qCDebug(networking).noquote() << "Failed load local KTX from" << path;
QMetaObject::invokeMethod(this, "setImage", QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(gpu::TexturePointer, nullptr),
@ -655,11 +656,11 @@ void NetworkTexture::makeLocalRequest() {
} }
_ktxResourceState = PENDING_MIP_REQUEST; _ktxResourceState = PENDING_MIP_REQUEST;
_lowestKnownPopulatedMip = texture->minAvailableMipLevel(); _lowestKnownPopulatedMip = textureAndSize.first->minAvailableMipLevel();
QMetaObject::invokeMethod(this, "setImage", QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, texture), Q_ARG(gpu::TexturePointer, textureAndSize.first),
Q_ARG(int, texture->getWidth()), Q_ARG(int, textureAndSize.second.x),
Q_ARG(int, texture->getHeight())); Q_ARG(int, textureAndSize.second.y));
} }
@ -968,22 +969,22 @@ void NetworkTexture::handleFinishedInitialLoad() {
auto textureCache = DependencyManager::get<TextureCache>(); auto textureCache = DependencyManager::get<TextureCache>();
gpu::TexturePointer texture = textureCache->getTextureByHash(hash); std::pair<gpu::TexturePointer, glm::ivec2> textureAndSize = textureCache->getTextureByHash(hash);
if (!texture) { if (!textureAndSize.first) {
auto ktxFile = textureCache->_ktxCache->getFile(hash); auto ktxFile = textureCache->_ktxCache->getFile(hash);
if (ktxFile) { if (ktxFile) {
texture = gpu::Texture::unserialize(ktxFile); textureAndSize = gpu::Texture::unserialize(ktxFile);
if (texture) { if (textureAndSize.first) {
texture = textureCache->cacheTextureByHash(hash, texture); textureAndSize = textureCache->cacheTextureByHash(hash, textureAndSize);
if (texture->source().empty()) { if (textureAndSize.first->source().empty()) {
texture->setSource(url.toString().toStdString()); textureAndSize.first->setSource(url.toString().toStdString());
} }
} }
} }
} }
if (!texture) { if (!textureAndSize.first) {
auto memKtx = ktx::KTX::createBare(*header, keyValues); auto memKtx = ktx::KTX::createBare(*header, keyValues);
if (!memKtx) { if (!memKtx) {
qWarning() << " Ktx could not be created, bailing"; qWarning() << " Ktx could not be created, bailing";
@ -1010,9 +1011,9 @@ void NetworkTexture::handleFinishedInitialLoad() {
auto newKtxDescriptor = memKtx->toDescriptor(); auto newKtxDescriptor = memKtx->toDescriptor();
texture = gpu::Texture::build(newKtxDescriptor); textureAndSize = gpu::Texture::build(newKtxDescriptor);
texture->setKtxBacking(file); textureAndSize.first->setKtxBacking(file);
texture->setSource(filename); textureAndSize.first->setSource(filename);
auto& images = originalKtxDescriptor->images; auto& images = originalKtxDescriptor->images;
size_t imageSizeRemaining = ktxHighMipData.size(); size_t imageSizeRemaining = ktxHighMipData.size();
@ -1025,7 +1026,7 @@ void NetworkTexture::handleFinishedInitialLoad() {
break; break;
} }
ktxData -= image._imageSize; ktxData -= image._imageSize;
texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData); textureAndSize.first->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
ktxData -= ktx::IMAGE_SIZE_WIDTH; ktxData -= ktx::IMAGE_SIZE_WIDTH;
imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH); imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH);
} }
@ -1033,13 +1034,13 @@ void NetworkTexture::handleFinishedInitialLoad() {
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different // We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will // images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
// be the winner // be the winner
texture = textureCache->cacheTextureByHash(filename, texture); textureAndSize = textureCache->cacheTextureByHash(filename, textureAndSize);
} }
QMetaObject::invokeMethod(resource.data(), "setImage", QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture), Q_ARG(gpu::TexturePointer, textureAndSize.first),
Q_ARG(int, texture->getWidth()), Q_ARG(int, textureAndSize.second.x),
Q_ARG(int, texture->getHeight())); Q_ARG(int, textureAndSize.second.y));
QMetaObject::invokeMethod(resource.data(), "startRequestForNextMipLevel"); QMetaObject::invokeMethod(resource.data(), "startRequestForNextMipLevel");
}); });
@ -1229,15 +1230,15 @@ void ImageReader::read() {
auto textureCache = DependencyManager::get<TextureCache>(); auto textureCache = DependencyManager::get<TextureCache>();
if (textureCache) { if (textureCache) {
// If we already have a live texture with the same hash, use it // If we already have a live texture with the same hash, use it
auto texture = textureCache->getTextureByHash(hash); auto textureAndSize = textureCache->getTextureByHash(hash);
// If there is no live texture, check if there's an existing KTX file // If there is no live texture, check if there's an existing KTX file
if (!texture) { if (!textureAndSize.first) {
auto ktxFile = textureCache->_ktxCache->getFile(hash); auto ktxFile = textureCache->_ktxCache->getFile(hash);
if (ktxFile) { if (ktxFile) {
texture = gpu::Texture::unserialize(ktxFile, _url.toString().toStdString()); textureAndSize = gpu::Texture::unserialize(ktxFile, _url.toString().toStdString());
if (texture) { if (textureAndSize.first) {
texture = textureCache->cacheTextureByHash(hash, texture); textureAndSize = textureCache->cacheTextureByHash(hash, textureAndSize);
} else { } else {
qCWarning(materialnetworking) << "Invalid cached KTX " << _url << " under hash " << hash.c_str() << ", recreating..."; qCWarning(materialnetworking) << "Invalid cached KTX " << _url << " under hash " << hash.c_str() << ", recreating...";
} }
@ -1246,17 +1247,17 @@ void ImageReader::read() {
// If we found the texture either because it's in use or via KTX deserialization, // If we found the texture either because it's in use or via KTX deserialization,
// set the image and return immediately. // set the image and return immediately.
if (texture) { if (textureAndSize.first) {
QMetaObject::invokeMethod(resource.data(), "setImage", QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture), Q_ARG(gpu::TexturePointer, textureAndSize.first),
Q_ARG(int, texture->getWidth()), Q_ARG(int, textureAndSize.second.x),
Q_ARG(int, texture->getHeight())); Q_ARG(int, textureAndSize.second.y));
return; return;
} }
} }
// Proccess new texture // Proccess new texture
gpu::TexturePointer texture; std::pair<gpu::TexturePointer, ivec2> textureAndSize;
{ {
PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0); PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0);
@ -1269,23 +1270,23 @@ void ImageReader::read() {
constexpr bool shouldCompress = false; constexpr bool shouldCompress = false;
#endif #endif
auto target = getBackendTarget(); auto target = getBackendTarget();
texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _sourceChannel, _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); textureAndSize = image::processImage(std::move(buffer), _url.toString().toStdString(), _sourceChannel, _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target);
if (!texture) { if (!textureAndSize.first) {
QMetaObject::invokeMethod(resource.data(), "setImage", QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture), Q_ARG(gpu::TexturePointer, textureAndSize.first),
Q_ARG(int, 0), Q_ARG(int, 0),
Q_ARG(int, 0)); Q_ARG(int, 0));
return; return;
} }
texture->setSourceHash(hash); textureAndSize.first->setSourceHash(hash);
texture->setFallbackTexture(networkTexture->getFallbackTexture()); textureAndSize.first->setFallbackTexture(networkTexture->getFallbackTexture());
} }
// Save the image into a KTXFile // Save the image into a KTXFile
if (texture && textureCache) { if (textureAndSize.first && textureCache) {
auto memKtx = gpu::Texture::serialize(*texture); auto memKtx = gpu::Texture::serialize(*textureAndSize.first, textureAndSize.second);
// Move the texture into a memory mapped file // Move the texture into a memory mapped file
if (memKtx) { if (memKtx) {
@ -1294,20 +1295,20 @@ void ImageReader::read() {
auto& ktxCache = textureCache->_ktxCache; auto& ktxCache = textureCache->_ktxCache;
auto file = ktxCache->writeFile(data, KTXCache::Metadata(hash, length)); auto file = ktxCache->writeFile(data, KTXCache::Metadata(hash, length));
if (file) { if (file) {
texture->setKtxBacking(file); textureAndSize.first->setKtxBacking(file);
} }
} }
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different // We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will // images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
// be the winner // be the winner
texture = textureCache->cacheTextureByHash(hash, texture); textureAndSize = textureCache->cacheTextureByHash(hash, textureAndSize);
} }
QMetaObject::invokeMethod(resource.data(), "setImage", QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture), Q_ARG(gpu::TexturePointer, textureAndSize.first),
Q_ARG(int, texture->getWidth()), Q_ARG(int, textureAndSize.second.x),
Q_ARG(int, texture->getHeight())); Q_ARG(int, textureAndSize.second.y));
} }
NetworkTexturePointer TextureCache::getResourceTexture(const QUrl& resourceTextureUrl) { NetworkTexturePointer TextureCache::getResourceTexture(const QUrl& resourceTextureUrl) {

View file

@ -183,8 +183,8 @@ public:
const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS,
image::ColorChannel sourceChannel = image::ColorChannel::NONE); image::ColorChannel sourceChannel = image::ColorChannel::NONE);
gpu::TexturePointer getTextureByHash(const std::string& hash); std::pair<gpu::TexturePointer, glm::ivec2> getTextureByHash(const std::string& hash);
gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture); std::pair<gpu::TexturePointer, glm::ivec2> cacheTextureByHash(const std::string& hash, const std::pair<gpu::TexturePointer, glm::ivec2>& textureAndSize);
NetworkTexturePointer getResourceTexture(const QUrl& resourceTextureUrl); NetworkTexturePointer getResourceTexture(const QUrl& resourceTextureUrl);
const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height); const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height);
@ -226,7 +226,7 @@ private:
std::shared_ptr<cache::FileCache> _ktxCache { std::make_shared<KTXCache>(KTX_DIRNAME, KTX_EXT) }; std::shared_ptr<cache::FileCache> _ktxCache { std::make_shared<KTXCache>(KTX_DIRNAME, KTX_EXT) };
// Map from image hashes to texture weak pointers // Map from image hashes to texture weak pointers
std::unordered_map<std::string, std::weak_ptr<gpu::Texture>> _texturesByHashes; std::unordered_map<std::string, std::pair<std::weak_ptr<gpu::Texture>, glm::ivec2>> _texturesByHashes;
std::mutex _texturesByHashesMutex; std::mutex _texturesByHashesMutex;
gpu::TexturePointer _permutationNormalTexture; gpu::TexturePointer _permutationNormalTexture;