Merge remote-tracking branch 'upstream/master' into 21324

This commit is contained in:
Menithal 2017-06-02 06:39:55 +03:00
commit bdcf9a0070
51 changed files with 1178 additions and 622 deletions

View file

@ -31,15 +31,18 @@ These are not placed in your normal build tree when doing an out of source build
If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE_LOCAL_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
### OS Specific Build Guides
* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X.
* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows.
* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android
### CMake
Hifi uses CMake to generate build files and project files for your platform.
#### Qt
In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation.
This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
@ -51,6 +54,7 @@ The path it needs to be set to will depend on where and how Qt5 was installed. e
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
#### Generating build files
Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean.
mkdir build
@ -60,6 +64,7 @@ Create a build directory in the root of your checkout and then run the CMake bui
If cmake gives you the same error message repeatedly after the build fails (e.g. you had a typo in the QT_CMAKE_PREFIX_PATH that you fixed but the `.cmake` files still cannot be found), try removing `CMakeCache.txt`.
#### Variables
Any variables that need to be set for CMake to find dependencies can be set as ENV variables in your shell profile, or passed directly to CMake with a `-D` flag appended to the `cmake ..` command.
For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:

View file

@ -1,6 +1,7 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.
### Qt5 Dependencies
Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required:
libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev

View file

@ -1,6 +1,7 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
### Homebrew
[Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
brew tap homebrew/versions
@ -16,6 +17,7 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR:
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
### Qt
Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg).
Keep the default components checked when going through the installer.
@ -24,6 +26,7 @@ Once Qt is installed, you need to manually configure the following:
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory.
### Xcode
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
cmake .. -GXcode

View file

@ -24,7 +24,7 @@ Go to "Control Panel > System > Advanced System Settings > Environment Variables
### Step 5. Installing OpenSSL
Download and install the [Win64 OpenSSL v1.0.2k Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2k.exe).
Download and install the [Win64 OpenSSL v1.0.2L Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2L.exe).
### Step 6. Running CMake to Generate Build Files

View file

@ -410,6 +410,7 @@ void Agent::executeScript() {
bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened
bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed
_audioGateOpen = audioGateOpen;
Q_UNUSED(openedInLastBlock);
// the codec must be flushed to silence before sending silent packets,
// so delay the transition to silent packets by one packet after becoming silent.

View file

@ -388,9 +388,14 @@ Rectangle {
sortIndicatorColumn: settings.nearbySortIndicatorColumn;
sortIndicatorOrder: settings.nearbySortIndicatorOrder;
onSortIndicatorColumnChanged: {
if (sortIndicatorColumn > 2) {
// these are not sortable, switch back to last column
sortIndicatorColumn = settings.nearbySortIndicatorColumn;
} else {
settings.nearbySortIndicatorColumn = sortIndicatorColumn;
sortModel();
}
}
onSortIndicatorOrderChanged: {
settings.nearbySortIndicatorOrder = sortIndicatorOrder;
sortModel();

View file

@ -136,7 +136,7 @@ Item {
for (var i = 0; i < sections.length; i++) {
totalHeight += sections[i].height + sections[i].getPreferencesHeight();
}
var bottomPadding = 100;
var bottomPadding = 170;
return (totalHeight + bottomPadding);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -436,6 +436,10 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
const char* portStr = getCmdOption(argc, constArgv, "--listenPort");
const int listenPort = portStr ? atoi(portStr) : INVALID_PORT;
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
Setting::init();
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@ -456,10 +460,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
QCoreApplication::addLibraryPath(audioDLLPath);
#endif
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>();
@ -1459,6 +1459,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
updateSystemTabletMode();
connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
}
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {

View file

@ -24,12 +24,15 @@
#include "Application.h"
#include "Menu.h"
#include <SettingHandle.h>
#include <RunningMarker.h>
#include <SettingHandle.h>
#include <SettingHelpers.h>
bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) {
Settings settings;
QSettings::setDefaultFormat(JSON_FORMAT);
QSettings settings;
settings.beginGroup("Developer");
QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions);
QVariant askToResetSettingsOption = settings.value(MenuOption::AskToResetSettings);
@ -106,7 +109,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
return;
}
Settings settings;
QSettings settings;
const QString ADDRESS_MANAGER_GROUP = "AddressManager";
const QString ADDRESS_KEY = "address";
const QString AVATAR_GROUP = "Avatar";

View file

@ -25,7 +25,7 @@
#include <QThread>
const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Friends;
const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Connections;
DiscoverabilityManager::DiscoverabilityManager() :
_mode("discoverabilityMode", DEFAULT_DISCOVERABILITY_MODE)

View file

@ -2770,7 +2770,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const {
auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex);
return createMatFromQuatAndPos(leftFootRot, leftFootPos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS);
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS);
}
}
@ -2782,7 +2782,7 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const {
auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex);
return createMatFromQuatAndPos(rightFootRot, rightFootPos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS);
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS);
}
}
@ -2805,7 +2805,7 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const {
auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex);
return createMatFromQuatAndPos(leftArmRot, leftArmPos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS);
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS);
}
}

View file

@ -444,6 +444,9 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
if (this->_pressed && event.getType() == PointerEvent::Move) {
return;
}
// Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover.
// FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times).
// This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery".

View file

@ -13,6 +13,8 @@
#include <glm/glm.hpp>
#include <QRunnable>
#include <QThreadPool>
#include <QDataStream>
#include <QtCore/QDebug>
#include <QtNetwork/QNetworkRequest>
@ -49,13 +51,43 @@ Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
_isAmbisonic(isAmbisonic),
_isReady(false)
{
}
void Sound::downloadFinished(const QByteArray& data) {
// this is a QRunnable, will delete itself after it has finished running
SoundProcessor* soundProcessor = new SoundProcessor(_url, data, _isStereo, _isAmbisonic);
connect(soundProcessor, &SoundProcessor::onSuccess, this, &Sound::soundProcessSuccess);
connect(soundProcessor, &SoundProcessor::onError, this, &Sound::soundProcessError);
QThreadPool::globalInstance()->start(soundProcessor);
}
void Sound::soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration) {
qCDebug(audio) << "Setting ready state for sound file" << _url.toDisplayString();
_byteArray = data;
_isStereo = stereo;
_isAmbisonic = ambisonic;
_duration = duration;
_isReady = true;
finishedLoading(true);
emit ready();
}
void Sound::soundProcessError(int error, QString str) {
qCCritical(audio) << "Failed to process sound file" << _url.toDisplayString() << "code =" << error << str;
emit failed(QNetworkReply::UnknownContentError);
finishedLoading(false);
}
void SoundProcessor::run() {
qCDebug(audio) << "Processing sound file" << _url.toDisplayString();
// replace our byte array with the downloaded data
QByteArray rawAudioByteArray = QByteArray(data);
QString fileName = getURL().fileName().toLower();
QByteArray rawAudioByteArray = QByteArray(_data);
QString fileName = _url.fileName().toLower();
static const QString WAV_EXTENSION = ".wav";
static const QString RAW_EXTENSION = ".raw";
@ -72,31 +104,28 @@ void Sound::downloadFinished(const QByteArray& data) {
// since it's raw the only way for us to know that is if the file was called .stereo.raw
if (fileName.toLower().endsWith("stereo.raw")) {
_isStereo = true;
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file.";
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << _url << "as stereo audio file.";
}
// Process as 48khz RAW file
downSample(rawAudioByteArray, 48000);
} else {
qCDebug(audio) << "Unknown sound file type";
emit onError(300, "Failed to load sound file, reason: unknown sound file type");
return;
}
finishedLoading(true);
_isReady = true;
emit ready();
emit onSuccess(_data, _isStereo, _isAmbisonic, _duration);
}
void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
void SoundProcessor::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
// we want to convert it to the format that the audio-mixer wants
// which is signed, 16-bit, 24Khz
if (sampleRate == AudioConstants::SAMPLE_RATE) {
// no resampling needed
_byteArray = rawAudioByteArray;
_data = rawAudioByteArray;
} else {
int numChannels = _isAmbisonic ? AudioConstants::AMBISONIC : (_isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
@ -106,15 +135,15 @@ void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(maxDestinationBytes);
_data.resize(maxDestinationBytes);
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
(int16_t*)_byteArray.data(),
(int16_t*)_data.data(),
numSourceFrames);
// truncate to actual output
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(numDestinationBytes);
_data.resize(numDestinationBytes);
}
}
@ -163,7 +192,7 @@ struct WAVEFormat {
};
// returns wavfile sample rate, used for resampling
int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
// Create a data stream to analyze the data
QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly);

View file

@ -12,6 +12,7 @@
#ifndef hifi_Sound_h
#define hifi_Sound_h
#include <QRunnable>
#include <QtCore/QObject>
#include <QtNetwork/QNetworkReply>
#include <QtScript/qscriptengine.h>
@ -29,12 +30,15 @@ public:
bool isReady() const { return _isReady; }
float getDuration() const { return _duration; }
const QByteArray& getByteArray() const { return _byteArray; }
signals:
void ready();
protected slots:
void soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
void soundProcessError(int error, QString str);
private:
QByteArray _byteArray;
bool _isStereo;
@ -42,10 +46,33 @@ private:
bool _isReady;
float _duration; // In seconds
virtual void downloadFinished(const QByteArray& data) override;
};
class SoundProcessor : public QObject, public QRunnable {
Q_OBJECT
public:
SoundProcessor(const QUrl& url, const QByteArray& data, bool stereo, bool ambisonic)
: _url(url), _data(data), _isStereo(stereo), _isAmbisonic(ambisonic)
{
}
virtual void run() override;
void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
virtual void downloadFinished(const QByteArray& data) override;
signals:
void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
void onError(int error, QString str);
private:
QUrl _url;
QByteArray _data;
bool _isStereo;
bool _isAmbisonic;
float _duration;
};
typedef QSharedPointer<Sound> SharedSoundPointer;

View file

@ -850,7 +850,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
glm::quat sensorToWorldQuat;
unpackOrientationQuatFromSixBytes(data->sensorToWorldQuat, sensorToWorldQuat);
float sensorToWorldScale;
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&data->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
// Grab a local copy of sensorToWorldScale to be able to use the unpack function with a pointer on it,
// a direct pointer on the struct attribute triggers warnings because of potential misalignement.
auto srcSensorToWorldScale = data->sensorToWorldScale;
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&srcSensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
glm::vec3 sensorToWorldTrans(data->sensorToWorldTrans[0], data->sensorToWorldTrans[1], data->sensorToWorldTrans[2]);
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) {

View file

@ -42,8 +42,6 @@ enum class Action {
LEFT_HAND = NUM_COMBINED_AXES,
RIGHT_HAND,
LEFT_ARM,
RIGHT_ARM,
LEFT_FOOT,
RIGHT_FOOT,
HIPS,
@ -103,6 +101,8 @@ enum class Action {
// Bisected aliases for TRANSLATE_CAMERA_Z
BOOM_IN,
BOOM_OUT,
LEFT_ARM,
RIGHT_ARM,
NUM_ACTIONS,

View file

@ -9,7 +9,6 @@
#include "InputRecorder.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QFile>
#include <QDir>
@ -19,9 +18,12 @@
#include <QByteArray>
#include <QStandardPaths>
#include <PathUtils.h>
#include <QUrl>
#include <BuildInfo.h>
#include <GLMHelpers.h>
#include <DependencyManager.h>
#include "UserInputMapper.h"
QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/";
QString FILE_PREFIX_NAME = "input-recording-";
@ -91,7 +93,7 @@ namespace controller {
}
void exportToFile(QJsonObject& object) {
void exportToFile(const QJsonObject& object) {
if (!QDir(SAVE_DIRECTORY).exists()) {
QDir().mkdir(SAVE_DIRECTORY);
}
@ -108,6 +110,7 @@ namespace controller {
QJsonDocument saveData(object);
QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact));
saveFile.write(compressedData);
saveFile.close();
}
QJsonObject openFile(const QString& file, bool& status) {
@ -122,6 +125,7 @@ namespace controller {
QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData);
object = jsonDoc.object();
status = true;
openFile.close();
return object;
}
@ -146,31 +150,44 @@ namespace controller {
_actionStateList.clear();
}
void InputRecorder::saveRecording() {
QJsonObject InputRecorder::recordDataToJson() {
QJsonObject data;
data["frameCount"] = _framesRecorded;
data["version"] = "1.0";
QJsonArray actionArrayList;
QJsonArray poseArrayList;
for(const ActionStates actionState: _actionStateList) {
QJsonArray actionArray;
for (const float value: actionState) {
actionArray.append(value);
for (const auto action: actionState) {
QJsonObject actionJson;
actionJson["name"] = action.first;
actionJson["value"] = action.second;
actionArray.append(actionJson);
}
actionArrayList.append(actionArray);
}
for (const PoseStates poseState: _poseStateList) {
QJsonArray poseArray;
for (const Pose pose: poseState) {
poseArray.append(poseToJsonObject(pose));
for (const auto pose: poseState) {
QJsonObject poseJson;
poseJson["name"] = pose.first;
poseJson["pose"] = poseToJsonObject(pose.second);
poseArray.append(poseJson);
}
poseArrayList.append(poseArray);
}
data["actionList"] = actionArrayList;
data["poseList"] = poseArrayList;
exportToFile(data);
return data;
}
void InputRecorder::saveRecording() {
QJsonObject jsonData = recordDataToJson();
exportToFile(jsonData);
}
void InputRecorder::loadRecording(const QString& path) {
@ -181,8 +198,8 @@ namespace controller {
resetFrame();
_poseStateList.clear();
_actionStateList.clear();
QString filePath = path;
filePath.remove(0,8);
QUrl urlPath(path);
QString filePath = urlPath.toLocalFile();
QFileInfo info(filePath);
QString extension = info.suffix();
if (extension != "gz") {
@ -190,8 +207,9 @@ namespace controller {
return;
}
bool success = false;
QJsonObject data = openFile(info.absoluteFilePath(), success);
if (success) {
QJsonObject data = openFile(filePath, success);
auto keyValue = data.find("version");
if (success && keyValue != data.end()) {
_framesRecorded = data["frameCount"].toInt();
QJsonArray actionArrayList = data["actionList"].toArray();
QJsonArray poseArrayList = data["poseList"].toArray();
@ -199,19 +217,47 @@ namespace controller {
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
QJsonArray actionState = actionArrayList[actionIndex].toArray();
for (int index = 0; index < actionState.size(); index++) {
_currentFrameActions[index] = actionState[index].toDouble();
QJsonObject actionObject = actionState[index].toObject();
_currentFrameActions[actionObject["name"].toString()] = actionObject["value"].toDouble();
}
_actionStateList.push_back(_currentFrameActions);
_currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
_currentFrameActions.clear();
}
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
QJsonArray poseState = poseArrayList[poseIndex].toArray();
for (int index = 0; index < poseState.size(); index++) {
_currentFramePoses[index] = jsonObjectToPose(poseState[index].toObject());
QJsonObject poseObject = poseState[index].toObject();
_currentFramePoses[poseObject["name"].toString()] = jsonObjectToPose(poseObject["pose"].toObject());
}
_poseStateList.push_back(_currentFramePoses);
_currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
_currentFramePoses.clear();
}
} else if (success) {
//convert recording to new reacording standard and rewrite file
auto userInputMapper = DependencyManager::get<UserInputMapper>();
_framesRecorded = data["frameCount"].toInt();
QJsonArray actionArrayList = data["actionList"].toArray();
QJsonArray poseArrayList = data["poseList"].toArray();
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
QJsonArray actionState = actionArrayList[actionIndex].toArray();
for (int index = 0; index < actionState.size(); index++) {
QString actionName = userInputMapper->getActionName(Action(index));
_currentFrameActions[actionName] = actionState[index].toDouble();
}
_actionStateList.push_back(_currentFrameActions);
_currentFrameActions.clear();
}
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
QJsonArray poseState = poseArrayList[poseIndex].toArray();
for (int index = 0; index < poseState.size(); index++) {
QString actionName = userInputMapper->getActionName(Action(index));
_currentFramePoses[actionName] = jsonObjectToPose(poseState[index].toObject());
}
_poseStateList.push_back(_currentFramePoses);
_currentFramePoses.clear();
}
}
@ -234,41 +280,36 @@ namespace controller {
_playCount = 0;
}
void InputRecorder::setActionState(controller::Action action, float value) {
void InputRecorder::setActionState(const QString& action, float value) {
if (_recording) {
_currentFrameActions[toInt(action)] += value;
_currentFrameActions[action] += value;
}
}
void InputRecorder::setActionState(controller::Action action, const controller::Pose pose) {
void InputRecorder::setActionState(const QString& action, const controller::Pose& pose) {
if (_recording) {
_currentFramePoses[toInt(action)] = pose;
_currentFramePoses[action] = pose;
}
}
void InputRecorder::resetFrame() {
if (_recording) {
for(auto& channel : _currentFramePoses) {
channel = Pose();
}
for(auto& channel : _currentFrameActions) {
channel = 0.0f;
}
_currentFramePoses.clear();
_currentFrameActions.clear();
}
}
float InputRecorder::getActionState(controller::Action action) {
float InputRecorder::getActionState(const QString& action) {
if (_actionStateList.size() > 0 ) {
return _actionStateList[_playCount][toInt(action)];
return _actionStateList[_playCount][action];
}
return 0.0f;
}
controller::Pose InputRecorder::getPoseState(controller::Action action) {
controller::Pose InputRecorder::getPoseState(const QString& action) {
if (_poseStateList.size() > 0) {
return _poseStateList[_playCount][toInt(action)];
return _poseStateList[_playCount][action];
}
return Pose();

View file

@ -12,8 +12,10 @@
#include <mutex>
#include <atomic>
#include <vector>
#include <map>
#include <QString>
#include <QJsonObject>
#include "Pose.h"
#include "Actions.h"
@ -21,8 +23,8 @@
namespace controller {
class InputRecorder {
public:
using PoseStates = std::vector<Pose>;
using ActionStates = std::vector<float>;
using PoseStates = std::map<QString, Pose>;
using ActionStates = std::map<QString, float>;
InputRecorder();
~InputRecorder();
@ -40,20 +42,21 @@ namespace controller {
void resetFrame();
bool isRecording() { return _recording; }
bool isPlayingback() { return (_playback && !_loading); }
void setActionState(controller::Action action, float value);
void setActionState(controller::Action action, const controller::Pose pose);
float getActionState(controller::Action action);
controller::Pose getPoseState(controller::Action action);
void setActionState(const QString& action, float value);
void setActionState(const QString& action, const controller::Pose& pose);
float getActionState(const QString& action);
controller::Pose getPoseState(const QString& action);
QString getSaveDirectory();
void frameTick();
private:
QJsonObject recordDataToJson();
bool _recording { false };
bool _playback { false };
bool _loading { false };
std::vector<PoseStates> _poseStateList = std::vector<PoseStates>();
std::vector<ActionStates> _actionStateList = std::vector<ActionStates>();
PoseStates _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
ActionStates _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
PoseStates _currentFramePoses;
ActionStates _currentFrameActions;
int _framesRecorded { 0 };
int _playCount { 0 };

View file

@ -17,31 +17,33 @@ using namespace controller;
void ActionEndpoint::apply(float newValue, const Pointer& source) {
InputRecorder* inputRecorder = InputRecorder::getInstance();
auto userInputMapper = DependencyManager::get<UserInputMapper>();
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
if(inputRecorder->isPlayingback()) {
newValue = inputRecorder->getActionState(Action(_input.getChannel()));
newValue = inputRecorder->getActionState(actionName);
}
_currentValue += newValue;
if (_input != Input::INVALID_INPUT) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
}
inputRecorder->setActionState(Action(_input.getChannel()), newValue);
inputRecorder->setActionState(actionName, newValue);
}
void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
_currentPose = value;
InputRecorder* inputRecorder = InputRecorder::getInstance();
inputRecorder->setActionState(Action(_input.getChannel()), _currentPose);
auto userInputMapper = DependencyManager::get<UserInputMapper>();
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
inputRecorder->setActionState(actionName, _currentPose);
if (inputRecorder->isPlayingback()) {
_currentPose = inputRecorder->getPoseState(Action(_input.getChannel()));
_currentPose = inputRecorder->getPoseState(actionName);
}
if (!_currentPose.isValid()) {
return;
}
if (_input != Input::INVALID_INPUT) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
}
}

View file

@ -21,9 +21,9 @@ namespace controller {
LowVelocityFilter(float rotationConstant, float translationConstant) :
_translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
virtual float apply(float value) const override { return value; }
virtual Pose apply(Pose newPose) const;
virtual bool parseParameters(const QJsonValue& parameters) override;
float apply(float value) const override { return value; }
Pose apply(Pose newPose) const override;
bool parseParameters(const QJsonValue& parameters) override;
private:
float _translationConstant { 0.1f };

View file

@ -192,7 +192,7 @@ void EntityTreeRenderer::update() {
tree->update();
// Handle enter/leave entity logic
bool updated = checkEnterLeaveEntities();
checkEnterLeaveEntities();
// Even if we're not moving the mouse, if we started clicking on an entity and we have
// not yet released the hold then this is still considered a holdingClickOnEntity event

View file

@ -367,7 +367,6 @@ void RenderableZoneEntityItem::sceneUpdateRenderItemFromEntity(render::Transacti
bool sunChanged = _keyLightPropertiesChanged;
bool backgroundChanged = _backgroundPropertiesChanged;
bool stageChanged = _stagePropertiesChanged;
bool skyboxChanged = _skyboxPropertiesChanged;
transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [=](RenderableZoneEntityItemMeta& data) {

View file

@ -211,6 +211,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
break;
case gpu::NUINT32:
case gpu::NINT32:
case gpu::COMPRESSED:
case gpu::NUM_TYPES: // quiet compiler
Q_UNREACHABLE();
}
@ -484,6 +485,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
texel.internalFormat = GL_R8_SNORM;
break;
}
case gpu::COMPRESSED:
case gpu::NUM_TYPES: { // quiet compiler
Q_UNREACHABLE();
}
@ -527,6 +529,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
texel.internalFormat = GL_DEPTH_COMPONENT24;
break;
}
case gpu::COMPRESSED:
case gpu::NUM_TYPES: { // quiet compiler
Q_UNREACHABLE();
}
@ -641,6 +644,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
break;
case gpu::NUINT32:
case gpu::NINT32:
case gpu::COMPRESSED:
case gpu::NUM_TYPES: // quiet compiler
Q_UNREACHABLE();
}

View file

@ -211,6 +211,8 @@ namespace khronos {
template <uint32_t ALIGNMENT>
inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) {
enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) };
static_assert(val, "template parameter ALIGNMENT must be a power of 2");
// FIXME add static assert that ALIGNMENT is a power of 2
static uint32_t ALIGNMENT_REMAINDER = ALIGNMENT - 1;
return (value + ALIGNMENT_REMAINDER) / ALIGNMENT;

View file

@ -229,7 +229,7 @@ namespace ktx {
} else {
Image::FaceBytes faceBytes(NUM_CUBEMAPFACES);
auto faceSize = srcImages[l]._faceSize;
for (int face = 0; face < NUM_CUBEMAPFACES; face++) {
for (uint32_t face = 0; face < NUM_CUBEMAPFACES; face++) {
memcpy(currentPtr, srcImages[l]._faceBytes[face], faceSize);
faceBytes[face] = currentPtr;
currentPtr += faceSize;

View file

@ -201,6 +201,13 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
}
}
void AccountManager::setSessionID(const QUuid& sessionID) {
if (_sessionID != sessionID) {
qCDebug(networking) << "Metaverse session ID changed to" << uuidStringWithoutCurlyBraces(sessionID);
_sessionID = sessionID;
}
}
void AccountManager::sendRequest(const QString& path,
AccountManagerAuth::Type authType,
QNetworkAccessManager::Operation operation,

View file

@ -90,7 +90,7 @@ public:
static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply);
QUuid getSessionID() const { return _sessionID; }
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
void setSessionID(const QUuid& sessionID);
void setTemporaryDomain(const QUuid& domainID, const QString& key);
const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); }

View file

@ -11,52 +11,81 @@
#include "FileCache.h"
#include <cstdio>
#include <cassert>
#include <fstream>
#include <unordered_set>
#include <QDir>
#include <QSaveFile>
#include <unordered_set>
#include <queue>
#include <cassert>
#include <QtCore/QDateTime>
#include <QtCore/QDir>
#include <QtCore/QSaveFile>
#include <QtCore/QStorageInfo>
#include <PathUtils.h>
#include <NumericalConstants.h>
#ifdef Q_OS_WIN
#include <sys/utime.h>
#else
#include <utime.h>
#endif
#ifdef NDEBUG
Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg)
#else
Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache")
#endif
using namespace cache;
static const std::string MANIFEST_NAME = "manifest";
static const char DIR_SEP = '/';
static const char EXT_SEP = '.';
static const size_t BYTES_PER_MEGABYTES = 1024 * 1024;
static const size_t BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES;
const size_t FileCache::DEFAULT_UNUSED_MAX_SIZE = 5 * BYTES_PER_GIGABYTES; // 5GB
const size_t FileCache::MAX_UNUSED_MAX_SIZE = 100 * BYTES_PER_GIGABYTES; // 100GB
const size_t FileCache::DEFAULT_OFFLINE_MAX_SIZE = 2 * BYTES_PER_GIGABYTES; // 2GB
const size_t FileCache::DEFAULT_MAX_SIZE { GB_TO_BYTES(5) };
const size_t FileCache::MAX_MAX_SIZE { GB_TO_BYTES(100) };
const size_t FileCache::DEFAULT_MIN_FREE_STORAGE_SPACE { GB_TO_BYTES(1) };
void FileCache::setUnusedFileCacheSize(size_t unusedFilesMaxSize) {
_unusedFilesMaxSize = std::min(unusedFilesMaxSize, MAX_UNUSED_MAX_SIZE);
reserve(0);
std::string getCacheName(const std::string& dirname_str) {
QString dirname { dirname_str.c_str() };
QDir dir(dirname);
if (!dir.isAbsolute()) {
return dirname_str;
}
return dir.dirName().toStdString();
}
std::string getCachePath(const std::string& dirname_str) {
QString dirname { dirname_str.c_str() };
QDir dir(dirname);
if (dir.isAbsolute()) {
return dirname_str;
}
return PathUtils::getAppLocalDataFilePath(dirname).toStdString();
}
void FileCache::setMinFreeSize(size_t size) {
_minFreeSpaceSize = size;
clean();
emit dirty();
}
void FileCache::setOfflineFileCacheSize(size_t offlineFilesMaxSize) {
_offlineFilesMaxSize = std::min(offlineFilesMaxSize, MAX_UNUSED_MAX_SIZE);
void FileCache::setMaxSize(size_t maxSize) {
_maxSize = std::min(maxSize, MAX_MAX_SIZE);
clean();
emit dirty();
}
FileCache::FileCache(const std::string& dirname, const std::string& ext, QObject* parent) :
QObject(parent),
_ext(ext),
_dirname(dirname),
_dirpath(PathUtils::getAppLocalDataFilePath(dirname.c_str()).toStdString()) {}
_dirname(getCacheName(dirname)),
_dirpath(getCachePath(dirname)) {
}
FileCache::~FileCache() {
clear();
}
void fileDeleter(File* file) {
file->deleter();
}
void FileCache::initialize() {
QDir dir(_dirpath.c_str());
@ -84,7 +113,7 @@ void FileCache::initialize() {
}
FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) {
FilePointer file(createFile(std::move(metadata), filepath).release(), &fileDeleter);
FilePointer file(createFile(std::move(metadata), filepath).release(), std::mem_fn(&File::deleter));
if (file) {
_numTotalFiles += 1;
_totalFilesSize += file->getLength();
@ -141,6 +170,7 @@ FilePointer FileCache::getFile(const Key& key) {
if (it != _files.cend()) {
file = it->second.lock();
if (file) {
file->touch();
// if it exists, it is active - remove it from the cache
removeUnusedFile(file);
qCDebug(file_cache, "[%s] Found %s", _dirname.c_str(), key.c_str());
@ -155,44 +185,74 @@ FilePointer FileCache::getFile(const Key& key) {
}
std::string FileCache::getFilepath(const Key& key) {
return _dirpath + '/' + key + '.' + _ext;
return _dirpath + DIR_SEP + key + EXT_SEP + _ext;
}
void FileCache::addUnusedFile(const FilePointer file) {
void FileCache::addUnusedFile(const FilePointer& file) {
{
Lock lock(_filesMutex);
_files[file->getKey()] = file;
}
reserve(file->getLength());
file->_LRUKey = ++_lastLRUKey;
{
Lock lock(_unusedFilesMutex);
_unusedFiles.insert({ file->_LRUKey, file });
_unusedFiles.insert(file);
_numUnusedFiles += 1;
_unusedFilesSize += file->getLength();
}
clean();
emit dirty();
}
void FileCache::removeUnusedFile(const FilePointer file) {
void FileCache::removeUnusedFile(const FilePointer& file) {
Lock lock(_unusedFilesMutex);
const auto it = _unusedFiles.find(file->_LRUKey);
if (it != _unusedFiles.cend()) {
_unusedFiles.erase(it);
if (_unusedFiles.erase(file)) {
_numUnusedFiles -= 1;
_unusedFilesSize -= file->getLength();
}
}
void FileCache::reserve(size_t length) {
size_t FileCache::getOverbudgetAmount() const {
size_t result = 0;
size_t currentFreeSpace = QStorageInfo(_dirpath.c_str()).bytesFree();
if (_minFreeSpaceSize > currentFreeSpace) {
result = _minFreeSpaceSize - currentFreeSpace;
}
if (_totalFilesSize > _maxSize) {
result = std::max(_totalFilesSize - _maxSize, result);
}
return result;
}
namespace cache {
struct FilePointerComparator {
bool operator()(const FilePointer& a, const FilePointer& b) {
return a->_modified > b->_modified;
}
};
}
void FileCache::clean() {
size_t overbudgetAmount = getOverbudgetAmount();
// Avoid sorting the unused files by LRU if we're not over budget / under free space
if (0 == overbudgetAmount) {
return;
}
Lock unusedLock(_unusedFilesMutex);
while (!_unusedFiles.empty() &&
_unusedFilesSize + length > _unusedFilesMaxSize) {
auto it = _unusedFiles.begin();
auto file = it->second;
using Queue = std::priority_queue<FilePointer, std::vector<FilePointer>, FilePointerComparator>;
Queue queue;
for (const auto& file : _unusedFiles) {
queue.push(file);
}
while (!queue.empty() && overbudgetAmount > 0) {
auto file = queue.top();
queue.pop();
auto length = file->getLength();
unusedLock.unlock();
@ -203,34 +263,32 @@ void FileCache::reserve(size_t length) {
}
unusedLock.lock();
_unusedFiles.erase(it);
_unusedFiles.erase(file);
_numTotalFiles -= 1;
_numUnusedFiles -= 1;
_totalFilesSize -= length;
_unusedFilesSize -= length;
overbudgetAmount -= std::min(length, overbudgetAmount);
}
}
void FileCache::clear() {
Lock unusedFilesLock(_unusedFilesMutex);
for (const auto& pair : _unusedFiles) {
auto& file = pair.second;
file->_cache = nullptr;
// Eliminate any overbudget files
clean();
if (_totalFilesSize > _offlineFilesMaxSize) {
_totalFilesSize -= file->getLength();
} else {
// Mark everything remaining as persisted
Lock unusedFilesLock(_unusedFilesMutex);
for (auto& file : _unusedFiles) {
file->_shouldPersist = true;
file->_cache = nullptr;
qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str());
}
}
_unusedFiles.clear();
}
void File::deleter() {
if (_cache) {
FilePointer self(this, &fileDeleter);
_cache->addUnusedFile(self);
_cache->addUnusedFile(FilePointer(this, std::mem_fn(&File::deleter)));
} else {
deleteLater();
}
@ -239,7 +297,9 @@ void File::deleter() {
File::File(Metadata&& metadata, const std::string& filepath) :
_key(std::move(metadata.key)),
_length(metadata.length),
_filepath(filepath) {}
_filepath(filepath),
_modified(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch()) {
}
File::~File() {
QFile file(getFilepath().c_str());
@ -248,3 +308,8 @@ File::~File() {
file.remove();
}
}
void File::touch() {
utime(_filepath.c_str(), nullptr);
_modified = std::max<int64_t>(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch(), _modified);
}

View file

@ -14,6 +14,7 @@
#include <memory>
#include <cstddef>
#include <map>
#include <unordered_set>
#include <mutex>
#include <string>
#include <unordered_map>
@ -35,24 +36,28 @@ class FileCache : public QObject {
Q_PROPERTY(size_t sizeTotal READ getSizeTotalFiles NOTIFY dirty)
Q_PROPERTY(size_t sizeCached READ getSizeCachedFiles NOTIFY dirty)
static const size_t DEFAULT_UNUSED_MAX_SIZE;
static const size_t MAX_UNUSED_MAX_SIZE;
static const size_t DEFAULT_OFFLINE_MAX_SIZE;
static const size_t DEFAULT_MAX_SIZE;
static const size_t MAX_MAX_SIZE;
static const size_t DEFAULT_MIN_FREE_STORAGE_SPACE;
public:
// You can initialize the FileCache with a directory name (ex.: "temp_jpgs") that will be relative to the application local data, OR with a full path
// The file cache will ignore any file that doesn't match the extension provided
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
virtual ~FileCache();
size_t getNumTotalFiles() const { return _numTotalFiles; }
size_t getNumCachedFiles() const { return _numUnusedFiles; }
size_t getSizeTotalFiles() const { return _totalFilesSize; }
size_t getSizeCachedFiles() const { return _unusedFilesSize; }
void setUnusedFileCacheSize(size_t unusedFilesMaxSize);
size_t getUnusedFileCacheSize() const { return _unusedFilesSize; }
// Set the maximum amount of disk space to use on disk
void setMaxSize(size_t maxCacheSize);
void setOfflineFileCacheSize(size_t offlineFilesMaxSize);
// initialize FileCache with a directory name (not a path, ex.: "temp_jpgs") and an ext (ex.: "jpg")
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
virtual ~FileCache();
// Set the minumum amount of free disk space to retain. This supercedes the max size,
// so if the cache is consuming all but 500 MB of the drive, unused entries will be ejected
// to free up more space, regardless of the cache max size
void setMinFreeSize(size_t size);
using Key = std::string;
struct Metadata {
@ -76,10 +81,11 @@ public:
signals:
void dirty();
protected:
public:
/// must be called after construction to create the cache on the fs and restore persisted files
void initialize();
// Add file to the cache and return the cache entry.
FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false);
FilePointer getFile(const Key& key);
@ -95,11 +101,17 @@ private:
std::string getFilepath(const Key& key);
FilePointer addFile(Metadata&& metadata, const std::string& filepath);
void addUnusedFile(const FilePointer file);
void removeUnusedFile(const FilePointer file);
void reserve(size_t length);
void addUnusedFile(const FilePointer& file);
void removeUnusedFile(const FilePointer& file);
void clean();
void clear();
size_t getOverbudgetAmount() const;
// FIXME it might be desirable to have the min free space variable be static so it can be
// shared among multiple instances of FileCache
std::atomic<size_t> _minFreeSpaceSize { DEFAULT_MIN_FREE_STORAGE_SPACE };
std::atomic<size_t> _maxSize { DEFAULT_MAX_SIZE };
std::atomic<size_t> _numTotalFiles { 0 };
std::atomic<size_t> _numUnusedFiles { 0 };
std::atomic<size_t> _totalFilesSize { 0 };
@ -113,12 +125,8 @@ private:
std::unordered_map<Key, std::weak_ptr<File>> _files;
Mutex _filesMutex;
std::map<int, FilePointer> _unusedFiles;
std::unordered_set<FilePointer> _unusedFiles;
Mutex _unusedFilesMutex;
size_t _unusedFilesMaxSize { DEFAULT_UNUSED_MAX_SIZE };
int _lastLRUKey { 0 };
size_t _offlineFilesMaxSize { DEFAULT_OFFLINE_MAX_SIZE };
};
class File : public QObject {
@ -142,13 +150,15 @@ protected:
private:
friend class FileCache;
friend struct FilePointerComparator;
const Key _key;
const size_t _length;
const std::string _filepath;
FileCache* _cache;
int _LRUKey { 0 };
void touch();
FileCache* _cache { nullptr };
int64_t _modified { 0 };
bool _shouldPersist { false };
};

View file

@ -241,6 +241,9 @@ void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) {
std::lock_guard<std::mutex> locker { _handshakeMutex };
_hasReceivedHandshakeACK = true;
}
_lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
// Notify on the handshake ACK condition
_handshakeACKCondition.notify_one();
}

View file

@ -112,7 +112,6 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext,
skybox->render(batch, args->getViewFrustum());
});
args->_batch = nullptr;
gpu::Batch& batch = *args->_batch;
// break;
}

View file

@ -169,7 +169,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I
batch.setUniformBuffer(ZONE_DEFERRED_TRANSFORM_BUFFER, deferredTransform->getFrameTransformBuffer());
batch.setPipeline(getKeyLightPipeline());
auto numKeys = keyLightStack.size();
auto numKeys = (int) keyLightStack.size();
for (int i = numKeys - 1; i >= 0; i--) {
model.setTranslation(glm::vec3(-4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
batch.setModelTransform(model);
@ -180,7 +180,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I
}
batch.setPipeline(getAmbientPipeline());
auto numAmbients = ambientLightStack.size();
auto numAmbients = (int) ambientLightStack.size();
for (int i = numAmbients - 1; i >= 0; i--) {
model.setTranslation(glm::vec3(0.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
batch.setModelTransform(model);
@ -194,7 +194,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I
}
batch.setPipeline(getBackgroundPipeline());
auto numBackgrounds = skyboxStack.size();
auto numBackgrounds = (int) skyboxStack.size();
for (int i = numBackgrounds - 1; i >= 0; i--) {
model.setTranslation(glm::vec3(4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
batch.setModelTransform(model);

View file

@ -50,10 +50,13 @@ const int KILO_PER_MEGA = 1000;
#define KB_TO_BYTES_SHIFT 10
#define MB_TO_BYTES_SHIFT 20
#define GB_TO_BYTES_SHIFT 30
#define GB_TO_BYTES(X) ((size_t)(X) << GB_TO_BYTES_SHIFT)
#define MB_TO_BYTES(X) ((size_t)(X) << MB_TO_BYTES_SHIFT)
#define KB_TO_BYTES(X) ((size_t)(X) << KB_TO_BYTES_SHIFT)
#define BYTES_TO_GB(X) (X >> GB_TO_BYTES_SHIFT)
#define BYTES_TO_MB(X) (X >> MB_TO_BYTES_SHIFT)
#define BYTES_TO_KB(X) (X >> KB_TO_BYTES_SHIFT)

View file

@ -24,7 +24,7 @@
#include <gl/QOpenGLContextWrapper.h>
#include <PerfStat.h>
#include <ViewFrustum.h>
#include <gpu/gl/GLbackend.h>
#include <gpu/gl/GLBackend.h>
#include <ui-plugins/PluginContainer.h>

View file

@ -50,11 +50,15 @@ static const char* RENDER_CONTROLLERS = "Render Hand Controllers";
static const int MIN_PUCK_COUNT = 2;
static const int MIN_FEET_AND_HIPS = 3;
static const int MIN_FEET_HIPS_CHEST = 4;
static const int MIN_FEET_HIPS_HEAD = 4;
static const int MIN_FEET_HIPS_SHOULDERS = 5;
static const int MIN_FEET_HIPS_CHEST_HEAD = 5;
static const int FIRST_FOOT = 0;
static const int SECOND_FOOT = 1;
static const int HIP = 2;
static const int CHEST = 3;
static float HEAD_PUCK_Y_OFFSET = -0.0254f;
static float HEAD_PUCK_Z_OFFSET = -0.152f;
const char* ViveControllerManager::NAME { "OpenVR" };
@ -80,6 +84,28 @@ static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck)
return (firstPuck.second.translation.x < secondPuck.second.translation.x);
}
static bool determineFeetOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) {
glm::vec3 poseAPosition = poseA.getTranslation();
glm::vec3 poseBPosition = poseB.getTranslation();
glm::vec3 poseAVector = poseAPosition - axisOrigin;
glm::vec3 poseBVector = poseBPosition - axisOrigin;
float poseAProjection = glm::dot(poseAVector, axis);
float poseBProjection = glm::dot(poseBVector, axis);
return (poseAProjection > poseBProjection);
}
static glm::vec3 getReferenceHeadXAxis(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
return glmExtractRotation(finalHead) * Vectors::UNIT_X;
}
static glm::vec3 getReferenceHeadPosition(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
return extractTranslation(finalHead);
}
static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) {
QString result;
auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult);
@ -175,6 +201,8 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : contro
_configStringMap[Config::FeetAndHips] = QString("FeetAndHips");
_configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest");
_configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders");
_configStringMap[Config::FeetHipsChestAndHead] = QString("FeetHipsChestAndHead");
_configStringMap[Config::FeetHipsAndHead] = QString("FeetHipsAndHead");
if (openVrSupported()) {
createPreferences();
@ -355,6 +383,23 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr
int firstShoulderIndex = 3;
int secondShoulderIndex = 4;
calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex);
} else if (_config == Config::FeetHipsAndHead && puckCount == MIN_FEET_HIPS_HEAD) {
glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
_overrideHead = true;
} else if (_config == Config::FeetHipsChestAndHead && puckCount == MIN_FEET_HIPS_CHEST_HEAD) {
glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
calibrateChest(headPuckDefaultToReferenceMat, inputCalibration);
calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
_overrideHead = true;
} else {
qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks";
uncalibrate();
@ -369,6 +414,7 @@ void ViveControllerManager::InputDevice::uncalibrate() {
_pucksOffset.clear();
_jointToPuckMap.clear();
_calibrated = false;
_overrideHead = false;
}
void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
@ -378,6 +424,10 @@ void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
_poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2);
_poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM);
_poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM);
if (_overrideHead) {
_poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD);
}
}
controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const {
@ -443,6 +493,43 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u
}
}
glm::mat4 ViveControllerManager::InputDevice::recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) {
glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat;
glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat;
size_t headPuckIndex = _validTrackedObjects.size() - 1;
controller::Pose headPuckPose = _validTrackedObjects[headPuckIndex].second;
glm::mat4 headPuckAvatarMat = createMatFromQuatAndPos(headPuckPose.getRotation(), headPuckPose.getTranslation()) * Matrices::Y_180;
glm::vec3 headPuckTranslation = extractTranslation(headPuckAvatarMat);
glm::vec3 headPuckZAxis = cancelOutRollAndPitch(glmExtractRotation(headPuckAvatarMat)) * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
// check that the head puck z axis is not parrallel to the world up
const float EPSILON = 1.0e-4f;
glm::vec3 zAxis = glmExtractRotation(headPuckAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f);
if (fabsf(fabsf(glm::dot(glm::normalize(worldUp), glm::normalize(zAxis))) - 1.0f) < EPSILON) {
headPuckZAxis = glm::vec3(1.0f, 0.0f, 0.0f);
}
glm::vec3 yPrime = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 xPrime = glm::normalize(glm::cross(worldUp, headPuckZAxis));
glm::vec3 zPrime = glm::normalize(glm::cross(xPrime, yPrime));
glm::mat4 newHeadPuck = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f),
glm::vec4(zPrime, 0.0f), glm::vec4(headPuckTranslation, 1.0f));
glm::mat4 headPuckOffset = glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, HEAD_PUCK_Y_OFFSET, HEAD_PUCK_Z_OFFSET, 1.0f));
glm::mat4 finalHeadPuck = newHeadPuck * headPuckOffset;
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
glm::mat4 currentHead = finalHeadPuck * defaultHeadOffset;
// calculate the defaultToRefrenceXform
glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat);
return defaultToReferenceMat;
}
void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) {
// Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values.
const float CENTER_DEADBAND = 0.6f;
@ -630,6 +717,26 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer
}
}
void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition) {
auto& firstFoot = _validTrackedObjects[FIRST_FOOT];
auto& secondFoot = _validTrackedObjects[SECOND_FOOT];
controller::Pose& firstFootPose = firstFoot.second;
controller::Pose& secondFootPose = secondFoot.second;
if (determineFeetOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) {
_jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first;
_pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose);
_jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first;
_pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose);
} else {
_jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first;
_pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose);
_jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first;
_pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose);
}
}
void ViveControllerManager::InputDevice::calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
_jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first;
_pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second);
@ -660,6 +767,18 @@ void ViveControllerManager::InputDevice::calibrateShoulders(glm::mat4& defaultTo
}
}
void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
size_t headIndex = _validTrackedObjects.size() - 1;
const PuckPosePair& head = _validTrackedObjects[headIndex];
// assume the person is wearing the head puck on his/her forehead
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
controller::Pose newHead = head.second.postTransform(defaultHeadOffset);
_jointToPuckMap[controller::HEAD] = head.first;
_pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead);
}
void ViveControllerManager::InputDevice::loadSettings() {
Settings settings;
@ -694,6 +813,10 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu
_preferedConfig = Config::FeetHipsAndChest;
} else if (value == "FeetHipsAndShoulders") {
_preferedConfig = Config::FeetHipsAndShoulders;
} else if (value == "FeetHipsChestAndHead") {
_preferedConfig = Config::FeetHipsChestAndHead;
} else if (value == "FeetHipsAndHead") {
_preferedConfig = Config::FeetHipsAndHead;
}
}
@ -702,11 +825,43 @@ void ViveControllerManager::InputDevice::createPreferences() {
auto preferences = DependencyManager::get<Preferences>();
static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration";
{
static const float MIN_VALUE = -3.0f;
static const float MAX_VALUE = 3.0f;
static const float STEP = 0.01f;
auto getter = [this]()->float { return HEAD_PUCK_Y_OFFSET; };
auto setter = [this](const float& value) { HEAD_PUCK_Y_OFFSET = value; };
auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckYOffset", getter, setter);
preference->setMin(MIN_VALUE);
preference->setMax(MAX_VALUE);
preference->setDecimals(3);
preference->setStep(STEP);
preferences->addPreference(preference);
}
{
static const float MIN_VALUE = -3.0f;
static const float MAX_VALUE = 3.0f;
static const float STEP = 0.01f;
auto getter = [this]()->float { return HEAD_PUCK_Z_OFFSET; };
auto setter = [this](const float& value) { HEAD_PUCK_Z_OFFSET = value; };
auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckXOffset", getter, setter);
preference->setMin(MIN_VALUE);
preference->setMax(MAX_VALUE);
preference->setStep(STEP);
preference->setDecimals(3);
preferences->addPreference(preference);
}
{
auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; };
auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); };
auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter);
QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders"};
QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders", "FeetHipsAndHead"};
preference->setItems(list);
preferences->addPreference(preference);

View file

@ -67,6 +67,7 @@ private:
void calibrate(const controller::InputCalibrationData& inputCalibration);
void uncalibrate();
controller::Pose addOffsetToPuckPose(int joint) const;
glm::mat4 recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration);
void updateCalibratedLimbs();
bool checkForCalibrationEvent();
void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand);
@ -84,11 +85,14 @@ private:
void loadSettings();
void saveSettings() const;
void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition);
void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration,
int firstShoulderIndex, int secondShoulderIndex);
void calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
class FilteredStick {
public:
@ -119,6 +123,8 @@ private:
FeetAndHips,
FeetHipsAndChest,
FeetHipsAndShoulders,
FeetHipsChestAndHead,
FeetHipsAndHead
};
Config _config { Config::Auto };
Config _preferedConfig { Config::Auto };
@ -145,6 +151,7 @@ private:
bool _triggersPressedHandled { false };
bool _calibrated { false };
bool _timeTilCalibrationSet { false };
bool _overrideHead { false };
mutable std::recursive_mutex _lock;
QString configToString(Config config);

View file

@ -21,6 +21,11 @@ var blastShareText = "Blast to my Connections",
hifiAlreadySharedText = "Already Shared to Snaps Feed",
facebookShareText = "Share to Facebook",
twitterShareText = "Share to Twitter";
function fileExtensionMatches(filePath, extension) {
return filePath.split('.').pop().toLowerCase() === extension;
}
function showSetupInstructions() {
var snapshotImagesDiv = document.getElementById("snapshot-images");
snapshotImagesDiv.className = "snapshotInstructions";
@ -276,10 +281,10 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi
if (!image_data.localPath) {
return;
}
var id = "p" + (idCounter++),
imageContainer = document.createElement("DIV"),
var imageContainer = document.createElement("DIV"),
img = document.createElement("IMG"),
isGif;
isGif = fileExtensionMatches(image_data.localPath, "gif"),
id = "p" + (isGif ? "1" : "0");
imageContainer.id = id;
imageContainer.style.width = "95%";
imageContainer.style.height = "240px";
@ -290,22 +295,27 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi
imageContainer.style.position = "relative";
img.id = id + "img";
img.src = image_data.localPath;
isGif = img.src.split('.').pop().toLowerCase() === "gif";
imageContainer.appendChild(img);
document.getElementById("snapshot-images").appendChild(imageContainer);
paths.push(image_data.localPath);
img.onload = function () {
if (isGif) {
imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
}
if (!isGifLoading) {
appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast);
}
if (!isGifLoading || (isShowingPreviousImages && !image_data.story_id)) {
if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) {
shareForUrl(id);
}
if (isShowingPreviousImages && isLoggedIn && image_data.story_id) {
updateShareInfo(id, image_data.story_id);
}
};
img.onerror = function () {
img.onload = null;
img.src = image_data.errorPath;
};
}
function showConfirmationMessage(selectedID, destination) {
if (selectedID.id) {
@ -632,9 +642,8 @@ window.onload = function () {
// The last element of the message contents list contains a bunch of options,
// including whether or not we can share stuff
// The other elements of the list contain image paths.
if (messageOptions.containsGif) {
if (messageOptions.processingGif) {
if (messageOptions.containsGif === true) {
if (messageOptions.processingGif === true) {
imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon
message.image_data.push({ localPath: messageOptions.loadingGifPath });
message.image_data.forEach(function (element, idx) {
@ -663,7 +672,7 @@ window.onload = function () {
handleCaptureSetting(message.setting);
break;
case 'snapshotUploadComplete':
var isGif = message.image_url.split('.').pop().toLowerCase() === "gif";
var isGif = fileExtensionMatches(message.image_url, "gif");
updateShareInfo(isGif ? "p1" : "p0", message.story_id);
break;
default:

View file

@ -82,11 +82,23 @@ function showElements(els, show) {
}
}
function updateProperty(propertyName, propertyValue) {
var properties = {};
properties[propertyName] = propertyValue;
updateProperties(properties);
}
function updateProperties(properties) {
EventBridge.emitWebEvent(JSON.stringify({
id: lastEntityID,
type: "update",
properties: properties
}));
}
function createEmitCheckedPropertyUpdateFunction(propertyName) {
return function() {
EventBridge.emitWebEvent(
'{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + this.checked + '}}'
);
updateProperty(propertyName, this.checked);
};
}
@ -105,13 +117,7 @@ function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) {
var properties = {};
properties[group] = {};
properties[group][propertyName] = this.checked;
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: properties
})
);
updateProperties(properties);
};
}
@ -119,10 +125,7 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) {
decimals = decimals == undefined ? 4 : decimals;
return function() {
var value = parseFloat(this.value).toFixed(decimals);
EventBridge.emitWebEvent(
'{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + value + '}}'
);
updateProperty(propertyName, value);
};
}
@ -131,28 +134,14 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) {
var properties = {};
properties[group] = {};
properties[group][propertyName] = this.value;
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: properties,
})
);
updateProperties(properties);
};
}
function createEmitTextPropertyUpdateFunction(propertyName) {
return function() {
var properties = {};
properties[propertyName] = this.value;
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: properties,
})
);
updateProperty(propertyName, this.value);
};
}
@ -161,62 +150,44 @@ function createEmitGroupTextPropertyUpdateFunction(group, propertyName) {
var properties = {};
properties[group] = {};
properties[group][propertyName] = this.value;
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: properties,
})
);
updateProperties(properties);
};
}
function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) {
return function() {
var data = {
id: lastEntityID,
type: "update",
properties: {}
};
data.properties[property] = {
var properties = {};
properties[property] = {
x: elX.value,
y: elY.value,
z: elZ.value,
};
EventBridge.emitWebEvent(JSON.stringify(data));
updateProperties(properties);
}
};
function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) {
return function() {
var data = {
id: lastEntityID,
type: "update",
properties: {}
};
data.properties[group] = {};
data.properties[group][property] = {
var properties = {};
properties[group] = {};
properties[group][property] = {
x: elX.value,
y: elY.value,
z: elZ ? elZ.value : 0,
};
EventBridge.emitWebEvent(JSON.stringify(data));
updateProperties(properties);
}
};
function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) {
return function() {
var data = {
id: lastEntityID,
type: "update",
properties: {}
};
data.properties[property] = {
var properties = {};
properties[property] = {
x: elX.value * multiplier,
y: elY.value * multiplier,
z: elZ.value * multiplier,
};
EventBridge.emitWebEvent(JSON.stringify(data));
updateProperties(properties);
}
};
@ -227,44 +198,35 @@ function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue)
};
function emitColorPropertyUpdate(property, red, green, blue, group) {
var data = {
id: lastEntityID,
type: "update",
properties: {}
};
var properties = {};
if (group) {
data.properties[group] = {};
data.properties[group][property] = {
properties[group] = {};
properties[group][property] = {
red: red,
green: green,
blue: blue,
};
} else {
data.properties[property] = {
properties[property] = {
red: red,
green: green,
blue: blue,
};
}
EventBridge.emitWebEvent(JSON.stringify(data));
updateProperties(properties);
};
function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) {
return function() {
var data = {
id: lastEntityID,
type: "update",
properties: {}
};
data.properties[group] = {};
data.properties[group][property] = {
var properties = {};
properties[group] = {};
properties[group][property] = {
red: elRed.value,
green: elGreen.value,
blue: elBlue.value,
};
EventBridge.emitWebEvent(JSON.stringify(data));
updateProperties(properties);
}
};
@ -277,18 +239,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen
// We've unchecked, so remove
propertyValue = propertyValue.replace(subPropertyString + ",", "");
}
var _properties = {}
_properties[propertyName] = propertyValue;
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: _properties
})
);
updateProperty(propertyName, propertyValue);
}
function setUserDataFromEditor(noUpdate) {
@ -314,18 +265,11 @@ function setUserDataFromEditor(noUpdate) {
);
return;
} else {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: {
userData: text
},
})
);
updateProperty('userData', text);
}
}
}
function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
var properties = {};
var parsedData = {};
@ -372,13 +316,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
userDataElement.value = properties['userData'];
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: properties,
})
);
updateProperties(properties);
}
function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) {
var val = {}, def = {};
@ -900,7 +838,6 @@ function loaded() {
elCloneable.checked = parsedUserData["grabbableKey"].cloneable;
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic;
elDynamic.checked = elCloneable.checked ? false: properties.dynamic;
if (elCloneable.checked) {
if ("cloneLifetime" in parsedUserData["grabbableKey"]) {
elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300;
@ -1213,17 +1150,22 @@ function loaded() {
elCloneable.addEventListener('change', function (event) {
var checked = event.target.checked;
if (checked) {
multiDataUpdater("grabbableKey",
{cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, cloneable: event.target},
elUserData, {});
multiDataUpdater("grabbableKey", {
cloneLifetime: elCloneableLifetime,
cloneLimit: elCloneableLimit,
cloneDynamic: elCloneableDynamic,
cloneable: event.target,
grabbable: null
}, elUserData, {});
elCloneableGroup.style.display = "block";
EventBridge.emitWebEvent(
'{"id":' + lastEntityID + ', "type":"update", "properties":{"dynamic":false, "grabbable": false}}'
);
updateProperty('dynamic', false);
} else {
multiDataUpdater("grabbableKey",
{cloneLifetime: null, cloneLimit: null, cloneDynamic: null, cloneable: false},
elUserData, {});
multiDataUpdater("grabbableKey", {
cloneLifetime: null,
cloneLimit: null,
cloneDynamic: null,
cloneable: false
}, elUserData, {});
elCloneableGroup.style.display = "none";
}
});
@ -1258,15 +1200,7 @@ function loaded() {
showUserDataTextArea();
showNewJSONEditorButton();
hideSaveUserDataButton();
var properties = {};
properties['userData'] = elUserData.value;
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "update",
properties: properties,
})
);
updateProperty('userData', elUserData.value)
});
elSaveUserData.addEventListener("click", function() {

View file

@ -613,7 +613,6 @@
error = "All participants must be logged in to connect.";
}
result = error ? {status: 'error', connection: error} : response;
UserActivityLogger.makeUserConnection(connectingId, false, error || response);
connectionRequestCompleted();
} else {
result = response;

View file

@ -36,6 +36,8 @@ var shareAfterLogin = false;
var snapshotToShareAfterLogin = [];
var METAVERSE_BASE = location.metaverseServerUrl;
var isLoggedIn;
var numGifSnapshotUploadsPending = 0;
var numStillSnapshotUploadsPending = 0;
// It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story,
// POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS
@ -56,6 +58,10 @@ function removeFromStoryIDsToMaybeDelete(story_id) {
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
}
function fileExtensionMatches(filePath, extension) {
return filePath.split('.').pop().toLowerCase() === extension;
}
function onMessage(message) {
// Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
// 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
@ -139,6 +145,12 @@ function onMessage(message) {
if (isLoggedIn) {
print('Sharing snapshot with audience "for_url":', message.data);
Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref"));
var isGif = fileExtensionMatches(message.data, "gif");
if (isGif) {
numGifSnapshotUploadsPending++;
} else {
numStillSnapshotUploadsPending++;
}
} else {
shareAfterLogin = true;
snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") });
@ -273,7 +285,8 @@ function fillImageDataFromPrevious() {
localPath: previousStillSnapPath,
story_id: previousStillSnapStoryID,
blastButtonDisabled: previousStillSnapBlastingDisabled,
hifiButtonDisabled: previousStillSnapHifiSharingDisabled
hifiButtonDisabled: previousStillSnapHifiSharingDisabled,
errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg')
});
}
if (previousAnimatedSnapPath !== "") {
@ -281,7 +294,8 @@ function fillImageDataFromPrevious() {
localPath: previousAnimatedSnapPath,
story_id: previousAnimatedSnapStoryID,
blastButtonDisabled: previousAnimatedSnapBlastingDisabled,
hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled
hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled,
errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg')
});
}
}
@ -305,11 +319,25 @@ function onButtonClicked() {
function snapshotUploaded(isError, reply) {
if (!isError) {
var replyJson = JSON.parse(reply);
var storyID = replyJson.user_story.id;
var replyJson = JSON.parse(reply),
storyID = replyJson.user_story.id,
imageURL = replyJson.user_story.details.image_url,
isGif = fileExtensionMatches(imageURL, "gif"),
ignoreGifSnapshotData = false,
ignoreStillSnapshotData = false;
storyIDsToMaybeDelete.push(storyID);
var imageURL = replyJson.user_story.details.image_url;
var isGif = imageURL.split('.').pop().toLowerCase() === "gif";
if (isGif) {
numGifSnapshotUploadsPending--;
if (numGifSnapshotUploadsPending !== 0) {
ignoreGifSnapshotData = true;
}
} else {
numStillSnapshotUploadsPending--;
if (numStillSnapshotUploadsPending !== 0) {
ignoreStillSnapshotData = true;
}
}
if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) {
print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
tablet.emitScriptEvent(JSON.stringify({
type: "snapshot",
@ -322,6 +350,9 @@ function snapshotUploaded(isError, reply) {
} else {
Settings.setValue("previousStillSnapStoryID", storyID);
}
} else {
print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID);
}
} else {
print(reply);
}
@ -566,6 +597,12 @@ function onUsernameChanged() {
snapshotToShareAfterLogin.forEach(function (element) {
print('Uploading snapshot after login:', element.path);
Window.shareSnapshot(element.path, element.href);
var isGif = fileExtensionMatches(element.path, "gif");
if (isGif) {
numGifSnapshotUploadsPending++;
} else {
numStillSnapshotUploadsPending++;
}
});
}
});

View file

@ -122,6 +122,10 @@ int main(int argc, char** argv) {
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4()
};
@ -144,6 +148,10 @@ int main(int argc, char** argv) {
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4(),
glm::mat4()
};

View file

@ -1,15 +1,9 @@
set(TARGET_NAME ktx-test)
if (WIN32)
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217")
endif()
# This is not a testcase -- just set it up as a regular hifi project
setup_hifi_project(Quick Gui OpenGL)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
# Declare dependencies
macro (SETUP_TESTCASE_DEPENDENCIES)
# link in the shared libraries
link_hifi_libraries(shared octree ktx 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 ktx gpu image)
package_libraries_for_deployment()
endmacro ()
setup_hifi_testcase()

195
tests/ktx/src/KtxTests.cpp Normal file
View file

@ -0,0 +1,195 @@
//
// Created by Bradley Austin Davis on 2016/07/01
// Copyright 2014 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
//
#include "KtxTests.h"
#include <mutex>
#include <QtTest/QtTest>
#include <ktx/KTX.h>
#include <gpu/Texture.h>
#include <image/Image.h>
QTEST_GUILESS_MAIN(KtxTests)
QString getRootPath() {
static std::once_flag once;
static QString result;
std::call_once(once, [&] {
QFileInfo file(__FILE__);
QDir parent = file.absolutePath();
result = QDir::cleanPath(parent.currentPath() + "/../../..");
});
return result;
}
void KtxTests::initTestCase() {
}
void KtxTests::cleanupTestCase() {
}
void KtxTests::testKhronosCompressionFunctions() {
using namespace khronos::gl::texture;
QCOMPARE(evalAlignedCompressedBlockCount<4>(0), (uint32_t)0x0);
QCOMPARE(evalAlignedCompressedBlockCount<4>(1), (uint32_t)0x1);
QCOMPARE(evalAlignedCompressedBlockCount<4>(4), (uint32_t)0x1);
QCOMPARE(evalAlignedCompressedBlockCount<4>(5), (uint32_t)0x2);
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x00), (uint32_t)0x00);
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x01), (uint32_t)0x01);
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x04), (uint32_t)0x01);
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x05), (uint32_t)0x02);
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x1000), (uint32_t)0x400);
QVERIFY_EXCEPTION_THROWN(evalCompressedBlockCount(InternalFormat::RGBA8, 0x00), std::runtime_error);
}
void KtxTests::testKtxEvalFunctions() {
QCOMPARE(sizeof(ktx::Header), (size_t)64);
QCOMPARE(ktx::evalPadding(0x0), (uint8_t)0);
QCOMPARE(ktx::evalPadding(0x1), (uint8_t)3);
QCOMPARE(ktx::evalPadding(0x2), (uint8_t)2);
QCOMPARE(ktx::evalPadding(0x3), (uint8_t)1);
QCOMPARE(ktx::evalPadding(0x4), (uint8_t)0);
QCOMPARE(ktx::evalPadding(0x400), (uint8_t)0);
QCOMPARE(ktx::evalPadding(0x401), (uint8_t)3);
QCOMPARE(ktx::evalPaddedSize(0x0), 0x0);
QCOMPARE(ktx::evalPaddedSize(0x1), 0x4);
QCOMPARE(ktx::evalPaddedSize(0x2), 0x4);
QCOMPARE(ktx::evalPaddedSize(0x3), 0x4);
QCOMPARE(ktx::evalPaddedSize(0x4), 0x4);
QCOMPARE(ktx::evalPaddedSize(0x400), 0x400);
QCOMPARE(ktx::evalPaddedSize(0x401), 0x404);
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x0), (uint32_t)0x0);
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x1), (uint32_t)0x1);
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x4), (uint32_t)0x1);
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x5), (uint32_t)0x2);
}
void KtxTests::testKtxSerialization() {
const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
QImage image(TEST_IMAGE);
gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
auto ktxMemory = gpu::Texture::serialize(*testTexture);
QVERIFY(ktxMemory.get());
// Serialize the image to a file
QTemporaryFile TEST_IMAGE_KTX;
{
const auto& ktxStorage = ktxMemory->getStorage();
QVERIFY(ktx::KTX::validate(ktxStorage));
QVERIFY(ktxMemory->isValid());
auto& outFile = TEST_IMAGE_KTX;
if (!outFile.open()) {
QFAIL("Unable to open file");
}
auto ktxSize = ktxStorage->size();
outFile.resize(ktxSize);
auto dest = outFile.map(0, ktxSize);
memcpy(dest, ktxStorage->data(), ktxSize);
outFile.unmap(dest);
outFile.close();
}
{
auto ktxStorage = std::make_shared<storage::FileStorage>(TEST_IMAGE_KTX.fileName());
QVERIFY(ktx::KTX::validate(ktxStorage));
auto ktxFile = ktx::KTX::create(ktxStorage);
QVERIFY(ktxFile.get());
QVERIFY(ktxFile->isValid());
{
const auto& memStorage = ktxMemory->getStorage();
const auto& fileStorage = ktxFile->getStorage();
QVERIFY(memStorage->size() == fileStorage->size());
QVERIFY(memStorage->data() != fileStorage->data());
QVERIFY(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
QVERIFY(ktxFile->_images.size() == ktxMemory->_images.size());
auto imageCount = ktxFile->_images.size();
auto startMemory = ktxMemory->_storage->data();
auto startFile = ktxFile->_storage->data();
for (size_t i = 0; i < imageCount; ++i) {
auto memImages = ktxMemory->_images[i];
auto fileImages = ktxFile->_images[i];
QVERIFY(memImages._padding == fileImages._padding);
QVERIFY(memImages._numFaces == fileImages._numFaces);
QVERIFY(memImages._imageSize == fileImages._imageSize);
QVERIFY(memImages._faceSize == fileImages._faceSize);
QVERIFY(memImages._faceBytes.size() == memImages._numFaces);
QVERIFY(fileImages._faceBytes.size() == fileImages._numFaces);
auto faceCount = fileImages._numFaces;
for (uint32_t face = 0; face < faceCount; ++face) {
auto memFace = memImages._faceBytes[face];
auto memOffset = memFace - startMemory;
auto fileFace = fileImages._faceBytes[face];
auto fileOffset = fileFace - startFile;
QVERIFY(memOffset % 4 == 0);
QVERIFY(memOffset == fileOffset);
}
}
}
}
testTexture->setKtxBacking(TEST_IMAGE_KTX.fileName().toStdString());
}
#if 0
static const QString TEST_FOLDER { "H:/ktx_cacheold" };
//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
static const QString EXTENSIONS { "*.ktx" };
int mainTemp(int, char**) {
qInstallMessageHandler(messageHandler);
auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
for (auto fileInfo : fileInfoList) {
qDebug() << fileInfo.filePath();
std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
if (!ktx::KTX::validate(storage)) {
qDebug() << "KTX invalid";
}
auto ktxFile = ktx::KTX::create(storage);
ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
for (const auto& kv : ktxDescriptor.keyValues) {
qDebug() << "\t" << kv._key.c_str();
}
auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
if (offsetToMinMipKV) {
auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
auto minMipLevelAvailable = *data;
qDebug() << "\tMin mip available " << minMipLevelAvailable;
assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
}
auto storageSize = storage->size();
for (const auto& faceImageDesc : ktxDescriptor.images) {
//assert(0 == (faceImageDesc._faceSize % 4));
for (const auto& faceOffset : faceImageDesc._faceOffsets) {
assert(0 == (faceOffset % 4));
auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
assert(faceEndOffset <= storageSize);
}
}
for (const auto& faceImage : ktxFile->_images) {
for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
}
}
}
return 0;
}
#endif

21
tests/ktx/src/KtxTests.h Normal file
View file

@ -0,0 +1,21 @@
//
// Created by Bradley Austin Davis on 2016/07/01
// Copyright 2014 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
//
#include <QtCore/QObject>
class KtxTests : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void cleanupTestCase();
void testKtxEvalFunctions();
void testKhronosCompressionFunctions();
void testKtxSerialization();
};

View file

@ -1,223 +0,0 @@
//
// Created by Bradley Austin Davis on 2016/07/01
// Copyright 2014 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
//
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <QProcessEnvironment>
#include <QtCore/QDir>
#include <QtCore/QElapsedTimer>
#include <QtCore/QLoggingCategory>
#include <QtCore/QRegularExpression>
#include <QtCore/QSettings>
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <QtCore/QThreadPool>
#include <QtCore/QSaveFile>
#include <QtGui/QGuiApplication>
#include <QtGui/QResizeEvent>
#include <QtGui/QWindow>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QApplication>
#include <shared/RateCounter.h>
#include <shared/NetworkUtils.h>
#include <shared/FileLogger.h>
#include <shared/FileUtils.h>
#include <StatTracker.h>
#include <LogHandler.h>
#include <gpu/Texture.h>
#include <gl/Config.h>
#include <model/TextureMap.h>
#include <ktx/KTX.h>
#include <image/Image.h>
QSharedPointer<FileLogger> logger;
gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true);
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message);
if (!logMessage.isEmpty()) {
#ifdef Q_OS_WIN
OutputDebugStringA(logMessage.toLocal8Bit().constData());
OutputDebugStringA("\n");
#endif
if (logger) {
logger->addMessage(qPrintable(logMessage + "\n"));
}
}
}
const char * LOG_FILTER_RULES = R"V0G0N(
hifi.gpu=true
)V0G0N";
QString getRootPath() {
static std::once_flag once;
static QString result;
std::call_once(once, [&] {
QFileInfo file(__FILE__);
QDir parent = file.absolutePath();
result = QDir::cleanPath(parent.currentPath() + "/../../..");
});
return result;
}
const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
const QString TEST_IMAGE_KTX = getRootPath() + "/scripts/developer/tests/cube_texture.ktx";
int main(int argc, char** argv) {
QApplication app(argc, argv);
QCoreApplication::setApplicationName("KTX");
QCoreApplication::setOrganizationName("High Fidelity");
QCoreApplication::setOrganizationDomain("highfidelity.com");
logger.reset(new FileLogger());
Q_ASSERT(ktx::evalPadding(0) == 0);
Q_ASSERT(ktx::evalPadding(1) == 3);
Q_ASSERT(ktx::evalPadding(2) == 2);
Q_ASSERT(ktx::evalPadding(3) == 1);
Q_ASSERT(ktx::evalPadding(4) == 0);
Q_ASSERT(ktx::evalPadding(1024) == 0);
Q_ASSERT(ktx::evalPadding(1025) == 3);
Q_ASSERT(ktx::evalPaddedSize(0) == 0);
Q_ASSERT(ktx::evalPaddedSize(1) == 4);
Q_ASSERT(ktx::evalPaddedSize(2) == 4);
Q_ASSERT(ktx::evalPaddedSize(3) == 4);
Q_ASSERT(ktx::evalPaddedSize(4) == 4);
Q_ASSERT(ktx::evalPaddedSize(1024) == 1024);
Q_ASSERT(ktx::evalPaddedSize(1025) == 1028);
Q_ASSERT(sizeof(ktx::Header) == 12 + (sizeof(uint32_t) * 13));
DependencyManager::set<tracing::Tracer>();
qInstallMessageHandler(messageHandler);
QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
QImage image(TEST_IMAGE);
gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
auto ktxMemory = gpu::Texture::serialize(*testTexture);
{
const auto& ktxStorage = ktxMemory->getStorage();
Q_ASSERT_X(ktx::KTX::validate(ktxStorage), __FUNCTION__, "KTX storage validation failed");
Q_ASSERT_X(ktxMemory->isValid(), __FUNCTION__, "KTX self-validation failed");
QSaveFile outFile(TEST_IMAGE_KTX);
if (!outFile.open(QFile::WriteOnly)) {
throw std::runtime_error("Unable to open file");
}
auto ktxSize = ktxStorage->size();
outFile.resize(ktxSize);
auto dest = outFile.map(0, ktxSize);
memcpy(dest, ktxStorage->data(), ktxSize);
outFile.unmap(dest);
outFile.commit();
}
{
auto ktxFile = ktx::KTX::create(std::shared_ptr<storage::Storage>(new storage::FileStorage(TEST_IMAGE_KTX)));
{
const auto& memStorage = ktxMemory->getStorage();
const auto& fileStorage = ktxFile->getStorage();
Q_ASSERT(memStorage->size() == fileStorage->size());
Q_ASSERT(memStorage->data() != fileStorage->data());
Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size());
auto imageCount = ktxFile->_images.size();
auto startMemory = ktxMemory->_storage->data();
auto startFile = ktxFile->_storage->data();
for (size_t i = 0; i < imageCount; ++i) {
auto memImages = ktxMemory->_images[i];
auto fileImages = ktxFile->_images[i];
Q_ASSERT(memImages._padding == fileImages._padding);
Q_ASSERT(memImages._numFaces == fileImages._numFaces);
Q_ASSERT(memImages._imageSize == fileImages._imageSize);
Q_ASSERT(memImages._faceSize == fileImages._faceSize);
Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces);
Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces);
auto faceCount = fileImages._numFaces;
for (uint32_t face = 0; face < faceCount; ++face) {
auto memFace = memImages._faceBytes[face];
auto memOffset = memFace - startMemory;
auto fileFace = fileImages._faceBytes[face];
auto fileOffset = fileFace - startFile;
Q_ASSERT(memOffset % 4 == 0);
Q_ASSERT(memOffset == fileOffset);
}
}
}
}
testTexture->setKtxBacking(TEST_IMAGE_KTX.toStdString());
return 0;
}
#if 0
static const QString TEST_FOLDER { "H:/ktx_cacheold" };
//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
static const QString EXTENSIONS { "*.ktx" };
int mainTemp(int, char**) {
qInstallMessageHandler(messageHandler);
auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
for (auto fileInfo : fileInfoList) {
qDebug() << fileInfo.filePath();
std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
if (!ktx::KTX::validate(storage)) {
qDebug() << "KTX invalid";
}
auto ktxFile = ktx::KTX::create(storage);
ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
for (const auto& kv : ktxDescriptor.keyValues) {
qDebug() << "\t" << kv._key.c_str();
}
auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
if (offsetToMinMipKV) {
auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
auto minMipLevelAvailable = *data;
qDebug() << "\tMin mip available " << minMipLevelAvailable;
assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
}
auto storageSize = storage->size();
for (const auto& faceImageDesc : ktxDescriptor.images) {
//assert(0 == (faceImageDesc._faceSize % 4));
for (const auto& faceOffset : faceImageDesc._faceOffsets) {
assert(0 == (faceOffset % 4));
auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
assert(faceEndOffset <= storageSize);
}
}
for (const auto& faceImage : ktxFile->_images) {
for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
}
}
}
return 0;
}
#endif
#include "main.moc"

View file

@ -0,0 +1,170 @@
//
// ResoruceTests.cpp
//
// Copyright 2015 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
//
#include "FileCacheTests.h"
#include <FileCache.h>
QTEST_GUILESS_MAIN(FileCacheTests)
using namespace cache;
// Limit the file size to 10 MB
static const size_t MAX_UNUSED_SIZE { 1024 * 1024 * 10 };
static const QByteArray TEST_DATA { 1024 * 1024, '0' };
static std::string getFileKey(int i) {
return QString(QByteArray { 1, (char)i }.toHex()).toStdString();
}
class TestFile : public File {
using Parent = File;
public:
TestFile(Metadata&& metadata, const std::string& filepath)
: Parent(std::move(metadata), filepath) {
}
};
class TestFileCache : public FileCache {
using Parent = FileCache;
public:
TestFileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr) : Parent(dirname, ext, nullptr) {
initialize();
}
std::unique_ptr<File> createFile(Metadata&& metadata, const std::string& filepath) override {
qCInfo(file_cache) << "Wrote KTX" << metadata.key.c_str();
return std::unique_ptr<File>(new TestFile(std::move(metadata), filepath));
}
};
using CachePointer = std::shared_ptr<TestFileCache>;
// The FileCache relies on deleteLater to clear unused files, but QTest classes don't run a conventional event loop
// so we need to call this function to force any pending deletes to occur in the File destructor
static void forceDeletes() {
while (QCoreApplication::hasPendingEvents()) {
QCoreApplication::sendPostedEvents();
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QCoreApplication::processEvents();
}
}
size_t FileCacheTests::getCacheDirectorySize() const {
size_t result = 0;
QDir dir(_testDir.path());
for (const auto& file : dir.entryList({ "*.tmp" })) {
result += QFileInfo(dir.absoluteFilePath(file)).size();
}
return result;
}
CachePointer makeFileCache(QString& location) {
auto result = std::make_shared<TestFileCache>(location.toStdString(), "tmp");
result->setMaxSize(MAX_UNUSED_SIZE);
return result;
}
void FileCacheTests::initTestCase() {
}
void FileCacheTests::testUnusedFiles() {
auto cache = makeFileCache(_testDir.path());
std::list<FilePointer> inUseFiles;
{
for (int i = 0; i < 100; ++i) {
std::string key = getFileKey(i);
auto file = cache->writeFile(TEST_DATA.data(), TestFileCache::Metadata(key, TEST_DATA.size()));
inUseFiles.push_back(file);
forceDeletes();
QThread::msleep(10);
}
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
QCOMPARE(cache->getNumTotalFiles(), (size_t)100);
// Release the in-use files
inUseFiles.clear();
// Cache state is updated, but the directory state is unchanged,
// because the file deletes are triggered by an event loop
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
QVERIFY(getCacheDirectorySize() > MAX_UNUSED_SIZE);
forceDeletes();
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
QVERIFY(getCacheDirectorySize() <= MAX_UNUSED_SIZE);
}
// Reset the cache
cache = makeFileCache(_testDir.path());
{
// Test files 0 to 89 are missing, because the LRU algorithm deleted them when we released the files
for (int i = 0; i < 90; ++i) {
std::string key = getFileKey(i);
auto file = cache->getFile(key);
QVERIFY(!file.get());
}
QThread::msleep(1000);
// Test files 90 to 99 are present
for (int i = 90; i < 100; ++i) {
std::string key = getFileKey(i);
auto file = cache->getFile(key);
QVERIFY(file.get());
inUseFiles.push_back(file);
// Each access touches the file, so we need to sleep here to ensure that the files are
// spaced out in numeric order, otherwise later tests can't reliably determine the order
// for cache ejection
QThread::msleep(1000);
}
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
inUseFiles.clear();
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
}
}
size_t FileCacheTests::getFreeSpace() const {
return QStorageInfo(_testDir.path()).bytesFree();
}
// FIXME if something else is changing the amount of free space on the target drive concurrently with this test
// running, then it may fail
void FileCacheTests::testFreeSpacePreservation() {
QCOMPARE(getCacheDirectorySize(), MAX_UNUSED_SIZE);
// Set the target free space to slightly above whatever the current free space is...
size_t targetFreeSpace = getFreeSpace() + MAX_UNUSED_SIZE / 2;
// Reset the cache
auto cache = makeFileCache(_testDir.path());
// Setting the min free space causes it to eject the oldest files that cause the cache to exceed the minimum space
cache->setMinFreeSize(targetFreeSpace);
QVERIFY(getFreeSpace() < targetFreeSpace);
forceDeletes();
QCOMPARE(cache->getNumCachedFiles(), (size_t)5);
QCOMPARE(cache->getNumTotalFiles(), (size_t)5);
QVERIFY(getFreeSpace() >= targetFreeSpace);
for (int i = 0; i < 95; ++i) {
std::string key = getFileKey(i);
auto file = cache->getFile(key);
QVERIFY(!file.get());
}
for (int i = 95; i < 100; ++i) {
std::string key = getFileKey(i);
auto file = cache->getFile(key);
QVERIFY(file.get());
}
}
void FileCacheTests::cleanupTestCase() {
}

View file

@ -0,0 +1,30 @@
//
// ResourceTests.h
//
// Copyright 2015 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_ResourceTests_h
#define hifi_ResourceTests_h
#include <QtTest/QtTest>
#include <QtCore/QTemporaryDir>
class FileCacheTests : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void testUnusedFiles();
void testFreeSpacePreservation();
void cleanupTestCase();
private:
size_t getFreeSpace() const;
size_t getCacheDirectorySize() const;
QTemporaryDir _testDir;
};
#endif // hifi_ResourceTests_h