mirror of
https://github.com/overte-org/overte.git
synced 2025-07-23 11:44:09 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into bullet-constraints-2-redux
This commit is contained in:
commit
68b59e4601
63 changed files with 1451 additions and 446 deletions
|
@ -544,7 +544,7 @@ void Agent::setIsAvatar(bool isAvatar) {
|
||||||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
||||||
|
|
||||||
// start the timers
|
// start the timers
|
||||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets
|
||||||
|
|
||||||
// tell the avatarAudioTimer to start ticking
|
// tell the avatarAudioTimer to start ticking
|
||||||
emit startAvatarAudioTimer();
|
emit startAvatarAudioTimer();
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
#include "SendAssetTask.h"
|
#include "SendAssetTask.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
@ -21,6 +23,7 @@
|
||||||
#include <udt/Packet.h>
|
#include <udt/Packet.h>
|
||||||
|
|
||||||
#include "AssetUtils.h"
|
#include "AssetUtils.h"
|
||||||
|
#include "ByteRange.h"
|
||||||
#include "ClientServerUtils.h"
|
#include "ClientServerUtils.h"
|
||||||
|
|
||||||
SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
|
SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
|
||||||
|
@ -34,7 +37,7 @@ SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const Shar
|
||||||
|
|
||||||
void SendAssetTask::run() {
|
void SendAssetTask::run() {
|
||||||
MessageID messageID;
|
MessageID messageID;
|
||||||
DataOffset start, end;
|
ByteRange byteRange;
|
||||||
|
|
||||||
_message->readPrimitive(&messageID);
|
_message->readPrimitive(&messageID);
|
||||||
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
||||||
|
@ -42,12 +45,13 @@ void SendAssetTask::run() {
|
||||||
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
||||||
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
||||||
// starting at index 1.
|
// starting at index 1.
|
||||||
_message->readPrimitive(&start);
|
_message->readPrimitive(&byteRange.fromInclusive);
|
||||||
_message->readPrimitive(&end);
|
_message->readPrimitive(&byteRange.toExclusive);
|
||||||
|
|
||||||
QString hexHash = assetHash.toHex();
|
QString hexHash = assetHash.toHex();
|
||||||
|
|
||||||
qDebug() << "Received a request for the file (" << messageID << "): " << hexHash << " from " << start << " to " << end;
|
qDebug() << "Received a request for the file (" << messageID << "): " << hexHash << " from "
|
||||||
|
<< byteRange.fromInclusive << " to " << byteRange.toExclusive;
|
||||||
|
|
||||||
qDebug() << "Starting task to send asset: " << hexHash << " for messageID " << messageID;
|
qDebug() << "Starting task to send asset: " << hexHash << " for messageID " << messageID;
|
||||||
auto replyPacketList = NLPacketList::create(PacketType::AssetGetReply, QByteArray(), true, true);
|
auto replyPacketList = NLPacketList::create(PacketType::AssetGetReply, QByteArray(), true, true);
|
||||||
|
@ -56,7 +60,7 @@ void SendAssetTask::run() {
|
||||||
|
|
||||||
replyPacketList->writePrimitive(messageID);
|
replyPacketList->writePrimitive(messageID);
|
||||||
|
|
||||||
if (end <= start) {
|
if (!byteRange.isValid()) {
|
||||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||||
} else {
|
} else {
|
||||||
QString filePath = _resourcesDir.filePath(QString(hexHash));
|
QString filePath = _resourcesDir.filePath(QString(hexHash));
|
||||||
|
@ -64,15 +68,40 @@ void SendAssetTask::run() {
|
||||||
QFile file { filePath };
|
QFile file { filePath };
|
||||||
|
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
if (file.size() < end) {
|
|
||||||
|
// first fixup the range based on the now known file size
|
||||||
|
byteRange.fixupRange(file.size());
|
||||||
|
|
||||||
|
// check if we're being asked to read data that we just don't have
|
||||||
|
// because of the file size
|
||||||
|
if (file.size() < byteRange.fromInclusive || file.size() < byteRange.toExclusive) {
|
||||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||||
qCDebug(networking) << "Bad byte range: " << hexHash << " " << start << ":" << end;
|
qCDebug(networking) << "Bad byte range: " << hexHash << " "
|
||||||
|
<< byteRange.fromInclusive << ":" << byteRange.toExclusive;
|
||||||
} else {
|
} else {
|
||||||
auto size = end - start;
|
// we have a valid byte range, handle it and send the asset
|
||||||
file.seek(start);
|
auto size = byteRange.size();
|
||||||
|
|
||||||
|
if (byteRange.fromInclusive >= 0) {
|
||||||
|
|
||||||
|
// this range is positive, meaning we just need to seek into the file and then read from there
|
||||||
|
file.seek(byteRange.fromInclusive);
|
||||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||||
replyPacketList->writePrimitive(size);
|
replyPacketList->writePrimitive(size);
|
||||||
replyPacketList->write(file.read(size));
|
replyPacketList->write(file.read(size));
|
||||||
|
} else {
|
||||||
|
// this range is negative, at least the first part of the read will be back into the end of the file
|
||||||
|
|
||||||
|
// seek to the part of the file where the negative range begins
|
||||||
|
file.seek(file.size() + byteRange.fromInclusive);
|
||||||
|
|
||||||
|
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||||
|
replyPacketList->writePrimitive(size);
|
||||||
|
|
||||||
|
// first write everything from the negative range to the end of the file
|
||||||
|
replyPacketList->write(file.read(size));
|
||||||
|
}
|
||||||
|
|
||||||
qCDebug(networking) << "Sending asset: " << hexHash;
|
qCDebug(networking) << "Sending asset: " << hexHash;
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
|
|
|
@ -71,15 +71,10 @@ AvatarMixer::~AvatarMixer() {
|
||||||
|
|
||||||
void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) {
|
void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) {
|
||||||
QByteArray individualData = nodeData->getAvatar().identityByteArray();
|
QByteArray individualData = nodeData->getAvatar().identityByteArray();
|
||||||
|
|
||||||
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
|
|
||||||
|
|
||||||
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122());
|
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122());
|
||||||
|
auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
||||||
identityPacket->write(individualData);
|
identityPackets->write(individualData);
|
||||||
|
DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPackets), *destinationNode);
|
||||||
DependencyManager::get<NodeList>()->sendPacket(std::move(identityPacket), *destinationNode);
|
|
||||||
|
|
||||||
++_sumIdentityPackets;
|
++_sumIdentityPackets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -263,16 +263,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
||||||
// make sure we haven't already sent this data from this sender to this receiver
|
// make sure we haven't already sent this data from this sender to this receiver
|
||||||
// or that somehow we haven't sent
|
// or that somehow we haven't sent
|
||||||
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
||||||
// don't ignore this avatar if we haven't sent any update for a long while
|
|
||||||
// in an effort to prevent other interfaces from deleting a stale avatar instance
|
|
||||||
uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(avatarNode->getUUID());
|
|
||||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
|
||||||
const uint64_t AVATAR_UPDATE_STALE = AVATAR_UPDATE_TIMEOUT - USECS_PER_SECOND;
|
|
||||||
if (lastBroadcastTime > otherNodeData->getIdentityChangeTimestamp() &&
|
|
||||||
lastBroadcastTime + AVATAR_UPDATE_STALE > startIgnoreCalculation) {
|
|
||||||
++numAvatarsHeldBack;
|
++numAvatarsHeldBack;
|
||||||
shouldIgnore = true;
|
shouldIgnore = true;
|
||||||
}
|
|
||||||
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
||||||
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
||||||
++numAvatarsWithSkippedFrames;
|
++numAvatarsWithSkippedFrames;
|
||||||
|
|
|
@ -5216,11 +5216,7 @@ void Application::resettingDomain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::nodeAdded(SharedNodePointer node) const {
|
void Application::nodeAdded(SharedNodePointer node) const {
|
||||||
if (node->getType() == NodeType::AvatarMixer) {
|
// nothing to do here
|
||||||
// new avatar mixer, send off our identity packet right away
|
|
||||||
getMyAvatar()->sendIdentityPacket();
|
|
||||||
getMyAvatar()->resetLastSent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::nodeActivated(SharedNodePointer node) {
|
void Application::nodeActivated(SharedNodePointer node) {
|
||||||
|
@ -5256,6 +5252,13 @@ void Application::nodeActivated(SharedNodePointer node) {
|
||||||
if (node->getType() == NodeType::AudioMixer) {
|
if (node->getType() == NodeType::AudioMixer) {
|
||||||
DependencyManager::get<AudioClient>()->negotiateAudioFormat();
|
DependencyManager::get<AudioClient>()->negotiateAudioFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node->getType() == NodeType::AvatarMixer) {
|
||||||
|
// new avatar mixer, send off our identity packet right away
|
||||||
|
getMyAvatar()->markIdentityDataChanged();
|
||||||
|
getMyAvatar()->sendIdentityPacket();
|
||||||
|
getMyAvatar()->resetLastSent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::nodeKilled(SharedNodePointer node) {
|
void Application::nodeKilled(SharedNodePointer node) {
|
||||||
|
|
|
@ -115,8 +115,6 @@ Avatar::Avatar(QThread* thread, RigPointer rig) :
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar::~Avatar() {
|
Avatar::~Avatar() {
|
||||||
assert(isDead()); // mark dead before calling the dtor
|
|
||||||
|
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
if (entityTree) {
|
if (entityTree) {
|
||||||
|
@ -510,14 +508,15 @@ static TextRenderer3D* textRenderer(TextRendererType type) {
|
||||||
void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
|
void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||||
auto avatarPayload = new render::Payload<AvatarData>(self);
|
auto avatarPayload = new render::Payload<AvatarData>(self);
|
||||||
auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload);
|
auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload);
|
||||||
|
if (_skeletonModel->addToScene(scene, transaction)) {
|
||||||
_renderItemID = scene->allocateID();
|
_renderItemID = scene->allocateID();
|
||||||
transaction.resetItem(_renderItemID, avatarPayloadPointer);
|
transaction.resetItem(_renderItemID, avatarPayloadPointer);
|
||||||
_skeletonModel->addToScene(scene, transaction);
|
|
||||||
|
|
||||||
for (auto& attachmentModel : _attachmentModels) {
|
for (auto& attachmentModel : _attachmentModels) {
|
||||||
attachmentModel->addToScene(scene, transaction);
|
attachmentModel->addToScene(scene, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
|
void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||||
transaction.removeItem(_renderItemID);
|
transaction.removeItem(_renderItemID);
|
||||||
|
@ -1123,11 +1122,20 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
|
|
||||||
void Avatar::setModelURLFinished(bool success) {
|
void Avatar::setModelURLFinished(bool success) {
|
||||||
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
|
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
|
||||||
qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL;
|
const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts
|
||||||
|
if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 ||
|
||||||
|
_skeletonModel->getResourceDownloadAttempts() > MAX_SKELETON_DOWNLOAD_ATTEMPTS) {
|
||||||
|
qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL
|
||||||
|
<< "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts.";
|
||||||
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
|
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
|
||||||
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
|
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
|
||||||
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
|
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
|
||||||
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
|
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
|
||||||
|
} else {
|
||||||
|
qCWarning(interfaceapp) << "Avatar model: " << _skeletonModelURL
|
||||||
|
<< "failed to load... attempts:" << _skeletonModel->getResourceDownloadAttempts()
|
||||||
|
<< "out of:" << MAX_SKELETON_DOWNLOAD_ATTEMPTS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -213,10 +213,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
avatar->animateScaleChanges(deltaTime);
|
avatar->animateScaleChanges(deltaTime);
|
||||||
if (avatar->shouldDie()) {
|
|
||||||
avatar->die();
|
|
||||||
removeAvatar(avatar->getID());
|
|
||||||
}
|
|
||||||
|
|
||||||
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
|
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
|
||||||
uint64_t now = usecTimestampNow();
|
uint64_t now = usecTimestampNow();
|
||||||
|
@ -330,44 +326,12 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() {
|
||||||
return std::make_shared<Avatar>(qApp->thread(), std::make_shared<Rig>());
|
return std::make_shared<Avatar>(qApp->thread(), std::make_shared<Rig>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarManager::processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
|
||||||
PerformanceTimer perfTimer("receiveAvatar");
|
|
||||||
// enumerate over all of the avatars in this packet
|
|
||||||
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
|
|
||||||
while (message->getBytesLeftToRead()) {
|
|
||||||
AvatarSharedPointer avatarData = parseAvatarData(message, sendingNode);
|
|
||||||
if (avatarData) {
|
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
|
||||||
if (avatar->isInScene()) {
|
|
||||||
if (!_shouldRender) {
|
|
||||||
// rare transition so we process the transaction immediately
|
|
||||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
|
||||||
render::Transaction transaction;
|
|
||||||
avatar->removeFromScene(avatar, scene, transaction);
|
|
||||||
if (scene) {
|
|
||||||
scene->enqueueTransaction(transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (_shouldRender) {
|
|
||||||
// very rare transition so we process the transaction immediately
|
|
||||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
|
||||||
render::Transaction transaction;
|
|
||||||
avatar->addToScene(avatar, scene, transaction);
|
|
||||||
if (scene) {
|
|
||||||
scene->enqueueTransaction(transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||||
AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason);
|
AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason);
|
||||||
|
|
||||||
// removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar
|
// removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar
|
||||||
// class in this context so we can call methods that don't exist at the base class.
|
// class in this context so we can call methods that don't exist at the base class.
|
||||||
Avatar* avatar = static_cast<Avatar*>(removedAvatar.get());
|
auto avatar = std::static_pointer_cast<Avatar>(removedAvatar);
|
||||||
avatar->die();
|
|
||||||
|
|
||||||
AvatarMotionState* motionState = avatar->getMotionState();
|
AvatarMotionState* motionState = avatar->getMotionState();
|
||||||
if (motionState) {
|
if (motionState) {
|
||||||
|
@ -403,15 +367,12 @@ void AvatarManager::clearOtherAvatars() {
|
||||||
if (avatar->isInScene()) {
|
if (avatar->isInScene()) {
|
||||||
avatar->removeFromScene(avatar, scene, transaction);
|
avatar->removeFromScene(avatar, scene, transaction);
|
||||||
}
|
}
|
||||||
AvatarMotionState* motionState = avatar->getMotionState();
|
handleRemovedAvatar(avatar);
|
||||||
if (motionState) {
|
avatarIterator = _avatarHash.erase(avatarIterator);
|
||||||
_motionStatesThatMightUpdate.remove(motionState);
|
} else {
|
||||||
_motionStatesToAddToPhysics.remove(motionState);
|
|
||||||
_motionStatesToRemoveFromPhysics.push_back(motionState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++avatarIterator;
|
++avatarIterator;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
scene->enqueueTransaction(transaction);
|
scene->enqueueTransaction(transaction);
|
||||||
_myAvatar->clearLookAtTargetAvatar();
|
_myAvatar->clearLookAtTargetAvatar();
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,9 +98,6 @@ public slots:
|
||||||
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
||||||
void updateAvatarRenderStatus(bool shouldRenderAvatars);
|
void updateAvatarRenderStatus(bool shouldRenderAvatars);
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit AvatarManager(QObject* parent = 0);
|
explicit AvatarManager(QObject* parent = 0);
|
||||||
explicit AvatarManager(const AvatarManager& other);
|
explicit AvatarManager(const AvatarManager& other);
|
||||||
|
|
|
@ -412,9 +412,7 @@ void MyAvatar::update(float deltaTime) {
|
||||||
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
|
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
|
||||||
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
|
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
|
||||||
|
|
||||||
uint64_t now = usecTimestampNow();
|
if (getIdentityDataChanged()) {
|
||||||
if (now > _identityPacketExpiry || _avatarEntityDataLocallyEdited) {
|
|
||||||
_identityPacketExpiry = now + AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS;
|
|
||||||
sendIdentityPacket();
|
sendIdentityPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1258,7 +1256,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
||||||
setSkeletonModelURL(fullAvatarURL);
|
setSkeletonModelURL(fullAvatarURL);
|
||||||
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
||||||
}
|
}
|
||||||
_identityPacketExpiry = 0; // triggers an identity packet next update()
|
markIdentityDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||||
|
|
|
@ -701,8 +701,6 @@ private:
|
||||||
std::mutex _holdActionsMutex;
|
std::mutex _holdActionsMutex;
|
||||||
std::vector<AvatarActionHold*> _holdActions;
|
std::vector<AvatarActionHold*> _holdActions;
|
||||||
|
|
||||||
uint64_t _identityPacketExpiry { 0 };
|
|
||||||
|
|
||||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
||||||
|
|
|
@ -79,6 +79,25 @@ int main(int argc, const char* argv[]) {
|
||||||
instanceMightBeRunning = false;
|
instanceMightBeRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QCommandLineParser parser;
|
||||||
|
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
|
||||||
|
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
||||||
|
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
||||||
|
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
|
||||||
|
parser.addOption(checkMinSpecOption);
|
||||||
|
parser.addOption(runServerOption);
|
||||||
|
parser.addOption(serverContentPathOption);
|
||||||
|
parser.addOption(allowMultipleInstancesOption);
|
||||||
|
parser.parse(arguments);
|
||||||
|
bool runServer = parser.isSet(runServerOption);
|
||||||
|
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
||||||
|
QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
||||||
|
bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption);
|
||||||
|
|
||||||
|
if (allowMultipleInstances) {
|
||||||
|
instanceMightBeRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (instanceMightBeRunning) {
|
if (instanceMightBeRunning) {
|
||||||
// Try to connect and send message to existing interface instance
|
// Try to connect and send message to existing interface instance
|
||||||
QLocalSocket socket;
|
QLocalSocket socket;
|
||||||
|
@ -137,18 +156,6 @@ int main(int argc, const char* argv[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QCommandLineParser parser;
|
|
||||||
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
|
|
||||||
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
|
||||||
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
|
||||||
parser.addOption(checkMinSpecOption);
|
|
||||||
parser.addOption(runServerOption);
|
|
||||||
parser.addOption(serverContentPathOption);
|
|
||||||
parser.parse(arguments);
|
|
||||||
bool runServer = parser.isSet(runServerOption);
|
|
||||||
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
|
||||||
QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
|
||||||
|
|
||||||
QElapsedTimer startupTime;
|
QElapsedTimer startupTime;
|
||||||
startupTime.start();
|
startupTime.start();
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#include "AudioLogging.h"
|
#include "AudioLogging.h"
|
||||||
#include "SoundCache.h"
|
#include "SoundCache.h"
|
||||||
|
|
||||||
|
static const int SOUNDS_LOADING_PRIORITY { -7 }; // Make sure sounds load after the low rez texture mips
|
||||||
|
|
||||||
int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
|
int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
|
||||||
|
|
||||||
SoundCache::SoundCache(QObject* parent) :
|
SoundCache::SoundCache(QObject* parent) :
|
||||||
|
@ -37,5 +39,7 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) {
|
||||||
QSharedPointer<Resource> SoundCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
QSharedPointer<Resource> SoundCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||||
const void* extra) {
|
const void* extra) {
|
||||||
qCDebug(audio) << "Requesting sound at" << url.toString();
|
qCDebug(audio) << "Requesting sound at" << url.toString();
|
||||||
return QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
|
auto resource = QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
|
||||||
|
resource->setLoadPriority(this, SOUNDS_LOADING_PRIORITY);
|
||||||
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1473,7 +1473,22 @@ QStringList AvatarData::getJointNames() const {
|
||||||
void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) {
|
void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) {
|
||||||
QDataStream packetStream(data);
|
QDataStream packetStream(data);
|
||||||
|
|
||||||
packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.sessionDisplayName >> identityOut.avatarEntityData;
|
packetStream >> identityOut.uuid
|
||||||
|
>> identityOut.skeletonModelURL
|
||||||
|
>> identityOut.attachmentData
|
||||||
|
>> identityOut.displayName
|
||||||
|
>> identityOut.sessionDisplayName
|
||||||
|
>> identityOut.avatarEntityData
|
||||||
|
>> identityOut.updatedAt;
|
||||||
|
|
||||||
|
#ifdef WANT_DEBUG
|
||||||
|
qCDebug(avatars) << __FUNCTION__
|
||||||
|
<< "identityOut.uuid:" << identityOut.uuid
|
||||||
|
<< "identityOut.skeletonModelURL:" << identityOut.skeletonModelURL
|
||||||
|
<< "identityOut.displayName:" << identityOut.displayName
|
||||||
|
<< "identityOut.sessionDisplayName:" << identityOut.sessionDisplayName;
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const QUrl emptyURL("");
|
static const QUrl emptyURL("");
|
||||||
|
@ -1484,6 +1499,12 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
||||||
|
|
||||||
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) {
|
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) {
|
||||||
|
|
||||||
|
if (identity.updatedAt < _identityUpdatedAt) {
|
||||||
|
qCDebug(avatars) << "Ignoring late identity packet for avatar " << getSessionUUID()
|
||||||
|
<< "identity.updatedAt:" << identity.updatedAt << "_identityUpdatedAt:" << _identityUpdatedAt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
|
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
|
||||||
setSkeletonModelURL(identity.skeletonModelURL);
|
setSkeletonModelURL(identity.skeletonModelURL);
|
||||||
identityChanged = true;
|
identityChanged = true;
|
||||||
|
@ -1513,24 +1534,35 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC
|
||||||
setAvatarEntityData(identity.avatarEntityData);
|
setAvatarEntityData(identity.avatarEntityData);
|
||||||
identityChanged = true;
|
identityChanged = true;
|
||||||
}
|
}
|
||||||
// flag this avatar as non-stale by updating _averageBytesReceived
|
|
||||||
const int BOGUS_NUM_BYTES = 1;
|
// use the timestamp from this identity, since we want to honor the updated times in "server clock"
|
||||||
_averageBytesReceived.updateAverage(BOGUS_NUM_BYTES);
|
// this will overwrite any changes we made locally to this AvatarData's _identityUpdatedAt
|
||||||
|
_identityUpdatedAt = identity.updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray AvatarData::identityByteArray() const {
|
QByteArray AvatarData::identityByteArray() const {
|
||||||
QByteArray identityData;
|
QByteArray identityData;
|
||||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||||
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL);
|
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL
|
||||||
|
|
||||||
_avatarEntitiesLock.withReadLock([&] {
|
_avatarEntitiesLock.withReadLock([&] {
|
||||||
identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << getSessionDisplayNameForTransport() << _avatarEntityData;
|
identityStream << getSessionUUID()
|
||||||
|
<< urlToSend
|
||||||
|
<< _attachmentData
|
||||||
|
<< _displayName
|
||||||
|
<< getSessionDisplayNameForTransport() // depends on _sessionDisplayName
|
||||||
|
<< _avatarEntityData
|
||||||
|
<< _identityUpdatedAt;
|
||||||
});
|
});
|
||||||
|
|
||||||
return identityData;
|
return identityData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
|
if (skeletonModelURL.isEmpty()) {
|
||||||
|
qCDebug(avatars) << __FUNCTION__ << "caller called with empty URL.";
|
||||||
|
}
|
||||||
|
|
||||||
const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
|
const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
|
||||||
if (expanded == _skeletonModelURL) {
|
if (expanded == _skeletonModelURL) {
|
||||||
return;
|
return;
|
||||||
|
@ -1539,6 +1571,7 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString();
|
qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString();
|
||||||
|
|
||||||
updateJointMappings();
|
updateJointMappings();
|
||||||
|
markIdentityDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::setDisplayName(const QString& displayName) {
|
void AvatarData::setDisplayName(const QString& displayName) {
|
||||||
|
@ -1548,6 +1581,7 @@ void AvatarData::setDisplayName(const QString& displayName) {
|
||||||
sendIdentityPacket();
|
sendIdentityPacket();
|
||||||
|
|
||||||
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
|
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
|
||||||
|
markIdentityDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<AttachmentData> AvatarData::getAttachmentData() const {
|
QVector<AttachmentData> AvatarData::getAttachmentData() const {
|
||||||
|
@ -1566,6 +1600,7 @@ void AvatarData::setAttachmentData(const QVector<AttachmentData>& attachmentData
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_attachmentData = attachmentData;
|
_attachmentData = attachmentData;
|
||||||
|
markIdentityDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::attach(const QString& modelURL, const QString& jointName,
|
void AvatarData::attach(const QString& modelURL, const QString& jointName,
|
||||||
|
@ -1695,7 +1730,6 @@ void AvatarData::sendAvatarDataPacket() {
|
||||||
|
|
||||||
void AvatarData::sendIdentityPacket() {
|
void AvatarData::sendIdentityPacket() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
QByteArray identityData = identityByteArray();
|
QByteArray identityData = identityByteArray();
|
||||||
|
|
||||||
auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
||||||
|
@ -1709,6 +1743,7 @@ void AvatarData::sendIdentityPacket() {
|
||||||
});
|
});
|
||||||
|
|
||||||
_avatarEntityDataLocallyEdited = false;
|
_avatarEntityDataLocallyEdited = false;
|
||||||
|
_identityDataChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::updateJointMappings() {
|
void AvatarData::updateJointMappings() {
|
||||||
|
@ -2245,10 +2280,12 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent
|
||||||
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
|
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
|
||||||
_avatarEntityData.insert(entityID, entityData);
|
_avatarEntityData.insert(entityID, entityData);
|
||||||
_avatarEntityDataLocallyEdited = true;
|
_avatarEntityDataLocallyEdited = true;
|
||||||
|
markIdentityDataChanged();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
itr.value() = entityData;
|
itr.value() = entityData;
|
||||||
_avatarEntityDataLocallyEdited = true;
|
_avatarEntityDataLocallyEdited = true;
|
||||||
|
markIdentityDataChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2262,6 +2299,7 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID) {
|
||||||
_avatarEntitiesLock.withWriteLock([&] {
|
_avatarEntitiesLock.withWriteLock([&] {
|
||||||
_avatarEntityData.remove(entityID);
|
_avatarEntityData.remove(entityID);
|
||||||
_avatarEntityDataLocallyEdited = true;
|
_avatarEntityDataLocallyEdited = true;
|
||||||
|
markIdentityDataChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,8 +110,6 @@ const char LEFT_HAND_POINTING_FLAG = 1;
|
||||||
const char RIGHT_HAND_POINTING_FLAG = 2;
|
const char RIGHT_HAND_POINTING_FLAG = 2;
|
||||||
const char IS_FINGER_POINTING_FLAG = 4;
|
const char IS_FINGER_POINTING_FLAG = 4;
|
||||||
|
|
||||||
const qint64 AVATAR_UPDATE_TIMEOUT = 5 * USECS_PER_SECOND;
|
|
||||||
|
|
||||||
// AvatarData state flags - we store the details about the packet encoding in the first byte,
|
// AvatarData state flags - we store the details about the packet encoding in the first byte,
|
||||||
// before the "header" structure
|
// before the "header" structure
|
||||||
const char AVATARDATA_FLAGS_MINIMUM = 0;
|
const char AVATARDATA_FLAGS_MINIMUM = 0;
|
||||||
|
@ -531,6 +529,7 @@ public:
|
||||||
QString displayName;
|
QString displayName;
|
||||||
QString sessionDisplayName;
|
QString sessionDisplayName;
|
||||||
AvatarEntityMap avatarEntityData;
|
AvatarEntityMap avatarEntityData;
|
||||||
|
quint64 updatedAt;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut);
|
static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut);
|
||||||
|
@ -547,7 +546,10 @@ public:
|
||||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||||
|
|
||||||
virtual void setDisplayName(const QString& displayName);
|
virtual void setDisplayName(const QString& displayName);
|
||||||
virtual void setSessionDisplayName(const QString& sessionDisplayName) { _sessionDisplayName = sessionDisplayName; };
|
virtual void setSessionDisplayName(const QString& sessionDisplayName) {
|
||||||
|
_sessionDisplayName = sessionDisplayName;
|
||||||
|
markIdentityDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
|
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
|
||||||
Q_INVOKABLE virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
Q_INVOKABLE virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||||
|
@ -565,7 +567,6 @@ public:
|
||||||
|
|
||||||
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
|
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
|
||||||
|
|
||||||
int getUsecsSinceLastUpdate() const { return _averageBytesReceived.getUsecsSinceLastEvent(); }
|
|
||||||
int getAverageBytesReceivedPerSecond() const;
|
int getAverageBytesReceivedPerSecond() const;
|
||||||
int getReceiveRate() const;
|
int getReceiveRate() const;
|
||||||
|
|
||||||
|
@ -601,9 +602,6 @@ public:
|
||||||
return _lastSentJointData;
|
return _lastSentJointData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_UPDATE_TIMEOUT; }
|
|
||||||
|
|
||||||
static const float OUT_OF_VIEW_PENALTY;
|
static const float OUT_OF_VIEW_PENALTY;
|
||||||
|
|
||||||
static void sortAvatars(
|
static void sortAvatars(
|
||||||
|
@ -620,7 +618,11 @@ public:
|
||||||
static float _avatarSortCoefficientCenter;
|
static float _avatarSortCoefficientCenter;
|
||||||
static float _avatarSortCoefficientAge;
|
static float _avatarSortCoefficientAge;
|
||||||
|
|
||||||
|
bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called
|
||||||
|
void markIdentityDataChanged() {
|
||||||
|
_identityDataChanged = true;
|
||||||
|
_identityUpdatedAt = usecTimestampNow();
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void displayNameChanged();
|
void displayNameChanged();
|
||||||
|
@ -779,6 +781,9 @@ protected:
|
||||||
quint64 _audioLoudnessChanged { 0 };
|
quint64 _audioLoudnessChanged { 0 };
|
||||||
float _audioAverageLoudness { 0.0f };
|
float _audioAverageLoudness { 0.0f };
|
||||||
|
|
||||||
|
bool _identityDataChanged { false };
|
||||||
|
quint64 _identityUpdatedAt { 0 };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
||||||
static QUrl _defaultFullAvatarModelUrl;
|
static QUrl _defaultFullAvatarModelUrl;
|
||||||
|
|
|
@ -57,7 +57,7 @@ public slots:
|
||||||
protected slots:
|
protected slots:
|
||||||
void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID);
|
void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID);
|
||||||
|
|
||||||
virtual void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
void processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
|
|
|
@ -140,7 +140,7 @@ GLExternalTexture::~GLExternalTexture() {
|
||||||
if (recycler) {
|
if (recycler) {
|
||||||
backend->releaseExternalTexture(_id, recycler);
|
backend->releaseExternalTexture(_id, recycler);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "No recycler available for texture " << _id << " possible leak";
|
qCWarning(gpugllogging) << "No recycler available for texture " << _id << " possible leak";
|
||||||
}
|
}
|
||||||
const_cast<GLuint&>(_id) = 0;
|
const_cast<GLuint&>(_id) = 0;
|
||||||
}
|
}
|
||||||
|
@ -210,38 +210,32 @@ TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t t
|
||||||
format = texelFormat.format;
|
format = texelFormat.format;
|
||||||
internalFormat = texelFormat.internalFormat;
|
internalFormat = texelFormat.internalFormat;
|
||||||
type = texelFormat.type;
|
type = texelFormat.type;
|
||||||
auto mipSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face);
|
_transferSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face);
|
||||||
|
|
||||||
|
// If we're copying a subsection of the mip, do additional calculations to find the size and offset of the segment
|
||||||
if (0 == lines) {
|
if (0 != lines) {
|
||||||
_transferSize = mipSize;
|
|
||||||
_bufferingLambda = [=] {
|
|
||||||
auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face);
|
|
||||||
_buffer.resize(_transferSize);
|
|
||||||
memcpy(&_buffer[0], mipData->readData(), _transferSize);
|
|
||||||
_bufferingCompleted = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
} else {
|
|
||||||
transferDimensions.y = lines;
|
transferDimensions.y = lines;
|
||||||
auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip);
|
auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip);
|
||||||
auto bytesPerLine = (uint32_t)mipSize / dimensions.y;
|
auto bytesPerLine = (uint32_t)_transferSize / dimensions.y;
|
||||||
auto sourceOffset = bytesPerLine * lineOffset;
|
_transferOffset = bytesPerLine * lineOffset;
|
||||||
_transferSize = bytesPerLine * lines;
|
_transferSize = bytesPerLine * lines;
|
||||||
_bufferingLambda = [=] {
|
|
||||||
auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face);
|
|
||||||
_buffer.resize(_transferSize);
|
|
||||||
memcpy(&_buffer[0], mipData->readData() + sourceOffset, _transferSize);
|
|
||||||
_bufferingCompleted = true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Backend::updateTextureTransferPendingSize(0, _transferSize);
|
Backend::updateTextureTransferPendingSize(0, _transferSize);
|
||||||
|
|
||||||
|
if (_transferSize > GLVariableAllocationSupport::MAX_TRANSFER_SIZE) {
|
||||||
|
qCWarning(gpugllogging) << "Transfer size of " << _transferSize << " exceeds theoretical maximum transfer size";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffering can invoke disk IO, so it should be off of the main and render threads
|
||||||
|
_bufferingLambda = [=] {
|
||||||
|
_mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face)->createView(_transferSize, _transferOffset);
|
||||||
|
_bufferingCompleted = true;
|
||||||
|
};
|
||||||
|
|
||||||
_transferLambda = [=] {
|
_transferLambda = [=] {
|
||||||
_parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, internalFormat, format, type, _buffer.size(), _buffer.data());
|
_parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, internalFormat, format, type, _mipData->size(), _mipData->readData());
|
||||||
std::vector<uint8_t> emptyVector;
|
_mipData.reset();
|
||||||
_buffer.swap(emptyVector);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,10 +448,10 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
|
||||||
float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
|
float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
|
||||||
|
|
||||||
auto newState = MemoryPressureState::Idle;
|
auto newState = MemoryPressureState::Idle;
|
||||||
if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
|
if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) {
|
||||||
newState = MemoryPressureState::Oversubscribed;
|
|
||||||
} else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && unallocated != 0 && canPromote) {
|
|
||||||
newState = MemoryPressureState::Undersubscribed;
|
newState = MemoryPressureState::Undersubscribed;
|
||||||
|
} else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
|
||||||
|
newState = MemoryPressureState::Oversubscribed;
|
||||||
} else if (hasTransfers) {
|
} else if (hasTransfers) {
|
||||||
newState = MemoryPressureState::Transfer;
|
newState = MemoryPressureState::Transfer;
|
||||||
}
|
}
|
||||||
|
@ -535,6 +529,7 @@ void GLVariableAllocationSupport::processWorkQueues() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workQueue.empty()) {
|
if (workQueue.empty()) {
|
||||||
|
_memoryPressureState = MemoryPressureState::Idle;
|
||||||
_memoryPressureStateStale = true;
|
_memoryPressureStateStale = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,8 +49,10 @@ public:
|
||||||
using VoidLambdaQueue = std::queue<VoidLambda>;
|
using VoidLambdaQueue = std::queue<VoidLambda>;
|
||||||
using ThreadPointer = std::shared_ptr<std::thread>;
|
using ThreadPointer = std::shared_ptr<std::thread>;
|
||||||
const GLTexture& _parent;
|
const GLTexture& _parent;
|
||||||
// Holds the contents to transfer to the GPU in CPU memory
|
Texture::PixelsPointer _mipData;
|
||||||
std::vector<uint8_t> _buffer;
|
size_t _transferOffset { 0 };
|
||||||
|
size_t _transferSize { 0 };
|
||||||
|
|
||||||
// Indicates if a transfer from backing storage to interal storage has started
|
// Indicates if a transfer from backing storage to interal storage has started
|
||||||
bool _bufferingStarted { false };
|
bool _bufferingStarted { false };
|
||||||
bool _bufferingCompleted { false };
|
bool _bufferingCompleted { false };
|
||||||
|
@ -78,7 +80,6 @@ public:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t _transferSize { 0 };
|
|
||||||
#if THREADED_TEXTURE_BUFFERING
|
#if THREADED_TEXTURE_BUFFERING
|
||||||
void startBuffering();
|
void startBuffering();
|
||||||
#endif
|
#endif
|
||||||
|
@ -112,7 +113,7 @@ protected:
|
||||||
static void manageMemory();
|
static void manageMemory();
|
||||||
|
|
||||||
//bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
|
//bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
|
||||||
bool canPromote() const { return _allocatedMip > 0; }
|
bool canPromote() const { return _allocatedMip > _minAllocatedMip; }
|
||||||
bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
|
bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
|
||||||
bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
|
bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
|
||||||
void executeNextTransfer(const TexturePointer& currentTexture);
|
void executeNextTransfer(const TexturePointer& currentTexture);
|
||||||
|
@ -130,6 +131,9 @@ protected:
|
||||||
// The highest (lowest resolution) mip that we will support, relative to the number
|
// The highest (lowest resolution) mip that we will support, relative to the number
|
||||||
// of mips in the gpu::Texture object
|
// of mips in the gpu::Texture object
|
||||||
uint16 _maxAllocatedMip { 0 };
|
uint16 _maxAllocatedMip { 0 };
|
||||||
|
// The lowest (highest resolution) mip that we will support, relative to the number
|
||||||
|
// of mips in the gpu::Texture object
|
||||||
|
uint16 _minAllocatedMip { 0 };
|
||||||
// Contains a series of lambdas that when executed will transfer data to the GPU, modify
|
// Contains a series of lambdas that when executed will transfer data to the GPU, modify
|
||||||
// the _populatedMip and update the sampler in order to fully populate the allocated texture
|
// the _populatedMip and update the sampler in order to fully populate the allocated texture
|
||||||
// until _populatedMip == _allocatedMip
|
// until _populatedMip == _allocatedMip
|
||||||
|
|
|
@ -55,6 +55,18 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||||
default:
|
default:
|
||||||
Q_UNREACHABLE();
|
Q_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (texture.getUsageType() == TextureUsageType::RESOURCE) {
|
||||||
|
auto varTex = static_cast<GL41VariableAllocationTexture*> (object);
|
||||||
|
|
||||||
|
if (varTex->_minAllocatedMip > 0) {
|
||||||
|
auto minAvailableMip = texture.minAvailableMipLevel();
|
||||||
|
if (minAvailableMip < varTex->_minAllocatedMip) {
|
||||||
|
varTex->_minAllocatedMip = minAvailableMip;
|
||||||
|
GL41VariableAllocationTexture::_memoryPressureStateStale = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
|
@ -231,15 +243,20 @@ using GL41VariableAllocationTexture = GL41Backend::GL41VariableAllocationTexture
|
||||||
GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41Texture(backend, texture) {
|
GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41Texture(backend, texture) {
|
||||||
auto mipLevels = texture.getNumMips();
|
auto mipLevels = texture.getNumMips();
|
||||||
_allocatedMip = mipLevels;
|
_allocatedMip = mipLevels;
|
||||||
|
_maxAllocatedMip = _populatedMip = mipLevels;
|
||||||
|
_minAllocatedMip = texture.minAvailableMipLevel();
|
||||||
|
|
||||||
uvec3 mipDimensions;
|
uvec3 mipDimensions;
|
||||||
for (uint16_t mip = 0; mip < mipLevels; ++mip) {
|
for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) {
|
||||||
if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
|
if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
|
||||||
_maxAllocatedMip = _populatedMip = mip;
|
_maxAllocatedMip = _populatedMip = mip;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
|
auto targetMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
|
||||||
|
uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);
|
||||||
|
|
||||||
allocateStorage(allocatedMip);
|
allocateStorage(allocatedMip);
|
||||||
_memoryPressureStateStale = true;
|
_memoryPressureStateStale = true;
|
||||||
size_t maxFace = GLTexture::getFaceCount(_target);
|
size_t maxFace = GLTexture::getFaceCount(_target);
|
||||||
|
@ -292,6 +309,10 @@ void GL41VariableAllocationTexture::syncSampler() const {
|
||||||
void GL41VariableAllocationTexture::promote() {
|
void GL41VariableAllocationTexture::promote() {
|
||||||
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
|
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
|
||||||
Q_ASSERT(_allocatedMip > 0);
|
Q_ASSERT(_allocatedMip > 0);
|
||||||
|
|
||||||
|
uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
|
||||||
|
targetAllocatedMip = std::max<uint16_t>(_minAllocatedMip, targetAllocatedMip);
|
||||||
|
|
||||||
GLuint oldId = _id;
|
GLuint oldId = _id;
|
||||||
auto oldSize = _size;
|
auto oldSize = _size;
|
||||||
// create new texture
|
// create new texture
|
||||||
|
@ -299,7 +320,7 @@ void GL41VariableAllocationTexture::promote() {
|
||||||
uint16_t oldAllocatedMip = _allocatedMip;
|
uint16_t oldAllocatedMip = _allocatedMip;
|
||||||
|
|
||||||
// allocate storage for new level
|
// allocate storage for new level
|
||||||
allocateStorage(_allocatedMip - std::min<uint16_t>(_allocatedMip, 2));
|
allocateStorage(targetAllocatedMip);
|
||||||
|
|
||||||
withPreservedTexture([&] {
|
withPreservedTexture([&] {
|
||||||
GLuint fbo { 0 };
|
GLuint fbo { 0 };
|
||||||
|
|
|
@ -80,6 +80,19 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||||
default:
|
default:
|
||||||
Q_UNREACHABLE();
|
Q_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (texture.getUsageType() == TextureUsageType::RESOURCE) {
|
||||||
|
auto varTex = static_cast<GL45VariableAllocationTexture*> (object);
|
||||||
|
|
||||||
|
if (varTex->_minAllocatedMip > 0) {
|
||||||
|
auto minAvailableMip = texture.minAvailableMipLevel();
|
||||||
|
if (minAvailableMip < varTex->_minAllocatedMip) {
|
||||||
|
varTex->_minAllocatedMip = minAvailableMip;
|
||||||
|
GL45VariableAllocationTexture::_memoryPressureStateStale = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
|
@ -109,6 +122,10 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
|
||||||
GLuint GL45Texture::allocate(const Texture& texture) {
|
GLuint GL45Texture::allocate(const Texture& texture) {
|
||||||
GLuint result;
|
GLuint result;
|
||||||
glCreateTextures(getGLTextureType(texture), 1, &result);
|
glCreateTextures(getGLTextureType(texture), 1, &result);
|
||||||
|
#ifdef DEBUG
|
||||||
|
auto source = texture.source();
|
||||||
|
glObjectLabel(GL_TEXTURE, result, source.length(), source.data());
|
||||||
|
#endif
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,16 +43,22 @@ using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
|
||||||
GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {
|
GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {
|
||||||
auto mipLevels = texture.getNumMips();
|
auto mipLevels = texture.getNumMips();
|
||||||
_allocatedMip = mipLevels;
|
_allocatedMip = mipLevels;
|
||||||
|
_maxAllocatedMip = _populatedMip = mipLevels;
|
||||||
|
_minAllocatedMip = texture.minAvailableMipLevel();
|
||||||
|
|
||||||
uvec3 mipDimensions;
|
uvec3 mipDimensions;
|
||||||
for (uint16_t mip = 0; mip < mipLevels; ++mip) {
|
for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) {
|
||||||
if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
|
if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
|
||||||
_maxAllocatedMip = _populatedMip = mip;
|
_maxAllocatedMip = _populatedMip = mip;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
|
auto targetMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
|
||||||
|
uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);
|
||||||
|
|
||||||
allocateStorage(allocatedMip);
|
allocateStorage(allocatedMip);
|
||||||
|
_memoryPressureStateStale = true;
|
||||||
copyMipsFromTexture();
|
copyMipsFromTexture();
|
||||||
syncSampler();
|
syncSampler();
|
||||||
|
|
||||||
|
@ -70,6 +76,7 @@ void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) {
|
||||||
for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) {
|
for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) {
|
||||||
_size += _gpuObject.evalMipSize(mip);
|
_size += _gpuObject.evalMipSize(mip);
|
||||||
}
|
}
|
||||||
|
|
||||||
Backend::updateTextureGPUMemoryUsage(0, _size);
|
Backend::updateTextureGPUMemoryUsage(0, _size);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -93,13 +100,17 @@ void GL45ResourceTexture::syncSampler() const {
|
||||||
void GL45ResourceTexture::promote() {
|
void GL45ResourceTexture::promote() {
|
||||||
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
|
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
|
||||||
Q_ASSERT(_allocatedMip > 0);
|
Q_ASSERT(_allocatedMip > 0);
|
||||||
|
|
||||||
|
uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
|
||||||
|
targetAllocatedMip = std::max<uint16_t>(_minAllocatedMip, targetAllocatedMip);
|
||||||
|
|
||||||
GLuint oldId = _id;
|
GLuint oldId = _id;
|
||||||
auto oldSize = _size;
|
auto oldSize = _size;
|
||||||
// create new texture
|
// create new texture
|
||||||
const_cast<GLuint&>(_id) = allocate(_gpuObject);
|
const_cast<GLuint&>(_id) = allocate(_gpuObject);
|
||||||
uint16_t oldAllocatedMip = _allocatedMip;
|
uint16_t oldAllocatedMip = _allocatedMip;
|
||||||
// allocate storage for new level
|
// allocate storage for new level
|
||||||
allocateStorage(_allocatedMip - std::min<uint16_t>(_allocatedMip, 2));
|
allocateStorage(targetAllocatedMip);
|
||||||
uint16_t mips = _gpuObject.getNumMips();
|
uint16_t mips = _gpuObject.getNumMips();
|
||||||
// copy pre-existing mips
|
// copy pre-existing mips
|
||||||
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
|
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
|
||||||
|
|
|
@ -118,6 +118,7 @@ Texture::Size Texture::getAllowedGPUMemoryUsage() {
|
||||||
return _allowedCPUMemoryUsage;
|
return _allowedCPUMemoryUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Texture::setAllowedGPUMemoryUsage(Size size) {
|
void Texture::setAllowedGPUMemoryUsage(Size size) {
|
||||||
qCDebug(gpulogging) << "New MAX texture memory " << BYTES_TO_MB(size) << " MB";
|
qCDebug(gpulogging) << "New MAX texture memory " << BYTES_TO_MB(size) << " MB";
|
||||||
_allowedCPUMemoryUsage = size;
|
_allowedCPUMemoryUsage = size;
|
||||||
|
@ -411,6 +412,7 @@ const Element& Texture::getStoredMipFormat() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::assignStoredMip(uint16 level, Size size, const Byte* bytes) {
|
void Texture::assignStoredMip(uint16 level, Size size, const Byte* bytes) {
|
||||||
|
// TODO Skip the extra allocation here
|
||||||
storage::StoragePointer storage = std::make_shared<storage::MemoryStorage>(size, bytes);
|
storage::StoragePointer storage = std::make_shared<storage::MemoryStorage>(size, bytes);
|
||||||
assignStoredMip(level, storage);
|
assignStoredMip(level, storage);
|
||||||
}
|
}
|
||||||
|
@ -474,6 +476,10 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Texture::isStoredMipFaceAvailable(uint16 level, uint8 face) const {
|
||||||
|
return _storage->isMipAvailable(level, face);
|
||||||
|
}
|
||||||
|
|
||||||
void Texture::setAutoGenerateMips(bool enable) {
|
void Texture::setAutoGenerateMips(bool enable) {
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
if (!_autoGenerateMips) {
|
if (!_autoGenerateMips) {
|
||||||
|
@ -487,10 +493,9 @@ void Texture::setAutoGenerateMips(bool enable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Size Texture::getStoredMipSize(uint16 level) const {
|
Size Texture::getStoredMipSize(uint16 level) const {
|
||||||
PixelsPointer mipFace = accessStoredMipFace(level);
|
|
||||||
Size size = 0;
|
Size size = 0;
|
||||||
if (mipFace && mipFace->getSize()) {
|
|
||||||
for (int face = 0; face < getNumFaces(); face++) {
|
for (int face = 0; face < getNumFaces(); face++) {
|
||||||
|
if (isStoredMipFaceAvailable(level, face)) {
|
||||||
size += getStoredMipFaceSize(level, face);
|
size += getStoredMipFaceSize(level, face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,17 @@ namespace ktx {
|
||||||
struct KTXDescriptor;
|
struct KTXDescriptor;
|
||||||
using KTXDescriptorPointer = std::unique_ptr<KTXDescriptor>;
|
using KTXDescriptorPointer = std::unique_ptr<KTXDescriptor>;
|
||||||
struct Header;
|
struct Header;
|
||||||
|
struct KeyValue;
|
||||||
|
using KeyValues = std::list<KeyValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
|
|
||||||
|
const std::string SOURCE_HASH_KEY { "hifi.sourceHash" };
|
||||||
|
|
||||||
|
const uint8 SOURCE_HASH_BYTES = 16;
|
||||||
|
|
||||||
// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated
|
// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated
|
||||||
// with the cube texture
|
// with the cube texture
|
||||||
class Texture;
|
class Texture;
|
||||||
|
@ -150,7 +157,7 @@ protected:
|
||||||
Desc _desc;
|
Desc _desc;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TextureUsageType {
|
enum class TextureUsageType : uint8 {
|
||||||
RENDERBUFFER, // Used as attachments to a framebuffer
|
RENDERBUFFER, // Used as attachments to a framebuffer
|
||||||
RESOURCE, // Resource textures, like materials... subject to memory manipulation
|
RESOURCE, // Resource textures, like materials... subject to memory manipulation
|
||||||
STRICT_RESOURCE, // Resource textures not subject to manipulation, like the normal fitting texture
|
STRICT_RESOURCE, // Resource textures not subject to manipulation, like the normal fitting texture
|
||||||
|
@ -271,6 +278,7 @@ public:
|
||||||
virtual void assignMipData(uint16 level, const storage::StoragePointer& storage) = 0;
|
virtual void assignMipData(uint16 level, const storage::StoragePointer& storage) = 0;
|
||||||
virtual void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) = 0;
|
virtual void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) = 0;
|
||||||
virtual bool isMipAvailable(uint16 level, uint8 face = 0) const = 0;
|
virtual bool isMipAvailable(uint16 level, uint8 face = 0) const = 0;
|
||||||
|
virtual uint16 minAvailableMipLevel() const { return 0; }
|
||||||
Texture::Type getType() const { return _type; }
|
Texture::Type getType() const { return _type; }
|
||||||
|
|
||||||
Stamp getStamp() const { return _stamp; }
|
Stamp getStamp() const { return _stamp; }
|
||||||
|
@ -308,24 +316,30 @@ public:
|
||||||
KtxStorage(const std::string& filename);
|
KtxStorage(const std::string& filename);
|
||||||
PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override;
|
PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override;
|
||||||
Size getMipFaceSize(uint16 level, uint8 face = 0) const override;
|
Size getMipFaceSize(uint16 level, uint8 face = 0) const override;
|
||||||
// By convention, all mip levels and faces MUST be populated when using KTX backing
|
bool isMipAvailable(uint16 level, uint8 face = 0) const override;
|
||||||
bool isMipAvailable(uint16 level, uint8 face = 0) const override { return true; }
|
void assignMipData(uint16 level, const storage::StoragePointer& storage) override;
|
||||||
|
void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override;
|
||||||
|
uint16 minAvailableMipLevel() const override;
|
||||||
|
|
||||||
void assignMipData(uint16 level, const storage::StoragePointer& storage) override {
|
|
||||||
throw std::runtime_error("Invalid call");
|
|
||||||
}
|
|
||||||
|
|
||||||
void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override {
|
|
||||||
throw std::runtime_error("Invalid call");
|
|
||||||
}
|
|
||||||
void reset() override { }
|
void reset() override { }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
std::shared_ptr<storage::FileStorage> maybeOpenFile();
|
||||||
|
|
||||||
|
std::mutex _cacheFileCreateMutex;
|
||||||
|
std::mutex _cacheFileWriteMutex;
|
||||||
|
std::weak_ptr<storage::FileStorage> _cacheFile;
|
||||||
|
|
||||||
std::string _filename;
|
std::string _filename;
|
||||||
|
std::atomic<uint8_t> _minMipLevelAvailable;
|
||||||
|
size_t _offsetToMinMipKV;
|
||||||
|
|
||||||
ktx::KTXDescriptorPointer _ktxDescriptor;
|
ktx::KTXDescriptorPointer _ktxDescriptor;
|
||||||
friend class Texture;
|
friend class Texture;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
uint16 minAvailableMipLevel() const { return _storage->minAvailableMipLevel(); };
|
||||||
|
|
||||||
static const uint16 MAX_NUM_MIPS = 0;
|
static const uint16 MAX_NUM_MIPS = 0;
|
||||||
static const uint16 SINGLE_MIP = 1;
|
static const uint16 SINGLE_MIP = 1;
|
||||||
static TexturePointer create1D(const Element& texelFormat, uint16 width, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
|
static TexturePointer create1D(const Element& texelFormat, uint16 width, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
|
||||||
|
@ -469,7 +483,7 @@ public:
|
||||||
|
|
||||||
// Access the stored mips and faces
|
// Access the stored mips and faces
|
||||||
const PixelsPointer accessStoredMipFace(uint16 level, uint8 face = 0) const { return _storage->getMipFace(level, face); }
|
const PixelsPointer accessStoredMipFace(uint16 level, uint8 face = 0) const { return _storage->getMipFace(level, face); }
|
||||||
bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const { return _storage->isMipAvailable(level, face); }
|
bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const;
|
||||||
Size getStoredMipFaceSize(uint16 level, uint8 face = 0) const { return _storage->getMipFaceSize(level, face); }
|
Size getStoredMipFaceSize(uint16 level, uint8 face = 0) const { return _storage->getMipFaceSize(level, face); }
|
||||||
Size getStoredMipSize(uint16 level) const;
|
Size getStoredMipSize(uint16 level) const;
|
||||||
Size getStoredSize() const;
|
Size getStoredSize() const;
|
||||||
|
@ -503,9 +517,12 @@ public:
|
||||||
|
|
||||||
ExternalUpdates getUpdates() const;
|
ExternalUpdates getUpdates() const;
|
||||||
|
|
||||||
// Textures can be serialized directly to ktx data file, here is how
|
// Serialize a texture into a KTX file
|
||||||
static ktx::KTXUniquePointer serialize(const Texture& texture);
|
static ktx::KTXUniquePointer serialize(const Texture& texture);
|
||||||
static TexturePointer unserialize(const std::string& ktxFile, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
|
|
||||||
|
static TexturePointer unserialize(const std::string& ktxFile);
|
||||||
|
static TexturePointer unserialize(const std::string& ktxFile, const ktx::KTXDescriptor& descriptor);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|
|
@ -12,19 +12,78 @@
|
||||||
|
|
||||||
#include "Texture.h"
|
#include "Texture.h"
|
||||||
|
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
|
||||||
#include <ktx/KTX.h>
|
#include <ktx/KTX.h>
|
||||||
|
|
||||||
|
#include "GPULogging.h"
|
||||||
|
|
||||||
using namespace gpu;
|
using namespace gpu;
|
||||||
|
|
||||||
using PixelsPointer = Texture::PixelsPointer;
|
using PixelsPointer = Texture::PixelsPointer;
|
||||||
using KtxStorage = Texture::KtxStorage;
|
using KtxStorage = Texture::KtxStorage;
|
||||||
|
|
||||||
struct GPUKTXPayload {
|
struct GPUKTXPayload {
|
||||||
|
using Version = uint8;
|
||||||
|
|
||||||
|
static const std::string KEY;
|
||||||
|
static const Version CURRENT_VERSION { 1 };
|
||||||
|
static const size_t PADDING { 2 };
|
||||||
|
static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint32) + sizeof(TextureUsageType) + PADDING };
|
||||||
|
static_assert(GPUKTXPayload::SIZE == 36, "Packing size may differ between platforms");
|
||||||
|
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;
|
||||||
|
|
||||||
|
Byte* serialize(Byte* data) const {
|
||||||
|
*(Version*)data = CURRENT_VERSION;
|
||||||
|
data += sizeof(Version);
|
||||||
|
|
||||||
|
memcpy(data, &_samplerDesc, sizeof(Sampler::Desc));
|
||||||
|
data += sizeof(Sampler::Desc);
|
||||||
|
|
||||||
|
// We can't copy the bitset in Texture::Usage in a crossplateform manner
|
||||||
|
// So serialize it manually
|
||||||
|
*(uint32*)data = _usage._flags.to_ulong();
|
||||||
|
data += sizeof(uint32);
|
||||||
|
|
||||||
|
*(TextureUsageType*)data = _usageType;
|
||||||
|
data += sizeof(TextureUsageType);
|
||||||
|
|
||||||
|
return data + PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unserialize(const Byte* data, size_t size) {
|
||||||
|
if (size != SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Version version = *(const Version*)data;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data += sizeof(Version);
|
||||||
|
|
||||||
|
memcpy(&_samplerDesc, data, sizeof(Sampler::Desc));
|
||||||
|
data += sizeof(Sampler::Desc);
|
||||||
|
|
||||||
|
// We can't copy the bitset in Texture::Usage in a crossplateform manner
|
||||||
|
// So unserialize it manually
|
||||||
|
_usage = Texture::Usage(*(const uint32*)data);
|
||||||
|
data += sizeof(uint32);
|
||||||
|
|
||||||
|
_usageType = *(const TextureUsageType*)data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string KEY;
|
|
||||||
static bool isGPUKTX(const ktx::KeyValue& val) {
|
static bool isGPUKTX(const ktx::KeyValue& val) {
|
||||||
return (val._key.compare(KEY) == 0);
|
return (val._key.compare(KEY) == 0);
|
||||||
}
|
}
|
||||||
|
@ -32,24 +91,35 @@ struct GPUKTXPayload {
|
||||||
static bool findInKeyValues(const ktx::KeyValues& keyValues, GPUKTXPayload& payload) {
|
static bool findInKeyValues(const ktx::KeyValues& keyValues, GPUKTXPayload& payload) {
|
||||||
auto found = std::find_if(keyValues.begin(), keyValues.end(), isGPUKTX);
|
auto found = std::find_if(keyValues.begin(), keyValues.end(), isGPUKTX);
|
||||||
if (found != keyValues.end()) {
|
if (found != keyValues.end()) {
|
||||||
if ((*found)._value.size() == sizeof(GPUKTXPayload)) {
|
auto value = found->_value;
|
||||||
memcpy(&payload, (*found)._value.data(), sizeof(GPUKTXPayload));
|
return payload.unserialize(value.data(), value.size());
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const std::string GPUKTXPayload::KEY { "hifi.gpu" };
|
||||||
std::string GPUKTXPayload::KEY { "hifi.gpu" };
|
|
||||||
|
|
||||||
KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
|
KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
|
||||||
{
|
{
|
||||||
|
// We are doing a lot of work here just to get descriptor data
|
||||||
ktx::StoragePointer storage{ new storage::FileStorage(_filename.c_str()) };
|
ktx::StoragePointer storage{ new storage::FileStorage(_filename.c_str()) };
|
||||||
auto ktxPointer = ktx::KTX::create(storage);
|
auto ktxPointer = ktx::KTX::create(storage);
|
||||||
_ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor()));
|
_ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor()));
|
||||||
|
if (_ktxDescriptor->images.size() < _ktxDescriptor->header.numberOfMipmapLevels) {
|
||||||
|
qWarning() << "Bad images found in ktx";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_offsetToMinMipKV = _ktxDescriptor->getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
|
||||||
|
if (_offsetToMinMipKV) {
|
||||||
|
auto data = storage->data() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV;
|
||||||
|
_minMipLevelAvailable = *data;
|
||||||
|
} else {
|
||||||
|
// Assume all mip levels are available
|
||||||
|
_minMipLevelAvailable = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// now that we know the ktx, let's get the header info to configure this Texture::Storage:
|
// now that we know the ktx, let's get the header info to configure this Texture::Storage:
|
||||||
Format mipFormat = Format::COLOR_BGRA_32;
|
Format mipFormat = Format::COLOR_BGRA_32;
|
||||||
Format texelFormat = Format::COLOR_SRGBA_32;
|
Format texelFormat = Format::COLOR_SRGBA_32;
|
||||||
|
@ -58,6 +128,27 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() {
|
||||||
|
std::shared_ptr<storage::FileStorage> file = _cacheFile.lock();
|
||||||
|
if (file) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock{ _cacheFileCreateMutex };
|
||||||
|
|
||||||
|
file = _cacheFile.lock();
|
||||||
|
if (file) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = std::make_shared<storage::FileStorage>(_filename.c_str());
|
||||||
|
_cacheFile = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
|
PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
|
||||||
storage::StoragePointer result;
|
storage::StoragePointer result;
|
||||||
auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
|
auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
|
||||||
|
@ -72,6 +163,58 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
|
||||||
return _ktxDescriptor->getMipFaceTexelsSize(level, face);
|
return _ktxDescriptor->getMipFaceTexelsSize(level, face);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
|
||||||
|
return level >= _minMipLevelAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 KtxStorage::minAvailableMipLevel() const {
|
||||||
|
return _minMipLevelAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
|
||||||
|
if (level != _minMipLevelAvailable - 1) {
|
||||||
|
qWarning() << "Invalid level to be stored, expected: " << (_minMipLevelAvailable - 1) << ", got: " << level << " " << _filename.c_str();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level >= _ktxDescriptor->images.size()) {
|
||||||
|
throw std::runtime_error("Invalid level");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storage->size() != _ktxDescriptor->images[level]._imageSize) {
|
||||||
|
qWarning() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize
|
||||||
|
<< ", level: " << level << ", filename: " << QString::fromStdString(_filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file = maybeOpenFile();
|
||||||
|
|
||||||
|
auto imageData = file->mutableData();
|
||||||
|
imageData += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
|
||||||
|
imageData += ktx::IMAGE_SIZE_WIDTH;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock { _cacheFileWriteMutex };
|
||||||
|
|
||||||
|
if (level != _minMipLevelAvailable - 1) {
|
||||||
|
qWarning() << "Invalid level to be stored";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(imageData, storage->data(), _ktxDescriptor->images[level]._imageSize);
|
||||||
|
_minMipLevelAvailable = level;
|
||||||
|
if (_offsetToMinMipKV > 0) {
|
||||||
|
auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV;
|
||||||
|
memcpy(minMipKeyData, (void*)&_minMipLevelAvailable, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) {
|
||||||
|
throw std::runtime_error("Invalid call");
|
||||||
|
}
|
||||||
|
|
||||||
void Texture::setKtxBacking(const std::string& filename) {
|
void Texture::setKtxBacking(const std::string& filename) {
|
||||||
// Check the KTX file for validity before using it as backing storage
|
// Check the KTX file for validity before using it as backing storage
|
||||||
{
|
{
|
||||||
|
@ -86,6 +229,7 @@ void Texture::setKtxBacking(const std::string& filename) {
|
||||||
setStorage(newBacking);
|
setStorage(newBacking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
|
ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
|
||||||
ktx::Header header;
|
ktx::Header header;
|
||||||
|
|
||||||
|
@ -141,19 +285,21 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
|
||||||
header.numberOfMipmapLevels = texture.getNumMips();
|
header.numberOfMipmapLevels = texture.getNumMips();
|
||||||
|
|
||||||
ktx::Images images;
|
ktx::Images images;
|
||||||
|
uint32_t imageOffset = 0;
|
||||||
for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) {
|
for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) {
|
||||||
auto mip = texture.accessStoredMipFace(level);
|
auto mip = texture.accessStoredMipFace(level);
|
||||||
if (mip) {
|
if (mip) {
|
||||||
if (numFaces == 1) {
|
if (numFaces == 1) {
|
||||||
images.emplace_back(ktx::Image((uint32_t)mip->getSize(), 0, mip->readData()));
|
images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, mip->readData()));
|
||||||
} else {
|
} else {
|
||||||
ktx::Image::FaceBytes cubeFaces(Texture::CUBE_FACE_COUNT);
|
ktx::Image::FaceBytes cubeFaces(Texture::CUBE_FACE_COUNT);
|
||||||
cubeFaces[0] = mip->readData();
|
cubeFaces[0] = mip->readData();
|
||||||
for (uint32_t face = 1; face < Texture::CUBE_FACE_COUNT; face++) {
|
for (uint32_t face = 1; face < Texture::CUBE_FACE_COUNT; face++) {
|
||||||
cubeFaces[face] = texture.accessStoredMipFace(level, face)->readData();
|
cubeFaces[face] = texture.accessStoredMipFace(level, face)->readData();
|
||||||
}
|
}
|
||||||
images.emplace_back(ktx::Image((uint32_t)mip->getSize(), 0, cubeFaces));
|
images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, cubeFaces));
|
||||||
}
|
}
|
||||||
|
imageOffset += static_cast<uint32_t>(mip->getSize()) + ktx::IMAGE_SIZE_WIDTH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,13 +307,18 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
|
||||||
keyval._samplerDesc = texture.getSampler().getDesc();
|
keyval._samplerDesc = texture.getSampler().getDesc();
|
||||||
keyval._usage = texture.getUsage();
|
keyval._usage = texture.getUsage();
|
||||||
keyval._usageType = texture.getUsageType();
|
keyval._usageType = texture.getUsageType();
|
||||||
ktx::KeyValues keyValues;
|
Byte keyvalPayload[GPUKTXPayload::SIZE];
|
||||||
keyValues.emplace_back(ktx::KeyValue(GPUKTXPayload::KEY, sizeof(GPUKTXPayload), (ktx::Byte*) &keyval));
|
keyval.serialize(keyvalPayload);
|
||||||
|
|
||||||
|
ktx::KeyValues keyValues;
|
||||||
|
keyValues.emplace_back(GPUKTXPayload::KEY, (uint32)GPUKTXPayload::SIZE, (ktx::Byte*) &keyvalPayload);
|
||||||
|
|
||||||
static const std::string SOURCE_HASH_KEY = "hifi.sourceHash";
|
|
||||||
auto hash = texture.sourceHash();
|
auto hash = texture.sourceHash();
|
||||||
if (!hash.empty()) {
|
if (!hash.empty()) {
|
||||||
keyValues.emplace_back(ktx::KeyValue(SOURCE_HASH_KEY, static_cast<uint32>(hash.size()), (ktx::Byte*) hash.c_str()));
|
// the sourceHash is an std::string in hex
|
||||||
|
// we use QByteArray to take the hex and turn it into the smaller binary representation (16 bytes)
|
||||||
|
auto binaryHash = QByteArray::fromHex(QByteArray::fromStdString(hash));
|
||||||
|
keyValues.emplace_back(SOURCE_HASH_KEY, static_cast<uint32>(binaryHash.size()), (ktx::Byte*) binaryHash.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ktxBuffer = ktx::KTX::create(header, images, keyValues);
|
auto ktxBuffer = ktx::KTX::create(header, images, keyValues);
|
||||||
|
@ -200,13 +351,17 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
|
||||||
return ktxBuffer;
|
return ktxBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
TexturePointer Texture::unserialize(const std::string& ktxfile, TextureUsageType usageType, Usage usage, const Sampler::Desc& sampler) {
|
TexturePointer Texture::unserialize(const std::string& ktxfile) {
|
||||||
std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(ktx::StoragePointer { new 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
ktx::KTXDescriptor descriptor { ktxPointer->toDescriptor() };
|
ktx::KTXDescriptor descriptor { ktxPointer->toDescriptor() };
|
||||||
|
return unserialize(ktxfile, ktxPointer->toDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDescriptor& descriptor) {
|
||||||
const auto& header = descriptor.header;
|
const auto& header = descriptor.header;
|
||||||
|
|
||||||
Format mipFormat = Format::COLOR_BGRA_32;
|
Format mipFormat = Format::COLOR_BGRA_32;
|
||||||
|
@ -232,12 +387,13 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, TextureUsageType
|
||||||
type = TEX_3D;
|
type = TEX_3D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If found, use the
|
|
||||||
GPUKTXPayload gpuktxKeyValue;
|
GPUKTXPayload gpuktxKeyValue;
|
||||||
bool isGPUKTXPayload = GPUKTXPayload::findInKeyValues(descriptor.keyValues, gpuktxKeyValue);
|
if (!GPUKTXPayload::findInKeyValues(descriptor.keyValues, gpuktxKeyValue)) {
|
||||||
|
qCWarning(gpulogging) << "Could not find GPUKTX key values.";
|
||||||
|
return TexturePointer();
|
||||||
|
}
|
||||||
|
|
||||||
auto tex = Texture::create( (isGPUKTXPayload ? gpuktxKeyValue._usageType : usageType),
|
auto texture = create(gpuktxKeyValue._usageType,
|
||||||
type,
|
type,
|
||||||
texelFormat,
|
texelFormat,
|
||||||
header.getPixelWidth(),
|
header.getPixelWidth(),
|
||||||
|
@ -246,14 +402,13 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, TextureUsageType
|
||||||
1, // num Samples
|
1, // num Samples
|
||||||
header.getNumberOfSlices(),
|
header.getNumberOfSlices(),
|
||||||
header.getNumberOfLevels(),
|
header.getNumberOfLevels(),
|
||||||
(isGPUKTXPayload ? gpuktxKeyValue._samplerDesc : sampler));
|
gpuktxKeyValue._samplerDesc);
|
||||||
|
texture->setUsage(gpuktxKeyValue._usage);
|
||||||
tex->setUsage((isGPUKTXPayload ? gpuktxKeyValue._usage : usage));
|
|
||||||
|
|
||||||
// Assing the mips availables
|
// Assing the mips availables
|
||||||
tex->setStoredMipFormat(mipFormat);
|
texture->setStoredMipFormat(mipFormat);
|
||||||
tex->setKtxBacking(ktxfile);
|
texture->setKtxBacking(ktxfile);
|
||||||
return tex;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header) {
|
bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header) {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "KTX.h"
|
#include "KTX.h"
|
||||||
|
|
||||||
#include <algorithm> //min max and more
|
#include <algorithm> //min max and more
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
using namespace ktx;
|
using namespace ktx;
|
||||||
|
|
||||||
|
@ -34,30 +35,80 @@ uint32_t Header::evalMaxDimension() const {
|
||||||
return std::max(getPixelWidth(), std::max(getPixelHeight(), getPixelDepth()));
|
return std::max(getPixelWidth(), std::max(getPixelHeight(), getPixelDepth()));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Header::evalPixelWidth(uint32_t level) const {
|
uint32_t Header::evalPixelOrBlockWidth(uint32_t level) const {
|
||||||
return std::max(getPixelWidth() >> level, 1U);
|
auto pixelWidth = std::max(getPixelWidth() >> level, 1U);
|
||||||
|
if (getGLType() == GLType::COMPRESSED_TYPE) {
|
||||||
|
return (pixelWidth + 3) / 4;
|
||||||
|
} else {
|
||||||
|
return pixelWidth;
|
||||||
}
|
}
|
||||||
uint32_t Header::evalPixelHeight(uint32_t level) const {
|
|
||||||
return std::max(getPixelHeight() >> level, 1U);
|
|
||||||
}
|
}
|
||||||
uint32_t Header::evalPixelDepth(uint32_t level) const {
|
uint32_t Header::evalPixelOrBlockHeight(uint32_t level) const {
|
||||||
|
auto pixelWidth = std::max(getPixelHeight() >> level, 1U);
|
||||||
|
if (getGLType() == GLType::COMPRESSED_TYPE) {
|
||||||
|
auto format = getGLInternaFormat_Compressed();
|
||||||
|
switch (format) {
|
||||||
|
case GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT: // BC1
|
||||||
|
case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: // BC1A
|
||||||
|
case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: // BC3
|
||||||
|
case GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1: // BC4
|
||||||
|
case GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2: // BC5
|
||||||
|
return (pixelWidth + 3) / 4;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unknown format");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return pixelWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t Header::evalPixelOrBlockDepth(uint32_t level) const {
|
||||||
return std::max(getPixelDepth() >> level, 1U);
|
return std::max(getPixelDepth() >> level, 1U);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Header::evalPixelSize() const {
|
size_t Header::evalPixelOrBlockSize() const {
|
||||||
return glTypeSize; // Really we should generate the size from the FOrmat etc
|
if (getGLType() == GLType::COMPRESSED_TYPE) {
|
||||||
|
auto format = getGLInternaFormat_Compressed();
|
||||||
|
if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
||||||
|
return 8;
|
||||||
|
} else if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
|
||||||
|
return 8;
|
||||||
|
} else if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
|
||||||
|
return 16;
|
||||||
|
} else if (format == GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1) {
|
||||||
|
return 8;
|
||||||
|
} else if (format == GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto baseFormat = getGLBaseInternalFormat();
|
||||||
|
if (baseFormat == GLBaseInternalFormat::RED) {
|
||||||
|
return 1;
|
||||||
|
} else if (baseFormat == GLBaseInternalFormat::RG) {
|
||||||
|
return 2;
|
||||||
|
} else if (baseFormat == GLBaseInternalFormat::RGB) {
|
||||||
|
return 3;
|
||||||
|
} else if (baseFormat == GLBaseInternalFormat::RGBA) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "Unknown ktx format: " << glFormat << " " << glBaseInternalFormat << " " << glInternalFormat;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Header::evalRowSize(uint32_t level) const {
|
size_t Header::evalRowSize(uint32_t level) const {
|
||||||
auto pixWidth = evalPixelWidth(level);
|
auto pixWidth = evalPixelOrBlockWidth(level);
|
||||||
auto pixSize = evalPixelSize();
|
auto pixSize = evalPixelOrBlockSize();
|
||||||
|
if (pixSize == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
auto netSize = pixWidth * pixSize;
|
auto netSize = pixWidth * pixSize;
|
||||||
auto padding = evalPadding(netSize);
|
auto padding = evalPadding(netSize);
|
||||||
return netSize + padding;
|
return netSize + padding;
|
||||||
}
|
}
|
||||||
size_t Header::evalFaceSize(uint32_t level) const {
|
size_t Header::evalFaceSize(uint32_t level) const {
|
||||||
auto pixHeight = evalPixelHeight(level);
|
auto pixHeight = evalPixelOrBlockHeight(level);
|
||||||
auto pixDepth = evalPixelDepth(level);
|
auto pixDepth = evalPixelOrBlockDepth(level);
|
||||||
auto rowSize = evalRowSize(level);
|
auto rowSize = evalRowSize(level);
|
||||||
return pixDepth * pixHeight * rowSize;
|
return pixDepth * pixHeight * rowSize;
|
||||||
}
|
}
|
||||||
|
@ -71,6 +122,47 @@ size_t Header::evalImageSize(uint32_t level) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t KTXDescriptor::getValueOffsetForKey(const std::string& key) const {
|
||||||
|
size_t offset { 0 };
|
||||||
|
for (auto& kv : keyValues) {
|
||||||
|
if (kv._key == key) {
|
||||||
|
return offset + ktx::KV_SIZE_WIDTH + kv._key.size() + 1;
|
||||||
|
}
|
||||||
|
offset += kv.serializedByteSize();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDescriptors Header::generateImageDescriptors() const {
|
||||||
|
ImageDescriptors descriptors;
|
||||||
|
|
||||||
|
size_t imageOffset = 0;
|
||||||
|
for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) {
|
||||||
|
auto imageSize = static_cast<uint32_t>(evalImageSize(level));
|
||||||
|
if (imageSize == 0) {
|
||||||
|
return ImageDescriptors();
|
||||||
|
}
|
||||||
|
ImageHeader header {
|
||||||
|
numberOfFaces == NUM_CUBEMAPFACES,
|
||||||
|
imageOffset,
|
||||||
|
imageSize,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
imageOffset += (imageSize * numberOfFaces) + ktx::IMAGE_SIZE_WIDTH;
|
||||||
|
|
||||||
|
ImageHeader::FaceOffsets offsets;
|
||||||
|
// TODO Add correct face offsets
|
||||||
|
for (uint32_t i = 0; i < numberOfFaces; ++i) {
|
||||||
|
offsets.push_back(0);
|
||||||
|
}
|
||||||
|
descriptors.push_back(ImageDescriptor(header, offsets));
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
KeyValue::KeyValue(const std::string& key, uint32_t valueByteSize, const Byte* value) :
|
KeyValue::KeyValue(const std::string& key, uint32_t valueByteSize, const Byte* value) :
|
||||||
_byteSize((uint32_t) key.size() + 1 + valueByteSize), // keyString size + '\0' ending char + the value size
|
_byteSize((uint32_t) key.size() + 1 + valueByteSize), // keyString size + '\0' ending char + the value size
|
||||||
_key(key),
|
_key(key),
|
||||||
|
|
|
@ -71,6 +71,8 @@ end
|
||||||
|
|
||||||
namespace ktx {
|
namespace ktx {
|
||||||
const uint32_t PACKING_SIZE { sizeof(uint32_t) };
|
const uint32_t PACKING_SIZE { sizeof(uint32_t) };
|
||||||
|
const std::string HIFI_MIN_POPULATED_MIP_KEY{ "hifi.minMip" };
|
||||||
|
|
||||||
using Byte = uint8_t;
|
using Byte = uint8_t;
|
||||||
|
|
||||||
enum class GLType : uint32_t {
|
enum class GLType : uint32_t {
|
||||||
|
@ -292,6 +294,11 @@ namespace ktx {
|
||||||
using Storage = storage::Storage;
|
using Storage = storage::Storage;
|
||||||
using StoragePointer = std::shared_ptr<Storage>;
|
using StoragePointer = std::shared_ptr<Storage>;
|
||||||
|
|
||||||
|
struct ImageDescriptor;
|
||||||
|
using ImageDescriptors = std::vector<ImageDescriptor>;
|
||||||
|
|
||||||
|
bool checkIdentifier(const Byte* identifier);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
struct Header {
|
struct Header {
|
||||||
static const size_t IDENTIFIER_LENGTH = 12;
|
static const size_t IDENTIFIER_LENGTH = 12;
|
||||||
|
@ -330,11 +337,11 @@ namespace ktx {
|
||||||
uint32_t getNumberOfLevels() const { return (numberOfMipmapLevels ? numberOfMipmapLevels : 1); }
|
uint32_t getNumberOfLevels() const { return (numberOfMipmapLevels ? numberOfMipmapLevels : 1); }
|
||||||
|
|
||||||
uint32_t evalMaxDimension() const;
|
uint32_t evalMaxDimension() const;
|
||||||
uint32_t evalPixelWidth(uint32_t level) const;
|
uint32_t evalPixelOrBlockWidth(uint32_t level) const;
|
||||||
uint32_t evalPixelHeight(uint32_t level) const;
|
uint32_t evalPixelOrBlockHeight(uint32_t level) const;
|
||||||
uint32_t evalPixelDepth(uint32_t level) const;
|
uint32_t evalPixelOrBlockDepth(uint32_t level) const;
|
||||||
|
|
||||||
size_t evalPixelSize() const;
|
size_t evalPixelOrBlockSize() const;
|
||||||
size_t evalRowSize(uint32_t level) const;
|
size_t evalRowSize(uint32_t level) const;
|
||||||
size_t evalFaceSize(uint32_t level) const;
|
size_t evalFaceSize(uint32_t level) const;
|
||||||
size_t evalImageSize(uint32_t level) const;
|
size_t evalImageSize(uint32_t level) const;
|
||||||
|
@ -378,7 +385,12 @@ namespace ktx {
|
||||||
void setCube(uint32_t width, uint32_t height) { setDimensions(width, height, 0, 0, NUM_CUBEMAPFACES); }
|
void setCube(uint32_t width, uint32_t height) { setDimensions(width, height, 0, 0, NUM_CUBEMAPFACES); }
|
||||||
void setCubeArray(uint32_t width, uint32_t height, uint32_t numSlices) { setDimensions(width, height, 0, (numSlices > 0 ? numSlices : 1), NUM_CUBEMAPFACES); }
|
void setCubeArray(uint32_t width, uint32_t height, uint32_t numSlices) { setDimensions(width, height, 0, (numSlices > 0 ? numSlices : 1), NUM_CUBEMAPFACES); }
|
||||||
|
|
||||||
|
ImageDescriptors generateImageDescriptors() const;
|
||||||
};
|
};
|
||||||
|
static const size_t KTX_HEADER_SIZE = 64;
|
||||||
|
static_assert(sizeof(Header) == KTX_HEADER_SIZE, "KTX Header size is static and should not change from the spec");
|
||||||
|
static const size_t KV_SIZE_WIDTH = 4; // Number of bytes for keyAndValueByteSize
|
||||||
|
static const size_t IMAGE_SIZE_WIDTH = 4; // Number of bytes for imageSize
|
||||||
|
|
||||||
// Key Values
|
// Key Values
|
||||||
struct KeyValue {
|
struct KeyValue {
|
||||||
|
@ -405,12 +417,17 @@ namespace ktx {
|
||||||
struct ImageHeader {
|
struct ImageHeader {
|
||||||
using FaceOffsets = std::vector<size_t>;
|
using FaceOffsets = std::vector<size_t>;
|
||||||
using FaceBytes = std::vector<const Byte*>;
|
using FaceBytes = std::vector<const Byte*>;
|
||||||
|
|
||||||
|
// This is the byte offset from the _start_ of the image region. For example, level 0
|
||||||
|
// will have a byte offset of 0.
|
||||||
const uint32_t _numFaces;
|
const uint32_t _numFaces;
|
||||||
|
const size_t _imageOffset;
|
||||||
const uint32_t _imageSize;
|
const uint32_t _imageSize;
|
||||||
const uint32_t _faceSize;
|
const uint32_t _faceSize;
|
||||||
const uint32_t _padding;
|
const uint32_t _padding;
|
||||||
ImageHeader(bool cube, uint32_t imageSize, uint32_t padding) :
|
ImageHeader(bool cube, size_t imageOffset, uint32_t imageSize, uint32_t padding) :
|
||||||
_numFaces(cube ? NUM_CUBEMAPFACES : 1),
|
_numFaces(cube ? NUM_CUBEMAPFACES : 1),
|
||||||
|
_imageOffset(imageOffset),
|
||||||
_imageSize(imageSize * _numFaces),
|
_imageSize(imageSize * _numFaces),
|
||||||
_faceSize(imageSize),
|
_faceSize(imageSize),
|
||||||
_padding(padding) {
|
_padding(padding) {
|
||||||
|
@ -419,22 +436,22 @@ namespace ktx {
|
||||||
|
|
||||||
struct Image;
|
struct Image;
|
||||||
|
|
||||||
|
// Image without the image data itself
|
||||||
struct ImageDescriptor : public ImageHeader {
|
struct ImageDescriptor : public ImageHeader {
|
||||||
const FaceOffsets _faceOffsets;
|
const FaceOffsets _faceOffsets;
|
||||||
ImageDescriptor(const ImageHeader& header, const FaceOffsets& offsets) : ImageHeader(header), _faceOffsets(offsets) {}
|
ImageDescriptor(const ImageHeader& header, const FaceOffsets& offsets) : ImageHeader(header), _faceOffsets(offsets) {}
|
||||||
Image toImage(const ktx::StoragePointer& storage) const;
|
Image toImage(const ktx::StoragePointer& storage) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ImageDescriptors = std::vector<ImageDescriptor>;
|
// Image with the image data itself
|
||||||
|
|
||||||
struct Image : public ImageHeader {
|
struct Image : public ImageHeader {
|
||||||
FaceBytes _faceBytes;
|
FaceBytes _faceBytes;
|
||||||
Image(const ImageHeader& header, const FaceBytes& faces) : ImageHeader(header), _faceBytes(faces) {}
|
Image(const ImageHeader& header, const FaceBytes& faces) : ImageHeader(header), _faceBytes(faces) {}
|
||||||
Image(uint32_t imageSize, uint32_t padding, const Byte* bytes) :
|
Image(size_t imageOffset, uint32_t imageSize, uint32_t padding, const Byte* bytes) :
|
||||||
ImageHeader(false, imageSize, padding),
|
ImageHeader(false, imageOffset, imageSize, padding),
|
||||||
_faceBytes(1, bytes) {}
|
_faceBytes(1, bytes) {}
|
||||||
Image(uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) :
|
Image(size_t imageOffset, uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) :
|
||||||
ImageHeader(true, pageSize, padding)
|
ImageHeader(true, imageOffset, pageSize, padding)
|
||||||
{
|
{
|
||||||
if (cubeFaceBytes.size() == NUM_CUBEMAPFACES) {
|
if (cubeFaceBytes.size() == NUM_CUBEMAPFACES) {
|
||||||
_faceBytes = cubeFaceBytes;
|
_faceBytes = cubeFaceBytes;
|
||||||
|
@ -457,6 +474,7 @@ namespace ktx {
|
||||||
const ImageDescriptors images;
|
const ImageDescriptors images;
|
||||||
size_t getMipFaceTexelsSize(uint16_t mip = 0, uint8_t face = 0) const;
|
size_t getMipFaceTexelsSize(uint16_t mip = 0, uint8_t face = 0) const;
|
||||||
size_t getMipFaceTexelsOffset(uint16_t mip = 0, uint8_t face = 0) const;
|
size_t getMipFaceTexelsOffset(uint16_t mip = 0, uint8_t face = 0) const;
|
||||||
|
size_t getValueOffsetForKey(const std::string& key) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class KTX {
|
class KTX {
|
||||||
|
@ -471,6 +489,7 @@ namespace ktx {
|
||||||
// This path allocate the Storage where to store header, keyvalues and copy mips
|
// This path allocate the Storage where to store header, keyvalues and copy mips
|
||||||
// Then COPY all the data
|
// Then COPY all the data
|
||||||
static std::unique_ptr<KTX> create(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
|
static std::unique_ptr<KTX> create(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
|
||||||
|
static std::unique_ptr<KTX> createBare(const Header& header, const KeyValues& keyValues = KeyValues());
|
||||||
|
|
||||||
// Instead of creating a full Copy of the src data in a KTX object, the write serialization can be performed with the
|
// Instead of creating a full Copy of the src data in a KTX object, the write serialization can be performed with the
|
||||||
// following two functions
|
// following two functions
|
||||||
|
@ -484,10 +503,14 @@ namespace ktx {
|
||||||
//
|
//
|
||||||
// This is exactly what is done in the create function
|
// This is exactly what is done in the create function
|
||||||
static size_t evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
|
static size_t evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
|
||||||
|
static size_t evalStorageSize(const Header& header, const ImageDescriptors& images, const KeyValues& keyValues = KeyValues());
|
||||||
static size_t write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
|
static size_t write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
|
||||||
|
static size_t writeWithoutImages(Byte* destBytes, size_t destByteSize, const Header& header, const ImageDescriptors& descriptors, const KeyValues& keyValues = KeyValues());
|
||||||
static size_t writeKeyValues(Byte* destBytes, size_t destByteSize, const KeyValues& keyValues);
|
static size_t writeKeyValues(Byte* destBytes, size_t destByteSize, const KeyValues& keyValues);
|
||||||
static Images writeImages(Byte* destBytes, size_t destByteSize, const Images& images);
|
static Images writeImages(Byte* destBytes, size_t destByteSize, const Images& images);
|
||||||
|
|
||||||
|
void writeMipData(uint16_t level, const Byte* sourceBytes, size_t source_size);
|
||||||
|
|
||||||
// Parse a block of memory and create a KTX object from it
|
// Parse a block of memory and create a KTX object from it
|
||||||
static std::unique_ptr<KTX> create(const StoragePointer& src);
|
static std::unique_ptr<KTX> create(const StoragePointer& src);
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,7 @@ namespace ktx {
|
||||||
while ((currentPtr - srcBytes) + sizeof(uint32_t) <= (srcSize)) {
|
while ((currentPtr - srcBytes) + sizeof(uint32_t) <= (srcSize)) {
|
||||||
|
|
||||||
// Grab the imageSize coming up
|
// Grab the imageSize coming up
|
||||||
|
uint32_t imageOffset = currentPtr - srcBytes;
|
||||||
size_t imageSize = *reinterpret_cast<const uint32_t*>(currentPtr);
|
size_t imageSize = *reinterpret_cast<const uint32_t*>(currentPtr);
|
||||||
currentPtr += sizeof(uint32_t);
|
currentPtr += sizeof(uint32_t);
|
||||||
|
|
||||||
|
@ -158,10 +159,10 @@ namespace ktx {
|
||||||
faces[face] = currentPtr;
|
faces[face] = currentPtr;
|
||||||
currentPtr += faceSize;
|
currentPtr += faceSize;
|
||||||
}
|
}
|
||||||
images.emplace_back(Image((uint32_t) faceSize, padding, faces));
|
images.emplace_back(Image(imageOffset, (uint32_t) faceSize, padding, faces));
|
||||||
currentPtr += padding;
|
currentPtr += padding;
|
||||||
} else {
|
} else {
|
||||||
images.emplace_back(Image((uint32_t) imageSize, padding, currentPtr));
|
images.emplace_back(Image(imageOffset, (uint32_t) imageSize, padding, currentPtr));
|
||||||
currentPtr += imageSize + padding;
|
currentPtr += imageSize + padding;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -40,6 +40,24 @@ namespace ktx {
|
||||||
return create(storagePointer);
|
return create(storagePointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<KTX> KTX::createBare(const Header& header, const KeyValues& keyValues) {
|
||||||
|
auto descriptors = header.generateImageDescriptors();
|
||||||
|
|
||||||
|
Byte minMip = header.numberOfMipmapLevels;
|
||||||
|
auto newKeyValues = keyValues;
|
||||||
|
newKeyValues.emplace_back(KeyValue(HIFI_MIN_POPULATED_MIP_KEY, sizeof(Byte), &minMip));
|
||||||
|
|
||||||
|
StoragePointer storagePointer;
|
||||||
|
{
|
||||||
|
auto storageSize = ktx::KTX::evalStorageSize(header, descriptors, newKeyValues);
|
||||||
|
auto memoryStorage = new storage::MemoryStorage(storageSize);
|
||||||
|
qDebug() << "Memory storage size is: " << storageSize;
|
||||||
|
ktx::KTX::writeWithoutImages(memoryStorage->data(), memoryStorage->size(), header, descriptors, newKeyValues);
|
||||||
|
storagePointer.reset(memoryStorage);
|
||||||
|
}
|
||||||
|
return create(storagePointer);
|
||||||
|
}
|
||||||
|
|
||||||
size_t KTX::evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues) {
|
size_t KTX::evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues) {
|
||||||
size_t storageSize = sizeof(Header);
|
size_t storageSize = sizeof(Header);
|
||||||
|
|
||||||
|
@ -59,6 +77,25 @@ namespace ktx {
|
||||||
return storageSize;
|
return storageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t KTX::evalStorageSize(const Header& header, const ImageDescriptors& imageDescriptors, const KeyValues& keyValues) {
|
||||||
|
size_t storageSize = sizeof(Header);
|
||||||
|
|
||||||
|
if (!keyValues.empty()) {
|
||||||
|
size_t keyValuesSize = KeyValue::serializedKeyValuesByteSize(keyValues);
|
||||||
|
storageSize += keyValuesSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto numMips = header.getNumberOfLevels();
|
||||||
|
for (uint32_t l = 0; l < numMips; l++) {
|
||||||
|
if (imageDescriptors.size() > l) {
|
||||||
|
storageSize += sizeof(uint32_t);
|
||||||
|
storageSize += imageDescriptors[l]._imageSize;
|
||||||
|
storageSize += Header::evalPadding(imageDescriptors[l]._imageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return storageSize;
|
||||||
|
}
|
||||||
|
|
||||||
size_t KTX::write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& srcImages, const KeyValues& keyValues) {
|
size_t KTX::write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& srcImages, const KeyValues& keyValues) {
|
||||||
// Check again that we have enough destination capacity
|
// Check again that we have enough destination capacity
|
||||||
if (!destBytes || (destByteSize < evalStorageSize(header, srcImages, keyValues))) {
|
if (!destBytes || (destByteSize < evalStorageSize(header, srcImages, keyValues))) {
|
||||||
|
@ -87,6 +124,43 @@ namespace ktx {
|
||||||
return destByteSize;
|
return destByteSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t KTX::writeWithoutImages(Byte* destBytes, size_t destByteSize, const Header& header, const ImageDescriptors& descriptors, const KeyValues& keyValues) {
|
||||||
|
// Check again that we have enough destination capacity
|
||||||
|
if (!destBytes || (destByteSize < evalStorageSize(header, descriptors, keyValues))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentDestPtr = destBytes;
|
||||||
|
// Header
|
||||||
|
auto destHeader = reinterpret_cast<Header*>(currentDestPtr);
|
||||||
|
memcpy(currentDestPtr, &header, sizeof(Header));
|
||||||
|
currentDestPtr += sizeof(Header);
|
||||||
|
|
||||||
|
|
||||||
|
// KeyValues
|
||||||
|
if (!keyValues.empty()) {
|
||||||
|
destHeader->bytesOfKeyValueData = (uint32_t) writeKeyValues(currentDestPtr, destByteSize - sizeof(Header), keyValues);
|
||||||
|
} else {
|
||||||
|
// Make sure the header contains the right bytesOfKeyValueData size
|
||||||
|
destHeader->bytesOfKeyValueData = 0;
|
||||||
|
}
|
||||||
|
currentDestPtr += destHeader->bytesOfKeyValueData;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < descriptors.size(); ++i) {
|
||||||
|
auto ptr = reinterpret_cast<uint32_t*>(currentDestPtr);
|
||||||
|
*ptr = descriptors[i]._imageSize;
|
||||||
|
ptr++;
|
||||||
|
#ifdef DEBUG
|
||||||
|
for (size_t k = 0; k < descriptors[i]._imageSize/4; k++) {
|
||||||
|
*(ptr + k) = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
currentDestPtr += descriptors[i]._imageSize + sizeof(uint32_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return destByteSize;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t KeyValue::writeSerializedKeyAndValue(Byte* destBytes, uint32_t destByteSize, const KeyValue& keyval) {
|
uint32_t KeyValue::writeSerializedKeyAndValue(Byte* destBytes, uint32_t destByteSize, const KeyValue& keyval) {
|
||||||
uint32_t keyvalSize = keyval.serializedByteSize();
|
uint32_t keyvalSize = keyval.serializedByteSize();
|
||||||
if (keyvalSize > destByteSize) {
|
if (keyvalSize > destByteSize) {
|
||||||
|
@ -134,6 +208,7 @@ namespace ktx {
|
||||||
|
|
||||||
for (uint32_t l = 0; l < srcImages.size(); l++) {
|
for (uint32_t l = 0; l < srcImages.size(); l++) {
|
||||||
if (currentDataSize + sizeof(uint32_t) < allocatedImagesDataSize) {
|
if (currentDataSize + sizeof(uint32_t) < allocatedImagesDataSize) {
|
||||||
|
uint32_t imageOffset = currentPtr - destBytes;
|
||||||
size_t imageSize = srcImages[l]._imageSize;
|
size_t imageSize = srcImages[l]._imageSize;
|
||||||
*(reinterpret_cast<uint32_t*> (currentPtr)) = (uint32_t) imageSize;
|
*(reinterpret_cast<uint32_t*> (currentPtr)) = (uint32_t) imageSize;
|
||||||
currentPtr += sizeof(uint32_t);
|
currentPtr += sizeof(uint32_t);
|
||||||
|
@ -146,7 +221,7 @@ namespace ktx {
|
||||||
// Single face vs cubes
|
// Single face vs cubes
|
||||||
if (srcImages[l]._numFaces == 1) {
|
if (srcImages[l]._numFaces == 1) {
|
||||||
memcpy(currentPtr, srcImages[l]._faceBytes[0], imageSize);
|
memcpy(currentPtr, srcImages[l]._faceBytes[0], imageSize);
|
||||||
destImages.emplace_back(Image((uint32_t) imageSize, padding, currentPtr));
|
destImages.emplace_back(Image(imageOffset, (uint32_t) imageSize, padding, currentPtr));
|
||||||
currentPtr += imageSize;
|
currentPtr += imageSize;
|
||||||
} else {
|
} else {
|
||||||
Image::FaceBytes faceBytes(NUM_CUBEMAPFACES);
|
Image::FaceBytes faceBytes(NUM_CUBEMAPFACES);
|
||||||
|
@ -156,7 +231,7 @@ namespace ktx {
|
||||||
faceBytes[face] = currentPtr;
|
faceBytes[face] = currentPtr;
|
||||||
currentPtr += faceSize;
|
currentPtr += faceSize;
|
||||||
}
|
}
|
||||||
destImages.emplace_back(Image(faceSize, padding, faceBytes));
|
destImages.emplace_back(Image(imageOffset, faceSize, padding, faceBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPtr += padding;
|
currentPtr += padding;
|
||||||
|
@ -168,4 +243,11 @@ namespace ktx {
|
||||||
return destImages;
|
return destImages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KTX::writeMipData(uint16_t level, const Byte* sourceBytes, size_t sourceSize) {
|
||||||
|
Q_ASSERT(level > 0);
|
||||||
|
Q_ASSERT(level < _images.size());
|
||||||
|
Q_ASSERT(sourceSize == _images[level]._imageSize);
|
||||||
|
|
||||||
|
//memcpy(reinterpret_cast<void*>(_images[level]._faceBytes[0]), sourceBytes, sourceSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,8 @@ public:
|
||||||
void setResource(GeometryResource::Pointer resource);
|
void setResource(GeometryResource::Pointer resource);
|
||||||
|
|
||||||
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
||||||
|
int getResourceDownloadAttempts() { return _resource ? _resource->getDownloadAttempts() : 0; }
|
||||||
|
int getResourceDownloadAttemptsRemaining() { return _resource ? _resource->getDownloadAttemptsRemaining() : 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startWatching();
|
void startWatching();
|
||||||
|
|
|
@ -30,8 +30,6 @@
|
||||||
|
|
||||||
#include <gpu/Batch.h>
|
#include <gpu/Batch.h>
|
||||||
|
|
||||||
#include <ktx/KTX.h>
|
|
||||||
|
|
||||||
#include <image/Image.h>
|
#include <image/Image.h>
|
||||||
|
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
|
@ -40,6 +38,7 @@
|
||||||
#include <Finally.h>
|
#include <Finally.h>
|
||||||
#include <Profile.h>
|
#include <Profile.h>
|
||||||
|
|
||||||
|
#include "NetworkLogging.h"
|
||||||
#include "ModelNetworkingLogging.h"
|
#include "ModelNetworkingLogging.h"
|
||||||
#include <Trace.h>
|
#include <Trace.h>
|
||||||
#include <StatTracker.h>
|
#include <StatTracker.h>
|
||||||
|
@ -51,6 +50,8 @@ Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.k
|
||||||
const std::string TextureCache::KTX_DIRNAME { "ktx_cache" };
|
const std::string TextureCache::KTX_DIRNAME { "ktx_cache" };
|
||||||
const std::string TextureCache::KTX_EXT { "ktx" };
|
const std::string TextureCache::KTX_EXT { "ktx" };
|
||||||
|
|
||||||
|
static const int SKYBOX_LOAD_PRIORITY { 10 }; // Make sure skybox loads first
|
||||||
|
|
||||||
TextureCache::TextureCache() :
|
TextureCache::TextureCache() :
|
||||||
_ktxCache(KTX_DIRNAME, KTX_EXT) {
|
_ktxCache(KTX_DIRNAME, KTX_EXT) {
|
||||||
setUnusedResourceCacheSize(0);
|
setUnusedResourceCacheSize(0);
|
||||||
|
@ -260,15 +261,20 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSh
|
||||||
auto content = textureExtra ? textureExtra->content : QByteArray();
|
auto content = textureExtra ? textureExtra->content : QByteArray();
|
||||||
auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS;
|
auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS;
|
||||||
NetworkTexture* texture = new NetworkTexture(url, type, content, maxNumPixels);
|
NetworkTexture* texture = new NetworkTexture(url, type, content, maxNumPixels);
|
||||||
|
if (type == image::TextureUsage::CUBE_TEXTURE) {
|
||||||
|
texture->setLoadPriority(this, SKYBOX_LOAD_PRIORITY);
|
||||||
|
}
|
||||||
return QSharedPointer<Resource>(texture, &Resource::deleter);
|
return QSharedPointer<Resource>(texture, &Resource::deleter);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
|
NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
|
||||||
Resource(url),
|
Resource(url),
|
||||||
_type(type),
|
_type(type),
|
||||||
|
_sourceIsKTX(url.path().endsWith(".ktx")),
|
||||||
_maxNumPixels(maxNumPixels)
|
_maxNumPixels(maxNumPixels)
|
||||||
{
|
{
|
||||||
_textureSource = std::make_shared<gpu::TextureSource>();
|
_textureSource = std::make_shared<gpu::TextureSource>();
|
||||||
|
_lowestRequestedMipLevel = 0;
|
||||||
|
|
||||||
if (!url.isValid()) {
|
if (!url.isValid()) {
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
|
@ -324,11 +330,333 @@ private:
|
||||||
int _maxNumPixels;
|
int _maxNumPixels;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits<uint16_t>::max();
|
||||||
|
void NetworkTexture::makeRequest() {
|
||||||
|
if (!_sourceIsKTX) {
|
||||||
|
Resource::makeRequest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We special-handle ktx requests to run 2 concurrent requests right off the bat
|
||||||
|
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
|
||||||
|
|
||||||
|
if (_ktxResourceState == PENDING_INITIAL_LOAD) {
|
||||||
|
_ktxResourceState = LOADING_INITIAL_DATA;
|
||||||
|
|
||||||
|
// Add a fragment to the base url so we can identify the section of the ktx being requested when debugging
|
||||||
|
// The actual requested url is _activeUrl and will not contain the fragment
|
||||||
|
_url.setFragment("head");
|
||||||
|
_ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
|
||||||
|
|
||||||
|
if (!_ktxHeaderRequest) {
|
||||||
|
qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
||||||
|
|
||||||
|
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteRange range;
|
||||||
|
range.fromInclusive = 0;
|
||||||
|
range.toExclusive = 1000;
|
||||||
|
_ktxHeaderRequest->setByteRange(range);
|
||||||
|
|
||||||
|
emit loading();
|
||||||
|
|
||||||
|
connect(_ktxHeaderRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxHeaderRequestProgress);
|
||||||
|
connect(_ktxHeaderRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxHeaderRequestFinished);
|
||||||
|
|
||||||
|
_bytesReceived = _bytesTotal = _bytes = 0;
|
||||||
|
|
||||||
|
_ktxHeaderRequest->send();
|
||||||
|
|
||||||
|
startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
|
||||||
|
} else if (_ktxResourceState == PENDING_MIP_REQUEST) {
|
||||||
|
if (_lowestKnownPopulatedMip > 0) {
|
||||||
|
_ktxResourceState = REQUESTING_MIP;
|
||||||
|
|
||||||
|
// Add a fragment to the base url so we can identify the section of the ktx being requested when debugging
|
||||||
|
// The actual requested url is _activeUrl and will not contain the fragment
|
||||||
|
uint16_t nextMip = _lowestKnownPopulatedMip - 1;
|
||||||
|
_url.setFragment(QString::number(nextMip));
|
||||||
|
startMipRangeRequest(nextMip, nextMip);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning(networking) << "NetworkTexture::makeRequest() called while not in a valid state: " << _ktxResourceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTexture::startRequestForNextMipLevel() {
|
||||||
|
if (_lowestKnownPopulatedMip == 0) {
|
||||||
|
qWarning(networking) << "Requesting next mip level but all have been fulfilled: " << _lowestKnownPopulatedMip
|
||||||
|
<< " " << _textureSource->getGPUTexture()->minAvailableMipLevel() << " " << _url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
|
||||||
|
_ktxResourceState = PENDING_MIP_REQUEST;
|
||||||
|
|
||||||
|
init();
|
||||||
|
setLoadPriority(this, -static_cast<int>(_originalKtxDescriptor->header.numberOfMipmapLevels) + _lowestKnownPopulatedMip);
|
||||||
|
_url.setFragment(QString::number(_lowestKnownPopulatedMip - 1));
|
||||||
|
TextureCache::attemptRequest(_self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load mips in the range [low, high] (inclusive)
|
||||||
|
void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
|
||||||
|
if (_ktxMipRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
|
||||||
|
|
||||||
|
_ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
|
||||||
|
|
||||||
|
if (!_ktxMipRequest) {
|
||||||
|
qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
||||||
|
|
||||||
|
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ktxMipLevelRangeInFlight = { low, high };
|
||||||
|
if (isHighMipRequest) {
|
||||||
|
static const int HIGH_MIP_MAX_SIZE = 5516;
|
||||||
|
// This is a special case where we load the high 7 mips
|
||||||
|
ByteRange range;
|
||||||
|
range.fromInclusive = -HIGH_MIP_MAX_SIZE;
|
||||||
|
_ktxMipRequest->setByteRange(range);
|
||||||
|
} else {
|
||||||
|
ByteRange range;
|
||||||
|
range.fromInclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
|
||||||
|
+ _originalKtxDescriptor->images[low]._imageOffset + ktx::IMAGE_SIZE_WIDTH;
|
||||||
|
range.toExclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
|
||||||
|
+ _originalKtxDescriptor->images[high + 1]._imageOffset;
|
||||||
|
_ktxMipRequest->setByteRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(_ktxMipRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxMipRequestProgress);
|
||||||
|
connect(_ktxMipRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxMipRequestFinished);
|
||||||
|
|
||||||
|
_ktxMipRequest->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NetworkTexture::ktxHeaderRequestFinished() {
|
||||||
|
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
|
||||||
|
|
||||||
|
_ktxHeaderRequestFinished = true;
|
||||||
|
maybeHandleFinishedInitialLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTexture::ktxMipRequestFinished() {
|
||||||
|
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA || _ktxResourceState == REQUESTING_MIP);
|
||||||
|
|
||||||
|
if (_ktxResourceState == LOADING_INITIAL_DATA) {
|
||||||
|
_ktxHighMipRequestFinished = true;
|
||||||
|
maybeHandleFinishedInitialLoad();
|
||||||
|
} else if (_ktxResourceState == REQUESTING_MIP) {
|
||||||
|
Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
|
||||||
|
TextureCache::requestCompleted(_self);
|
||||||
|
|
||||||
|
if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
|
||||||
|
Q_ASSERT(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
|
||||||
|
|
||||||
|
auto texture = _textureSource->getGPUTexture();
|
||||||
|
if (texture) {
|
||||||
|
texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
|
||||||
|
_ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
|
||||||
|
_lowestKnownPopulatedMip = _textureSource->getGPUTexture()->minAvailableMipLevel();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning(networking) << "Trying to update mips but texture is null";
|
||||||
|
}
|
||||||
|
finishedLoading(true);
|
||||||
|
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
finishedLoading(false);
|
||||||
|
if (handleFailedRequest(_ktxMipRequest->getResult())) {
|
||||||
|
_ktxResourceState = PENDING_MIP_REQUEST;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning(networking) << "Failed to load mip: " << _url;
|
||||||
|
_ktxResourceState = FAILED_TO_LOAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ktxMipRequest->deleteLater();
|
||||||
|
_ktxMipRequest = nullptr;
|
||||||
|
|
||||||
|
if (_ktxResourceState == WAITING_FOR_MIP_REQUEST && _lowestRequestedMipLevel < _lowestKnownPopulatedMip) {
|
||||||
|
startRequestForNextMipLevel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "Mip request finished in an unexpected state: " << _ktxResourceState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called when the header or top mips have been loaded
|
||||||
|
void NetworkTexture::maybeHandleFinishedInitialLoad() {
|
||||||
|
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
|
||||||
|
|
||||||
|
if (_ktxHeaderRequestFinished && _ktxHighMipRequestFinished) {
|
||||||
|
|
||||||
|
TextureCache::requestCompleted(_self);
|
||||||
|
|
||||||
|
if (_ktxHeaderRequest->getResult() != ResourceRequest::Success || _ktxMipRequest->getResult() != ResourceRequest::Success) {
|
||||||
|
if (handleFailedRequest(_ktxMipRequest->getResult())) {
|
||||||
|
_ktxResourceState = PENDING_INITIAL_LOAD;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_ktxResourceState = FAILED_TO_LOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ktxHeaderRequest->deleteLater();
|
||||||
|
_ktxHeaderRequest = nullptr;
|
||||||
|
_ktxMipRequest->deleteLater();
|
||||||
|
_ktxMipRequest = nullptr;
|
||||||
|
} else {
|
||||||
|
// create ktx...
|
||||||
|
auto ktxHeaderData = _ktxHeaderRequest->getData();
|
||||||
|
auto ktxHighMipData = _ktxMipRequest->getData();
|
||||||
|
|
||||||
|
auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
|
||||||
|
|
||||||
|
if (!ktx::checkIdentifier(header->identifier)) {
|
||||||
|
qWarning() << "Cannot load " << _url << ", invalid header identifier";
|
||||||
|
_ktxResourceState = FAILED_TO_LOAD;
|
||||||
|
finishedLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto kvSize = header->bytesOfKeyValueData;
|
||||||
|
if (kvSize > (ktxHeaderData.size() - ktx::KTX_HEADER_SIZE)) {
|
||||||
|
qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
|
||||||
|
_ktxResourceState = FAILED_TO_LOAD;
|
||||||
|
finishedLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
|
||||||
|
|
||||||
|
auto imageDescriptors = header->generateImageDescriptors();
|
||||||
|
if (imageDescriptors.size() == 0) {
|
||||||
|
qWarning(networking) << "Failed to process ktx file " << _url;
|
||||||
|
_ktxResourceState = FAILED_TO_LOAD;
|
||||||
|
finishedLoading(false);
|
||||||
|
}
|
||||||
|
_originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
|
||||||
|
|
||||||
|
// Create bare ktx in memory
|
||||||
|
auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
|
||||||
|
return val._key.compare(gpu::SOURCE_HASH_KEY) == 0;
|
||||||
|
});
|
||||||
|
std::string filename;
|
||||||
|
std::string hash;
|
||||||
|
if (found == keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
|
||||||
|
qWarning("Invalid source hash key found, bailing");
|
||||||
|
_ktxResourceState = FAILED_TO_LOAD;
|
||||||
|
finishedLoading(false);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// at this point the source hash is in binary 16-byte form
|
||||||
|
// and we need it in a hexadecimal string
|
||||||
|
auto binaryHash = QByteArray(reinterpret_cast<char*>(found->_value.data()), gpu::SOURCE_HASH_BYTES);
|
||||||
|
hash = filename = binaryHash.toHex().toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
|
||||||
|
gpu::TexturePointer texture = textureCache->getTextureByHash(hash);
|
||||||
|
|
||||||
|
if (!texture) {
|
||||||
|
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
|
||||||
|
if (ktxFile) {
|
||||||
|
texture = gpu::Texture::unserialize(ktxFile->getFilepath());
|
||||||
|
if (texture) {
|
||||||
|
texture = textureCache->cacheTextureByHash(hash, texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!texture) {
|
||||||
|
|
||||||
|
auto memKtx = ktx::KTX::createBare(*header, keyValues);
|
||||||
|
if (!memKtx) {
|
||||||
|
qWarning() << " Ktx could not be created, bailing";
|
||||||
|
finishedLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move ktx to file
|
||||||
|
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
|
||||||
|
size_t length = memKtx->_storage->size();
|
||||||
|
KTXFilePointer file;
|
||||||
|
auto& ktxCache = textureCache->_ktxCache;
|
||||||
|
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
|
||||||
|
qCWarning(modelnetworking) << _url << " failed to write cache file";
|
||||||
|
_ktxResourceState = FAILED_TO_LOAD;
|
||||||
|
finishedLoading(false);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newKtxDescriptor = memKtx->toDescriptor();
|
||||||
|
|
||||||
|
texture = gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor);
|
||||||
|
texture->setKtxBacking(file->getFilepath());
|
||||||
|
texture->setSource(filename);
|
||||||
|
|
||||||
|
auto& images = _originalKtxDescriptor->images;
|
||||||
|
size_t imageSizeRemaining = ktxHighMipData.size();
|
||||||
|
uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
|
||||||
|
ktxData += ktxHighMipData.size();
|
||||||
|
// TODO Move image offset calculation to ktx ImageDescriptor
|
||||||
|
for (int level = static_cast<int>(images.size()) - 1; level >= 0; --level) {
|
||||||
|
auto& image = images[level];
|
||||||
|
if (image._imageSize > imageSizeRemaining) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ktxData -= image._imageSize;
|
||||||
|
texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
|
||||||
|
ktxData -= ktx::IMAGE_SIZE_WIDTH;
|
||||||
|
imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// be the winner
|
||||||
|
texture = textureCache->cacheTextureByHash(filename, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lowestKnownPopulatedMip = texture->minAvailableMipLevel();
|
||||||
|
|
||||||
|
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
|
||||||
|
setImage(texture, header->getPixelWidth(), header->getPixelHeight());
|
||||||
|
|
||||||
|
_ktxHeaderRequest->deleteLater();
|
||||||
|
_ktxHeaderRequest = nullptr;
|
||||||
|
_ktxMipRequest->deleteLater();
|
||||||
|
_ktxMipRequest = nullptr;
|
||||||
|
}
|
||||||
|
startRequestForNextMipLevel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
||||||
loadContent(data);
|
loadContent(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTexture::loadContent(const QByteArray& content) {
|
void NetworkTexture::loadContent(const QByteArray& content) {
|
||||||
|
if (_sourceIsKTX) {
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels));
|
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,6 +779,7 @@ void ImageReader::read() {
|
||||||
if (texture && textureCache) {
|
if (texture && textureCache) {
|
||||||
auto memKtx = gpu::Texture::serialize(*texture);
|
auto memKtx = gpu::Texture::serialize(*texture);
|
||||||
|
|
||||||
|
// Move the texture into a memory mapped file
|
||||||
if (memKtx) {
|
if (memKtx) {
|
||||||
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
|
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
|
||||||
size_t length = memKtx->_storage->size();
|
size_t length = memKtx->_storage->size();
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <ResourceCache.h>
|
#include <ResourceCache.h>
|
||||||
#include <model/TextureMap.h>
|
#include <model/TextureMap.h>
|
||||||
#include <image/Image.h>
|
#include <image/Image.h>
|
||||||
|
#include <ktx/KTX.h>
|
||||||
|
|
||||||
#include "KTXCache.h"
|
#include "KTXCache.h"
|
||||||
|
|
||||||
|
@ -59,7 +60,16 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
|
void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void ktxHeaderRequestProgress(uint64_t bytesReceived, uint64_t bytesTotal) { }
|
||||||
|
void ktxHeaderRequestFinished();
|
||||||
|
|
||||||
|
void ktxMipRequestProgress(uint64_t bytesReceived, uint64_t bytesTotal) { }
|
||||||
|
void ktxMipRequestFinished();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void makeRequest() override;
|
||||||
|
|
||||||
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;
|
||||||
|
@ -67,12 +77,51 @@ protected:
|
||||||
Q_INVOKABLE void loadContent(const QByteArray& content);
|
Q_INVOKABLE void loadContent(const QByteArray& content);
|
||||||
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
|
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
|
||||||
|
|
||||||
|
void startRequestForNextMipLevel();
|
||||||
|
|
||||||
|
void startMipRangeRequest(uint16_t low, uint16_t high);
|
||||||
|
void maybeHandleFinishedInitialLoad();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class KTXReader;
|
friend class KTXReader;
|
||||||
friend class ImageReader;
|
friend class ImageReader;
|
||||||
|
|
||||||
image::TextureUsage::Type _type;
|
image::TextureUsage::Type _type;
|
||||||
|
|
||||||
|
static const uint16_t NULL_MIP_LEVEL;
|
||||||
|
enum KTXResourceState {
|
||||||
|
PENDING_INITIAL_LOAD = 0,
|
||||||
|
LOADING_INITIAL_DATA, // Loading KTX Header + Low Resolution Mips
|
||||||
|
WAITING_FOR_MIP_REQUEST, // Waiting for the gpu layer to report that it needs higher resolution mips
|
||||||
|
PENDING_MIP_REQUEST, // We have added ourselves to the ResourceCache queue
|
||||||
|
REQUESTING_MIP, // We have a mip in flight
|
||||||
|
FAILED_TO_LOAD
|
||||||
|
};
|
||||||
|
|
||||||
|
bool _sourceIsKTX { false };
|
||||||
|
KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
|
||||||
|
|
||||||
|
// TODO Can this be removed?
|
||||||
KTXFilePointer _file;
|
KTXFilePointer _file;
|
||||||
|
|
||||||
|
// The current mips that are currently being requested w/ _ktxMipRequest
|
||||||
|
std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
|
||||||
|
|
||||||
|
ResourceRequest* _ktxHeaderRequest { nullptr };
|
||||||
|
ResourceRequest* _ktxMipRequest { nullptr };
|
||||||
|
bool _ktxHeaderRequestFinished{ false };
|
||||||
|
bool _ktxHighMipRequestFinished{ false };
|
||||||
|
|
||||||
|
uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
|
||||||
|
uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
|
||||||
|
|
||||||
|
// This is a copy of the original KTX descriptor from the source url.
|
||||||
|
// We need this because the KTX that will be cached will likely include extra data
|
||||||
|
// in its key/value data, and so will not match up with the original, causing
|
||||||
|
// mip offsets to change.
|
||||||
|
ktx::KTXDescriptorPointer _originalKtxDescriptor;
|
||||||
|
|
||||||
|
|
||||||
int _originalWidth { 0 };
|
int _originalWidth { 0 };
|
||||||
int _originalHeight { 0 };
|
int _originalHeight { 0 };
|
||||||
int _width { 0 };
|
int _width { 0 };
|
||||||
|
|
|
@ -67,7 +67,6 @@ void AssetClient::init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) {
|
void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "cacheInfoRequest", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(this, "cacheInfoRequest", Qt::QueuedConnection,
|
||||||
|
@ -182,8 +181,8 @@ RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& o
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetRequest* AssetClient::createRequest(const AssetHash& hash) {
|
AssetRequest* AssetClient::createRequest(const AssetHash& hash, const ByteRange& byteRange) {
|
||||||
auto request = new AssetRequest(hash);
|
auto request = new AssetRequest(hash, byteRange);
|
||||||
|
|
||||||
// Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
|
// Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
|
||||||
request->moveToThread(thread());
|
request->moveToThread(thread());
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
|
||||||
#include "AssetUtils.h"
|
#include "AssetUtils.h"
|
||||||
|
#include "ByteRange.h"
|
||||||
#include "ClientServerUtils.h"
|
#include "ClientServerUtils.h"
|
||||||
#include "LimitedNodeList.h"
|
#include "LimitedNodeList.h"
|
||||||
#include "Node.h"
|
#include "Node.h"
|
||||||
|
@ -55,7 +56,7 @@ public:
|
||||||
Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetPathList& paths);
|
Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetPathList& paths);
|
||||||
Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetPath& path, const AssetHash& hash);
|
Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetPath& path, const AssetHash& hash);
|
||||||
Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
|
Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
|
||||||
Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash);
|
Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash, const ByteRange& byteRange = ByteRange());
|
||||||
Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
|
Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
|
||||||
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
|
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,12 @@
|
||||||
|
|
||||||
static int requestID = 0;
|
static int requestID = 0;
|
||||||
|
|
||||||
AssetRequest::AssetRequest(const QString& hash) :
|
AssetRequest::AssetRequest(const QString& hash, const ByteRange& byteRange) :
|
||||||
_requestID(++requestID),
|
_requestID(++requestID),
|
||||||
_hash(hash)
|
_hash(hash),
|
||||||
|
_byteRange(byteRange)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetRequest::~AssetRequest() {
|
AssetRequest::~AssetRequest() {
|
||||||
|
@ -34,9 +36,6 @@ AssetRequest::~AssetRequest() {
|
||||||
if (_assetRequestID) {
|
if (_assetRequestID) {
|
||||||
assetClient->cancelGetAssetRequest(_assetRequestID);
|
assetClient->cancelGetAssetRequest(_assetRequestID);
|
||||||
}
|
}
|
||||||
if (_assetInfoRequestID) {
|
|
||||||
assetClient->cancelGetAssetInfoRequest(_assetInfoRequestID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetRequest::start() {
|
void AssetRequest::start() {
|
||||||
|
@ -62,8 +61,6 @@ void AssetRequest::start() {
|
||||||
// Try to load from cache
|
// Try to load from cache
|
||||||
_data = loadFromCache(getUrl());
|
_data = loadFromCache(getUrl());
|
||||||
if (!_data.isNull()) {
|
if (!_data.isNull()) {
|
||||||
_info.hash = _hash;
|
|
||||||
_info.size = _data.size();
|
|
||||||
_error = NoError;
|
_error = NoError;
|
||||||
|
|
||||||
_state = Finished;
|
_state = Finished;
|
||||||
|
@ -71,49 +68,15 @@ void AssetRequest::start() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_state = WaitingForInfo;
|
|
||||||
|
|
||||||
auto assetClient = DependencyManager::get<AssetClient>();
|
|
||||||
_assetInfoRequestID = assetClient->getAssetInfo(_hash,
|
|
||||||
[this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
|
|
||||||
|
|
||||||
_assetInfoRequestID = INVALID_MESSAGE_ID;
|
|
||||||
|
|
||||||
_info = info;
|
|
||||||
|
|
||||||
if (!responseReceived) {
|
|
||||||
_error = NetworkError;
|
|
||||||
} else if (serverError != AssetServerError::NoError) {
|
|
||||||
switch(serverError) {
|
|
||||||
case AssetServerError::AssetNotFound:
|
|
||||||
_error = NotFound;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
_error = UnknownError;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_error != NoError) {
|
|
||||||
qCWarning(asset_client) << "Got error retrieving asset info for" << _hash;
|
|
||||||
_state = Finished;
|
|
||||||
emit finished(this);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_state = WaitingForData;
|
_state = WaitingForData;
|
||||||
_data.resize(info.size);
|
|
||||||
|
|
||||||
qCDebug(asset_client) << "Got size of " << _hash << " : " << info.size << " bytes";
|
|
||||||
|
|
||||||
int start = 0, end = _info.size;
|
|
||||||
|
|
||||||
auto assetClient = DependencyManager::get<AssetClient>();
|
auto assetClient = DependencyManager::get<AssetClient>();
|
||||||
auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
|
auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
|
||||||
auto hash = _hash;
|
auto hash = _hash;
|
||||||
_assetRequestID = assetClient->getAsset(_hash, start, end,
|
|
||||||
[this, that, hash, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
|
_assetRequestID = assetClient->getAsset(_hash, _byteRange.fromInclusive, _byteRange.toExclusive,
|
||||||
|
[this, that, hash](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
|
||||||
|
|
||||||
if (!that) {
|
if (!that) {
|
||||||
qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
|
qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
|
||||||
// If the request is dead, return
|
// If the request is dead, return
|
||||||
|
@ -136,20 +99,23 @@ void AssetRequest::start() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Q_ASSERT(data.size() == (end - start));
|
if (_byteRange.isSet()) {
|
||||||
|
// we had a byte range, the size of the data does not match what we expect, so we return an error
|
||||||
// we need to check the hash of the received data to make sure it matches what we expect
|
if (data.size() != _byteRange.size()) {
|
||||||
if (hashData(data).toHex() == _hash) {
|
_error = SizeVerificationFailed;
|
||||||
memcpy(_data.data() + start, data.constData(), data.size());
|
}
|
||||||
_totalReceived += data.size();
|
} else if (hashData(data).toHex() != _hash) {
|
||||||
emit progress(_totalReceived, _info.size);
|
// the hash of the received data does not match what we expect, so we return an error
|
||||||
|
|
||||||
saveToCache(getUrl(), data);
|
|
||||||
} else {
|
|
||||||
// hash doesn't match - we have an error
|
|
||||||
_error = HashVerificationFailed;
|
_error = HashVerificationFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_error == NoError) {
|
||||||
|
_data = data;
|
||||||
|
_totalReceived += data.size();
|
||||||
|
emit progress(_totalReceived, data.size());
|
||||||
|
|
||||||
|
saveToCache(getUrl(), data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_error != NoError) {
|
if (_error != NoError) {
|
||||||
|
@ -165,5 +131,4 @@ void AssetRequest::start() {
|
||||||
}
|
}
|
||||||
emit progress(totalReceived, total);
|
emit progress(totalReceived, total);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,15 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "AssetClient.h"
|
#include "AssetClient.h"
|
||||||
|
|
||||||
#include "AssetUtils.h"
|
#include "AssetUtils.h"
|
||||||
|
|
||||||
|
#include "ByteRange.h"
|
||||||
|
|
||||||
class AssetRequest : public QObject {
|
class AssetRequest : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum State {
|
enum State {
|
||||||
NotStarted = 0,
|
NotStarted = 0,
|
||||||
WaitingForInfo,
|
|
||||||
WaitingForData,
|
WaitingForData,
|
||||||
Finished
|
Finished
|
||||||
};
|
};
|
||||||
|
@ -36,11 +36,12 @@ public:
|
||||||
InvalidByteRange,
|
InvalidByteRange,
|
||||||
InvalidHash,
|
InvalidHash,
|
||||||
HashVerificationFailed,
|
HashVerificationFailed,
|
||||||
|
SizeVerificationFailed,
|
||||||
NetworkError,
|
NetworkError,
|
||||||
UnknownError
|
UnknownError
|
||||||
};
|
};
|
||||||
|
|
||||||
AssetRequest(const QString& hash);
|
AssetRequest(const QString& hash, const ByteRange& byteRange = ByteRange());
|
||||||
virtual ~AssetRequest() override;
|
virtual ~AssetRequest() override;
|
||||||
|
|
||||||
Q_INVOKABLE void start();
|
Q_INVOKABLE void start();
|
||||||
|
@ -59,13 +60,12 @@ private:
|
||||||
int _requestID;
|
int _requestID;
|
||||||
State _state = NotStarted;
|
State _state = NotStarted;
|
||||||
Error _error = NoError;
|
Error _error = NoError;
|
||||||
AssetInfo _info;
|
|
||||||
uint64_t _totalReceived { 0 };
|
uint64_t _totalReceived { 0 };
|
||||||
QString _hash;
|
QString _hash;
|
||||||
QByteArray _data;
|
QByteArray _data;
|
||||||
int _numPendingRequests { 0 };
|
int _numPendingRequests { 0 };
|
||||||
MessageID _assetRequestID { INVALID_MESSAGE_ID };
|
MessageID _assetRequestID { INVALID_MESSAGE_ID };
|
||||||
MessageID _assetInfoRequestID { INVALID_MESSAGE_ID };
|
const ByteRange _byteRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -114,7 +114,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
|
||||||
void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
||||||
// Make request to atp
|
// Make request to atp
|
||||||
auto assetClient = DependencyManager::get<AssetClient>();
|
auto assetClient = DependencyManager::get<AssetClient>();
|
||||||
_assetRequest = assetClient->createRequest(hash);
|
_assetRequest = assetClient->createRequest(hash, _byteRange);
|
||||||
|
|
||||||
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::onDownloadProgress);
|
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::onDownloadProgress);
|
||||||
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
|
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
|
||||||
|
|
53
libraries/networking/src/ByteRange.h
Normal file
53
libraries/networking/src/ByteRange.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// ByteRange.h
|
||||||
|
// libraries/networking/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 4/17/17.
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_ByteRange_h
|
||||||
|
#define hifi_ByteRange_h
|
||||||
|
|
||||||
|
struct ByteRange {
|
||||||
|
int64_t fromInclusive { 0 };
|
||||||
|
int64_t toExclusive { 0 };
|
||||||
|
|
||||||
|
bool isSet() const { return fromInclusive < 0 || fromInclusive < toExclusive; }
|
||||||
|
int64_t size() const { return toExclusive - fromInclusive; }
|
||||||
|
|
||||||
|
// byte ranges are invalid if:
|
||||||
|
// (1) the toExclusive of the range is negative
|
||||||
|
// (2) the toExclusive of the range is less than the fromInclusive, and isn't zero
|
||||||
|
// (3) the fromExclusive of the range is negative, and the toExclusive isn't zero
|
||||||
|
bool isValid() {
|
||||||
|
return toExclusive >= 0
|
||||||
|
&& (toExclusive >= fromInclusive || toExclusive == 0)
|
||||||
|
&& (fromInclusive >= 0 || toExclusive == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fixupRange(int64_t fileSize) {
|
||||||
|
if (!isSet()) {
|
||||||
|
// if the byte range is not set, force it to be from 0 to the end of the file
|
||||||
|
fromInclusive = 0;
|
||||||
|
toExclusive = fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromInclusive > 0 && toExclusive == 0) {
|
||||||
|
// we have a left side of the range that is non-zero
|
||||||
|
// if the RHS of the range is zero, set it to the end of the file now
|
||||||
|
toExclusive = fileSize;
|
||||||
|
} else if (-fromInclusive >= fileSize) {
|
||||||
|
// we have a negative range that is equal or greater than the full size of the file
|
||||||
|
// so we just set this to be a range across the entire file, from 0
|
||||||
|
fromInclusive = 0;
|
||||||
|
toExclusive = fileSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // hifi_ByteRange_h
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
#include "FileResourceRequest.h"
|
#include "FileResourceRequest.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
void FileResourceRequest::doSend() {
|
void FileResourceRequest::doSend() {
|
||||||
|
@ -22,17 +24,39 @@ void FileResourceRequest::doSend() {
|
||||||
filename = _url.toString();
|
filename = _url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_byteRange.isValid()) {
|
||||||
|
_result = ResourceRequest::InvalidByteRange;
|
||||||
|
} else {
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
if (file.open(QFile::ReadOnly)) {
|
if (file.open(QFile::ReadOnly)) {
|
||||||
_data = file.readAll();
|
|
||||||
|
if (file.size() < _byteRange.fromInclusive || file.size() < _byteRange.toExclusive) {
|
||||||
|
_result = ResourceRequest::InvalidByteRange;
|
||||||
|
} else {
|
||||||
|
// fix it up based on the known size of the file
|
||||||
|
_byteRange.fixupRange(file.size());
|
||||||
|
|
||||||
|
if (_byteRange.fromInclusive >= 0) {
|
||||||
|
// this is a positive byte range, simply skip to that part of the file and read from there
|
||||||
|
file.seek(_byteRange.fromInclusive);
|
||||||
|
_data = file.read(_byteRange.size());
|
||||||
|
} else {
|
||||||
|
// this is a negative byte range, we'll need to grab data from the end of the file first
|
||||||
|
file.seek(file.size() + _byteRange.fromInclusive);
|
||||||
|
_data = file.read(_byteRange.size());
|
||||||
|
}
|
||||||
|
|
||||||
_result = ResourceRequest::Success;
|
_result = ResourceRequest::Success;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
_result = ResourceRequest::AccessDenied;
|
_result = ResourceRequest::AccessDenied;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_result = ResourceRequest::NotFound;
|
_result = ResourceRequest::NotFound;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_state = Finished;
|
_state = Finished;
|
||||||
emit finished();
|
emit finished();
|
||||||
|
|
|
@ -59,6 +59,18 @@ void HTTPResourceRequest::doSend() {
|
||||||
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_byteRange.isSet()) {
|
||||||
|
QString byteRange;
|
||||||
|
if (_byteRange.fromInclusive < 0) {
|
||||||
|
byteRange = QString("bytes=%1").arg(_byteRange.fromInclusive);
|
||||||
|
} else {
|
||||||
|
// HTTP byte ranges are inclusive on the `to` end: [from, to]
|
||||||
|
byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive - 1);
|
||||||
|
}
|
||||||
|
networkRequest.setRawHeader("Range", byteRange.toLatin1());
|
||||||
|
}
|
||||||
|
networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, false);
|
||||||
|
|
||||||
_reply = NetworkAccessManager::getInstance().get(networkRequest);
|
_reply = NetworkAccessManager::getInstance().get(networkRequest);
|
||||||
|
|
||||||
connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished);
|
connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished);
|
||||||
|
@ -73,11 +85,59 @@ void HTTPResourceRequest::onRequestFinished() {
|
||||||
|
|
||||||
cleanupTimer();
|
cleanupTimer();
|
||||||
|
|
||||||
|
// Content-Range headers have the form:
|
||||||
|
//
|
||||||
|
// Content-Range: <unit> <range-start>-<range-end>/<size>
|
||||||
|
// Content-Range: <unit> <range-start>-<range-end>/*
|
||||||
|
// Content-Range: <unit> */<size>
|
||||||
|
//
|
||||||
|
auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
|
||||||
|
auto unitRangeParts = contentRangeHeader.split(' ');
|
||||||
|
if (unitRangeParts.size() != 2) {
|
||||||
|
return { false, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rangeSizeParts = unitRangeParts[1].split('/');
|
||||||
|
if (rangeSizeParts.size() != 2) {
|
||||||
|
return { false, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sizeStr = rangeSizeParts[1];
|
||||||
|
if (sizeStr == "*") {
|
||||||
|
return { true, 0 };
|
||||||
|
} else {
|
||||||
|
bool ok;
|
||||||
|
auto size = sizeStr.toLong(&ok);
|
||||||
|
return { ok, size };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
switch(_reply->error()) {
|
switch(_reply->error()) {
|
||||||
case QNetworkReply::NoError:
|
case QNetworkReply::NoError:
|
||||||
_data = _reply->readAll();
|
_data = _reply->readAll();
|
||||||
_loadedFromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
|
_loadedFromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
|
||||||
_result = Success;
|
_result = Success;
|
||||||
|
|
||||||
|
if (_byteRange.isSet()) {
|
||||||
|
auto statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
if (statusCode == 206) {
|
||||||
|
_rangeRequestSuccessful = true;
|
||||||
|
auto contentRangeHeader = _reply->rawHeader("Content-Range");
|
||||||
|
bool success;
|
||||||
|
uint64_t size;
|
||||||
|
std::tie(success, size) = parseContentRangeHeader(contentRangeHeader);
|
||||||
|
if (success) {
|
||||||
|
_totalSizeOfResource = size;
|
||||||
|
} else {
|
||||||
|
qWarning(networking) << "Error parsing content-range header: " << contentRangeHeader;
|
||||||
|
_totalSizeOfResource = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_rangeRequestSuccessful = false;
|
||||||
|
_totalSizeOfResource = _data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case QNetworkReply::TimeoutError:
|
case QNetworkReply::TimeoutError:
|
||||||
|
@ -130,6 +190,7 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTPResourceRequest::onTimeout() {
|
void HTTPResourceRequest::onTimeout() {
|
||||||
|
qDebug() << "Timeout: " << _url << ":" << _reply->isFinished();
|
||||||
Q_ASSERT(_state == InProgress);
|
Q_ASSERT(_state == InProgress);
|
||||||
_reply->disconnect(this);
|
_reply->disconnect(this);
|
||||||
_reply->abort();
|
_reply->abort();
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#include "AtpReply.h"
|
#include "AtpReply.h"
|
||||||
#include "NetworkAccessManager.h"
|
#include "NetworkAccessManager.h"
|
||||||
|
#include <QtNetwork/QNetworkProxy>
|
||||||
|
|
||||||
QThreadStorage<QNetworkAccessManager*> networkAccessManagers;
|
QThreadStorage<QNetworkAccessManager*> networkAccessManagers;
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,6 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
|
||||||
NetworkPeer(uuid, publicSocket, localSocket, parent),
|
NetworkPeer(uuid, publicSocket, localSocket, parent),
|
||||||
_type(type),
|
_type(type),
|
||||||
_connectionSecret(connectionSecret),
|
_connectionSecret(connectionSecret),
|
||||||
_isAlive(true),
|
|
||||||
_pingMs(-1), // "Uninitialized"
|
_pingMs(-1), // "Uninitialized"
|
||||||
_clockSkewUsec(0),
|
_clockSkewUsec(0),
|
||||||
_mutex(),
|
_mutex(),
|
||||||
|
|
|
@ -54,9 +54,6 @@ public:
|
||||||
NodeData* getLinkedData() const { return _linkedData.get(); }
|
NodeData* getLinkedData() const { return _linkedData.get(); }
|
||||||
void setLinkedData(std::unique_ptr<NodeData> linkedData) { _linkedData = std::move(linkedData); }
|
void setLinkedData(std::unique_ptr<NodeData> linkedData) { _linkedData = std::move(linkedData); }
|
||||||
|
|
||||||
bool isAlive() const { return _isAlive; }
|
|
||||||
void setAlive(bool isAlive) { _isAlive = isAlive; }
|
|
||||||
|
|
||||||
int getPingMs() const { return _pingMs; }
|
int getPingMs() const { return _pingMs; }
|
||||||
void setPingMs(int pingMs) { _pingMs = pingMs; }
|
void setPingMs(int pingMs) { _pingMs = pingMs; }
|
||||||
|
|
||||||
|
@ -92,7 +89,6 @@ private:
|
||||||
|
|
||||||
QUuid _connectionSecret;
|
QUuid _connectionSecret;
|
||||||
std::unique_ptr<NodeData> _linkedData;
|
std::unique_ptr<NodeData> _linkedData;
|
||||||
bool _isAlive;
|
|
||||||
int _pingMs;
|
int _pingMs;
|
||||||
qint64 _clockSkewUsec;
|
qint64 _clockSkewUsec;
|
||||||
QMutex _mutex;
|
QMutex _mutex;
|
||||||
|
|
|
@ -26,8 +26,7 @@ ReceivedMessage::ReceivedMessage(const NLPacketList& packetList)
|
||||||
_sourceID(packetList.getSourceID()),
|
_sourceID(packetList.getSourceID()),
|
||||||
_packetType(packetList.getType()),
|
_packetType(packetList.getType()),
|
||||||
_packetVersion(packetList.getVersion()),
|
_packetVersion(packetList.getVersion()),
|
||||||
_senderSockAddr(packetList.getSenderSockAddr()),
|
_senderSockAddr(packetList.getSenderSockAddr())
|
||||||
_isComplete(true)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -474,8 +474,9 @@ int ResourceCache::getLoadingRequestCount() {
|
||||||
|
|
||||||
bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
|
bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
|
||||||
Q_ASSERT(!resource.isNull());
|
Q_ASSERT(!resource.isNull());
|
||||||
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
|
||||||
|
|
||||||
|
|
||||||
|
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
||||||
if (_requestsActive >= _requestLimit) {
|
if (_requestsActive >= _requestLimit) {
|
||||||
// wait until a slot becomes available
|
// wait until a slot becomes available
|
||||||
sharedItems->appendPendingRequest(resource);
|
sharedItems->appendPendingRequest(resource);
|
||||||
|
@ -490,6 +491,7 @@ bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
|
||||||
|
|
||||||
void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
|
void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
|
||||||
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
||||||
|
|
||||||
sharedItems->removeRequest(resource);
|
sharedItems->removeRequest(resource);
|
||||||
--_requestsActive;
|
--_requestsActive;
|
||||||
|
|
||||||
|
@ -553,6 +555,10 @@ void Resource::clearLoadPriority(const QPointer<QObject>& owner) {
|
||||||
}
|
}
|
||||||
|
|
||||||
float Resource::getLoadPriority() {
|
float Resource::getLoadPriority() {
|
||||||
|
if (_loadPriorities.size() == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
float highestPriority = -FLT_MAX;
|
float highestPriority = -FLT_MAX;
|
||||||
for (QHash<QPointer<QObject>, float>::iterator it = _loadPriorities.begin(); it != _loadPriorities.end(); ) {
|
for (QHash<QPointer<QObject>, float>::iterator it = _loadPriorities.begin(); it != _loadPriorities.end(); ) {
|
||||||
if (it.key().isNull()) {
|
if (it.key().isNull()) {
|
||||||
|
@ -621,8 +627,6 @@ void Resource::init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int MAX_ATTEMPTS = 8;
|
|
||||||
|
|
||||||
void Resource::attemptRequest() {
|
void Resource::attemptRequest() {
|
||||||
_startedLoading = true;
|
_startedLoading = true;
|
||||||
|
|
||||||
|
@ -637,12 +641,12 @@ void Resource::attemptRequest() {
|
||||||
void Resource::finishedLoading(bool success) {
|
void Resource::finishedLoading(bool success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
qCDebug(networking).noquote() << "Finished loading:" << _url.toDisplayString();
|
qCDebug(networking).noquote() << "Finished loading:" << _url.toDisplayString();
|
||||||
|
_loadPriorities.clear();
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
} else {
|
} else {
|
||||||
qCDebug(networking).noquote() << "Failed to load:" << _url.toDisplayString();
|
qCDebug(networking).noquote() << "Failed to load:" << _url.toDisplayString();
|
||||||
_failedToLoad = true;
|
_failedToLoad = true;
|
||||||
}
|
}
|
||||||
_loadPriorities.clear();
|
|
||||||
emit finished(success);
|
emit finished(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,6 +680,8 @@ void Resource::makeRequest() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_request->setByteRange(_requestByteRange);
|
||||||
|
|
||||||
qCDebug(resourceLog).noquote() << "Starting request for:" << _url.toDisplayString();
|
qCDebug(resourceLog).noquote() << "Starting request for:" << _url.toDisplayString();
|
||||||
emit loading();
|
emit loading();
|
||||||
|
|
||||||
|
@ -722,34 +728,7 @@ void Resource::handleReplyFinished() {
|
||||||
emit loaded(data);
|
emit loaded(data);
|
||||||
downloadFinished(data);
|
downloadFinished(data);
|
||||||
} else {
|
} else {
|
||||||
switch (result) {
|
handleFailedRequest(result);
|
||||||
case ResourceRequest::Result::Timeout: {
|
|
||||||
qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
|
|
||||||
// Fall through to other cases
|
|
||||||
}
|
|
||||||
case ResourceRequest::Result::ServerUnavailable: {
|
|
||||||
// retry with increasing delays
|
|
||||||
const int BASE_DELAY_MS = 1000;
|
|
||||||
if (_attempts++ < MAX_ATTEMPTS) {
|
|
||||||
auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts);
|
|
||||||
|
|
||||||
qCDebug(networking).noquote() << "Server unavailable for" << _url << "- may retry in" << waitTime << "ms"
|
|
||||||
<< "if resource is still needed";
|
|
||||||
|
|
||||||
QTimer::singleShot(waitTime, this, &Resource::attemptRequest);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// fall through to final failure
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
qCDebug(networking) << "Error loading " << _url;
|
|
||||||
auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError
|
|
||||||
: QNetworkReply::UnknownNetworkError;
|
|
||||||
emit failed(error);
|
|
||||||
finishedLoading(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_request->disconnect(this);
|
_request->disconnect(this);
|
||||||
|
@ -757,6 +736,45 @@ void Resource::handleReplyFinished() {
|
||||||
_request = nullptr;
|
_request = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Resource::handleFailedRequest(ResourceRequest::Result result) {
|
||||||
|
bool willRetry = false;
|
||||||
|
switch (result) {
|
||||||
|
case ResourceRequest::Result::Timeout: {
|
||||||
|
qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
|
||||||
|
// Fall through to other cases
|
||||||
|
}
|
||||||
|
case ResourceRequest::Result::ServerUnavailable: {
|
||||||
|
_attempts++;
|
||||||
|
_attemptsRemaining--;
|
||||||
|
|
||||||
|
qCDebug(networking) << "Retryable error while loading" << _url << "attempt:" << _attempts << "attemptsRemaining:" << _attemptsRemaining;
|
||||||
|
|
||||||
|
// retry with increasing delays
|
||||||
|
const int BASE_DELAY_MS = 1000;
|
||||||
|
if (_attempts < MAX_ATTEMPTS) {
|
||||||
|
auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts);
|
||||||
|
qCDebug(networking).noquote() << "Server unavailable for" << _url << "- may retry in" << waitTime << "ms"
|
||||||
|
<< "if resource is still needed";
|
||||||
|
QTimer::singleShot(waitTime, this, &Resource::attemptRequest);
|
||||||
|
willRetry = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// fall through to final failure
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
_attemptsRemaining = 0;
|
||||||
|
qCDebug(networking) << "Error loading " << _url << "attempt:" << _attempts << "attemptsRemaining:" << _attemptsRemaining;
|
||||||
|
auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError
|
||||||
|
: QNetworkReply::UnknownNetworkError;
|
||||||
|
emit failed(error);
|
||||||
|
willRetry = false;
|
||||||
|
finishedLoading(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return willRetry;
|
||||||
|
}
|
||||||
|
|
||||||
uint qHash(const QPointer<QObject>& value, uint seed) {
|
uint qHash(const QPointer<QObject>& value, uint seed) {
|
||||||
return qHash(value.data(), seed);
|
return qHash(value.data(), seed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,6 +395,9 @@ public:
|
||||||
|
|
||||||
const QUrl& getURL() const { return _url; }
|
const QUrl& getURL() const { return _url; }
|
||||||
|
|
||||||
|
unsigned int getDownloadAttempts() { return _attempts; }
|
||||||
|
unsigned int getDownloadAttemptsRemaining() { return _attemptsRemaining; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/// Fired when the resource begins downloading.
|
/// Fired when the resource begins downloading.
|
||||||
void loading();
|
void loading();
|
||||||
|
@ -424,6 +427,11 @@ protected slots:
|
||||||
protected:
|
protected:
|
||||||
virtual void init();
|
virtual void init();
|
||||||
|
|
||||||
|
/// Called by ResourceCache to begin loading this Resource.
|
||||||
|
/// This method can be overriden to provide custom request functionality. If this is done,
|
||||||
|
/// downloadFinished and ResourceCache::requestCompleted must be called.
|
||||||
|
virtual void makeRequest();
|
||||||
|
|
||||||
/// Checks whether the resource is cacheable.
|
/// Checks whether the resource is cacheable.
|
||||||
virtual bool isCacheable() const { return true; }
|
virtual bool isCacheable() const { return true; }
|
||||||
|
|
||||||
|
@ -440,8 +448,12 @@ protected:
|
||||||
|
|
||||||
Q_INVOKABLE void allReferencesCleared();
|
Q_INVOKABLE void allReferencesCleared();
|
||||||
|
|
||||||
|
/// Return true if the resource will be retried
|
||||||
|
bool handleFailedRequest(ResourceRequest::Result result);
|
||||||
|
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
QUrl _activeUrl;
|
QUrl _activeUrl;
|
||||||
|
ByteRange _requestByteRange;
|
||||||
bool _startedLoading = false;
|
bool _startedLoading = false;
|
||||||
bool _failedToLoad = false;
|
bool _failedToLoad = false;
|
||||||
bool _loaded = false;
|
bool _loaded = false;
|
||||||
|
@ -449,7 +461,14 @@ protected:
|
||||||
QWeakPointer<Resource> _self;
|
QWeakPointer<Resource> _self;
|
||||||
QPointer<ResourceCache> _cache;
|
QPointer<ResourceCache> _cache;
|
||||||
|
|
||||||
private slots:
|
qint64 _bytesReceived{ 0 };
|
||||||
|
qint64 _bytesTotal{ 0 };
|
||||||
|
qint64 _bytes{ 0 };
|
||||||
|
|
||||||
|
int _requestID;
|
||||||
|
ResourceRequest* _request{ nullptr };
|
||||||
|
|
||||||
|
public slots:
|
||||||
void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
|
void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
|
||||||
void handleReplyFinished();
|
void handleReplyFinished();
|
||||||
|
|
||||||
|
@ -459,21 +478,17 @@ private:
|
||||||
|
|
||||||
void setLRUKey(int lruKey) { _lruKey = lruKey; }
|
void setLRUKey(int lruKey) { _lruKey = lruKey; }
|
||||||
|
|
||||||
void makeRequest();
|
|
||||||
void retry();
|
void retry();
|
||||||
void reinsert();
|
void reinsert();
|
||||||
|
|
||||||
bool isInScript() const { return _isInScript; }
|
bool isInScript() const { return _isInScript; }
|
||||||
void setInScript(bool isInScript) { _isInScript = isInScript; }
|
void setInScript(bool isInScript) { _isInScript = isInScript; }
|
||||||
|
|
||||||
int _requestID;
|
|
||||||
ResourceRequest* _request{ nullptr };
|
|
||||||
int _lruKey{ 0 };
|
int _lruKey{ 0 };
|
||||||
QTimer* _replyTimer{ nullptr };
|
QTimer* _replyTimer{ nullptr };
|
||||||
qint64 _bytesReceived{ 0 };
|
unsigned int _attempts{ 0 };
|
||||||
qint64 _bytesTotal{ 0 };
|
static const int MAX_ATTEMPTS = 8;
|
||||||
qint64 _bytes{ 0 };
|
unsigned int _attemptsRemaining { MAX_ATTEMPTS };
|
||||||
int _attempts{ 0 };
|
|
||||||
bool _isInScript{ false };
|
bool _isInScript{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ const QString URL_SCHEME_ATP = "atp";
|
||||||
|
|
||||||
class ResourceManager {
|
class ResourceManager {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static void setUrlPrefixOverride(const QString& prefix, const QString& replacement);
|
static void setUrlPrefixOverride(const QString& prefix, const QString& replacement);
|
||||||
static QString normalizeURL(const QString& urlString);
|
static QString normalizeURL(const QString& urlString);
|
||||||
static QUrl normalizeURL(const QUrl& url);
|
static QUrl normalizeURL(const QUrl& url);
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "ByteRange.h"
|
||||||
|
|
||||||
class ResourceRequest : public QObject {
|
class ResourceRequest : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -35,6 +37,7 @@ public:
|
||||||
Timeout,
|
Timeout,
|
||||||
ServerUnavailable,
|
ServerUnavailable,
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
|
InvalidByteRange,
|
||||||
InvalidURL,
|
InvalidURL,
|
||||||
NotFound
|
NotFound
|
||||||
};
|
};
|
||||||
|
@ -46,8 +49,11 @@ public:
|
||||||
QString getResultString() const;
|
QString getResultString() const;
|
||||||
QUrl getUrl() const { return _url; }
|
QUrl getUrl() const { return _url; }
|
||||||
bool loadedFromCache() const { return _loadedFromCache; }
|
bool loadedFromCache() const { return _loadedFromCache; }
|
||||||
|
bool getRangeRequestSuccessful() const { return _rangeRequestSuccessful; }
|
||||||
|
bool getTotalSizeOfResource() const { return _totalSizeOfResource; }
|
||||||
|
|
||||||
void setCacheEnabled(bool value) { _cacheEnabled = value; }
|
void setCacheEnabled(bool value) { _cacheEnabled = value; }
|
||||||
|
void setByteRange(ByteRange byteRange) { _byteRange = byteRange; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void send();
|
void send();
|
||||||
|
@ -65,6 +71,9 @@ protected:
|
||||||
QByteArray _data;
|
QByteArray _data;
|
||||||
bool _cacheEnabled { true };
|
bool _cacheEnabled { true };
|
||||||
bool _loadedFromCache { false };
|
bool _loadedFromCache { false };
|
||||||
|
ByteRange _byteRange;
|
||||||
|
bool _rangeRequestSuccessful { false };
|
||||||
|
uint64_t _totalSizeOfResource { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
case PacketType::BulkAvatarData:
|
case PacketType::BulkAvatarData:
|
||||||
case PacketType::KillAvatar:
|
case PacketType::KillAvatar:
|
||||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::StickAndBallDefaultAvatar);
|
return static_cast<PacketVersion>(AvatarMixerPacketVersion::IdentityPacketsIncludeUpdateTime);
|
||||||
case PacketType::MessagesData:
|
case PacketType::MessagesData:
|
||||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||||
case PacketType::ICEServerHeartbeat:
|
case PacketType::ICEServerHeartbeat:
|
||||||
|
@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::AssetGetInfo:
|
case PacketType::AssetGetInfo:
|
||||||
case PacketType::AssetGet:
|
case PacketType::AssetGet:
|
||||||
case PacketType::AssetUpload:
|
case PacketType::AssetUpload:
|
||||||
return static_cast<PacketVersion>(AssetServerPacketVersion::VegasCongestionControl);
|
return static_cast<PacketVersion>(AssetServerPacketVersion::RangeRequestSupport);
|
||||||
case PacketType::NodeIgnoreRequest:
|
case PacketType::NodeIgnoreRequest:
|
||||||
return 18; // Introduction of node ignore request (which replaced an unused packet tpye)
|
return 18; // Introduction of node ignore request (which replaced an unused packet tpye)
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,8 @@ enum class EntityQueryPacketVersion: PacketVersion {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AssetServerPacketVersion: PacketVersion {
|
enum class AssetServerPacketVersion: PacketVersion {
|
||||||
VegasCongestionControl = 19
|
VegasCongestionControl = 19,
|
||||||
|
RangeRequestSupport
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||||
|
@ -231,7 +232,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
||||||
ImmediateSessionDisplayNameUpdates,
|
ImmediateSessionDisplayNameUpdates,
|
||||||
VariableAvatarData,
|
VariableAvatarData,
|
||||||
AvatarAsChildFixes,
|
AvatarAsChildFixes,
|
||||||
StickAndBallDefaultAvatar
|
StickAndBallDefaultAvatar,
|
||||||
|
IdentityPacketsIncludeUpdateTime
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DomainConnectRequestVersion : PacketVersion {
|
enum class DomainConnectRequestVersion : PacketVersion {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
set(TARGET_NAME procedural)
|
set(TARGET_NAME procedural)
|
||||||
AUTOSCRIBE_SHADER_LIB(gpu model)
|
AUTOSCRIBE_SHADER_LIB(gpu model)
|
||||||
setup_hifi_library()
|
setup_hifi_library()
|
||||||
link_hifi_libraries(shared gpu gpu-gl networking model model-networking image)
|
link_hifi_libraries(shared gpu gpu-gl networking model model-networking ktx image)
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ void MeshPartPayload::drawCall(gpu::Batch& batch) const {
|
||||||
batch.drawIndexed(gpu::TRIANGLES, _drawPart._numIndices, _drawPart._startIndex);
|
batch.drawIndexed(gpu::TRIANGLES, _drawPart._numIndices, _drawPart._startIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshPartPayload::bindMesh(gpu::Batch& batch) const {
|
void MeshPartPayload::bindMesh(gpu::Batch& batch) {
|
||||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||||
|
|
||||||
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
||||||
|
@ -255,7 +255,7 @@ void MeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::Loca
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshPartPayload::render(RenderArgs* args) const {
|
void MeshPartPayload::render(RenderArgs* args) {
|
||||||
PerformanceTimer perfTimer("MeshPartPayload::render");
|
PerformanceTimer perfTimer("MeshPartPayload::render");
|
||||||
|
|
||||||
gpu::Batch& batch = *(args->_batch);
|
gpu::Batch& batch = *(args->_batch);
|
||||||
|
@ -485,7 +485,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
|
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) {
|
||||||
if (!_isBlendShaped) {
|
if (!_isBlendShaped) {
|
||||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||||
|
|
||||||
|
@ -517,7 +517,7 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline:
|
||||||
batch.setModelTransform(_transform);
|
batch.setModelTransform(_transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
float ModelMeshPartPayload::computeFadeAlpha() const {
|
float ModelMeshPartPayload::computeFadeAlpha() {
|
||||||
if (_fadeState == FADE_WAITING_TO_START) {
|
if (_fadeState == FADE_WAITING_TO_START) {
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
@ -536,7 +536,7 @@ float ModelMeshPartPayload::computeFadeAlpha() const {
|
||||||
return Interpolate::simpleNonLinearBlend(fadeAlpha);
|
return Interpolate::simpleNonLinearBlend(fadeAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelMeshPartPayload::render(RenderArgs* args) const {
|
void ModelMeshPartPayload::render(RenderArgs* args) {
|
||||||
PerformanceTimer perfTimer("ModelMeshPartPayload::render");
|
PerformanceTimer perfTimer("ModelMeshPartPayload::render");
|
||||||
|
|
||||||
if (!_model->addedToScene() || !_model->isVisible()) {
|
if (!_model->addedToScene() || !_model->isVisible()) {
|
||||||
|
@ -544,7 +544,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_fadeState == FADE_WAITING_TO_START) {
|
if (_fadeState == FADE_WAITING_TO_START) {
|
||||||
if (_model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
|
if (_model->isLoaded()) {
|
||||||
if (EntityItem::getEntitiesShouldFadeFunction()()) {
|
if (EntityItem::getEntitiesShouldFadeFunction()()) {
|
||||||
_fadeStartTime = usecTimestampNow();
|
_fadeStartTime = usecTimestampNow();
|
||||||
_fadeState = FADE_IN_PROGRESS;
|
_fadeState = FADE_IN_PROGRESS;
|
||||||
|
@ -557,6 +557,11 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_materialNeedsUpdate && _model->getGeometry()->areTexturesLoaded()) {
|
||||||
|
_model->setRenderItemsNeedUpdate();
|
||||||
|
_materialNeedsUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!args) {
|
if (!args) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,11 +46,11 @@ public:
|
||||||
virtual render::ItemKey getKey() const;
|
virtual render::ItemKey getKey() const;
|
||||||
virtual render::Item::Bound getBound() const;
|
virtual render::Item::Bound getBound() const;
|
||||||
virtual render::ShapeKey getShapeKey() const; // shape interface
|
virtual render::ShapeKey getShapeKey() const; // shape interface
|
||||||
virtual void render(RenderArgs* args) const;
|
virtual void render(RenderArgs* args);
|
||||||
|
|
||||||
// ModelMeshPartPayload functions to perform render
|
// ModelMeshPartPayload functions to perform render
|
||||||
void drawCall(gpu::Batch& batch) const;
|
void drawCall(gpu::Batch& batch) const;
|
||||||
virtual void bindMesh(gpu::Batch& batch) const;
|
virtual void bindMesh(gpu::Batch& batch);
|
||||||
virtual void bindMaterial(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, bool enableTextures) const;
|
virtual void bindMaterial(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, bool enableTextures) const;
|
||||||
virtual void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const;
|
virtual void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const;
|
||||||
|
|
||||||
|
@ -93,16 +93,16 @@ public:
|
||||||
const Transform& boundTransform,
|
const Transform& boundTransform,
|
||||||
const gpu::BufferPointer& buffer);
|
const gpu::BufferPointer& buffer);
|
||||||
|
|
||||||
float computeFadeAlpha() const;
|
float computeFadeAlpha();
|
||||||
|
|
||||||
// Render Item interface
|
// Render Item interface
|
||||||
render::ItemKey getKey() const override;
|
render::ItemKey getKey() const override;
|
||||||
int getLayer() const;
|
int getLayer() const;
|
||||||
render::ShapeKey getShapeKey() const override; // shape interface
|
render::ShapeKey getShapeKey() const override; // shape interface
|
||||||
void render(RenderArgs* args) const override;
|
void render(RenderArgs* args) override;
|
||||||
|
|
||||||
// ModelMeshPartPayload functions to perform render
|
// ModelMeshPartPayload functions to perform render
|
||||||
void bindMesh(gpu::Batch& batch) const override;
|
void bindMesh(gpu::Batch& batch) override;
|
||||||
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
|
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
|
||||||
|
|
||||||
void initCache();
|
void initCache();
|
||||||
|
@ -117,10 +117,11 @@ public:
|
||||||
|
|
||||||
bool _isSkinned{ false };
|
bool _isSkinned{ false };
|
||||||
bool _isBlendShaped { false };
|
bool _isBlendShaped { false };
|
||||||
|
bool _materialNeedsUpdate { true };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable quint64 _fadeStartTime { 0 };
|
quint64 _fadeStartTime { 0 };
|
||||||
mutable uint8_t _fadeState { FADE_WAITING_TO_START };
|
uint8_t _fadeState { FADE_WAITING_TO_START };
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace render {
|
namespace render {
|
||||||
|
|
|
@ -252,6 +252,8 @@ public:
|
||||||
|
|
||||||
void renderDebugMeshBoxes(gpu::Batch& batch);
|
void renderDebugMeshBoxes(gpu::Batch& batch);
|
||||||
|
|
||||||
|
int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); }
|
||||||
|
int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void loadURLFinished(bool success);
|
void loadURLFinished(bool success);
|
||||||
|
|
|
@ -16,6 +16,6 @@ if (NOT ANDROID)
|
||||||
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics image)
|
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image)
|
||||||
# ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit
|
# ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit
|
||||||
include_hifi_library_headers(gl)
|
include_hifi_library_headers(gl)
|
||||||
|
|
|
@ -2320,6 +2320,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
|
||||||
|
|
||||||
if (_entityScripts.contains(entityID)) {
|
if (_entityScripts.contains(entityID)) {
|
||||||
const EntityScriptDetails &oldDetails = _entityScripts[entityID];
|
const EntityScriptDetails &oldDetails = _entityScripts[entityID];
|
||||||
|
auto scriptText = oldDetails.scriptText;
|
||||||
|
|
||||||
if (isEntityScriptRunning(entityID)) {
|
if (isEntityScriptRunning(entityID)) {
|
||||||
callEntityScriptMethod(entityID, "unload");
|
callEntityScriptMethod(entityID, "unload");
|
||||||
}
|
}
|
||||||
|
@ -2337,14 +2339,14 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
|
||||||
newDetails.status = EntityScriptStatus::UNLOADED;
|
newDetails.status = EntityScriptStatus::UNLOADED;
|
||||||
newDetails.lastModified = QDateTime::currentMSecsSinceEpoch();
|
newDetails.lastModified = QDateTime::currentMSecsSinceEpoch();
|
||||||
// keep scriptText populated for the current need to "debouce" duplicate calls to unloadEntityScript
|
// keep scriptText populated for the current need to "debouce" duplicate calls to unloadEntityScript
|
||||||
newDetails.scriptText = oldDetails.scriptText;
|
newDetails.scriptText = scriptText;
|
||||||
setEntityScriptDetails(entityID, newDetails);
|
setEntityScriptDetails(entityID, newDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopAllTimersForEntityScript(entityID);
|
stopAllTimersForEntityScript(entityID);
|
||||||
{
|
{
|
||||||
// FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests
|
// FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests
|
||||||
processDeferredEntityLoads(oldDetails.scriptText, entityID);
|
processDeferredEntityLoads(scriptText, entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u
|
||||||
}
|
}
|
||||||
|
|
||||||
FileStorage::FileStorage(const QString& filename) : _file(filename) {
|
FileStorage::FileStorage(const QString& filename) : _file(filename) {
|
||||||
if (_file.open(QFile::ReadOnly)) {
|
if (_file.open(QFile::ReadWrite)) {
|
||||||
_mapped = _file.map(0, _file.size());
|
_mapped = _file.map(0, _file.size());
|
||||||
if (_mapped) {
|
if (_mapped) {
|
||||||
_valid = true;
|
_valid = true;
|
||||||
|
|
|
@ -20,10 +20,12 @@ namespace storage {
|
||||||
class Storage;
|
class Storage;
|
||||||
using StoragePointer = std::shared_ptr<const Storage>;
|
using StoragePointer = std::shared_ptr<const Storage>;
|
||||||
|
|
||||||
|
// Abstract class to represent memory that stored _somewhere_ (in system memory or in a file, for example)
|
||||||
class Storage : public std::enable_shared_from_this<Storage> {
|
class Storage : public std::enable_shared_from_this<Storage> {
|
||||||
public:
|
public:
|
||||||
virtual ~Storage() {}
|
virtual ~Storage() {}
|
||||||
virtual const uint8_t* data() const = 0;
|
virtual const uint8_t* data() const = 0;
|
||||||
|
virtual uint8_t* mutableData() = 0;
|
||||||
virtual size_t size() const = 0;
|
virtual size_t size() const = 0;
|
||||||
virtual operator bool() const { return true; }
|
virtual operator bool() const { return true; }
|
||||||
|
|
||||||
|
@ -41,6 +43,7 @@ namespace storage {
|
||||||
MemoryStorage(size_t size, const uint8_t* data = nullptr);
|
MemoryStorage(size_t size, const uint8_t* data = nullptr);
|
||||||
const uint8_t* data() const override { return _data.data(); }
|
const uint8_t* data() const override { return _data.data(); }
|
||||||
uint8_t* data() { return _data.data(); }
|
uint8_t* data() { return _data.data(); }
|
||||||
|
uint8_t* mutableData() override { return _data.data(); }
|
||||||
size_t size() const override { return _data.size(); }
|
size_t size() const override { return _data.size(); }
|
||||||
operator bool() const override { return true; }
|
operator bool() const override { return true; }
|
||||||
private:
|
private:
|
||||||
|
@ -57,6 +60,7 @@ namespace storage {
|
||||||
FileStorage& operator=(const FileStorage& other) = delete;
|
FileStorage& operator=(const FileStorage& other) = delete;
|
||||||
|
|
||||||
const uint8_t* data() const override { return _mapped; }
|
const uint8_t* data() const override { return _mapped; }
|
||||||
|
uint8_t* mutableData() override { return _mapped; }
|
||||||
size_t size() const override { return _file.size(); }
|
size_t size() const override { return _file.size(); }
|
||||||
operator bool() const override { return _valid; }
|
operator bool() const override { return _valid; }
|
||||||
private:
|
private:
|
||||||
|
@ -69,6 +73,7 @@ namespace storage {
|
||||||
public:
|
public:
|
||||||
ViewStorage(const storage::StoragePointer& owner, size_t size, const uint8_t* data);
|
ViewStorage(const storage::StoragePointer& owner, size_t size, const uint8_t* data);
|
||||||
const uint8_t* data() const override { return _data; }
|
const uint8_t* data() const override { return _data; }
|
||||||
|
uint8_t* mutableData() override { throw std::runtime_error("Cannot modify ViewStorage"); }
|
||||||
size_t size() const override { return _size; }
|
size_t size() const override { return _size; }
|
||||||
operator bool() const override { return *_owner; }
|
operator bool() const override { return *_owner; }
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -13,7 +13,7 @@ if (WIN32)
|
||||||
setup_hifi_plugin(OpenGL Script Qml Widgets)
|
setup_hifi_plugin(OpenGL Script Qml Widgets)
|
||||||
link_hifi_libraries(shared gl networking controllers ui
|
link_hifi_libraries(shared gl networking controllers ui
|
||||||
plugins display-plugins ui-plugins input-plugins script-engine
|
plugins display-plugins ui-plugins input-plugins script-engine
|
||||||
render-utils model gpu gpu-gl render model-networking fbx image)
|
render-utils model gpu gpu-gl render model-networking fbx ktx image)
|
||||||
|
|
||||||
include_hifi_library_headers(octree)
|
include_hifi_library_headers(octree)
|
||||||
|
|
||||||
|
|
|
@ -12,49 +12,56 @@
|
||||||
font-family: Raleway-Regular;
|
font-family: Raleway-Regular;
|
||||||
src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */
|
src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */
|
||||||
url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */
|
url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */
|
||||||
url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */
|
url(../../../../interface/resources/fonts/Raleway-Regular.ttf), /* Development, running script in /HiFi/examples */
|
||||||
|
url(../fonts/Raleway-Regular.ttf); /* Marketplace script */
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Raleway-Light;
|
font-family: Raleway-Light;
|
||||||
src: url(../../../../resources/fonts/Raleway-Light.ttf),
|
src: url(../../../../resources/fonts/Raleway-Light.ttf),
|
||||||
url(../../../../fonts/Raleway-Light.ttf),
|
url(../../../../fonts/Raleway-Light.ttf),
|
||||||
url(../../../../interface/resources/fonts/Raleway-Light.ttf);
|
url(../../../../interface/resources/fonts/Raleway-Light.ttf),
|
||||||
|
url(../fonts/Raleway-Light.ttf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Raleway-Bold;
|
font-family: Raleway-Bold;
|
||||||
src: url(../../../../resources/fonts/Raleway-Bold.ttf),
|
src: url(../../../../resources/fonts/Raleway-Bold.ttf),
|
||||||
url(../../../../fonts/Raleway-Bold.ttf),
|
url(../../../../fonts/Raleway-Bold.ttf),
|
||||||
url(../../../../interface/resources/fonts/Raleway-Bold.ttf);
|
url(../../../../interface/resources/fonts/Raleway-Bold.ttf),
|
||||||
|
url(../fonts/Raleway-Bold.ttf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Raleway-SemiBold;
|
font-family: Raleway-SemiBold;
|
||||||
src: url(../../../../resources/fonts/Raleway-SemiBold.ttf),
|
src: url(../../../../resources/fonts/Raleway-SemiBold.ttf),
|
||||||
url(../../../../fonts/Raleway-SemiBold.ttf),
|
url(../../../../fonts/Raleway-SemiBold.ttf),
|
||||||
url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf);
|
url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf),
|
||||||
|
url(../fonts/Raleway-SemiBold.ttf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: FiraSans-SemiBold;
|
font-family: FiraSans-SemiBold;
|
||||||
src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf),
|
src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf),
|
||||||
url(../../../../fonts/FiraSans-SemiBold.ttf),
|
url(../../../../fonts/FiraSans-SemiBold.ttf),
|
||||||
url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf);
|
url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf),
|
||||||
|
url(../fonts/FiraSans-SemiBold.ttf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: AnonymousPro-Regular;
|
font-family: AnonymousPro-Regular;
|
||||||
src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf),
|
src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf),
|
||||||
url(../../../../fonts/AnonymousPro-Regular.ttf),
|
url(../../../../fonts/AnonymousPro-Regular.ttf),
|
||||||
url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf);
|
url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf),
|
||||||
|
url(../fonts/AnonymousPro-Regular.ttf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: HiFi-Glyphs;
|
font-family: HiFi-Glyphs;
|
||||||
src: url(../../../../resources/fonts/hifi-glyphs.ttf),
|
src: url(../../../../resources/fonts/hifi-glyphs.ttf),
|
||||||
url(../../../../fonts/hifi-glyphs.ttf),
|
url(../../../../fonts/hifi-glyphs.ttf),
|
||||||
url(../../../../interface/resources/fonts/hifi-glyphs.ttf);
|
url(../../../../interface/resources/fonts/hifi-glyphs.ttf),
|
||||||
|
url(../fonts/hifi-glyphs.ttf);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
BIN
scripts/system/html/img/loader-red-countdown-ring.gif
Normal file
BIN
scripts/system/html/img/loader-red-countdown-ring.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
|
@ -65,7 +65,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="spinner" class="hidden">
|
<div id="spinner" class="hidden">
|
||||||
<img src="../../../resources/icons/loader-red-countdown-ring.gif" />
|
<img src="img/loader-red-countdown-ring.gif" />
|
||||||
<span id="countdown-number">3</span>
|
<span id="countdown-number">3</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="record-controls">
|
<div id="record-controls">
|
||||||
|
|
|
@ -34,7 +34,8 @@ Script.update.connect(function () {
|
||||||
var avatars = AvatarList.getAvatarIdentifiers();
|
var avatars = AvatarList.getAvatarIdentifiers();
|
||||||
avatars.forEach(function (id) {
|
avatars.forEach(function (id) {
|
||||||
var avatar = AvatarList.getAvatar(id);
|
var avatar = AvatarList.getAvatar(id);
|
||||||
if ((MyAvatar.sessionUUID !== avatar.sessionUUID) && (avatar.displayName.indexOf(MICROPHONE_DISPLAY_NAME) !== 0)) {
|
if (MyAvatar.sessionUUID !== avatar.sessionUUID) {
|
||||||
|
if (avatar.displayName.indexOf(MICROPHONE_DISPLAY_NAME) !== 0) {
|
||||||
othersLoudness += Math.round(avatar.audioLoudness);
|
othersLoudness += Math.round(avatar.audioLoudness);
|
||||||
}
|
}
|
||||||
// Mute other microphone avatars to not feedback with muti-source environment
|
// Mute other microphone avatars to not feedback with muti-source environment
|
||||||
|
@ -43,6 +44,7 @@ Script.update.connect(function () {
|
||||||
Users.personalMute(avatar.sessionUUID, true);
|
Users.personalMute(avatar.sessionUUID, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
averageLoudness = AVERAGING_TIME * averageLoudness + (1.0 - AVERAGING_TIME) * othersLoudness;
|
averageLoudness = AVERAGING_TIME * averageLoudness + (1.0 - AVERAGING_TIME) * othersLoudness;
|
||||||
|
|
|
@ -3,7 +3,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model render-utils)
|
||||||
# This is not a testcase -- just set it up as a regular hifi project
|
# This is not a testcase -- just set it up as a regular hifi project
|
||||||
setup_hifi_project(Quick Gui OpenGL Script Widgets)
|
setup_hifi_project(Quick Gui OpenGL Script Widgets)
|
||||||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
||||||
link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils octree image)
|
link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils octree image ktx)
|
||||||
package_libraries_for_deployment()
|
package_libraries_for_deployment()
|
||||||
|
|
||||||
target_nsight()
|
target_nsight()
|
||||||
|
|
|
@ -10,7 +10,7 @@ setup_hifi_project(Quick Gui OpenGL)
|
||||||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
||||||
|
|
||||||
# link in the shared libraries
|
# link in the shared libraries
|
||||||
link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image)
|
link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics ktx image)
|
||||||
|
|
||||||
package_libraries_for_deployment()
|
package_libraries_for_deployment()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue