Merge branch 'master' of github.com:highfidelity/hifi into clone-server

This commit is contained in:
Liv Erickson 2018-05-18 10:42:26 -07:00
commit de05388ebf
148 changed files with 5354 additions and 1817 deletions

View file

@ -6,79 +6,110 @@ module.exports = {
},
"globals": {
"Account": false,
"Agent": false,
"AnimationCache": false,
"Assets": false,
"Audio": false,
"AudioDevice": false,
"AudioEffectOptions": false,
"AudioScope": false,
"AudioStats": false,
"Avatar": false,
"AvatarBookmarks": false,
"AvatarInputs": false,
"AvatarList": false,
"AvatarManager": false,
"Camera": false,
"Clipboard": false,
"console": false,
"ContextOverlay": false,
"Controller": false,
"DialogsManager": false,
"DebugDraw": false,
"Desktop": false,
"DesktopPreviewProvider": false,
"DialogsManager": false,
"document": false,
"Entities": false,
"EntityViewer": false,
"EventBridge": false,
"FaceTracker": false,
"GlobalServices": false,
"GooglePoly": false,
"Graphics": false,
"HMD": false,
"LaserPointers": false,
"location": true,
"LocationBookmarks": false,
"LODManager": false,
"Mat4": false,
"Menu": false,
"Messages": false,
"Midi": false,
"ModelCache": false,
"module": false,
"MyAvatar": false,
"OffscreenFlags": false,
"Overlays": false,
"OverlayWebWindow": false,
"OverlayWindow": false,
"Paths": false,
"Picks": false,
"PickType": false,
"PointerEvent": false,
"Pointers": false,
"print": false,
"QmlFragment": false,
"Quat": false,
"Rates": false,
"RayPick": false,
"Recording": false,
"Render": false,
"Resource": false,
"Reticle": false,
"Scene": false,
"Script": false,
"ScriptDiscoveryService": false,
"Selection": false,
"Settings": false,
"Snapshot": false,
"SoundCache": false,
"SpeechRecognizer": false,
"Stats": false,
"Steam": false,
"Tablet": false,
"TextureCache": false,
"Toolbars": false,
"Uuid": false,
"UndoStack": false,
"UserActivityLogger": false,
"Users": false,
"Uuid": false,
"Vec3": false,
"Wallet": false,
"WebSocket": false,
"WebWindow": false,
"Window": false,
"XMLHttpRequest": false,
"location": false,
"print": false,
"RayPick": false,
"LaserPointers": false,
"ContextOverlay": false,
"module": false
"XMLHttpRequest": false
},
"rules": {
"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
"comma-dangle": ["error", "never"],
"brace-style": ["error", "1tbs", {"allowSingleLine": false}],
"camelcase": ["error"],
"comma-dangle": ["error", "never"],
"curly": ["error", "all"],
"eqeqeq": ["error", "always"],
"indent": ["error", 4, { "SwitchCase": 1 }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"indent": ["error", 4, {"SwitchCase": 1}],
"key-spacing": ["error", {"beforeColon": false, "afterColon": true, "mode": "strict"}],
"keyword-spacing": ["error", {"before": true, "after": true}],
"max-len": ["error", 128, 4],
"new-cap": ["error"],
"no-console": ["off"],
"no-floating-decimal": ["error"],
//"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }],
"no-multiple-empty-lines": ["error"],
"no-magic-numbers": ["error", {"ignore": [0.5, -1, 0, 1, 2], "ignoreArrayIndexes": true}],
"no-multi-spaces": ["error"],
"no-unused-vars": ["error", { "args": "none", "vars": "local" }],
"no-multiple-empty-lines": ["error"],
"no-unused-vars": ["error", {"args": "none", "vars": "local"}],
"semi": ["error", "always"],
"spaced-comment": ["error", "always", {
"line": { "markers": ["/"] }
}],
"space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}]
"space-before-blocks": ["error"],
"space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}],
"spaced-comment": ["error", "always", {"line": {"markers": ["/"]}}]
}
};

View file

@ -110,6 +110,10 @@ dependencies {
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.picasso:picasso:2.71828'
compile 'com.squareup.retrofit2:retrofit:2.4.0'
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation fileTree(include: ['*.jar'], dir: 'libs')
}

View file

@ -39,17 +39,10 @@
</activity>
-->
<activity
android:name=".HomeActivity"
android:label="@string/home"
android:name=".MainActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar">
</activity>
<activity
android:name=".GotoActivity"
android:label="@string/go_to"
android:theme="@style/AppTheme" />
<activity android:name=".LoginActivity"
android:theme="@style/AppTheme"/>
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name=".InterfaceActivity"

View file

@ -0,0 +1,121 @@
<html>
<body>
<b>High Fidelity Privacy Policy</b>
<br/>
<p>High Fidelity, Inc. ("High Fidelity") respects the privacy of its online visitors and users of its products and services. We recognize the importance of protecting information collected from our users and have adopted this Privacy Policy to inform you about how we gather, store and use information derived from your use of our products, services and online sites in accordance with local law in the places where we operate.
By using our online sites, products, and services (collectively, the "Service"), you agree that we may collect personally identifiable information (as defined below). We will not share your personally identifiable information except as described herein.
</p>
<b><font size="2">1. Types of Information We Collect</font></b>
<p>
We collect two basic types of information - personal information and anonymous information - and we may use personal and anonymous information to create a third type of information, aggregate information. Personal Information means information that identifies (whether directly or indirectly) a particular individual, such as the individual's first and last name, postal address, e-mail address and/or telephone number. Anonymous Information means information that does not directly or indirectly identify, and cannot reasonably be used to identify, an individual (including an individual's computing device). Aggregate Information means information about groups or categories of individuals which does not identify and cannot reasonably be used to identify an individual. We may share Aggregate and Anonymous Information with other parties without restriction.
We collect the following categories of information:
Registration information you provide when you create an account, which may include your first name and surname, country of residence, gender, date of birth, e-mail address, username and password.
Transaction information you provide when you request information or purchase a product or service from us, whether on our sites or through our applications, including your postal address, telephone number and payment information. If you conduct transactions, we may collect and retain some or all of the information related to these transactions, including transaction amount(s), parties involved, time and manner of exchange, and other transaction circumstances.
Information you provide in public forums on our Service. Please note that our sites and applications may offer chat, forums, community environments (including multiplayer gameplay) or other tools that do not have a restricted audience. If you provide Personal Information when you use any of these features, that Personal Information may be publicly posted and otherwise disclosed without limitation as to its use by us or by a third party. We have no obligation to keep private personally identifiable information that you have made available to other users or the public using these functions. To request removal of your Personal Information from a public forum on one of our sites or applications, please contact Customer Support.
Information sent either one-to-one or within a limited group using our message, chat, post or similar functionality, where we are permitted by law to collect this information.
Information you provide to us when you use our sites and applications, our applications on third-party sites or platforms (such as social networking sites), or link your profile from a third-party site or platform to your registered account.
Location information when you visit our sites or use our applications, including location information either provided by a mobile device interacting with one of our sites or applications, or associated with your IP address, where we are permitted by law to process this information.
Usage, viewing and technical data, including your device identifier or IP address, when you visit our sites, use our applications on third-party sites or platforms, or open e-mails that we send.
Additionally there are a few special circumstances to note:
Intellectual Property Claim Notices: If you notify us of an intellectual property claim, the information in your claim notice may be shared with other parties to the disagreement or third parties in our discretion and as required by law.
Beta Service User: If you volunteer to serve as a beta participant for our pre-commercial content, we may track bug reports and individual system performance in an effort to test our technology rigorously before it is deployed.
Former Customer: If you discontinue your use of our Service, we may keep your registration file in our database for use in the event that you elect to renew your use of our Service, as well as for anti-fraud and other such protective measures.
Job Postings or Unsolicited Communications: Please note that information we receive in reference to a job posting or by unsolicited communications does not fall within the terms outlined in this Privacy Policy, however information from your resume will be used solely for the purpose of evaluating your candidacy for employment.
</p>
<b>2. How We Collect Your Information</b>
<p>
We collect information you provide to us when you request products, services or information from us, register with us, participate in public forums or other activities on our sites and applications, respond to customer inquiries or surveys, or otherwise interact with us.
We collect information through technology, such as cookies and other technologies (such as web beacons and pixel tags), including when you visit our sites and applications or use our applications on third-party sites or platforms. A cookie is a small string of data which often includes an anonymous unique identifier sent to your Internet browser from a website's computers, which is stored on your computer's hard drive and is used to customize your use of a product or online site, keep records of your access to an online site or product, or store information needed by you on a regular basis (e.g. password retention functionality). High Fidelity (itself or through third parties acting on our behalf) use cookies for a number of purposes relating to our websites, applications and services, including to access your account information where you "login" to our websites, forums or other areas and to keep track of your website session data. You can configure your browser to accept all cookies, reject all cookies, or notify you when a cookie is set. Each browser is different, so consult the "Help" menu of your browser to learn how you change your cookie preferences. Please note that if you reject all cookies, you may not be able to use certain of our (or other companies') web pages.
We may participate in ad and/or affiliate networks operated by various third party companies. These companies collect and may use certain anonymous information about your visits to our Service as a function of referring Internet traffic to our Service. We do not permit these companies to collect any Personal Information about you; however these companies may collect your IP address. These companies may set and use cookies, web beacons, pixels and other technologies to collect anonymous information about your visits to our Service, and may otherwise aggregate, analyze and anonymize that data. If you would like to learn more about these specialized advertising technologies, the Network Advertising Initiative offers useful information about Internet advertising companies, including information about how to opt-out of certain information collection.
We acquire information from other trusted sources to update or supplement the information you provided or we collected automatically. Local law may require that you authorize the third party to share your information with us before we can acquire it.
</p>
<b>3. Use of Your Information by High Fidelity</b>
<p>
High Fidelity will be the data controller for your information, and will have access to your information for use for the following purposes (unless prohibited by law):
Provide you with the products and services you request
Communicate with you about your account or transactions with us and send you information about features on our sites and applications or changes to our policies
Consistent with local law and choices and controls that may be available to you:
Send you offers and promotions for our products and services or, as permitted, third-party products and services
Personalize content and experiences on our sites and applications
Provide you with advertising based on your activity on our sites and applications and on third-party sites and applications.
Optimize or improve our products, services and operations
Detect, investigate and prevent activities that may violate our policies or be illegal
Except under certain limited circumstances as set forth here and in our Terms of Service, High Fidelity does not disclose to third parties the Personal Information or other account-related information that you provide to us without your permission. You understand, however, that High Fidelity may disclose your Personal Information or other account-related information under the following circumstances:
If we believe in good faith that such disclosure is necessary under applicable law, or to comply with legal process served on High Fidelity;
In order to protect and defend the rights or interests of High Fidelity, its products and services, and/or the other users of such products and services;
In order to report to law enforcement authorities, or assist in their investigation of suspected illegal or wrongful activity, or to report any instance in which we believe a person may be in danger;
To service providers with whom we have contracted to assist us with the features or operations (such as anti-fraud functions, billing, collections, registration, customer support, e-mail delivery, age verification), to fulfill your service requests, offer new content or help us improve our products and/or services.
Our contracts with third parties prohibit them from using any of your Personal Information for purposes unrelated to the product or services they are providing;
To other third parties (a) to provide you with services you have requested, (b) to offer you information about our products or services (e.g. events or features), or (c) to whom you explicitly ask us to send your information (or about whom you are otherwise explicitly notified and consent to when using a specific service). For instance, we may provide certain information to our payment processor, to credit card associations, banks or issuers (if you are using a credit card), to PayPal (if you are using a PayPal account), or to providers of other services you request. If you choose to use these third parties' products or services, then their use of your information is governed by their privacy policies. You should evaluate the practices of third party providers before deciding to use their services; and
To other business entities, should we plan to merge with or be acquired by that business entity.
</p>
<b>4. Sharing Your Information with Other Companies</b>
<p>
We will not share your Personal Information outside of High Fidelity except in limited circumstances, including:
When you allow us to share your Personal Information with another company, such as:
Directing us to share your Personal Information with third-party sites or platforms, such as social networking sites
Please note that once we share your Personal Information with another company, the information received by the other company becomes subject to the other company's privacy practices.
When companies perform services on our behalf; however, these companies are prohibited from using your Personal Information for purposes other than those requested by us or required by law.
When we share Personal Information with third parties in connection with the sale of a business, to enforce our Terms of Service or rules, to ensure the safety and security of our users and third parties, to comply with legal process or in other cases if we believe in good faith that disclosure is required by law.
</p>
<b>5. Data Transfers, Storage and Processing Globally</b>
<p>
We operate globally and may transfer your Personal Information to locations around the world for the purposes described in this Privacy Policy. Whenever your Personal Information is transferred, stored or processed by us, we will take reasonable steps to safeguard the privacy of your Personal Information in accordance with applicable law.
</p>
<b>6. Changes to this Privacy Policy</b>
<p>
From time to time, we may change this Privacy Policy to accommodate new technologies, industry practices, regulatory requirements or for other purposes. If we decide to change our privacy policy, we will post those changes to this privacy statement, and other places we deem appropriate so that you are aware of what information we collect, how we use it, and under what circumstances, if any, we disclose it. We reserve the right to modify this privacy statement at any time, so please review it frequently. If we make material changes to this policy, we will notify you here, by email, or by means of a notice on our home page.
</p>
<b>7. Comments and Questions</b>
<p>
If you have a comment or question about this Privacy Policy or our privacy practices, please send an e-mail to privacy@highfidelity.com.
</p>
<br/>
<b>Notice to California Residents:</b>
<p>
If you are a California resident, California Civil Code Section 1798.83 permits you to request information regarding the disclosure of your Personal Information by High Fidelity to third parties for the third parties' direct marketing purposes. With respect to these entities, this Privacy Policy applies only to their activities within the State of California. To make such a request, please send an e-mail to privacy@highfidelity.com or write us at the address listed immediately above.
</p>
</body>
</html>

View file

@ -24,9 +24,9 @@
#include <udt/PacketHeaders.h>
QAndroidJniObject __interfaceActivity;
QAndroidJniObject __loginActivity;
QAndroidJniObject __loginCompletedListener;
QAndroidJniObject __loadCompleteListener;
QAndroidJniObject __usernameChangedListener;
void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
const char * local=message.toStdString().c_str();
@ -152,9 +152,15 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
unpackAndroidAssets();
qInstallMessageHandler(oldMessageHandler);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a) {
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a, const bool backToScene) {
QAndroidJniObject string = QAndroidJniObject::fromString(a);
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;)V", string.object<jstring>());
jboolean jBackToScene = (jboolean) backToScene;
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;Z)V", string.object<jstring>(), jBackToScene);
});
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) {
jint iDuration = (jint) duration;
__interfaceActivity.callMethod<void>("performHapticFeedback", "(I)V", iDuration);
});
}
@ -178,10 +184,6 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResu
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) {
AndroidHelper::instance().goBackFromAndroidActivity();
}
// HifiUtils
JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurrentAddress(JNIEnv *env, jobject instance) {
QSharedPointer<AddressManager> addressManager = DependencyManager::get<AddressManager>();
@ -209,8 +211,9 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_protocolV
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_) {
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_,
jobject usernameChangedListener) {
const char *c_username = env->GetStringUTFChars(username_, 0);
const char *c_password = env->GetStringUTFChars(password_, 0);
QString username = QString(c_username);
@ -220,19 +223,26 @@ Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobjec
auto accountManager = AndroidHelper::instance().getAccountManager();
__loginActivity = QAndroidJniObject(instance);
__loginCompletedListener = QAndroidJniObject(instance);
__usernameChangedListener = QAndroidJniObject(usernameChangedListener);
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
jboolean jSuccess = (jboolean) true;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
jboolean jSuccess = (jboolean) false;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken", Q_ARG(const QString&, username), Q_ARG(const QString&, password));
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, [](const QString& username) {
QAndroidJniObject string = QAndroidJniObject::fromString(username);
__usernameChangedListener.callMethod<void>("handleUsernameChanged", "(Ljava/lang/String;)V", string.object<jstring>());
});
QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken",
Q_ARG(const QString&, username), Q_ARG(const QString&, password));
}
JNIEXPORT void JNICALL
@ -252,20 +262,30 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
}
JNIEXPORT jboolean JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
Java_io_highfidelity_hifiinterface_MainActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
return AndroidHelper::instance().getAccountManager()->isLoggedIn();
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeLogout(JNIEnv *env, jobject instance) {
Java_io_highfidelity_hifiinterface_MainActivity_nativeLogout(JNIEnv *env, jobject instance) {
AndroidHelper::instance().getAccountManager()->logout();
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeGetDisplayName(JNIEnv *env,
Java_io_highfidelity_hifiinterface_MainActivity_nativeGetDisplayName(JNIEnv *env,
jobject instance) {
QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername();
return env->NewStringUTF(username.toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
AndroidHelper::instance().notifyEnterBackground();
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) {
AndroidHelper::instance().notifyEnterForeground();
}
}

View file

@ -1,82 +0,0 @@
package io.highfidelity.hifiinterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.Toolbar;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
public class GotoActivity extends AppCompatActivity {
public static final String PARAM_DOMAIN_URL = "domain_url";
private EditText mUrlEditText;
private AppCompatButton mGoBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goto);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
setSupportActionBar(toolbar);
ActionBar actionbar = getSupportActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
mUrlEditText = (EditText) findViewById(R.id.url_text);
mUrlEditText.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if (i == KeyEvent.KEYCODE_ENTER) {
actionGo();
return true;
}
return false;
}
});
mUrlEditText.setText(HifiUtils.getInstance().getCurrentAddress());
mGoBtn = (AppCompatButton) findViewById(R.id.go_btn);
mGoBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
actionGo();
}
});
}
private void actionGo() {
String urlString = mUrlEditText.getText().toString();
if (!urlString.trim().isEmpty()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
Intent intent = new Intent();
intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString);
setResult(RESULT_OK, intent);
finish();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch (id) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -9,7 +9,7 @@ import java.net.URISyntaxException;
public class HifiUtils {
public static final String BASE_URL = "https://metaverse.highfidelity.com";
public static final String METAVERSE_BASE_URL = "https://metaverse.highfidelity.com";
private static HifiUtils instance;
@ -39,7 +39,12 @@ public class HifiUtils {
return urlString;
}
public String absoluteHifiAssetUrl(String urlString) {
return absoluteHifiAssetUrl(urlString, METAVERSE_BASE_URL);
}
public String absoluteHifiAssetUrl(String urlString, String baseUrl) {
urlString = urlString.trim();
if (!urlString.isEmpty()) {
URI uri;
@ -49,7 +54,7 @@ public class HifiUtils {
return urlString;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = BASE_URL + urlString;
urlString = baseUrl + urlString;
}
}
return urlString;

View file

@ -1,245 +0,0 @@
package io.highfidelity.hifiinterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import org.qtproject.qt5.android.bindings.QtActivity;
import io.highfidelity.hifiinterface.view.DomainAdapter;
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
public native boolean nativeIsLoggedIn();
public native void nativeLogout();
public native String nativeGetDisplayName();
/**
* Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected"
*/
//public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity";
public static final int ENTER_DOMAIN_URL = 1;
private DomainAdapter mDomainAdapter;
private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;
private RecyclerView mDomainsView;
private TextView searchNoResultsView;
private ImageView mSearchIconView;
private ImageView mClearSearch;
private EditText mSearchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
setSupportActionBar(toolbar);
ActionBar actionbar = getSupportActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
actionbar.setHomeAsUpIndicator(R.drawable.ic_menu);
mDrawerLayout = findViewById(R.id.drawer_layout);
mNavigationView = (NavigationView) findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
searchNoResultsView = findViewById(R.id.searchNoResultsView);
mDomainsView = findViewById(R.id.rvDomains);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(this, numberOfColumns);
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(this, HifiUtils.getInstance().protocolVersionSignature());
mDomainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
@Override
public void onItemClick(View view, int position, DomainAdapter.Domain domain) {
new Handler(getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
gotoDomain(domain.url);
}
}, 400); // a delay so the ripple effect can be seen
}
});
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
@Override
public void onEmptyAdapter() {
searchNoResultsView.setText(R.string.search_no_results);
searchNoResultsView.setVisibility(View.VISIBLE);
mDomainsView.setVisibility(View.GONE);
}
@Override
public void onNonEmptyAdapter() {
searchNoResultsView.setVisibility(View.GONE);
mDomainsView.setVisibility(View.VISIBLE);
}
@Override
public void onError(Exception e, String message) {
}
});
mDomainsView.setAdapter(mDomainAdapter);
mSearchView = findViewById(R.id.searchView);
mSearchIconView = findViewById(R.id.search_mag_icon);
mClearSearch = findViewById(R.id.search_clear);
mSearchView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mDomainAdapter.loadDomains(editable.toString());
if (editable.length() > 0) {
mSearchIconView.setVisibility(View.GONE);
mClearSearch.setVisibility(View.VISIBLE);
} else {
mSearchIconView.setVisibility(View.VISIBLE);
mClearSearch.setVisibility(View.GONE);
}
}
});
mSearchView.setOnKeyListener((view, i, keyEvent) -> {
if (i == KeyEvent.KEYCODE_ENTER) {
String urlString = mSearchView.getText().toString();
if (!urlString.trim().isEmpty()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
}
gotoDomain(urlString);
return true;
}
return false;
});
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
updateLoginMenu();
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(ContextCompat.getColor(this, R.color.statusbar_color));
}
private void updateLoginMenu() {
TextView loginOption = findViewById(R.id.login);
TextView logoutOption = findViewById(R.id.logout);
if (nativeIsLoggedIn()) {
loginOption.setVisibility(View.GONE);
logoutOption.setVisibility(View.VISIBLE);
} else {
loginOption.setVisibility(View.VISIBLE);
logoutOption.setVisibility(View.GONE);
}
}
private void gotoDomain(String domainUrl) {
Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class);
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
HomeActivity.this.finish();
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
//getMenuInflater().inflate(R.menu.menu_home, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch (id) {
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()) {
case R.id.action_goto:
Intent i = new Intent(this, GotoActivity.class);
startActivityForResult(i, ENTER_DOMAIN_URL);
return true;
}
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ENTER_DOMAIN_URL && resultCode == RESULT_OK) {
gotoDomain(data.getStringExtra(GotoActivity.PARAM_DOMAIN_URL));
}
}
@Override
protected void onStart() {
super.onStart();
updateLoginMenu();
}
public void onLoginClicked(View view) {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
public void onLogoutClicked(View view) {
nativeLogout();
updateLoginMenu();
}
@Override
public void onBackPressed() {
finishAffinity();
}
public void onSearchClear(View view) {
mSearchView.setText("");
}
}

View file

@ -15,6 +15,9 @@ import android.content.Intent;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.view.HapticFeedbackConstants;
import android.view.WindowManager;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtActivity;
@ -33,16 +36,14 @@ public class InterfaceActivity extends QtActivity {
public static final String DOMAIN_URL = "url";
private static final String TAG = "Interface";
private Vibrator mVibrator;
//public static native void handleHifiURL(String hifiURLString);
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
private native void nativeOnDestroy();
private native void nativeGotoUrl(String url);
private native void nativeGoBackFromAndroidActivity();
private native void nativeEnterBackground();
private native void nativeEnterForeground();
//private native void saveRealScreenSize(int width, int height);
//private native void setAppVersion(String version);
private native long nativeOnExitVr();
private AssetManager assetManager;
@ -89,7 +90,6 @@ public class InterfaceActivity extends QtActivity {
Point size = new Point();
getWindowManager().getDefaultDisplay().getRealSize(size);
// saveRealScreenSize(size.x, size.y);
try {
PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0);
@ -102,17 +102,13 @@ public class InterfaceActivity extends QtActivity {
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
// This is a workaround to hide the menu bar when the virtual keyboard is shown from Qt
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight();
if (getActionBar().isShowing()) {
getActionBar().hide();
}
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
if (getActionBar() != null && getActionBar().isShowing()) {
getActionBar().hide();
}
});
startActivity(new Intent(this, SplashActivity.class));
mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
}
@Override
@ -195,13 +191,16 @@ public class InterfaceActivity extends QtActivity {
if (intent.hasExtra(DOMAIN_URL)) {
nativeGotoUrl(intent.getStringExtra(DOMAIN_URL));
}
nativeGoBackFromAndroidActivity();
}
public void openAndroidActivity(String activityName) {
public void openAndroidActivity(String activityName, boolean backToScene) {
switch (activityName) {
case "Home": {
Intent intent = new Intent(this, HomeActivity.class);
case "Home":
case "Privacy Policy":
case "Login": {
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
intent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
startActivity(intent);
break;
}
@ -216,8 +215,14 @@ public class InterfaceActivity extends QtActivity {
super.isLoading = false;
}
public void performHapticFeedback(int duration) {
if (duration > 0) {
mVibrator.vibrate(duration);
}
}
@Override
public void onBackPressed() {
openAndroidActivity("Home");
openAndroidActivity("Home", false);
}
}
}

View file

@ -1,106 +0,0 @@
package io.highfidelity.hifiinterface;
import android.app.ProgressDialog;
import android.support.annotation.MainThread;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class LoginActivity extends AppCompatActivity {
public native void nativeLogin(String username, String password);
private EditText mUsername;
private EditText mPassword;
private TextView mError;
private Button mLoginButton;
private ProgressDialog mDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mUsername = findViewById(R.id.username);
mPassword = findViewById(R.id.password);
mError = findViewById(R.id.error);
mLoginButton = findViewById(R.id.loginButton);
mPassword.setOnEditorActionListener(
new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mLoginButton.performClick();
return true;
}
return false;
}
});
}
@Override
protected void onStop() {
super.onStop();
cancelActivityIndicator();
}
public void login(View view) {
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
if (username.isEmpty() || password.isEmpty()) {
showError(getString(R.string.login_username_or_password_incorrect));
} else {
mLoginButton.setEnabled(false);
hideError();
showActivityIndicator();
nativeLogin(username, password);
}
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(this);
}
mDialog.setMessage(getString(R.string.logging_in));
mDialog.setCancelable(false);
mDialog.show();
}
private void cancelActivityIndicator() {
if (mDialog != null) {
mDialog.cancel();
}
}
private void showError(String error) {
mError.setText(error);
mError.setVisibility(View.VISIBLE);
}
private void hideError() {
mError.setText("");
mError.setVisibility(View.INVISIBLE);
}
public void handleLoginCompleted(boolean success) {
runOnUiThread(() -> {
mLoginButton.setEnabled(true);
cancelActivityIndicator();
if (success) {
finish();
} else {
showError(getString(R.string.login_username_or_password_incorrect));
}
});
}
}

View file

@ -0,0 +1,310 @@
package io.highfidelity.hifiinterface;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.LoginFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginInteractionListener,
HomeFragment.OnHomeInteractionListener {
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
public static final String DEFAULT_FRAGMENT = "Home";
public static final String EXTRA_FRAGMENT = "fragment";
public static final String EXTRA_BACK_TO_SCENE = "backToScene";
private String TAG = "HighFidelity";
public native boolean nativeIsLoggedIn();
public native void nativeLogout();
public native String nativeGetDisplayName();
private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;
private ImageView mProfilePicture;
private TextView mDisplayName;
private View mLoginPanel;
private View mProfilePanel;
private TextView mLogoutOption;
private boolean backToScene;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigationView = findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
mLoginPanel = mNavigationView.getHeaderView(0).findViewById(R.id.loginPanel);
mProfilePanel = mNavigationView.getHeaderView(0).findViewById(R.id.profilePanel);
mLogoutOption = mNavigationView.findViewById(R.id.logout);
mDisplayName = mNavigationView.getHeaderView(0).findViewById(R.id.displayName);
mProfilePicture = mNavigationView.getHeaderView(0).findViewById(R.id.profilePicture);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
setSupportActionBar(toolbar);
ActionBar actionbar = getSupportActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
actionbar.setHomeAsUpIndicator(R.drawable.ic_menu);
mDrawerLayout = findViewById(R.id.drawer_layout);
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(ContextCompat.getColor(this, R.color.statusbar_color));
if (getIntent() != null) {
if (getIntent().hasExtra(EXTRA_FRAGMENT)) {
loadFragment(getIntent().getStringExtra(EXTRA_FRAGMENT));
} else {
loadFragment(DEFAULT_FRAGMENT);
}
if (getIntent().hasExtra(EXTRA_BACK_TO_SCENE)) {
backToScene = getIntent().getBooleanExtra(EXTRA_BACK_TO_SCENE, false);
}
}
}
private void loadFragment(String fragment) {
switch (fragment) {
case "Login":
loadLoginFragment();
break;
case "Home":
loadHomeFragment();
break;
case "Privacy Policy":
loadPrivacyPolicyFragment();
break;
default:
Log.e(TAG, "Unknown fragment " + fragment);
}
}
private void loadHomeFragment() {
Fragment fragment = HomeFragment.newInstance();
loadFragment(fragment, getString(R.string.home), false);
}
private void loadLoginFragment() {
Fragment fragment = LoginFragment.newInstance();
loadFragment(fragment, getString(R.string.login), true);
}
private void loadPrivacyPolicyFragment() {
Fragment fragment = PolicyFragment.newInstance();
loadFragment(fragment, getString(R.string.privacyPolicy), true);
}
private void loadFragment(Fragment fragment, String title, boolean addToBackStack) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
if (addToBackStack) {
ft.addToBackStack(null);
}
ft.commit();
setTitle(title);
mDrawerLayout.closeDrawer(mNavigationView);
}
private void updateLoginMenu() {
if (nativeIsLoggedIn()) {
mLoginPanel.setVisibility(View.GONE);
mProfilePanel.setVisibility(View.VISIBLE);
mLogoutOption.setVisibility(View.VISIBLE);
updateProfileHeader();
} else {
mLoginPanel.setVisibility(View.VISIBLE);
mProfilePanel.setVisibility(View.GONE);
mLogoutOption.setVisibility(View.GONE);
mDisplayName.setText("");
}
}
private void updateProfileHeader() {
updateProfileHeader(nativeGetDisplayName());
}
private void updateProfileHeader(String username) {
if (!username.isEmpty()) {
mDisplayName.setText(username);
updateProfilePicture(username);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
//getMenuInflater().inflate(R.menu.menu_navigation, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch (id) {
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()) {
case R.id.action_home:
loadHomeFragment();
return true;
}
return false;
}
@Override
protected void onStart() {
super.onStart();
updateLoginMenu();
}
public void onLoginClicked(View view) {
loadLoginFragment();
}
public void onLogoutClicked(View view) {
nativeLogout();
updateLoginMenu();
}
public void onSelectedDomain(String domainUrl) {
goToDomain(domainUrl);
}
private void goToLastLocation() {
goToDomain("");
}
private void goToDomain(String domainUrl) {
Intent intent = new Intent(this, InterfaceActivity.class);
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
finish();
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
@Override
public void onLoginCompleted() {
loadHomeFragment();
updateLoginMenu();
if (backToScene) {
backToScene = false;
goToLastLocation();
}
}
public void handleUsernameChanged(String username) {
runOnUiThread(() -> updateProfileHeader(username));
}
/**
* This is a temporary way to get the profile picture URL
* TODO: this should be get from an API (at the moment there is no one for this)
*/
private void updateProfilePicture(String username) {
mProfilePicture.setImageResource(PROFILE_PICTURE_PLACEHOLDER);
new DownloadProfileImageTask(url -> { if (url != null && !url.isEmpty()) {
Picasso.get().load(url).into(mProfilePicture, new RoundProfilePictureCallback());
} } ).execute(username);
}
public void onPrivacyPolicyClicked(View view) {
loadPrivacyPolicyFragment();
}
private class RoundProfilePictureCallback implements Callback {
@Override
public void onSuccess() {
Bitmap imageBitmap = ((BitmapDrawable) mProfilePicture.getDrawable()).getBitmap();
RoundedBitmapDrawable imageDrawable = RoundedBitmapDrawableFactory.create(getResources(), imageBitmap);
imageDrawable.setCircular(true);
imageDrawable.setCornerRadius(Math.max(imageBitmap.getWidth(), imageBitmap.getHeight()) / 2.0f);
mProfilePicture.setImageDrawable(imageDrawable);
}
@Override
public void onError(Exception e) {
mProfilePicture.setImageResource(PROFILE_PICTURE_PLACEHOLDER);
}
}
@Override
public void onBackPressed() {
int index = getFragmentManager().getBackStackEntryCount() - 1;
if (index > -1) {
super.onBackPressed();
if (backToScene) {
backToScene = false;
goToLastLocation();
}
} else {
finishAffinity();
}
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean(EXTRA_BACK_TO_SCENE, backToScene);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
backToScene = savedInstanceState.getBoolean(EXTRA_BACK_TO_SCENE, false);
}
}

View file

@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
public class SplashActivity extends Activity {
@ -21,13 +22,22 @@ public class SplashActivity extends Activity {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
View decorView = getWindow().getDecorView();
// Hide the status bar.
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
@Override
protected void onStop() {
super.onStop();
}
public void onAppLoadedComplete() {
startActivity(new Intent(this, HomeActivity.class)); // + 2 sec
startActivity(new Intent(this, MainActivity.class));
SplashActivity.this.finish();
}
}

View file

@ -0,0 +1,156 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import io.highfidelity.hifiinterface.HifiUtils;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.view.DomainAdapter;
public class HomeFragment extends Fragment {
private DomainAdapter mDomainAdapter;
private RecyclerView mDomainsView;
private TextView searchNoResultsView;
private ImageView mSearchIconView;
private ImageView mClearSearch;
private EditText mSearchView;
private OnHomeInteractionListener mListener;
public HomeFragment() {
// Required empty public constructor
}
public static HomeFragment newInstance() {
HomeFragment fragment = new HomeFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
searchNoResultsView = rootView.findViewById(R.id.searchNoResultsView);
mDomainsView = rootView.findViewById(R.id.rvDomains);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature());
mDomainAdapter.setClickListener((view, position, domain) -> {
new Handler(getActivity().getMainLooper()).postDelayed(() -> mListener.onSelectedDomain(domain.url), 400); // a delay so the ripple effect can be seen
});
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
@Override
public void onEmptyAdapter() {
searchNoResultsView.setText(R.string.search_no_results);
searchNoResultsView.setVisibility(View.VISIBLE);
mDomainsView.setVisibility(View.GONE);
}
@Override
public void onNonEmptyAdapter() {
searchNoResultsView.setVisibility(View.GONE);
mDomainsView.setVisibility(View.VISIBLE);
}
@Override
public void onError(Exception e, String message) {
}
});
mDomainsView.setAdapter(mDomainAdapter);
mSearchView = rootView.findViewById(R.id.searchView);
mSearchIconView = rootView.findViewById(R.id.search_mag_icon);
mClearSearch = rootView.findViewById(R.id.search_clear);
mSearchView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mDomainAdapter.loadDomains(editable.toString());
if (editable.length() > 0) {
mSearchIconView.setVisibility(View.GONE);
mClearSearch.setVisibility(View.VISIBLE);
} else {
mSearchIconView.setVisibility(View.VISIBLE);
mClearSearch.setVisibility(View.GONE);
}
}
});
mSearchView.setOnKeyListener((view, i, keyEvent) -> {
if (i == KeyEvent.KEYCODE_ENTER) {
String urlString = mSearchView.getText().toString();
if (!urlString.trim().isEmpty()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
}
mListener.onSelectedDomain(urlString);
return true;
}
return false;
});
mClearSearch.setOnClickListener(view -> onSearchClear(view));
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
return rootView;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnHomeInteractionListener) {
mListener = (OnHomeInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnHomeInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnHomeInteractionListener {
void onSelectedDomain(String domainUrl);
}
public void onSearchClear(View view) {
mSearchView.setText("");
}
}

View file

@ -0,0 +1,205 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Activity;
import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import io.highfidelity.hifiinterface.R;
public class LoginFragment extends Fragment {
private EditText mUsername;
private EditText mPassword;
private TextView mError;
private TextView mForgotPassword;
private Button mLoginButton;
private ProgressDialog mDialog;
public native void nativeLogin(String username, String password, Activity usernameChangedListener);
private LoginFragment.OnLoginInteractionListener mListener;
public LoginFragment() {
// Required empty public constructor
}
public static LoginFragment newInstance() {
LoginFragment fragment = new LoginFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_login, container, false);
mUsername = rootView.findViewById(R.id.username);
mPassword = rootView.findViewById(R.id.password);
mError = rootView.findViewById(R.id.error);
mLoginButton = rootView.findViewById(R.id.loginButton);
mForgotPassword = rootView.findViewById(R.id.forgotPassword);
mUsername.addTextChangedListener(new TextWatcher() {
boolean ignoreNextChange = false;
boolean hadBlankSpace = false;
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
hadBlankSpace = charSequence.length() > 0 && charSequence.charAt(charSequence.length()-1) == ' ';
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
if (!ignoreNextChange) {
ignoreNextChange = true;
boolean spaceFound = false;
for (int i = 0; i < editable.length(); i++) {
if (editable.charAt(i) == ' ') {
spaceFound=true;
editable.delete(i, i + 1);
i--;
}
}
if (hadBlankSpace && !spaceFound && editable.length() > 0) {
editable.delete(editable.length()-1, editable.length());
}
editable.append(' ');
ignoreNextChange = false;
}
}
});
mLoginButton.setOnClickListener(view -> login());
mForgotPassword.setOnClickListener(view -> forgotPassword());
mPassword.setOnEditorActionListener(
(textView, actionId, keyEvent) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mLoginButton.performClick();
return true;
}
return false;
});
return rootView;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnLoginInteractionListener) {
mListener = (OnLoginInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnLoginInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public void onStop() {
super.onStop();
cancelActivityIndicator();
hideKeyboard();
}
public void login() {
String username = mUsername.getText().toString().trim();
String password = mPassword.getText().toString();
hideKeyboard();
if (username.isEmpty() || password.isEmpty()) {
showError(getString(R.string.login_username_or_password_incorrect));
} else {
mLoginButton.setEnabled(false);
hideError();
showActivityIndicator();
nativeLogin(username, password, getActivity());
}
}
private void hideKeyboard() {
View view = getActivity().getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
private void forgotPassword() {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://highfidelity.com/users/password/new"));
startActivity(intent);
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(getContext());
}
mDialog.setMessage(getString(R.string.logging_in));
mDialog.setCancelable(false);
mDialog.show();
}
private void cancelActivityIndicator() {
if (mDialog != null) {
mDialog.cancel();
}
}
private void showError(String error) {
mError.setText(error);
mError.setVisibility(View.VISIBLE);
}
private void hideError() {
mError.setText("");
mError.setVisibility(View.INVISIBLE);
}
public void handleLoginCompleted(boolean success) {
Log.d("[LOGIN]", "handleLoginCompleted " + success);
getActivity().runOnUiThread(() -> {
mLoginButton.setEnabled(true);
cancelActivityIndicator();
if (success) {
if (mListener != null) {
mListener.onLoginCompleted();
}
} else {
showError(getString(R.string.login_username_or_password_incorrect));
}
});
}
public interface OnLoginInteractionListener {
void onLoginCompleted();
}
}

View file

@ -0,0 +1,60 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import io.highfidelity.hifiinterface.R;
public class PolicyFragment extends Fragment {
private static final String POLICY_FILE = "privacy_policy.html";
public PolicyFragment() {
// Required empty public constructor
}
public static PolicyFragment newInstance() {
PolicyFragment fragment = new PolicyFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_policy, container, false);
TextView policy = rootView.findViewById(R.id.policyText);
Spanned myStringSpanned = null;
try {
myStringSpanned = Html.fromHtml(loadHTMLFromAsset(), null, null);
policy.setText(myStringSpanned, TextView.BufferType.SPANNABLE);
} catch (IOException e) {
policy.setText("N/A");
}
return rootView;
}
public String loadHTMLFromAsset() throws IOException {
String html = null;
InputStream is = getContext().getAssets().open(POLICY_FILE);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
html = new String(buffer, "UTF-8");
return html;
}
}

View file

@ -0,0 +1,71 @@
package io.highfidelity.hifiinterface.task;
import android.os.AsyncTask;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import io.highfidelity.hifiinterface.HifiUtils;
/**
* This is a temporary solution until the profile picture URL is
* available in an API
*/
public class DownloadProfileImageTask extends AsyncTask<String, Void, String> {
private static final String BASE_PROFILE_URL = "https://highfidelity.com";
private static final String TAG = "Interface";
private final DownloadProfileImageResultProcessor mResultProcessor;
public interface DownloadProfileImageResultProcessor {
void onResultAvailable(String url);
}
public DownloadProfileImageTask(DownloadProfileImageResultProcessor resultProcessor) {
mResultProcessor = resultProcessor;
}
@Override
protected String doInBackground(String... usernames) {
URL userPage = null;
for (String username: usernames) {
try {
userPage = new URL(BASE_PROFILE_URL + "/users/" + username);
BufferedReader in = new BufferedReader(
new InputStreamReader(
userPage.openStream()));
StringBuffer strBuff = new StringBuffer();
String inputLine;
while ((inputLine = in.readLine()) != null) {
strBuff.append(inputLine);
}
in.close();
String substr = "img class=\"users-img\" src=\"";
int indexBegin = strBuff.indexOf(substr) + substr.length();
if (indexBegin >= substr.length()) {
int indexEnd = strBuff.indexOf("\"", indexBegin);
if (indexEnd > 0) {
String url = strBuff.substring(indexBegin, indexEnd);
return HifiUtils.getInstance().absoluteHifiAssetUrl(url, BASE_PROFILE_URL);
}
}
} catch (IOException e) {
Log.e(TAG, "Error getting profile picture for username " + username);
}
}
return null;
}
@Override
protected void onPostExecute(String url) {
super.onPostExecute(url);
if (mResultProcessor != null) {
mResultProcessor.onResultAvailable(url);
}
}
}

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:fillColor="#8E8E8E"
android:pathData="M50.5,3.42969 C76.4962,3.42969,97.5703,24.5038,97.5703,50.5 C97.5703,76.4962,76.4962,97.5703,50.5,97.5703 C24.5038,97.5703,3.42969,76.4962,3.42969,50.5 C3.42969,24.5038,24.5038,3.42969,50.5,3.42969 Z" />
<path
android:fillColor="#333333"
android:pathData="M53.7813,80.0898 C51.5938,80.0898,49.4063,80.0898,47.1992,80.0898 C47.0625,80.0703,46.9258,80.0507,46.789,80.0312 C44.621,79.914,42.4726,79.8164,40.3046,79.6601 C37.2968,79.4257,34.3085,79.0351,31.3788,78.3515 C29.8945,78,28.4297,77.6094,27.1406,76.7695 C26.6328,76.457,26.3008,76.0664,26.3203,75.4023 C26.3789,72.8632,26.5547,70.3632,27.2383,67.9023 C27.707,66.2226,28.0586,64.5039,28.8594,62.9414 C29.4258,61.8281,29.9336,60.6758,30.5391,59.582 C31.0079,58.7422,31.5547,57.9414,32.1016,57.1406 C33.0196,55.7539,34.1328,54.5234,35.3438,53.3711 C36.3399,52.4141,37.4141,51.5547,38.5469,50.7734 C40.0703,49.7382,41.6914,48.9179,43.4297,48.2734 C45.3242,47.5703,47.2969,47.1601,49.3086,47.0625 C54.1523,46.7891,58.5664,48.1172,62.5508,50.832 C63.6836,51.5937,64.7578,52.4531,65.7149,53.4101 C66.8672,54.5429,67.9024,55.8124,68.918,57.0624 C69.5039,57.8046,70.0118,58.6249,70.4805,59.4647 C71.0664,60.5194,71.6524,61.5741,72.0821,62.6678 C72.668,64.1131,73.2149,65.5975,73.6055,67.1209 C73.9766,68.5662,74.1719,70.0506,74.4258,71.5154 C74.543,72.2771,74.6016,73.0584,74.6797,73.8396 C74.6797,74.5232,74.6797,75.2068,74.6797,75.8904 C74.3672,76.2224,74.0938,76.6326,73.7227,76.867 C73.2149,77.199,72.668,77.4529,72.1016,77.6287 C70.6172,78.0584,69.1329,78.4685,67.6094,78.7615 C64.1719,79.4256,60.6758,79.7185,57.1602,79.9138 C56.0273,79.9727,54.9141,80.0313,53.7813,80.0898 Z" />
<path
android:fillColor="#333333"
android:pathData="M51.6523,20.9102 C52.4922,21.0664,53.3516,21.2031,54.1719,21.418 C54.836,21.5938,55.5,21.7891,56.1055,22.0821 C56.9258,22.4923,57.7266,22.9415,58.4688,23.4688 C60.0508,24.5626,61.2813,25.9883,62.1993,27.6876 C64.1329,31.3009,64.1134,34.9728,62.4727,38.6642 C61.7696,40.2462,60.6758,41.6134,59.3282,42.7072 C56.047,45.383,52.297,46.3791,48.1173,45.715 C46.379,45.4416,44.7775,44.8166,43.2736,43.8791 C42.0627,43.1174,41.008,42.1799,40.09,41.0861 C39.4064,40.2658,38.8986,39.3674,38.4494,38.4103 C36.9455,35.2853,37.0041,32.0822,38.1955,28.8986 C38.5471,27.9416,39.0939,27.0236,39.7189,26.2033 C41.9259,23.3322,44.8361,21.6135,48.4103,21.0666 C48.7228,21.0275,49.0158,20.9689,49.3283,20.9104 C50.1094,20.9102,50.8906,20.9102,51.6523,20.9102 Z" />
</vector>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="680.5dp"
android:height="680.5dp"
android:viewportWidth="680.5"
android:viewportHeight="680.5">
<path
android:fillColor="#E8E6E8"
android:pathData="M340.3,642.6 C299.5,642.6,259.9,634.6,222.6,618.8 C186.6,603.6,154.3,581.8,126.5,554 S77,493.9,61.7,457.9 C45.9,420.6,37.9,381,37.9,340.3 S45.9,259.9,61.7,222.6 C76.9,186.6,98.7,154.3,126.5,126.5 S186.6,77,222.6,61.7 C259.9,45.9,299.5,37.9,340.3,37.9 S420.7,45.9,458,61.7 C494,76.9,526.3,98.7,554.1,126.5 C581.9,154.3,603.6,186.6,618.9,222.6 C634.7,259.9,642.7,299.5,642.7,340.3 S634.7,420.7,618.9,458 C603.7,494,581.9,526.3,554.1,554.1 C526.3,581.9,494,603.6,458,618.9 C420.6,634.6,381.1,642.6,340.3,642.6 Z M340.3,69.4 C191,69.4,69.5,190.9,69.5,340.2 S191,611,340.3,611 S611,489.5,611,340.3 C611.2,190.9,489.6,69.4,340.3,69.4 Z" />
<path
android:fillColor="#E8E6E8"
android:pathData="M431.7,468.3 L431.7,256.1 C444.9,251.6,454.4,239.1,454.4,224.4 C454.4,205.9,439.4,190.9,420.9,190.9 S387.4,205.9,387.4,224.4 C387.4,238.5,396.1,250.6,408.5,255.5 L408.5,354.1 L270.4,290 L270.4,212.2 C283.6,207.7,293.1,195.2,293.1,180.5 C293.1,162,278.1,147,259.6,147 S226.1,162,226.1,180.5 C226.1,194.6,234.8,206.7,247.2,211.6 L247.2,425 C234.8,429.9,226.1,442,226.1,456.1 C226.1,474.6,241.1,489.6,259.6,489.6 S293.1,474.6,293.1,456.1 C293.1,441.4,283.6,428.9,270.4,424.4 L270.4,317.4 L408.6,381.5 L408.6,468.9 C396.2,473.8,387.5,485.9,387.5,500 C387.5,518.5,402.5,533.5,421,533.5 S454.5,518.5,454.5,500 C454.4,485.3,444.9,472.8,431.7,468.3 Z" />
</vector>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="798.5dp"
android:height="798.1dp"
android:viewportWidth="798.5"
android:viewportHeight="798.1">
<path
android:fillColor="#23B2E7"
android:pathData="M402.9,630.1 C362.1,630.1,322.5,622.1,285.2,606.3 C249.2,591.1,216.9,569.3,189.1,541.5 C161.3,513.7,139.6,481.4,124.3,445.4 C108.5,408.1,100.5,368.5,100.5,327.7 C100.5,286.9,108.5,247.3,124.3,210 C139.5,174,161.3,141.7,189.1,113.9 C216.9,86.1,249.2,64.4,285.2,49.1 C322.5,33.3,362.1,25.3,402.9,25.3 C443.7,25.3,483.3,33.3,520.6,49.1 C556.6,64.3,588.9,86.1,616.7,113.9 C644.5,141.7,666.2,174,681.5,210 C697.3,247.3,705.3,286.9,705.3,327.7 C705.3,368.5,697.3,408.1,681.5,445.4 C666.3,481.4,644.5,513.7,616.7,541.5 C588.9,569.3,556.6,591,520.6,606.3 C483.3,622.1,443.7,630.1,402.9,630.1 Z M402.9,56.9 C253.6,56.9,132.1,178.4,132.1,327.7 C132.1,477,253.6,598.5,402.9,598.5 C552.2,598.5,673.7,477,673.7,327.7 C673.8,178.4,552.3,56.9,402.9,56.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M495.3,455.9 L495.3,243.7 C508.5,239.2,518,226.7,518,212 C518,193.5,503,178.5,484.5,178.5 S451,193.5,451,212 C451,226.1,459.7,238.2,472.1,243.1 L472.1,341.7 L333.9,277.6 L333.9,199.8 C347.1,195.3,356.6,182.8,356.6,168.1 C356.6,149.6,341.6,134.6,323.1,134.6 S289.6,149.6,289.6,168.1 C289.6,182.2,298.3,194.3,310.7,199.2 L310.7,412.6 C298.3,417.5,289.6,429.6,289.6,443.7 C289.6,462.2,304.6,477.2,323.1,477.2 S356.6,462.2,356.6,443.7 C356.6,429,347.1,416.5,333.9,412 L333.9,305 L472.1,369.1 L472.1,456.5 C459.7,461.4,451,473.5,451,487.6 C451,506.1,466,521.1,484.5,521.1 S518,506.1,518,487.6 C518,472.9,508.5,460.4,495.3,455.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M113.3,695.9 L113.3,773.1 L96.8,773.1 L96.8,739.4 L69.6,739.4 L69.6,773.1 L53.1,773.1 L53.1,695.9 L69.6,695.9 L69.6,725.6 L96.8,725.6 L96.8,695.9 L113.3,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M142.6,773.1 L126.1,773.1 L126.1,695.9 L142.6,695.9 L142.6,773.1 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M190.3,708.3 C186.4,708.3,183.1,708.7,180.4,709.4 C177.7,710.1,175.5,711.4,173.8,713.1 C172.1,714.9,170.9,717.2,170.2,720.2 C169.5,723.2,169.1,726.8,169.1,731.3 L169.1,738.2 C169.1,742.8,169.4,746.6,170.1,749.5 C170.8,752.4,171.8,754.7,173.3,756.4 C174.8,758.1,176.7,759.2,179.1,759.8 C181.5,760.4,184.4,760.7,187.9,760.7 C188.9,760.7,189.9,760.7,191,760.6 C192,760.5,193.2,760.4,194.4,760.3 L194.4,739.5 L184.8,739.5 L186.3,726.7 L209.9,726.7 L209.9,770.7 C206.9,771.7,203.1,772.5,198.7,773.2 C194.3,773.9,189.7,774.2,184.9,774.2 C179,774.2,174,773.5,169.9,772.1 C165.7,770.7,162.4,768.5,159.8,765.6 C157.2,762.7,155.3,759,154.1,754.6 C152.9,750.2,152.3,745,152.3,739 L152.3,731 C152.3,725.6,152.9,720.6,154.1,716.2 C155.3,711.8,157.3,707.9,160.1,704.8 C162.9,701.6,166.6,699.2,171.2,697.4 C175.8,695.7,181.5,694.8,188.3,694.8 C192.1,694.8,195.7,695.1,199.4,695.8 C203,696.5,206,697.2,208.3,698 L205.6,710.5 C203.5,709.9,201.2,709.4,198.8,709 C196.3,708.5,193.5,708.3,190.3,708.3 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M284,695.9 L284,773.1 L267.5,773.1 L267.5,739.4 L240.3,739.4 L240.3,773.1 L223.8,773.1 L223.8,695.9 L240.3,695.9 L240.3,725.6 L267.5,725.6 L267.5,695.9 L284,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M372,695.9 L370.2,709.3 L341.4,709.3 L341.4,725.4 L368,725.4 L366.2,738.8 L341.4,738.8 L341.4,773 L324.9,773 L324.9,695.8 L372,695.8 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M398.9,773.1 L382.4,773.1 L382.4,695.9 L398.9,695.9 L398.9,773.1 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M412.1,695.9 L437.3,695.9 C443.9,695.9,449.3,696.8,453.7,698.5 C458.1,700.2,461.6,702.6,464.2,705.7 C466.8,708.8,468.6,712.5,469.7,716.8 C470.7,721.1,471.3,725.8,471.3,730.8 L471.3,738.1 C471.3,743.2,470.8,747.9,469.7,752.2 C468.6,756.5,466.8,760.2,464.2,763.3 C461.6,766.4,458.1,768.8,453.7,770.5 C449.3,772.2,443.9,773.1,437.3,773.1 L412.2,773.1 L412.2,695.9 Z M428.5,759.6 L435.7,759.6 C438.8,759.6,441.5,759.2,443.9,758.5 C446.2,757.8,448.2,756.5,449.8,754.8 C451.4,753.1,452.5,750.8,453.3,747.9 C454.1,745,454.4,741.5,454.4,737.2 L454.4,731.5 C454.4,727.2,454,723.6,453.3,720.7 C452.5,717.8,451.4,715.6,449.8,713.9 C448.2,712.2,446.3,711,443.9,710.3 C441.6,709.6,438.8,709.3,435.7,709.3 L428.5,709.3 L428.5,759.6 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M529.9,695.9 L528.2,708.8 L498.5,708.8 L498.5,725.7 L526.5,725.7 L524.9,738.6 L498.5,738.6 L498.5,760.2 L530,760.2 L528.3,773.1 L482,773.1 L482,695.9 L529.9,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M557.2,695.9 L557.2,759.6 L588.5,759.6 L586.7,773 L540.8,773 L540.8,695.8 L557.2,695.8 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M615.7,773.1 L599.2,773.1 L599.2,695.9 L615.7,695.9 L615.7,773.1 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M687.1,695.9 L685.3,709.3 L664.4,709.3 L664.4,773 L647.9,773 L647.9,709.3 L625.1,709.3 L626.9,695.9 L687.1,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M726.2,726 L742.3,695.9 L759.3,695.9 L734,739.4 L734,773.1 L717.5,773.1 L717.5,739.4 L692.6,695.9 L710.7,695.9 L726.2,726 Z" />
</vector>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Raleway&amp;italic=1"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_activity_goto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.highfidelity.hifiinterface.GotoActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="@id/root_activity_goto"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<EditText
android:id="@+id/url_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/HifiEditText"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:hint="@string/goto_url_hint"
android:inputType="textUri"
android:imeOptions="actionGo"
app:layout_constraintTop_toBottomOf="@id/toolbar"
/>
<android.support.v7.widget.AppCompatButton
android:id="@+id/go_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/button_horizontal_margin"
android:text="@string/go"
app:layout_constraintTop_toBottomOf="@id/url_text"
app:layout_constraintEnd_toEndOf="@id/root_activity_goto"/>
</android.support.constraint.ConstraintLayout>

View file

@ -22,8 +22,10 @@
android:elevation="4dp"
/>
<include layout="@layout/content_home" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.AppBarLayout>
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
@ -32,9 +34,10 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/navigation_header"
android:fitsSystemWindows="true"
android:background="@color/colorPrimaryDark"
app:menu="@menu/menu_home"
app:menu="@menu/menu_navigation"
>
<LinearLayout
android:layout_width="match_parent"
@ -43,20 +46,24 @@
android:clickable="true"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:id="@+id/login"
android:text="@string/login"
android:onClick="onLoginClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
<TextView
android:id="@+id/logout"
android:text="@string/logout"
android:onClick="onLogoutClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
android:textColor="@color/menuOption"
android:paddingTop="15dp"
android:paddingBottom="15dp"/>
<TextView
android:id="@+id/policy"
android:text="@string/privacyPolicy"
android:onClick="onPrivacyPolicyClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption"
android:paddingTop="15dp"
android:paddingBottom="30dp"/>
</LinearLayout>
</android.support.design.widget.NavigationView>

View file

@ -5,8 +5,11 @@
android:id="@+id/root_activity_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<!-- TODO -->
android:background="@android:color/black">
<ImageView
android:layout_width="242dp"
android:layout_height="242dp"
android:src="@drawable/hifi_logo_splash"
android:layout_centerHorizontal="true"
android:layout_marginTop="225dp"/>
</RelativeLayout>

View file

@ -7,7 +7,7 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:background="@color/colorPrimary"
tools:context="io.highfidelity.hifiinterface.HomeActivity"
tools:context="io.highfidelity.hifiinterface.MainActivity"
tools:showIn="@layout/activity_home">
<EditText
@ -48,7 +48,6 @@
android:layout_marginEnd="@dimen/searchEditClearMarginEnd"
android:visibility="gone"
android:src="@drawable/ic_clear"
android:onClick="onSearchClear"
/>
<TextView

View file

@ -4,19 +4,18 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight"
tools:context="io.highfidelity.hifiinterface.LoginActivity">
android:background="@color/backgroundLight">
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/hifi_header"
android:id="@+id/header"
android:layout_width="@dimen/header_hifi_width"
android:layout_height="@dimen/header_hifi_height"
android:layout_marginTop="@dimen/header_hifi_margin_top"
android:contentDescription="HighFidelity"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/username"
android:layout_marginBottom="75dp"
/>
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<TextView
android:id="@+id/error"
@ -33,71 +32,79 @@
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="9dp"
android:padding="7dp"
android:paddingRight="12dp"
android:paddingTop="14dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="14sp"
android:inputType="textEmailAddress"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="right|center_vertical"
app:layout_constraintTop_toBottomOf="@id/header"
android:layout_marginTop="70dp"
android:hint="@string/username_or_email" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="9dp"
android:padding="7dp"
android:paddingRight="12dp"
android:paddingTop="14dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="14sp"
android:inputType="textPassword"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right"
android:layout_marginTop="14dp"
android:gravity="right|center_vertical"
app:layout_constraintTop_toBottomOf="@id/username"
android:hint="@string/password"
android:layout_marginTop="13dp"
android:imeOptions="actionDone"/>
<Button
android:id="@+id/loginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="154dp"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:background="@drawable/rounded_button"
android:textColor="@color/white_opaque"
android:fontFamily="@font/raleway_semibold"
android:paddingBottom="0dp"
android:paddingLeft="55dp"
android:paddingRight="55dp"
android:paddingTop="0dp"
android:text="@string/login"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="15sp"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/password"
android:paddingRight="30dp"
android:paddingLeft="30dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginTop="16dp"
android:onClick="login"/>
app:layout_goneMarginTop="4dp"/>
<TextView
android:id="@+id/textView"
android:id="@+id/forgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:fontFamily="@font/raleway_semibold"
android:textSize="14dp"
android:text="@string/forgot_password"
android:textStyle="italic"
android:padding="10dp"
app:layout_constraintLeft_toLeftOf="parent"
android:paddingRight="10dp"
app:layout_constraintLeft_toLeftOf="@id/password"
app:layout_constraintTop_toTopOf="@id/loginButton"
app:layout_constraintRight_toLeftOf="@id/loginButton"
android:textColor="@color/colorButton1"/>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="io.highfidelity.hifiinterface.MainActivity"
android:background="@color/backgroundLight">
<ImageView
android:id="@+id/header"
android:layout_width="@dimen/header_hifi_width"
android:layout_height="@dimen/header_hifi_height"
android:layout_marginTop="@dimen/header_hifi_margin_top"
android:contentDescription="HighFidelity"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:layout_marginTop="195dp"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintBottom_toBottomOf="parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
>
<TextView
android:id="@+id/policyText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway"
android:textSize="12sp" />
</ScrollView>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="176dp"
android:minHeight="176dp">
<LinearLayout
android:id="@+id/loginPanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:visibility="visible">
<ImageView
android:id="@+id/hifiLogo"
android:layout_width="74dp"
android:layout_height="74dp"
android:src="@drawable/hifi_logo_header"
android:layout_gravity="center_vertical|left" />
<TextView
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:text="@string/login"
android:textSize="18sp"
android:layout_marginLeft="22dp"
android:layout_gravity="center_vertical"
android:onClick="onLoginClicked"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/profilePanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:visibility="gone">
<ImageView
android:id="@+id/profilePicture"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/default_profile_avatar"
android:layout_gravity="center_vertical|left" />
<TextView
android:id="@+id/displayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:text=""
android:textSize="18sp"
app:layout_constraintLeft_toRightOf="@id/profilePicture"
android:layout_marginLeft="22dp"
android:layout_gravity="center_vertical"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View file

@ -1,15 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="io.highfidelity.hifiinterface.HomeActivity">
<item
android:id="@+id/action_goto"
android:orderInCategory="90"
android:title="@string/action_goto"
app:showAsAction="never" />
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="io.highfidelity.hifiinterface.MainActivity">
<item
android:id="@+id/action_home"
android:title="@string/home"
/>
</menu>

View file

@ -33,4 +33,8 @@
<dimen name="domainMarginBottom">6dp</dimen>
<dimen name="domainNameHeight">64dp</dimen>
</resources>
<dimen name="header_hifi_margin_top">56dp</dimen>
<dimen name="header_hifi_height">101dp</dimen>
<dimen name="header_hifi_width">425dp</dimen>
</resources>

View file

@ -2,6 +2,7 @@
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/raleway_bold</item>
<item>@font/raleway_italic</item>
<item>@font/raleway_light_italic</item>
<item>@font/raleway_medium</item>
<item>@font/raleway_semibold</item>

View file

@ -1,7 +1,6 @@
<resources>
<string name="app_name" translatable="false">Interface</string>
<string name="home">Home</string>
<string name="go_to">Go To</string>
<string name="web_view_action_open_in_browser" translatable="false">Open in browser</string>
<string name="web_view_action_share" translatable="false">Share link</string>
<string name="web_view_action_share_subject" translatable="false">Shared a link</string>
@ -9,18 +8,17 @@
<string name="featured">FEATURED</string>
<string name="popular">POPULAR</string>
<string name="bookmarks">BOOKMARKS</string>
<string name="action_settings">Settings</string>
<string name="action_goto">Go To</string>
<string name="goto_url_hint">Type a domain url</string>
<string name="go">Go</string>
<string name="username_or_email">Username or email</string>
<string name="password">Password</string>
<string name="username_or_email">Username or email\u00A0</string>
<string name="password">Password\u00A0</string>
<string name="login">Login</string>
<string name="logout">Logout</string>
<string name="forgot_password">Forgot password?</string>
<string name="forgot_password">Forgot password?\u00A0</string>
<string name="login_username_or_password_incorrect">Username or password incorrect.</string>
<string name="logging_in">Logging into High Fidelity</string>
<string name="search_hint"><i>Search for a place by name</i>\u00A0</string>
<string name="search_loading">Loading places…</string>
<string name="search_no_results">No places exist with that name</string>
<string name="privacyPolicy">Privacy Policy</string>
</resources>

View file

@ -33,6 +33,19 @@
#include "entities/EntityTreeHeadlessViewer.h"
#include "avatars/ScriptableAvatar.h"
/**jsdoc
* @namespace Agent
*
* @hifi-assignment-client
*
* @property {boolean} isAvatar
* @property {boolean} isPlayingAvatarSound <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream
* @property {boolean} isNoiseGateEnabled
* @property {number} lastReceivedAudioLoudness <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
*/
class Agent : public ThreadedAssignment {
Q_OBJECT
@ -60,10 +73,28 @@ public:
virtual void aboutToFinish() override;
public slots:
/**jsdoc
* @function Agent.run
* @deprecated This function is being removed from the API.
*/
void run() override;
/**jsdoc
* @function Agent.playAvatarSound
* @param {object} avatarSound
*/
void playAvatarSound(SharedSoundPointer avatarSound);
/**jsdoc
* @function Agent.setIsAvatar
* @param {boolean} isAvatar
*/
void setIsAvatar(bool isAvatar);
/**jsdoc
* @function Agent.isAvatar
* @returns {boolean}
*/
bool isAvatar() const { return _isAvatar; }
private slots:

View file

@ -17,20 +17,144 @@
#include <AvatarData.h>
#include <ScriptEngine.h>
/**jsdoc
* The <code>Avatar</code> API is used to manipulate scriptable avatars on the domain. This API is a subset of the
* {@link MyAvatar} API.
*
* <p><strong>Note:</strong> In the examples, use "<code>Avatar</code>" instead of "<code>MyAvatar</code>".</p>
*
* @namespace Avatar
*
* @hifi-assignment-client
*
* @property {Vec3} position
* @property {number} scale
* @property {number} density <em>Read-only.</em>
* @property {Vec3} handPosition
* @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar.
* Yaw is sometimes called "heading".
* @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
* sometimes called "elevation".
* @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
* sometimes called "bank".
* @property {Quat} orientation
* @property {Quat} headOrientation - The orientation of the avatar's head.
* @property {number} headPitch - The rotation about an axis running from ear to ear of the avatar's head. Pitch is
* sometimes called "elevation".
* @property {number} headYaw - The rotation left or right about an axis running from the base to the crown of the avatar's
* head. Yaw is sometimes called "heading".
* @property {number} headRoll - The rotation about an axis running from the nose to the back of the avatar's head. Roll is
* sometimes called "bank".
* @property {Vec3} velocity
* @property {Vec3} angularVelocity
* @property {number} audioLoudness
* @property {number} audioAverageLoudness
* @property {string} displayName
* @property {string} sessionDisplayName - Sanitized, defaulted version displayName that is defined by the AvatarMixer
* rather than by Interface clients. The result is unique among all avatars present at the time.
* @property {boolean} lookAtSnappingEnabled
* @property {string} skeletonModelURL
* @property {AttachmentData[]} attachmentData
* @property {string[]} jointNames - The list of joints in the current avatar model. <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
* @property {Mat4} sensorToWorldMatrix <em>Read-only.</em>
* @property {Mat4} controllerLeftHandMatrix <em>Read-only.</em>
* @property {Mat4} controllerRightHandMatrix <em>Read-only.</em>
* @property {number} sensorToWorldScale <em>Read-only.</em>
*
* @borrows MyAvatar.getDomainMinScale as getDomainMinScale
* @borrows MyAvatar.getDomainMaxScale as getDomainMaxScale
* @borrows MyAvatar.canMeasureEyeHeight as canMeasureEyeHeight
* @borrows MyAvatar.getEyeHeight as getEyeHeight
* @borrows MyAvatar.getHeight as getHeight
* @borrows MyAvatar.setHandState as setHandState
* @borrows MyAvatar.getHandState as getHandState
* @borrows MyAvatar.setRawJointData as setRawJointData
* @borrows MyAvatar.setJointData as setJointData
* @borrows MyAvatar.setJointRotation as setJointRotation
* @borrows MyAvatar.setJointTranslation as setJointTranslation
* @borrows MyAvatar.clearJointData as clearJointData
* @borrows MyAvatar.isJointDataValid as isJointDataValid
* @borrows MyAvatar.getJointRotation as getJointRotation
* @borrows MyAvatar.getJointTranslation as getJointTranslation
* @borrows MyAvatar.getJointRotations as getJointRotations
* @borrows MyAvatar.getJointTranslations as getJointTranslations
* @borrows MyAvatar.setJointRotations as setJointRotations
* @borrows MyAvatar.setJointTranslations as setJointTranslations
* @borrows MyAvatar.clearJointsData as clearJointsData
* @borrows MyAvatar.getJointIndex as getJointIndex
* @borrows MyAvatar.getJointNames as getJointNames
* @borrows MyAvatar.setBlendshape as setBlendshape
* @borrows MyAvatar.getAttachmentsVariant as getAttachmentsVariant
* @borrows MyAvatar.setAttachmentsVariant as setAttachmentsVariant
* @borrows MyAvatar.updateAvatarEntity as updateAvatarEntity
* @borrows MyAvatar.clearAvatarEntity as clearAvatarEntity
* @borrows MyAvatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected
* @borrows MyAvatar.getAttachmentData as getAttachmentData
* @borrows MyAvatar.setAttachmentData as setAttachmentData
* @borrows MyAvatar.attach as attach
* @borrows MyAvatar.detachOne as detachOne
* @borrows MyAvatar.detachAll as detachAll
* @borrows MyAvatar.getAvatarEntityData as getAvatarEntityData
* @borrows MyAvatar.setAvatarEntityData as setAvatarEntityData
* @borrows MyAvatar.getSensorToWorldMatrix as getSensorToWorldMatrix
* @borrows MyAvatar.getSensorToWorldScale as getSensorToWorldScale
* @borrows MyAvatar.getControllerLeftHandMatrix as getControllerLeftHandMatrix
* @borrows MyAvatar.getControllerRightHandMatrix as getControllerRightHandMatrix
* @borrows MyAvatar.getDataRate as getDataRate
* @borrows MyAvatar.getUpdateRate as getUpdateRate
* @borrows MyAvatar.displayNameChanged as displayNameChanged
* @borrows MyAvatar.sessionDisplayNameChanged as sessionDisplayNameChanged
* @borrows MyAvatar.skeletonModelURLChanged as skeletonModelURLChanged
* @borrows MyAvatar.lookAtSnappingChanged as lookAtSnappingChanged
* @borrows MyAvatar.sessionUUIDChanged as sessionUUIDChanged
* @borrows MyAvatar.sendAvatarDataPacket as sendAvatarDataPacket
* @borrows MyAvatar.sendIdentityPacket as sendIdentityPacket
* @borrows MyAvatar.setJointMappingsFromNetworkReply as setJointMappingsFromNetworkReply
* @borrows MyAvatar.setSessionUUID as setSessionUUID
* @borrows MyAvatar.getAbsoluteJointRotationInObjectFrame as getAbsoluteJointRotationInObjectFrame
* @borrows MyAvatar.getAbsoluteJointTranslationInObjectFrame as getAbsoluteJointTranslationInObjectFrame
* @borrows MyAvatar.setAbsoluteJointRotationInObjectFrame as setAbsoluteJointRotationInObjectFrame
* @borrows MyAvatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame
* @borrows MyAvatar.getTargetScale as getTargetScale
* @borrows MyAvatar.resetLastSent as resetLastSent
*/
class ScriptableAvatar : public AvatarData, public Dependency {
Q_OBJECT
public:
/**jsdoc
* @function Avatar.startAnimation
* @param {string} url
* @param {number} [fps=30]
* @param {number} [priority=1]
* @param {boolean} [loop=false]
* @param {boolean} [hold=false]
* @param {number} [firstFrame=0]
* @param {number} [lastFrame=3.403e+38]
* @param {string[]} [maskedJoints=[]]
*/
/// Allows scripts to run animations.
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX,
const QStringList& maskedJoints = QStringList());
/**jsdoc
* @function Avatar.stopAnimation
*/
Q_INVOKABLE void stopAnimation();
/**jsdoc
* @function Avatar.getAnimationDetails
* @returns {Avatar.AnimationDetails}
*/
Q_INVOKABLE AnimationDetails getAnimationDetails();
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
private slots:
void update(float deltatime);

View file

@ -23,6 +23,13 @@
class EntitySimulation;
/**jsdoc
* @namespace EntityViewer
*
* @hifi-assignment-client
*/
// API functions are defined in OctreeHeadlessViewer.
// Generic client side Octree renderer class.
class EntityTreeHeadlessViewer : public OctreeHeadlessViewer {
Q_OBJECT

View file

@ -26,28 +26,101 @@ public:
static void trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket);
public slots:
/**jsdoc
* @function EntityViewer.queryOctree
*/
void queryOctree();
// setters for camera attributes
/**jsdoc
* @function EntityViewer.setPosition
* @param {Vec3} position
*/
void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
/**jsdoc
* @function EntityViewer.setOrientation
* @param {Quat} orientation
*/
void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
/**jsdoc
* @function EntityViewer.setCenterRadius
* @param {number} radius
*/
void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
/**jsdoc
* @function EntityViewer.setKeyholeRadius
* @param {number} radius
* @deprecated Use {@link EntityViewer.setCenterRadius|setCenterRadius} instead.
*/
void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
// setters for LOD and PPS
/**jsdoc
* @function EntityViewer.setVoxelSizeScale
* @param {number} sizeScale
*/
void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
/**jsdoc
* @function EntityViewer.setBoundaryLevelAdjust
* @param {number} boundaryLevelAdjust
*/
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
/**jsdoc
* @function EntityViewer.setMaxPacketsPerSecond
* @param {number} maxPacketsPerSecond
*/
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
// getters for camera attributes
/**jsdoc
* @function EntityViewer.getPosition
* @returns {Vec3}
*/
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
/**jsdoc
* @function EntityViewer.getOrientation
* @returns {Quat}
*/
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
// getters for LOD and PPS
/**jsdoc
* @function EntityViewer.getVoxelSizeScale
* @returns {number}
*/
float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
/**jsdoc
* @function EntityViewer.getBoundaryLevelAdjust
* @returns {number}
*/
int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
/**jsdoc
* @function EntityViewer.getMaxPacketsPerSecond
* @returns {number}
*/
int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
/**jsdoc
* @function EntityViewer.getOctreeElementsCount
* @returns {number}
*/
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
private:

View file

@ -281,10 +281,12 @@ Item {
text: " Pressure State: " + root.gpuTextureMemoryPressureState;
}
StatText {
text: " Resource Allocated / Populated / Pending: ";
property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory);
text: " Resource Allocated " + (showIdeal ? "(Ideal)" : "") + " / Populated / Pending: ";
}
StatText {
text: " " + root.gpuTextureResourceMemory + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB";
property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory);
text: " " + root.gpuTextureResourceMemory + (showIdeal ? ("(" + root.gpuTextureResourceIdealMemory + ")") : "") + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB";
}
StatText {
text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB";

View file

@ -37,15 +37,26 @@ void AndroidHelper::init() {
_accountManager->moveToThread(&workerThread);
}
void AndroidHelper::requestActivity(const QString &activityName) {
emit androidActivityRequested(activityName);
void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene) {
emit androidActivityRequested(activityName, backToScene);
}
void AndroidHelper::notifyLoadComplete() {
emit qtAppLoadComplete();
}
void AndroidHelper::goBackFromAndroidActivity() {
emit backFromAndroidActivity();
void AndroidHelper::notifyEnterForeground() {
emit enterForeground();
}
void AndroidHelper::notifyEnterBackground() {
emit enterBackground();
}
void AndroidHelper::performHapticFeedback(int duration) {
emit hapticFeedbackRequested(duration);
}
void AndroidHelper::showLoginDialog() {
emit androidActivityRequested("Login", true);
}

View file

@ -24,17 +24,27 @@ public:
return instance;
}
void init();
void requestActivity(const QString &activityName);
void requestActivity(const QString &activityName, const bool backToScene);
void notifyLoadComplete();
void goBackFromAndroidActivity();
QSharedPointer<AccountManager> getAccountManager() { return _accountManager; }
void notifyEnterForeground();
void notifyEnterBackground();
void performHapticFeedback(int duration);
QSharedPointer<AccountManager> getAccountManager() { return _accountManager; }
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
public slots:
void showLoginDialog();
signals:
void androidActivityRequested(const QString &activityName);
void backFromAndroidActivity();
void androidActivityRequested(const QString &activityName, const bool backToScene);
void qtAppLoadComplete();
void enterForeground();
void enterBackground();
void hapticFeedbackRequested(int duration);
private:
AndroidHelper();
@ -43,4 +53,4 @@ private:
QThread workerThread;
};
#endif
#endif

View file

@ -1238,7 +1238,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
nodeList.data(), SLOT(reset()));
auto dialogsManager = DependencyManager::get<DialogsManager>();
#if defined(Q_OS_ANDROID)
connect(accountManager.data(), &AccountManager::authRequired, this, []() {
AndroidHelper::instance().showLoginDialog();
});
#else
connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
#endif
connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
connect(accountManager.data(), &AccountManager::usernameChanged, [](QString username){
setCrashAnnotation("username", username.toStdString());
@ -2259,6 +2265,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().init();
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
AndroidHelper::instance().notifyLoadComplete();
#endif
}
@ -2858,6 +2866,13 @@ void Application::initializeUi() {
}
if (TouchscreenVirtualPadDevice::NAME == inputPlugin->getName()) {
_touchscreenVirtualPadDevice = std::dynamic_pointer_cast<TouchscreenVirtualPadDevice>(inputPlugin);
#if defined(Q_OS_ANDROID)
auto& virtualPadManager = VirtualPad::Manager::instance();
connect(&virtualPadManager, &VirtualPad::Manager::hapticFeedbackRequested,
this, [](int duration) {
AndroidHelper::instance().performHapticFeedback(duration);
});
#endif
}
}
@ -3874,7 +3889,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
#if defined(Q_OS_ANDROID)
if (event->key() == Qt::Key_Back) {
event->accept();
openAndroidActivity("Home");
AndroidHelper::instance().requestActivity("Home", false);
}
#endif
_controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts
@ -6591,8 +6606,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get<AddressManager>().data());
scriptEngine->registerGlobalObject("App", this);
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
@ -8269,12 +8282,6 @@ void Application::saveNextPhysicsStats(QString filename) {
_physicsEngine->saveNextPhysicsStats(filename);
}
void Application::openAndroidActivity(const QString& activityName) {
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().requestActivity(activityName);
#endif
}
#if defined(Q_OS_ANDROID)
void Application::enterBackground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
@ -8293,5 +8300,4 @@ void Application::enterForeground() {
}
#endif
#include "Application_jni.cpp"
#include "Application.moc"

View file

@ -415,10 +415,7 @@ public slots:
void setIsServerlessMode(bool serverlessDomain);
void loadServerlessDomain(QUrl domainURL);
Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); }
void updateVerboseLogging();
Q_INVOKABLE void openAndroidActivity(const QString& activityName);
private slots:
void onDesktopRootItemCreated(QQuickItem* qmlContext);

View file

@ -1,24 +0,0 @@
#if defined(Q_OS_ANDROID)
#include <QtAndroidExtras/QAndroidJniObject>
#include "AndroidHelper.h"
extern "C" {
JNIEXPORT void
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
if (qApp) {
qApp->enterBackground();
}
}
JNIEXPORT void
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) {
if (qApp) {
qApp->enterForeground();
}
}
}
#endif

View file

@ -51,6 +51,9 @@ protected slots:
/**jsdoc
* @function AvatarBookmarks.deleteBookmark
*/
/**jsdoc
* @function LocationBookmarks.deleteBookmark
*/
void deleteBookmark();
private:

View file

@ -16,6 +16,13 @@
#include "Bookmarks.h"
/**jsdoc
* @namespace LocationBookmarks
*
* @hifi-client-entity
* @hifi-interface
*/
class LocationBookmarks : public Bookmarks, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -27,7 +34,16 @@ public:
static const QString HOME_BOOKMARK;
public slots:
/**jsdoc
* @function LocationBookmarks.addBookmark
*/
void addBookmark();
/**jsdoc
* @function LocationBookmarks.setHomeLocationToAddress
* @param {string} address
*/
void setHomeLocationToAddress(const QVariant& address);
protected:

View file

@ -49,6 +49,7 @@
#include "AmbientOcclusionEffect.h"
#include "RenderShadowTask.h"
#include "AntialiasingEffect.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
@ -380,6 +381,25 @@ Menu::Menu() {
// Developer > Render >>>
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");
action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AntiAliasing, 0, true);
connect(action, &QAction::triggered, [action] {
auto renderConfig = qApp->getRenderEngine()->getConfiguration();
if (renderConfig) {
auto mainViewJitterCamConfig = renderConfig->getConfig<JitterSample>("RenderMainView.JitterCam");
auto mainViewAntialiasingConfig = renderConfig->getConfig<Antialiasing>("RenderMainView.Antialiasing");
if (mainViewJitterCamConfig && mainViewAntialiasingConfig) {
if (action->isChecked()) {
mainViewJitterCamConfig->play();
mainViewAntialiasingConfig->setDebugFXAA(false);
} else {
mainViewJitterCamConfig->none();
mainViewAntialiasingConfig->setDebugFXAA(true);
}
}
}
});
action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, true);
connect(action, &QAction::triggered, [action] {
auto renderConfig = qApp->getRenderEngine()->getConfiguration();

View file

@ -215,6 +215,7 @@ namespace MenuOption {
const QString DesktopTabletToToolbar = "Desktop Tablet Becomes Toolbar";
const QString HMDTabletToToolbar = "HMD Tablet Becomes Toolbar";
const QString Shadows = "Shadows";
const QString AntiAliasing = "Temporal Antialiasing (FXAA if disabled)";
const QString AmbientOcclusion = "Ambient Occlusion";
}

View file

@ -29,10 +29,25 @@
/**jsdoc
* The AvatarManager API has properties and methods which manage Avatars within the same domain.
*
* <p><strong>Note:</strong> This API is also provided to Interface and client entity scripts as the synonym,
* <code>AvatarList</code>. For assignment client scripts, see the separate {@link AvatarList} API.
*
* @namespace AvatarManager
*
* @hifi-interface
* @hifi-client-entity
*
* @borrows AvatarList.getAvatarIdentifiers as getAvatarIdentifiers
* @borrows AvatarList.getAvatarsInRange as getAvatarsInRange
* @borrows AvatarList.avatarAddedEvent as avatarAddedEvent
* @borrows AvatarList.avatarRemovedEvent as avatarRemovedEvent
* @borrows AvatarList.avatarSessionChangedEvent as avatarSessionChangedEvent
* @borrows AvatarList.isAvatarInRange as isAvatarInRange
* @borrows AvatarList.sessionUUIDChanged as sessionUUIDChanged
* @borrows AvatarList.processAvatarDataPacket as processAvatarDataPacket
* @borrows AvatarList.processAvatarIdentityPacket as processAvatarIdentityPacket
* @borrows AvatarList.processKillAvatar as processKillAvatar
*/
class AvatarManager : public AvatarHashMap {

View file

@ -137,9 +137,9 @@ class MyAvatar : public Avatar {
* @property {number} scale
* @property {number} density <em>Read-only.</em>
* @property {Vec3} handPosition
* @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of MyAvatar. Yaw
* is sometimes called "heading".
* @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of MyAvatar. Pitch is
* @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar.
* Yaw is sometimes called "heading".
* @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
* sometimes called "elevation".
* @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
* sometimes called "bank".

View file

@ -20,24 +20,122 @@ class LaserPointerScriptingInterface : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
/**jsdoc
* Synonym for {@link Pointers} as used for laser pointers.
*
* @namespace LaserPointers
*
* @hifi-interface
* @hifi-client-entity
*/
public:
/**jsdoc
* @function LaserPointers.createLaserPointer
* @param {Pointers.LaserPointerProperties} properties
* @returns {number}
*/
Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const;
/**jsdoc
* @function LaserPointers.enableLaserPointer
* @param {number} id
*/
Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); }
/**jsdoc
* @function LaserPointers.disableLaserPointer
* @param {number} id
*/
Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); }
/**jsdoc
* @function LaserPointers.removeLaserPointer
* @param {number} id
*/
Q_INVOKABLE void removeLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
/**jsdoc
* @function LaserPointers.editRenderState
* @param {number} id
* @param {string} renderState
* @param {Pointers.RayPointerRenderState} properties
*/
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
/**jsdoc
* @function LaserPointers.setRenderState
* @param {string} renderState
* @param {number} id
*/
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
/**jsdoc
* @function LaserPointers.getPrevRayPickResult
* @param {number} id
* @returns {RayPickResult}
*/
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid) const;
/**jsdoc
* @function LaserPointers.setPrecisionPicking
* @param {number} id
* @param {boolean} precisionPicking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
/**jsdoc
* @function LaserPointers.setLaserLength
* @param {number} id
* @param {number} laserLength
*/
Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); }
/**jsdoc
* @function LaserPointers.setIgnoreItems
* @param {number} id
* @param {Uuid[]} ignoreItems
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const;
/**jsdoc
* @function LaserPointers.setIncludeItems
* @param {number} id
* @param {Uuid[]} includeItems
*/
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const;
/**jsdoc
* @function LaserPointers.setLockEndUUID
* @param {number} id
* @param {Uuid} itemID
* @param {boolean} isOverlay
* @param {Mat4} [offsetMat]
*/
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
/**jsdoc
* @function LaserPointers.isLeftHand
* @param {number} id
* @returns {boolean}
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isLeftHand(uid); }
/**jsdoc
* @function LaserPointers.isRightHand
* @param {number} id
* @returns {boolean}
*/
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isRightHand(uid); }
/**jsdoc
* @function LaserPointers.isMouse
* @param {number} id
* @returns {boolean}
*/
Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get<PointerManager>()->isMouse(uid); }
};

View file

@ -31,6 +31,20 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
}
}
/**jsdoc
* A set of properties that can be passed to {@link Picks.createPick} to create a new Ray Pick.
* @typedef {object} Picks.RayPickProperties
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
* @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
* @property {string} [joint] Only for Joint or Mouse Ray Picks. If "Mouse", it will create a Ray Pick that follows the system mouse, in desktop or HMD.
* If "Avatar", it will create a Joint Ray Pick that follows your avatar's head. Otherwise, it will create a Joint Ray Pick that follows the given joint, if it
* exists on your current avatar.
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Ray Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Ray Picks. A local joint direction offset. x = upward, y = forward, z = lateral
* @property {Vec3} [position] Only for Static Ray Picks. The world-space origin of the ray.
* @property {Vec3} [direction=-Vec3.UP] Only for Static Ray Picks. The world-space direction of the ray.
*/
unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
@ -83,6 +97,14 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
return PickManager::INVALID_PICK_ID;
}
/**jsdoc
* A set of properties that can be passed to {@link Picks.createPick} to create a new Stylus Pick.
* @typedef {object} Picks.StylusPickProperties
* @property {number} [hand=-1] An integer. 0 == left, 1 == right. Invalid otherwise.
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
* @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
*/
unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();

View file

@ -22,19 +22,22 @@
* @hifi-interface
* @hifi-client-entity
*
* @property PICK_NOTHING {number} A filter flag. Don't intersect with anything.
* @property PICK_ENTITIES {number} A filter flag. Include entities when intersecting.
* @property PICK_OVERLAYS {number} A filter flag. Include overlays when intersecting.
* @property PICK_AVATARS {number} A filter flag. Include avatars when intersecting.
* @property PICK_HUD {number} A filter flag. Include the HUD sphere when intersecting in HMD mode.
* @property PICK_COARSE {number} A filter flag. Pick against coarse meshes, instead of exact meshes.
* @property PICK_INCLUDE_INVISIBLE {number} A filter flag. Include invisible objects when intersecting.
* @property PICK_INCLUDE_NONCOLLIDABLE {number} A filter flag. Include non-collidable objects when intersecting.
* @property INTERSECTED_NONE {number} An intersection type. Intersected nothing with the given filter flags.
* @property INTERSECTED_ENTITY {number} An intersection type. Intersected an entity.
* @property INTERSECTED_OVERLAY {number} An intersection type. Intersected an overlay.
* @property INTERSECTED_AVATAR {number} An intersection type. Intersected an avatar.
* @property INTERSECTED_HUD {number} An intersection type. Intersected the HUD sphere.
* @property PICK_NOTHING {number} A filter flag. Don't intersect with anything. <em>Read-only.</em>
* @property PICK_ENTITIES {number} A filter flag. Include entities when intersecting. <em>Read-only.</em>
* @property PICK_OVERLAYS {number} A filter flag. Include overlays when intersecting. <em>Read-only.</em>
* @property PICK_AVATARS {number} A filter flag. Include avatars when intersecting. <em>Read-only.</em>
* @property PICK_HUD {number} A filter flag. Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
* @property PICK_COARSE {number} A filter flag. Pick against coarse meshes, instead of exact meshes. <em>Read-only.</em>
* @property PICK_INCLUDE_INVISIBLE {number} A filter flag. Include invisible objects when intersecting. <em>Read-only.</em>
* @property PICK_INCLUDE_NONCOLLIDABLE {number} A filter flag. Include non-collidable objects when intersecting.
* <em>Read-only.</em>
* @property PICK_ALL_INTERSECTIONS {number} <em>Read-only.</em>
* @property INTERSECTED_NONE {number} An intersection type. Intersected nothing with the given filter flags. <em>Read-only.</em>
* @property INTERSECTED_ENTITY {number} An intersection type. Intersected an entity. <em>Read-only.</em>
* @property INTERSECTED_OVERLAY {number} An intersection type. Intersected an overlay. <em>Read-only.</em>
* @property INTERSECTED_AVATAR {number} An intersection type. Intersected an avatar. <em>Read-only.</em>
* @property INTERSECTED_HUD {number} An intersection type. Intersected the HUD sphere. <em>Read-only.</em>
* @property {number} perFrameTimeBudget - The max number of usec to spend per frame updating Pick results. <em>Read-only.</em>
*/
class PickScriptingInterface : public QObject, public Dependency {
@ -61,46 +64,31 @@ public:
void registerMetaTypes(QScriptEngine* engine);
/**jsdoc
* A set of properties that can be passed to {@link Picks.createPick} to create a new Pick.
*
* Different {@link Picks.PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
*
* @typedef {Object} Picks.PickProperties
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
* @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
* @property {string} [joint] Only for Joint or Mouse Ray Picks. If "Mouse", it will create a Ray Pick that follows the system mouse, in desktop or HMD.
* If "Avatar", it will create a Joint Ray Pick that follows your avatar's head. Otherwise, it will create a Joint Ray Pick that follows the given joint, if it
* exists on your current avatar.
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Ray Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Ray Picks. A local joint direction offset. x = upward, y = forward, z = lateral
* @property {Vec3} [position] Only for Static Ray Picks. The world-space origin of the ray.
* @property {Vec3} [direction=-Vec3.UP] Only for Static Ray Picks. The world-space direction of the ray.
* @property {number} [hand=-1] Only for Stylus Picks. An integer. 0 == left, 1 == right. Invalid otherwise.
*/
/**jsdoc
* Adds a new Pick.
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
* @function Picks.createPick
* @param {Picks.PickType} type A PickType that specifies the method of picking to use
* @param {Picks.PickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @param {PickType} type A PickType that specifies the method of picking to use
* @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
*/
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
/**jsdoc
* Enables a Pick.
* @function Picks.enablePick
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
*/
Q_INVOKABLE void enablePick(unsigned int uid);
/**jsdoc
* Disables a Pick.
* @function Picks.disablePick
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
*/
Q_INVOKABLE void disablePick(unsigned int uid);
/**jsdoc
* Removes a Pick.
* @function Picks.removePick
@ -111,7 +99,7 @@ public:
/**jsdoc
* An intersection result for a Ray Pick.
*
* @typedef {Object} Picks.RayPickResult
* @typedef {Object} RayPickResult
* @property {number} type The intersection type.
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
@ -125,7 +113,7 @@ public:
/**jsdoc
* An intersection result for a Stylus Pick.
*
* @typedef {Object} Picks.StylusPickResult
* @typedef {Object} StylusPickResult
* @property {number} type The intersection type.
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
@ -140,7 +128,7 @@ public:
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
* @function Picks.getPrevPickResult
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {PickResult} The most recent intersection result. This will be slightly different for different PickTypes. See {@link Picks.RayPickResult} and {@link Picks.StylusPickResult}.
* @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be different for different PickTypes.
*/
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);
@ -151,6 +139,7 @@ public:
* @param {boolean} precisionPicking Whether or not to use precision picking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Picks.
* @function Picks.setIgnoreItems
@ -158,6 +147,7 @@ public:
* @param {Uuid[]} ignoreItems A list of IDs to ignore.
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems);
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus
* Picks <b>only</b> intersect with objects in their include list.
@ -174,6 +164,7 @@ public:
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid);
/**jsdoc
* Check if a Pick is associated with the right hand.
* @function Picks.isRightHand
@ -181,6 +172,7 @@ public:
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
*/
Q_INVOKABLE bool isRightHand(unsigned int uid);
/**jsdoc
* Check if a Pick is associated with the system mouse.
* @function Picks.isMouse
@ -189,28 +181,96 @@ public:
*/
Q_INVOKABLE bool isMouse(unsigned int uid);
// FIXME: Move to other property definitions.
Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget)
/**jsdoc
* The max number of usec to spend per frame updating Pick results.
* @typedef {number} Picks.perFrameTimeBudget
*/
unsigned int getPerFrameTimeBudget() const;
void setPerFrameTimeBudget(unsigned int numUsecs);
public slots:
/**jsdoc
* @function Picks.PICK_NOTHING
* @returns {number}
*/
static constexpr unsigned int PICK_NOTHING() { return 0; }
/**jsdoc
* @function Picks.PICK_ENTITIES
* @returns {number}
*/
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); }
/**jsdoc
* @function Picks.PICK_OVERLAYS
* @returns {number}
*/
static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_OVERLAYS); }
/**jsdoc
* @function Picks.PICK_AVATARS
* @returns {number}
*/
static constexpr unsigned int PICK_AVATARS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_AVATARS); }
/**jsdoc
* @function Picks.PICK_HUD
* @returns {number}
*/
static constexpr unsigned int PICK_HUD() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_HUD); }
/**jsdoc
* @function Picks.PICK_COARSE
* @returns {number}
*/
static constexpr unsigned int PICK_COARSE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_COARSE); }
/**jsdoc
* @function Picks.PICK_INCLUDE_INVISIBLE
* @returns {number}
*/
static constexpr unsigned int PICK_INCLUDE_INVISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_INVISIBLE); }
/**jsdoc
* @function Picks.PICK_INCLUDE_NONCOLLIDABLE
* @returns {number}
*/
static constexpr unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_NONCOLLIDABLE); }
/**jsdoc
* @function Picks.PICK_ALL_INTERSECTIONS
* @returns {number}
*/
static constexpr unsigned int PICK_ALL_INTERSECTIONS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ALL_INTERSECTIONS); }
/**jsdoc
* @function Picks.INTERSECTED_NONE
* @returns {number}
*/
static constexpr unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; }
/**jsdoc
* @function Picks.INTERSECTED_ENTITY
* @returns {number}
*/
static constexpr unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; }
/**jsdoc
* @function Picks.INTERSECTED_OVERLAY
* @returns {number}
*/
static constexpr unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; }
/**jsdoc
* @function Picks.INTERSECTED_AVATAR
* @returns {number}
*/
static constexpr unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; }
/**jsdoc
* @function Picks.INTERSECTED_HUD
* @returns {number}
*/
static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; }
};

View file

@ -42,6 +42,12 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType&
}
}
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
* @typedef {object} Pointers.StylusPointerProperties
* @property {boolean} [hover=false] If this pointer should generate hover events.
* @property {boolean} [enabled=false]
*/
unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
@ -58,6 +64,48 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled));
}
/**jsdoc
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState},
* but with an additional distance field.
*
* @typedef {Object} Pointers.DefaultRayPointerRenderState
* @augments Pointers.RayPointerRenderState
* @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something.
*
* @typedef {Object} Pointers.RayPointerRenderState
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the beginning of the Ray Pointer, if desired.
* @property {Overlays.OverlayProperties} [path] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field), which <b>must</b> be <code>"line3d"</code>.
* An overlay to represent the path of the Ray Pointer, if desired.
* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the end of the Ray Pointer, if desired.
*/
/**jsdoc
* A trigger mechanism for Ray Pointers.
*
* @typedef {Object} Pointers.Trigger
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
*/
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
* @typedef {object} Pointers.LaserPointerProperties
* @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [enabled=false]
* @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
* @property {boolean} [hover=false] If this Pointer should generate hover events.
* @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
*/
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();

View file

@ -16,7 +16,7 @@
/**jsdoc
* The Pointers API lets you create and manage objects for repeatedly calculating intersections in different ways, as well as the visual representation of those objects.
* Pointers can also be configured to automatically generate PointerEvents.
* Pointers can also be configured to automatically generate {@link PointerEvent}s on {@link Entities} and {@link Overlays}.
*
* @namespace Pointers
*
@ -33,59 +33,12 @@ public:
unsigned int createStylus(const QVariant& properties) const;
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Also contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
*
* Adds a new Pointer
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
*
* @typedef {Object} Pointers.PointerProperties
* @property {boolean} [hover=false] If this Pointer should generate hover events.
* @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
* @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something.
*
* @typedef {Object} Pointers.RayPointerRenderState
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
* @property {OverlayProperties} [start] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the beginning of the Ray Pointer, if desired.
* @property {OverlayProperties} [path] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a <code>type</code> field), which <b>must</b> be "line3d".
* An overlay to represent the path of the Ray Pointer, if desired.
* @property {OverlayProperties} [end] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the end of the Ray Pointer, if desired.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState},
* but with an additional distance field.
*
* @typedef {Object} Pointers.DefaultRayPointerRenderState
* @augments Pointers.RayPointerRenderState
* @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined.
*/
/**jsdoc
* A trigger mechanism for Ray Pointers.
*
* @typedef {Object} Pointers.Trigger
* @property {Controller.Action} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
*/
/**jsdoc
* Adds a new Pointer
* @function Pointers.createPointer
* @param {Picks.PickType} type A PickType that specifies the method of picking to use
* @param {Pointers.PointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
* @param {PickType} type A PickType that specifies the method of picking to use
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
* this Pointer will use to do its picking.
* @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid.
*
@ -121,32 +74,37 @@ public:
* Pointers.setRenderState(pointer, "test");
*/
Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties);
/**jsdoc
* Enables a Pointer.
* @function Pointers.enablePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void enablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); }
/**jsdoc
* Disables a Pointer.
* @function Pointers.disablePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void disablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); }
/**jsdoc
* Removes a Pointer.
* @function Pointers.removePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void removePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
/**jsdoc
* Edit some visual aspect of a Pointer. Currently only supported for Ray Pointers.
* @function Pointers.editRenderState
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {string} renderState The name of the render state you want to edit.
* @param {RenderState} properties The new properties for <code>renderState</code>. For Ray Pointers, a {@link Pointers.RayPointerRenderState}.
* @param {Pointers.RayPointerRenderState} properties The new properties for <code>renderStates</code> item.
*/
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
/**jsdoc
* Set the render state of a Pointer. For Ray Pointers, this means switching between their {@link Pointers.RayPointerRenderState}s, or "" to turn off rendering and hover/trigger events.
* For Stylus Pointers, there are three built-in options: "events on" (render and send events, the default), "events off" (render but don't send events), and "disabled" (don't render, don't send events).
@ -156,14 +114,16 @@ public:
*/
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
/**jsdoc
* Get the most recent pick result from this Pointer. This will be updated as long as the Pointer is enabled, regardless of the render state.
* @function Pointers.getPrevPickResult
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @returns {PickResult} The most recent intersection result. This will be slightly different for different PickTypes. See {@link Picks.RayPickResult} and {@link Picks.StylusPickResult}.
* @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be slightly different for different PickTypes.
*/
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid) const;
/**jsdoc
* Sets whether or not to use precision picking.
* @function Pointers.setPrecisionPicking
@ -171,6 +131,7 @@ public:
* @param {boolean} precisionPicking Whether or not to use precision picking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
/**jsdoc
* Sets the length of this Pointer. No effect on Stylus Pointers.
* @function Pointers.setLength
@ -178,6 +139,7 @@ public:
* @param {float} length The desired length of the Pointer.
*/
Q_INVOKABLE void setLength(unsigned int uid, float length) const { DependencyManager::get<PointerManager>()->setLength(uid, length); }
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Pointers.
* @function Pointers.setIgnoreItems
@ -185,6 +147,7 @@ public:
* @param {Uuid[]} ignoreItems A list of IDs to ignore.
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const;
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus
* Pointers <b>only</b> intersect with objects in their include list.
@ -194,17 +157,19 @@ public:
*/
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const;
/**jsdoc
* Lock a Pointer onto a specific object (overlay, entity, or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object.
* Not used by Stylus Pointers.
* @function Pointers.setLockEndUUID
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {QUuid} objectID The ID of the object to which to lock on.
* @param {Uuid} objectID The ID of the object to which to lock on.
* @param {boolean} isOverlay False for entities or avatars, true for overlays
* @param {Mat4} [offsetMat] The offset matrix to use if you do not want to lock on to the center of the object.
*/
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
/**jsdoc
* Check if a Pointer is associated with the left hand.
* @function Pointers.isLeftHand
@ -212,6 +177,7 @@ public:
* @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pointer with hand == 0
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isLeftHand(uid); }
/**jsdoc
* Check if a Pointer is associated with the right hand.
* @function Pointers.isRightHand
@ -219,6 +185,7 @@ public:
* @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pointer with hand == 1
*/
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isRightHand(uid); }
/**jsdoc
* Check if a Pointer is associated with the system mouse.
* @function Pointers.isMouse

View file

@ -18,6 +18,30 @@
#include "PickScriptingInterface.h"
/**jsdoc
* Synonym for {@link Picks} as used for ray picks.
*
* @namespace RayPick
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} PICK_NOTHING <em>Read-only.</em>
* @property {number} PICK_ENTITIES <em>Read-only.</em>
* @property {number} PICK_OVERLAYS <em>Read-only.</em>
* @property {number} PICK_AVATARS <em>Read-only.</em>
* @property {number} PICK_HUD <em>Read-only.</em>
* @property {number} PICK_COARSE <em>Read-only.</em>
* @property {number} PICK_INCLUDE_INVISIBLE <em>Read-only.</em>
* @property {number} PICK_INCLUDE_NONCOLLIDABLE <em>Read-only.</em>
* @property {number} PICK_ALL_INTERSECTIONS <em>Read-only.</em>
* @property {number} INTERSECTED_NONE <em>Read-only.</em>
* @property {number} INTERSECTED_ENTITY <em>Read-only.</em>
* @property {number} INTERSECTED_OVERLAY <em>Read-only.</em>
* @property {number} INTERSECTED_AVATAR <em>Read-only.</em>
* @property {number} INTERSECTED_HUD <em>Read-only.</em>
*/
class RayPickScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT)
@ -37,34 +61,167 @@ class RayPickScriptingInterface : public QObject, public Dependency {
SINGLETON_DEPENDENCY
public:
/**jsdoc
* @function RayPick.createRayPick
* @param {Picks.RayPickProperties}
* @returns {number}
*/
Q_INVOKABLE unsigned int createRayPick(const QVariant& properties);
/**jsdoc
* @function RayPick.enableRayPick
* @param {number} id
*/
Q_INVOKABLE void enableRayPick(unsigned int uid);
/**jsdoc
* @function RayPick.disableRayPick
* @param {number} id
*/
Q_INVOKABLE void disableRayPick(unsigned int uid);
/**jsdoc
* @function RayPick.removeRayPick
* @param {number} id
*/
Q_INVOKABLE void removeRayPick(unsigned int uid);
/**jsdoc
* @function RayPick.getPrevRayPickResult
* @param {number} id
* @returns {RayPickResult}
*/
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid);
/**jsdoc
* @function RayPick.setPrecisionPicking
* @param {number} id
* @param {boolean} precisionPicking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
/**jsdoc
* @function RayPick.setIgnoreItems
* @param {number} id
* @param {Uuid[]) ignoreEntities
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities);
/**jsdoc
* @function RayPick.setIncludeItems
* @param {number} id
* @param {Uuid[]) includeEntities
*/
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities);
/**jsdoc
* @function RayPick.isLeftHand
* @param {number} id
* @returns {boolean}
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid);
/**jsdoc
* @function RayPick.isRightHand
* @param {number} id
* @returns {boolean}
*/
Q_INVOKABLE bool isRightHand(unsigned int uid);
/**jsdoc
* @function RayPick.isMouse
* @param {number} id
* @returns {boolean}
*/
Q_INVOKABLE bool isMouse(unsigned int uid);
public slots:
/**jsdoc
* @function RayPick.PICK_NOTHING
* @returns {number}
*/
static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); }
/**jsdoc
* @function RayPick.PICK_ENTITIES
* @returns {number}
*/
static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); }
/**jsdoc
* @function RayPick.PICK_OVERLAYS
* @returns {number}
*/
static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); }
/**jsdoc
* @function RayPick.PICK_AVATARS
* @returns {number}
*/
static unsigned int PICK_AVATARS() { return PickScriptingInterface::PICK_AVATARS(); }
/**jsdoc
* @function RayPick.PICK_HUD
* @returns {number}
*/
static unsigned int PICK_HUD() { return PickScriptingInterface::PICK_HUD(); }
/**jsdoc
* @function RayPick.PICK_COARSE
* @returns {number}
*/
static unsigned int PICK_COARSE() { return PickScriptingInterface::PICK_COARSE(); }
/**jsdoc
* @function RayPick.PICK_INCLUDE_INVISIBLE
* @returns {number}
*/
static unsigned int PICK_INCLUDE_INVISIBLE() { return PickScriptingInterface::PICK_INCLUDE_INVISIBLE(); }
/**jsdoc
* @function RayPick.PICK_INCLUDE_NONCOLLIDABLE
* @returns {number}
*/
static unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE(); }
/**jsdoc
* @function RayPick.PICK_ALL_INTERSECTIONS
* @returns {number}
*/
static unsigned int PICK_ALL_INTERSECTIONS() { return PickScriptingInterface::PICK_ALL_INTERSECTIONS(); }
/**jsdoc
* @function RayPick.INTERSECTED_NONE
* @returns {number}
*/
static unsigned int INTERSECTED_NONE() { return PickScriptingInterface::INTERSECTED_NONE(); }
/**jsdoc
* @function RayPick.INTERSECTED_ENTITY
* @returns {number}
*/
static unsigned int INTERSECTED_ENTITY() { return PickScriptingInterface::INTERSECTED_ENTITY(); }
/**jsdoc
* @function RayPick.INTERSECTED_OVERLAY
* @returns {number}
*/
static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_OVERLAY(); }
/**jsdoc
* @function RayPick.INTERSECTED_AVATAR
* @returns {number}
*/
static unsigned int INTERSECTED_AVATAR() { return PickScriptingInterface::INTERSECTED_AVATAR(); }
/**jsdoc
* @function RayPick.INTERSECTED_HUD
* @returns {number}
*/
static unsigned int INTERSECTED_HUD() { return PickScriptingInterface::INTERSECTED_HUD(); }
};

View file

@ -89,7 +89,7 @@ public:
/**jsdoc
* @function Audio.setReverbOptions
* @param {} options
* @param {AudioEffectOptions} options
*/
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);

View file

@ -30,6 +30,14 @@ public:
};
/**jsdoc
* @namespace Wallet
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} walletStatus
*/
class WalletScriptingInterface : public QObject, public Dependency {
Q_OBJECT
@ -38,17 +46,53 @@ class WalletScriptingInterface : public QObject, public Dependency {
public:
WalletScriptingInterface();
/**jsdoc
* @function Wallet.refreshWalletStatus
*/
Q_INVOKABLE void refreshWalletStatus();
/**jsdoc
* @function Wallet.getWalletStatus
* @returns {number}
*/
Q_INVOKABLE uint getWalletStatus() { return _walletStatus; }
/**jsdoc
* @function Wallet.proveAvatarEntityOwnershipVerification
* @param {Uuid} entityID
*/
Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID);
// setWalletStatus() should never be made Q_INVOKABLE. If it were,
// scripts could cause the Wallet to incorrectly report its status.
void setWalletStatus(const uint& status);
signals:
/**jsdoc
* @function Wallet.walletStatusChanged
* @returns {Signal}
*/
void walletStatusChanged();
/**jsdoc
* @function Wallet.walletNotSetup
* @returns {Signal}
*/
void walletNotSetup();
/**jsdoc
* @function Wallet.ownershipVerificationSuccess
* @param {Uuid} entityID
* @returns {Signal}
*/
void ownershipVerificationSuccess(const QUuid& entityID);
/**jsdoc
* @function Wallet.ownershipVerificationFailed
* @param {Uuid} entityID
* @returns {Signal}
*/
void ownershipVerificationFailed(const QUuid& entityID);
private:

View file

@ -15,12 +15,13 @@
#include <QtCore/QDir>
#include <QMessageBox>
#include <QScriptValue>
#include <QtGui/QDesktopServices>
#include <shared/QtHelpers.h>
#include <SettingHandle.h>
#include <display-plugins/CompositorHelper.h>
#include <AddressManager.h>
#include "AndroidHelper.h"
#include "Application.h"
#include "DomainHandler.h"
#include "MainWindow.h"
@ -131,6 +132,24 @@ void WindowScriptingInterface::disconnectedFromDomain() {
emit domainChanged(QUrl());
}
void WindowScriptingInterface::openUrl(const QUrl& url) {
if (!url.isEmpty()) {
if (url.scheme() == URL_SCHEME_HIFI) {
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
} else {
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
}
}
}
void WindowScriptingInterface::openAndroidActivity(const QString& activityName, const bool backToScene) {
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().requestActivity(activityName, backToScene);
#endif
}
QString fixupPathForMac(const QString& directory) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.

View file

@ -374,6 +374,8 @@ public slots:
* Takes a 360 snapshot given a position of the secondary camera (which does not need to have been previously set up).
* @function Window.takeSecondaryCameraSnapshot
* @param {vec3} [cameraPosition] - The (x, y, z) position of the camera for the 360 snapshot
* @param {boolean} [cubemapOutputFormat=false] - If <code>true</code> then the snapshot is saved as a cube map image,
* otherwise is saved as an equirectangular image.
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
@ -520,6 +522,20 @@ public slots:
*/
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
/**jsdoc
* Open the given resource in the Interface window or in a web browser depending on the url scheme
* @function Window.openUrl
* @param {string} url - The resource to open
*/
void openUrl(const QUrl& url);
/**jsdoc
* (Android only) Open the requested Activity and optionally back to the scene when the activity is done
* @function Window.openAndroidActivity
* @param {string} activityName - The name of the activity to open. One of "Home", "Login" or "Privacy Policy"
*/
void openAndroidActivity(const QString& activityName, const bool backToScene);
/**jsdoc
* Update the content of a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
* @function Window.updateMessageBox

View file

@ -31,10 +31,13 @@ HIFI_QML_DEF(LoginDialog)
LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
auto accountManager = DependencyManager::get<AccountManager>();
#if !defined(Q_OS_ANDROID)
connect(accountManager.data(), &AccountManager::loginComplete,
this, &LoginDialog::handleLoginCompleted);
connect(accountManager.data(), &AccountManager::loginFailed,
this, &LoginDialog::handleLoginFailed);
#endif
}
void LoginDialog::showWithSelection()

View file

@ -36,6 +36,14 @@ private:
QUrl _URL;
};
/**jsdoc
* @namespace Snapshot
*
* @hifi-interface
* @hifi-client-entity
*/
class Snapshot : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -50,10 +58,26 @@ public:
void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
signals:
/**jsdoc
* @function Snapshot.snapshotLocationSet
* @param {string} location
* @returns {Signal}
*/
void snapshotLocationSet(const QString& value);
public slots:
/**jsdoc
* @function Snapshot.getSnapshotsLocation
* @returns {string}
*/
Q_INVOKABLE QString getSnapshotsLocation();
/**jsdoc
* @function Snapshot.setSnapshotsLocation
* @param {String} location
*/
Q_INVOKABLE void setSnapshotsLocation(const QString& location);
private slots:

View file

@ -354,6 +354,7 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(gpuTextureResidentMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResidentGPUMemSize()));
STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Context::getTextureFramebufferGPUMemSize()));
STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize()));
STAT_UPDATE(gpuTextureResourceIdealMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceIdealGPUMemSize()));
STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize()));
STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize()));
#if !defined(Q_OS_ANDROID)

File diff suppressed because it is too large Load diff

View file

@ -58,6 +58,30 @@ static void setOption(QScriptValue arguments, const QString name, float defaultV
variable = arguments.property(name).isNumber() ? (float)arguments.property(name).toNumber() : defaultValue;
}
/**jsdoc
* @typedef {object} AudioEffectOptions.ReverbOptions
* @property {number} bandwidth
* @property {number} preDelay
* @property {number} lateDelay
* @property {number} reverbTime
* @property {number} earlyDiffusion
* @property {number} lateDiffusion
* @property {number} roomSize
* @property {number} density
* @property {number} bassMult
* @property {number} bassFreq
* @property {number} highGain
* @property {number} highFreq
* @property {number} modRate
* @property {number} modDepth
* @property {number} earlyGain
* @property {number} lateGain
* @property {number} earlyMixLeft
* @property {number} earlyMixRight
* @property {number} lateMixLeft
* @property {number} lateMixRight
* @property {number} wetDryMix
*/
AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) {
setOption(arguments, BANDWIDTH_HANDLE, BANDWIDTH_DEFAULT, _bandwidth);
setOption(arguments, PRE_DELAY_HANDLE, PRE_DELAY_DEFAULT, _preDelay);

View file

@ -15,6 +15,38 @@
#include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine>
/**jsdoc
* @class AudioEffectOptions
* @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null]
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*
* @property {number} bandwidth=10000
* @property {number} preDelay=20
* @property {number} lateDelay=0
* @property {number} reverbTime=2
* @property {number} earlyDiffusion=100
* @property {number} lateDiffusion=100
* @property {number} roomSize=50
* @property {number} density=100
* @property {number} bassMult=1.5
* @property {number} bassFreq=250
* @property {number} highGain=-6
* @property {number} highFreq=3000
* @property {number} modRate=2.3
* @property {number} modDepth=50
* @property {number} earlyGain=0
* @property {number} lateGain=0
* @property {number} earlyMixLeft=20
* @property {number} earlyMixRight=20
* @property {number} lateMixLeft=90
* @property {number} lateMixRight=90
* @property {number} wetDryMix=50
*/
class AudioEffectOptions : public QObject {
Q_OBJECT

View file

@ -2363,7 +2363,7 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const
}
/**jsdoc
* @typedef MyAvatar.AttachmentData
* @typedef AttachmentData
* @property {string} modelUrl
* @property {string} jointName
* @property {Vec3} translation

View file

@ -351,7 +351,7 @@ public:
class AvatarData : public QObject, public SpatiallyNestable {
Q_OBJECT
// The following properties have JSDoc in MyAvatar.h.
// The following properties have JSDoc in MyAvatar.h and ScriptableAvatar.h
Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript)
Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale)
Q_PROPERTY(float density READ getDensity)
@ -502,7 +502,7 @@ public:
float getDomainLimitedScale() const;
/**jsdoc
* returns the minimum scale allowed for this avatar in the current domain.
* Returns the minimum scale allowed for this avatar in the current domain.
* This value can change as the user changes avatars or when changing domains.
* @function MyAvatar.getDomainMinScale
* @returns {number} minimum scale allowed for this avatar in the current domain.
@ -510,14 +510,14 @@ public:
Q_INVOKABLE float getDomainMinScale() const;
/**jsdoc
* returns the maximum scale allowed for this avatar in the current domain.
* Returns the maximum scale allowed for this avatar in the current domain.
* This value can change as the user changes avatars or when changing domains.
* @function MyAvatar.getDomainMaxScale
* @returns {number} maximum scale allowed for this avatar in the current domain.
*/
Q_INVOKABLE float getDomainMaxScale() const;
// returns eye height of avatar in meters, ignoreing avatar scale.
// Returns eye height of avatar in meters, ignoring avatar scale.
// if _targetScale is 1 then this will be identical to getEyeHeight;
virtual float getUnscaledEyeHeight() const { return DEFAULT_AVATAR_EYE_HEIGHT; }
@ -775,7 +775,7 @@ public:
* Get the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint.
* @function MyAvatar.getJointRotations
* @returns {Quat[]} The rotations of all joints relative to each's parent. The values are in the same order as the array
* returned by {@link MyAvatar.getJointNames}.
* returned by {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
* @example <caption>Report the rotations of all your avatar's joints.</caption>
* print(JSON.stringify(MyAvatar.getJointRotations()));
*/
@ -796,7 +796,7 @@ public:
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.</p>
* @function MyAvatar.setJointRotations
* @param {Quat[]} jointRotations - The rotations for all joints in the avatar. The values are in the same order as the
* array returned by {@link MyAvatar.getJointNames}.
* array returned by {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
* @example <caption>Set your avatar to its default T-pose then rotate its right arm.<br />
* <img alt="Avatar in T-pose" src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/armpose.png" />
* </caption>
@ -852,7 +852,7 @@ public:
/**jsdoc
* Get the joint index for a named joint. The joint index value is the position of the joint in the array returned by
* {@link MyAvatar.getJointNames}.
* {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
* @function MyAvatar.getJointIndex
* @param {string} name - The name of the joint.
* @returns {number} The index of the joint.
@ -952,7 +952,7 @@ public:
/**jsdoc
* Get information about all models currently attached to your avatar.
* @function MyAvatar.getAttachmentData
* @returns {MyAvatar.AttachmentData[]} Information about all models attached to your avatar.
* @returns {AttachmentData[]} Information about all models attached to your avatar.
* @example <caption>Report the URLs of all current attachments.</caption>
* var attachments = MyAvatar.getaAttachmentData();
* for (var i = 0; i < attachments.length; i++) {
@ -963,10 +963,10 @@ public:
/**jsdoc
* Set all models currently attached to your avatar. For example, if you retrieve attachment data using
* {@link MyAvatar.getAttachmentData}, make changes to it, and then want to update your avatar's attachments per the
* {@link MyAvatar.getAttachmentData} or {@link Avatar.getAttachmentData}, make changes to it, and then want to update your avatar's attachments per the
* changed data. You can also remove all attachments by using setting <code>attachmentData</code> to <code>null</code>.
* @function MyAvatar.setAttachmentData
* @param {MyAvatar.AttachmentData[]} attachmentData - The attachment data defining the models to have attached to your avatar. Use
* @param {AttachmentData[]} attachmentData - The attachment data defining the models to have attached to your avatar. Use
* <code>null</code> to remove all attachments.
* @example <caption>Remove a hat attachment if your avatar is wearing it.</caption>
* var hatURL = "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx";
@ -989,7 +989,7 @@ public:
* Nor can you use this function to attach an entity (such as a sphere or a box) to your avatar.</p>
* @function MyAvatar.attach
* @param {string} modelURL - The URL of the model to attach. Models can be .FBX or .OBJ format.
* @param {string} [jointName=""] - The name of the avatar joint (see {@link MyAvatar.getJointNames}) to attach the model
* @param {string} [jointName=""] - The name of the avatar joint (see {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}) to attach the model
* to.
* @param {Vec3} [translation=Vec3.ZERO] - The offset to apply to the model relative to the joint position.
* @param {Quat} [rotation=Quat.IDENTITY] - The rotation to apply to the model relative to the joint orientation.

View file

@ -30,6 +30,15 @@
#include "AvatarData.h"
/**jsdoc
* <strong>Note:</strong> An <code>AvatarList</code> API is also provided for Interface and client entity scripts: it is a
* synonym for the {@link AvatarManager} API.
*
* @namespace AvatarList
*
* @hifi-assignment-client
*/
class AvatarHashMap : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -42,20 +51,24 @@ public:
// Currently, your own avatar will be included as the null avatar id.
/**jsdoc
* @function AvatarManager.getAvatarIdentifiers
* @function AvatarList.getAvatarIdentifiers
* @returns {Uuid[]}
*/
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
/**jsdoc
* @function AvatarManager.getAvatarsInRange
* @function AvatarList.getAvatarsInRange
* @param {Vec3} position
* @param {number} range
* @returns {Uuid[]}
*/
Q_INVOKABLE QVector<QUuid> getAvatarsInRange(const glm::vec3& position, float rangeMeters) const;
// No JSDod because it's documwented in AvatarManager.
/**jsdoc
* @function AvatarList.getAvatar
* @param {Uuid} avatarID
* @returns {AvatarData}
*/
// Null/Default-constructed QUuids will return MyAvatar
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); }
@ -65,21 +78,21 @@ public:
signals:
/**jsdoc
* @function AvatarManager.avatarAddedEvent
* @function AvatarList.avatarAddedEvent
* @param {Uuid} sessionUUID
* @returns {Signal}
*/
void avatarAddedEvent(const QUuid& sessionUUID);
/**jsdoc
* @function AvatarManager.avatarRemovedEvent
* @function AvatarList.avatarRemovedEvent
* @param {Uuid} sessionUUID
* @returns {Signal}
*/
void avatarRemovedEvent(const QUuid& sessionUUID);
/**jsdoc
* @function AvatarManager.avatarSessionChangedEvent
* @function AvatarList.avatarSessionChangedEvent
* @param {Uuid} sessionUUID
* @param {Uuid} oldSessionUUID
* @returns {Signal}
@ -89,7 +102,7 @@ signals:
public slots:
/**jsdoc
* @function AvatarManager.isAvatarInRange
* @function AvatarList.isAvatarInRange
* @param {string} position
* @param {string} range
* @returns {boolean}
@ -99,28 +112,28 @@ public slots:
protected slots:
/**jsdoc
* @function AvatarManager.sessionUUIDChanged
* @function AvatarList.sessionUUIDChanged
* @param {Uuid} sessionUUID
* @param {Uuid} oldSessionUUID
*/
void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID);
/**jsdoc
* @function AvatarManager.processAvatarDataPacket
* @function AvatarList.processAvatarDataPacket
* @param {} message
* @param {} sendingNode
*/
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
/**jsdoc
* @function AvatarManager.processAvatarIdentityPacket
* @function AvatarList.processAvatarIdentityPacket
* @param {} message
* @param {} sendingNode
*/
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
/**jsdoc
* @function AvatarManager.processKillAvatar
* @function AvatarList.processKillAvatar
* @param {} message
* @param {} sendingNode
*/

View file

@ -44,9 +44,9 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
(&::gpu::gl::GLBackend::do_setModelTransform),
(&::gpu::gl::GLBackend::do_setViewTransform),
(&::gpu::gl::GLBackend::do_setProjectionTransform),
(&::gpu::gl::GLBackend::do_setProjectionJitter),
(&::gpu::gl::GLBackend::do_setViewportTransform),
(&::gpu::gl::GLBackend::do_setProjectionTransform),
(&::gpu::gl::GLBackend::do_setProjectionJitter),
(&::gpu::gl::GLBackend::do_setViewportTransform),
(&::gpu::gl::GLBackend::do_setDepthRangeTransform),
(&::gpu::gl::GLBackend::do_setPipeline),
@ -118,12 +118,6 @@ void GLBackend::init() {
#if !defined(USE_GLES)
qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF"));
#endif
#if THREADED_TEXTURE_BUFFERING
// This has to happen on the main thread in order to give the thread
// pool a reasonable parent object
GLVariableAllocationSupport::TransferJob::startBufferingThread();
#endif
});
}
@ -136,6 +130,7 @@ GLBackend::GLBackend() {
GLBackend::~GLBackend() {
killInput();
killTransform();
killTextureManagementStage();
}
void GLBackend::renderPassTransfer(const Batch& batch) {
@ -167,18 +162,18 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
case Batch::COMMAND_drawIndexedInstanced:
case Batch::COMMAND_multiDrawIndirect:
case Batch::COMMAND_multiDrawIndexedIndirect:
{
Vec2u outputSize{ 1,1 };
{
Vec2u outputSize{ 1,1 };
if (_output._framebuffer) {
outputSize.x = _output._framebuffer->getWidth();
outputSize.y = _output._framebuffer->getHeight();
} else if (glm::dot(_transform._projectionJitter, _transform._projectionJitter)>0.0f) {
qCWarning(gpugllogging) << "Jittering needs to have a frame buffer to be set";
}
if (_output._framebuffer) {
outputSize.x = _output._framebuffer->getWidth();
outputSize.y = _output._framebuffer->getHeight();
} else if (glm::dot(_transform._projectionJitter, _transform._projectionJitter)>0.0f) {
qCWarning(gpugllogging) << "Jittering needs to have a frame buffer to be set";
}
_transform.preUpdate(_commandIndex, _stereo, outputSize);
}
_transform.preUpdate(_commandIndex, _stereo, outputSize);
}
break;
case Batch::COMMAND_disableContextStereo:
@ -191,10 +186,10 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
case Batch::COMMAND_setViewportTransform:
case Batch::COMMAND_setViewTransform:
case Batch::COMMAND_setProjectionTransform:
case Batch::COMMAND_setProjectionJitter:
{
CommandCall call = _commandCalls[(*command)];
case Batch::COMMAND_setProjectionTransform:
case Batch::COMMAND_setProjectionJitter:
{
CommandCall call = _commandCalls[(*command)];
(this->*(call))(batch, *offset);
break;
}
@ -268,8 +263,8 @@ void GLBackend::render(const Batch& batch) {
if (!batch.isStereoEnabled()) {
_stereo._enable = false;
}
// Reset jitter
_transform._projectionJitter = Vec2(0.0f, 0.0f);
// Reset jitter
_transform._projectionJitter = Vec2(0.0f, 0.0f);
{
PROFILE_RANGE(render_gpu_gl_detail, "Transfer");
@ -729,9 +724,8 @@ void GLBackend::recycle() const {
glDeleteQueries((GLsizei)ids.size(), ids.data());
}
}
GLVariableAllocationSupport::manageMemory();
GLVariableAllocationSupport::_frameTexturesCreated = 0;
_textureManagement._transferEngine->manageMemory();
Texture::KtxStorage::releaseOpenKtxFiles();
}

View file

@ -491,8 +491,10 @@ protected:
struct TextureManagementStageState {
bool _sparseCapable { false };
GLTextureTransferEnginePointer _transferEngine;
} _textureManagement;
virtual void initTextureManagementStage() {}
virtual void initTextureManagementStage();
virtual void killTextureManagementStage();
typedef void (GLBackend::*CommandCall)(const Batch&, size_t);
static CommandCall _commandCalls[Batch::NUM_COMMANDS];

View file

@ -137,6 +137,8 @@ class GLQuery;
class GLState;
class GLShader;
class GLTexture;
class GLTextureTransferEngine;
using GLTextureTransferEnginePointer = std::shared_ptr<GLTextureTransferEngine>;
struct ShaderObject;
} } // namespace gpu::gl

View file

@ -48,6 +48,14 @@ const GLFilterMode GLTexture::FILTER_MODES[Sampler::NUM_FILTERS] = {
{ GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR } //FILTER_ANISOTROPIC,
};
static constexpr size_t MAX_PIXEL_BYTE_SIZE{ 4 };
static constexpr size_t MAX_TRANSFER_DIMENSION{ 1024 };
const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS{ MAX_TRANSFER_DIMENSION, MAX_TRANSFER_DIMENSION, 1 };
const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS{ 64, 64, 1 };
const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSION * MAX_TRANSFER_DIMENSION * MAX_PIXEL_BYTE_SIZE;
const size_t GLVariableAllocationSupport::MAX_BUFFER_SIZE = MAX_TRANSFER_SIZE;
GLenum GLTexture::getGLTextureType(const Texture& texture) {
switch (texture.getType()) {
case Texture::TEX_2D:
@ -131,7 +139,6 @@ Size GLTexture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, u
return 0;
}
GLExternalTexture::GLExternalTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id)
: Parent(backend, texture, id) {
Backend::textureExternalCount.increment();
@ -151,65 +158,58 @@ GLExternalTexture::~GLExternalTexture() {
Backend::textureExternalCount.decrement();
}
// Variable sized textures
using MemoryPressureState = GLVariableAllocationSupport::MemoryPressureState;
using WorkQueue = GLVariableAllocationSupport::WorkQueue;
using TransferJobPointer = GLVariableAllocationSupport::TransferJobPointer;
std::list<TextureWeakPointer> GLVariableAllocationSupport::_memoryManagedTextures;
MemoryPressureState GLVariableAllocationSupport::_memoryPressureState { MemoryPressureState::Idle };
std::atomic<bool> GLVariableAllocationSupport::_memoryPressureStateStale { false };
const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 };
WorkQueue GLVariableAllocationSupport::_transferQueue;
WorkQueue GLVariableAllocationSupport::_promoteQueue;
WorkQueue GLVariableAllocationSupport::_demoteQueue;
size_t GLVariableAllocationSupport::_frameTexturesCreated { 0 };
#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f
#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f
#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024)
static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB);
using TransferJob = GLVariableAllocationSupport::TransferJob;
const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 };
const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.x * GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.y * 4;
#if THREADED_TEXTURE_BUFFERING
TexturePointer GLVariableAllocationSupport::_currentTransferTexture;
TransferJobPointer GLVariableAllocationSupport::_currentTransferJob;
QThreadPool* TransferJob::_bufferThreadPool { nullptr };
void TransferJob::startBufferingThread() {
static std::once_flag once;
std::call_once(once, [&] {
_bufferThreadPool = new QThreadPool(qApp);
_bufferThreadPool->setMaxThreadCount(1);
});
GLVariableAllocationSupport::GLVariableAllocationSupport() {
}
#endif
GLVariableAllocationSupport::~GLVariableAllocationSupport() {
}
TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset)
: _parent(parent) {
void GLVariableAllocationSupport::incrementPopulatedSize(Size delta) const {
_populatedSize += delta;
// Keep the 2 code paths to be able to debug
if (_size < _populatedSize) {
Backend::textureResourcePopulatedGPUMemSize.update(0, delta);
} else {
Backend::textureResourcePopulatedGPUMemSize.update(0, delta);
}
}
auto transferDimensions = _parent._gpuObject.evalMipDimensions(sourceMip);
void GLVariableAllocationSupport::decrementPopulatedSize(Size delta) const {
_populatedSize -= delta;
// Keep the 2 code paths to be able to debug
if (_size < _populatedSize) {
Backend::textureResourcePopulatedGPUMemSize.update(delta, 0);
} else {
Backend::textureResourcePopulatedGPUMemSize.update(delta, 0);
}
}
void GLVariableAllocationSupport::sanityCheck() const {
if (_populatedMip < _allocatedMip) {
qCWarning(gpugllogging) << "Invalid mip levels";
}
}
TransferJob::TransferJob(const Texture& texture,
uint16_t sourceMip,
uint16_t targetMip,
uint8_t face,
uint32_t lines,
uint32_t lineOffset) {
auto transferDimensions = texture.evalMipDimensions(sourceMip);
GLenum format;
GLenum internalFormat;
GLenum type;
GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), _parent._gpuObject.getStoredMipFormat());
GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), texture.getStoredMipFormat());
format = texelFormat.format;
internalFormat = texelFormat.internalFormat;
type = texelFormat.type;
_transferSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face);
_transferSize = texture.getStoredMipFaceSize(sourceMip, face);
// If we're copying a subsection of the mip, do additional calculations to find the size and offset of the segment
if (0 != lines) {
transferDimensions.y = lines;
auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip);
auto dimensions = texture.evalMipDimensions(sourceMip);
auto bytesPerLine = (uint32_t)_transferSize / dimensions.y;
_transferOffset = bytesPerLine * lineOffset;
_transferSize = bytesPerLine * lines;
@ -222,481 +222,34 @@ TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t t
}
// Buffering can invoke disk IO, so it should be off of the main and render threads
_bufferingLambda = [=] {
auto mipStorage = _parent._gpuObject.accessStoredMipFace(sourceMip, face);
_bufferingLambda = [=](const TexturePointer& texture) {
auto mipStorage = texture->accessStoredMipFace(sourceMip, face);
if (mipStorage) {
_mipData = mipStorage->createView(_transferSize, _transferOffset);
} else {
qCWarning(gpugllogging) << "Buffering failed because mip could not be retrieved from texture " << _parent._source.c_str() ;
qCWarning(gpugllogging) << "Buffering failed because mip could not be retrieved from texture "
<< texture->source().c_str();
}
};
_transferLambda = [=] {
_transferLambda = [=](const TexturePointer& texture) {
if (_mipData) {
_parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, internalFormat, format, type, _mipData->size(), _mipData->readData());
auto gltexture = Backend::getGPUObject<GLTexture>(*texture);
;
gltexture->copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, internalFormat, format,
type, _mipData->size(), _mipData->readData());
_mipData.reset();
} else {
qCWarning(gpugllogging) << "Transfer failed because mip could not be retrieved from texture " << _parent._source.c_str();
qCWarning(gpugllogging) << "Transfer failed because mip could not be retrieved from texture "
<< texture->source().c_str();
}
};
}
TransferJob::TransferJob(const GLTexture& parent, std::function<void()> transferLambda)
: _parent(parent), _bufferingRequired(false), _transferLambda(transferLambda) {
}
TransferJob::TransferJob(const std::function<void()>& transferLambda) :
_bufferingRequired(false), _transferLambda([=](const TexturePointer&) { transferLambda(); }) {}
TransferJob::~TransferJob() {
Backend::texturePendingGPUTransferMemSize.update(_transferSize, 0);
}
bool TransferJob::tryTransfer() {
#if THREADED_TEXTURE_BUFFERING
// Are we ready to transfer
if (!bufferingCompleted()) {
startBuffering();
return false;
}
#else
if (_bufferingRequired) {
_bufferingLambda();
}
#endif
_transferLambda();
return true;
}
#if THREADED_TEXTURE_BUFFERING
bool TransferJob::bufferingRequired() const {
if (!_bufferingRequired) {
return false;
}
// The default state of a QFuture is with status Canceled | Started | Finished,
// so we have to check isCancelled before we check the actual state
if (_bufferingStatus.isCanceled()) {
return true;
}
return !_bufferingStatus.isStarted();
}
bool TransferJob::bufferingCompleted() const {
if (!_bufferingRequired) {
return true;
}
// The default state of a QFuture is with status Canceled | Started | Finished,
// so we have to check isCancelled before we check the actual state
if (_bufferingStatus.isCanceled()) {
return false;
}
return _bufferingStatus.isFinished();
}
void TransferJob::startBuffering() {
if (bufferingRequired()) {
assert(_bufferingStatus.isCanceled());
_bufferingStatus = QtConcurrent::run(_bufferThreadPool, [=] {
_bufferingLambda();
});
assert(!_bufferingStatus.isCanceled());
assert(_bufferingStatus.isStarted());
}
}
#endif
GLVariableAllocationSupport::GLVariableAllocationSupport() {
_memoryPressureStateStale = true;
}
GLVariableAllocationSupport::~GLVariableAllocationSupport() {
_memoryPressureStateStale = true;
}
void GLVariableAllocationSupport::addMemoryManagedTexture(const TexturePointer& texturePointer) {
_memoryManagedTextures.push_back(texturePointer);
if (MemoryPressureState::Idle != _memoryPressureState) {
addToWorkQueue(texturePointer);
}
}
void GLVariableAllocationSupport::addToWorkQueue(const TexturePointer& texturePointer) {
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texturePointer);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
switch (_memoryPressureState) {
case MemoryPressureState::Oversubscribed:
if (vargltexture->canDemote()) {
// Demote largest first
_demoteQueue.push({ texturePointer, (float)gltexture->size() });
}
break;
case MemoryPressureState::Undersubscribed:
if (vargltexture->canPromote()) {
// Promote smallest first
_promoteQueue.push({ texturePointer, 1.0f / (float)gltexture->size() });
}
break;
case MemoryPressureState::Transfer:
if (vargltexture->hasPendingTransfers()) {
// Transfer priority given to smaller mips first
_transferQueue.push({ texturePointer, 1.0f / (float)gltexture->_gpuObject.evalMipSize(vargltexture->_populatedMip) });
}
break;
case MemoryPressureState::Idle:
Q_UNREACHABLE();
break;
}
}
WorkQueue& GLVariableAllocationSupport::getActiveWorkQueue() {
static WorkQueue empty;
switch (_memoryPressureState) {
case MemoryPressureState::Oversubscribed:
return _demoteQueue;
case MemoryPressureState::Undersubscribed:
return _promoteQueue;
case MemoryPressureState::Transfer:
return _transferQueue;
case MemoryPressureState::Idle:
Q_UNREACHABLE();
break;
}
return empty;
}
// FIXME hack for stats display
QString getTextureMemoryPressureModeString() {
switch (GLVariableAllocationSupport::_memoryPressureState) {
case MemoryPressureState::Oversubscribed:
return "Oversubscribed";
case MemoryPressureState::Undersubscribed:
return "Undersubscribed";
case MemoryPressureState::Transfer:
return "Transfer";
case MemoryPressureState::Idle:
return "Idle";
}
Q_UNREACHABLE();
return "Unknown";
}
void GLVariableAllocationSupport::updateMemoryPressure() {
static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage();
size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage();
if (0 == allowedMemoryAllocation) {
allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY;
}
// If the user explicitly changed the allowed memory usage, we need to mark ourselves stale
// so that we react
if (allowedMemoryAllocation != lastAllowedMemoryAllocation) {
_memoryPressureStateStale = true;
lastAllowedMemoryAllocation = allowedMemoryAllocation;
}
if (!_memoryPressureStateStale.exchange(false)) {
return;
}
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
// Clear any defunct textures (weak pointers that no longer have a valid texture)
_memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) {
return weakPointer.expired();
});
// Convert weak pointers to strong. This new list may still contain nulls if a texture was
// deleted on another thread between the previous line and this one
std::vector<TexturePointer> strongTextures; {
strongTextures.reserve(_memoryManagedTextures.size());
std::transform(
_memoryManagedTextures.begin(), _memoryManagedTextures.end(),
std::back_inserter(strongTextures),
[](const TextureWeakPointer& p) { return p.lock(); });
}
size_t totalVariableMemoryAllocation = 0;
size_t idealMemoryAllocation = 0;
bool canDemote = false;
bool canPromote = false;
bool hasTransfers = false;
for (const auto& texture : strongTextures) {
// Race conditions can still leave nulls in the list, so we need to check
if (!texture) {
continue;
}
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
// Track how much the texture thinks it should be using
idealMemoryAllocation += texture->evalTotalSize();
// Track how much we're actually using
totalVariableMemoryAllocation += gltexture->size();
canDemote |= vartexture->canDemote();
canPromote |= vartexture->canPromote();
hasTransfers |= vartexture->hasPendingTransfers();
}
size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation;
float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
auto newState = MemoryPressureState::Idle;
if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) {
newState = MemoryPressureState::Undersubscribed;
} else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
newState = MemoryPressureState::Oversubscribed;
} else if (hasTransfers) {
newState = MemoryPressureState::Transfer;
}
if (newState != _memoryPressureState) {
_memoryPressureState = newState;
// Clear the existing queue
_transferQueue = WorkQueue();
_promoteQueue = WorkQueue();
_demoteQueue = WorkQueue();
// Populate the existing textures into the queue
if (_memoryPressureState != MemoryPressureState::Idle) {
for (const auto& texture : strongTextures) {
// Race conditions can still leave nulls in the list, so we need to check
if (!texture) {
continue;
}
addToWorkQueue(texture);
}
}
}
}
TexturePointer GLVariableAllocationSupport::getNextWorkQueueItem(WorkQueue& workQueue) {
while (!workQueue.empty()) {
auto workTarget = workQueue.top();
auto texture = workTarget.first.lock();
if (!texture) {
workQueue.pop();
continue;
}
// Check whether the resulting texture can actually have work performed
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
switch (_memoryPressureState) {
case MemoryPressureState::Oversubscribed:
if (vartexture->canDemote()) {
return texture;
}
break;
case MemoryPressureState::Undersubscribed:
if (vartexture->canPromote()) {
return texture;
}
break;
case MemoryPressureState::Transfer:
if (vartexture->hasPendingTransfers()) {
return texture;
}
break;
case MemoryPressureState::Idle:
Q_UNREACHABLE();
break;
}
// If we got here, then the texture has no work to do in the current state,
// so pop it off the queue and continue
workQueue.pop();
}
return TexturePointer();
}
void GLVariableAllocationSupport::processWorkQueue(WorkQueue& workQueue) {
if (workQueue.empty()) {
return;
}
// Get the front of the work queue to perform work
auto texture = getNextWorkQueueItem(workQueue);
if (!texture) {
return;
}
// Grab the first item off the demote queue
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
switch (_memoryPressureState) {
case MemoryPressureState::Oversubscribed:
vartexture->demote();
workQueue.pop();
addToWorkQueue(texture);
_memoryPressureStateStale = true;
break;
case MemoryPressureState::Undersubscribed:
vartexture->promote();
workQueue.pop();
addToWorkQueue(texture);
_memoryPressureStateStale = true;
break;
case MemoryPressureState::Transfer:
if (vartexture->executeNextTransfer(texture)) {
workQueue.pop();
addToWorkQueue(texture);
#if THREADED_TEXTURE_BUFFERING
// Eagerly start the next buffering job if possible
texture = getNextWorkQueueItem(workQueue);
if (texture) {
gltexture = Backend::getGPUObject<GLTexture>(*texture);
vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
vartexture->executeNextBuffer(texture);
}
#endif
}
break;
case MemoryPressureState::Idle:
Q_UNREACHABLE();
break;
}
}
void GLVariableAllocationSupport::processWorkQueues() {
if (MemoryPressureState::Idle == _memoryPressureState) {
return;
}
auto& workQueue = getActiveWorkQueue();
// Do work on the front of the queue
processWorkQueue(workQueue);
if (workQueue.empty()) {
_memoryPressureState = MemoryPressureState::Idle;
_memoryPressureStateStale = true;
}
}
void GLVariableAllocationSupport::manageMemory() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
updateMemoryPressure();
processWorkQueues();
}
bool GLVariableAllocationSupport::executeNextTransfer(const TexturePointer& currentTexture) {
#if THREADED_TEXTURE_BUFFERING
// If a transfer job is active on the buffering thread, but has not completed it's buffering lambda,
// then we need to exit early, since we don't want to have the transfer job leave scope while it's
// being used in another thread -- See https://highfidelity.fogbugz.com/f/cases/4626
if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) {
return false;
}
#endif
if (_populatedMip <= _allocatedMip) {
#if THREADED_TEXTURE_BUFFERING
_currentTransferJob.reset();
_currentTransferTexture.reset();
#endif
return true;
}
// If the transfer queue is empty, rebuild it
if (_pendingTransfers.empty()) {
populateTransferQueue();
}
bool result = false;
if (!_pendingTransfers.empty()) {
#if THREADED_TEXTURE_BUFFERING
// If there is a current transfer, but it's not the top of the pending transfer queue, then it's an orphan, so we want to abandon it.
if (_currentTransferJob && _currentTransferJob != _pendingTransfers.front()) {
_currentTransferJob.reset();
}
if (!_currentTransferJob) {
// Keeping hold of a strong pointer to the transfer job ensures that if the pending transfer queue is rebuilt, the transfer job
// doesn't leave scope, causing a crash in the buffering thread
_currentTransferJob = _pendingTransfers.front();
// Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture
_currentTransferTexture = currentTexture;
}
// transfer jobs use asynchronous buffering of the texture data because it may involve disk IO, so we execute a try here to determine if the buffering
// is complete
if (_currentTransferJob->tryTransfer()) {
_pendingTransfers.pop();
// Once a given job is finished, release the shared pointers keeping them alive
_currentTransferTexture.reset();
_currentTransferJob.reset();
result = true;
}
#else
if (_pendingTransfers.front()->tryTransfer()) {
_pendingTransfers.pop();
result = true;
}
#endif
}
return result;
}
#if THREADED_TEXTURE_BUFFERING
void GLVariableAllocationSupport::executeNextBuffer(const TexturePointer& currentTexture) {
if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) {
return;
}
// If the transfer queue is empty, rebuild it
if (_pendingTransfers.empty()) {
populateTransferQueue();
}
if (!_pendingTransfers.empty()) {
if (!_currentTransferJob) {
_currentTransferJob = _pendingTransfers.front();
_currentTransferTexture = currentTexture;
}
_currentTransferJob->startBuffering();
}
}
#endif
void GLVariableAllocationSupport::incrementPopulatedSize(Size delta) const {
_populatedSize += delta;
// Keep the 2 code paths to be able to debug
if (_size < _populatedSize) {
Backend::textureResourcePopulatedGPUMemSize.update(0, delta);
} else {
Backend::textureResourcePopulatedGPUMemSize.update(0, delta);
}
}
void GLVariableAllocationSupport::decrementPopulatedSize(Size delta) const {
_populatedSize -= delta;
// Keep the 2 code paths to be able to debug
if (_size < _populatedSize) {
Backend::textureResourcePopulatedGPUMemSize.update(delta, 0);
} else {
Backend::textureResourcePopulatedGPUMemSize.update(delta, 0);
}
}

View file

@ -16,8 +16,6 @@
#include "GLTexelFormat.h"
#include <thread>
#define THREADED_TEXTURE_BUFFERING 1
namespace gpu { namespace gl {
struct GLFilterMode {
@ -25,107 +23,92 @@ struct GLFilterMode {
GLint magFilter;
};
class GLTextureTransferEngine {
public:
using Pointer = std::shared_ptr<GLTextureTransferEngine>;
/// Called once per frame to perform any require memory management or transfer work
virtual void manageMemory() = 0;
virtual void shutdown() = 0;
/// Called whenever a client wants to create a new texture. This allows the transfer engine to
/// potentially limit the number of GL textures created per frame
bool allowCreate() const { return _frameTexturesCreated < MAX_RESOURCE_TEXTURES_PER_FRAME; }
/// Called whenever a client creates a new resource texture that should use managed memory
/// and incremental transfer
void addMemoryManagedTexture(const TexturePointer& texturePointer);
protected:
// Fetch all the currently active textures as strong pointers, while clearing the
// empty weak pointers out of _registeredTextures
std::vector<TexturePointer> getAllTextures();
void resetFrameTextureCreated() { _frameTexturesCreated = 0; }
private:
static const size_t MAX_RESOURCE_TEXTURES_PER_FRAME{ 2 };
size_t _frameTexturesCreated{ 0 };
std::list<TextureWeakPointer> _registeredTextures;
};
/**
A transfer job encapsulates an individual piece of work required to upload texture data to the GPU.
The work can be broken down into two parts, expressed as lambdas. The buffering lambda is repsonsible
for putting the data to be uploaded into a CPU memory buffer. The transfer lambda is repsonsible for
uploading the data from the CPU memory buffer to the GPU using OpenGL calls. Ideally the buffering lambda
will be executed on a seprate thread from the OpenGL work to ensure that disk IO operations do not block
OpenGL calls
Additionally, a TransferJob can encapsulate some kind of post-upload work that changes the state of the
GLTexture derived object wrapping the actual texture ID, such as changing the _populateMip value once
a given mip level has been compeltely uploaded
*/
class TransferJob {
public:
using Pointer = std::shared_ptr<TransferJob>;
using Queue = std::queue<Pointer>;
using Lambda = std::function<void(const TexturePointer&)>;
private:
Texture::PixelsPointer _mipData;
size_t _transferOffset{ 0 };
size_t _transferSize{ 0 };
bool _bufferingRequired{ true };
Lambda _transferLambda{ [](const TexturePointer&) {} };
Lambda _bufferingLambda{ [](const TexturePointer&) {} };
public:
TransferJob(const TransferJob& other) = delete;
TransferJob(const std::function<void()>& transferLambda);
TransferJob(const Texture& texture, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0);
~TransferJob();
const size_t& size() const { return _transferSize; }
bool bufferingRequired() const { return _bufferingRequired; }
void buffer(const TexturePointer& texture) { _bufferingLambda(texture); }
void transfer(const TexturePointer& texture) { _transferLambda(texture); }
};
using TransferJobPointer = std::shared_ptr<TransferJob>;
using TransferQueue = std::queue<TransferJobPointer>;
class GLVariableAllocationSupport {
friend class GLBackend;
public:
GLVariableAllocationSupport();
virtual ~GLVariableAllocationSupport();
virtual void populateTransferQueue(TransferQueue& pendingTransfers) = 0;
enum class MemoryPressureState {
Idle,
Transfer,
Oversubscribed,
Undersubscribed,
};
using QueuePair = std::pair<TextureWeakPointer, float>;
struct QueuePairLess {
bool operator()(const QueuePair& a, const QueuePair& b) {
return a.second < b.second;
}
};
using WorkQueue = std::priority_queue<QueuePair, std::vector<QueuePair>, QueuePairLess>;
class TransferJob {
using VoidLambda = std::function<void()>;
using VoidLambdaQueue = std::queue<VoidLambda>;
const GLTexture& _parent;
Texture::PixelsPointer _mipData;
size_t _transferOffset { 0 };
size_t _transferSize { 0 };
bool _bufferingRequired { true };
VoidLambda _transferLambda;
VoidLambda _bufferingLambda;
#if THREADED_TEXTURE_BUFFERING
// Indicates if a transfer from backing storage to interal storage has started
QFuture<void> _bufferingStatus;
static QThreadPool* _bufferThreadPool;
#endif
public:
TransferJob(const TransferJob& other) = delete;
TransferJob(const GLTexture& parent, std::function<void()> transferLambda);
TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0);
~TransferJob();
bool tryTransfer();
#if THREADED_TEXTURE_BUFFERING
void startBuffering();
bool bufferingRequired() const;
bool bufferingCompleted() const;
static void startBufferingThread();
#endif
private:
void transfer();
};
using TransferJobPointer = std::shared_ptr<TransferJob>;
using TransferQueue = std::queue<TransferJobPointer>;
static MemoryPressureState _memoryPressureState;
public:
static void addMemoryManagedTexture(const TexturePointer& texturePointer);
protected:
static size_t _frameTexturesCreated;
static std::atomic<bool> _memoryPressureStateStale;
static std::list<TextureWeakPointer> _memoryManagedTextures;
static WorkQueue _transferQueue;
static WorkQueue _promoteQueue;
static WorkQueue _demoteQueue;
#if THREADED_TEXTURE_BUFFERING
static TexturePointer _currentTransferTexture;
static TransferJobPointer _currentTransferJob;
#endif
static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS;
static const uvec3 MAX_TRANSFER_DIMENSIONS;
static const size_t MAX_TRANSFER_SIZE;
static void updateMemoryPressure();
static void processWorkQueues();
static void processWorkQueue(WorkQueue& workQueue);
static TexturePointer getNextWorkQueueItem(WorkQueue& workQueue);
static void addToWorkQueue(const TexturePointer& texture);
static WorkQueue& getActiveWorkQueue();
static void manageMemory();
//bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
void sanityCheck() const;
bool canPromote() const { return _allocatedMip > _minAllocatedMip; }
bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
#if THREADED_TEXTURE_BUFFERING
void executeNextBuffer(const TexturePointer& currentTexture);
#endif
bool executeNextTransfer(const TexturePointer& currentTexture);
virtual void populateTransferQueue() = 0;
virtual void promote() = 0;
virtual void demote() = 0;
virtual size_t promote() = 0;
virtual size_t demote() = 0;
static const uvec3 MAX_TRANSFER_DIMENSIONS;
static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS;
static const size_t MAX_TRANSFER_SIZE;
static const size_t MAX_BUFFER_SIZE;
protected:
// THe amount of memory currently allocated
Size _size { 0 };
@ -148,10 +131,6 @@ protected:
// The lowest (highest resolution) mip that we will support, relative to the number
// of mips in the gpu::Texture object
uint16 _minAllocatedMip { 0 };
// Contains a series of lambdas that when executed will transfer data to the GPU, modify
// the _populatedMip and update the sampler in order to fully populate the allocated texture
// until _populatedMip == _allocatedMip
TransferQueue _pendingTransfers;
};
class GLTexture : public GLObject<Texture> {
@ -172,6 +151,9 @@ public:
static const std::vector<GLenum>& getFaceTargets(GLenum textureType);
static uint8_t getFaceCount(GLenum textureType);
static GLenum getGLTextureType(const Texture& texture);
virtual Size size() const = 0;
virtual Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const = 0;
virtual Size copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const final;
static const uint8_t TEXTURE_2D_NUM_FACES = 1;
static const uint8_t TEXTURE_CUBE_NUM_FACES = 6;
@ -180,12 +162,9 @@ public:
static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES];
protected:
virtual Size size() const = 0;
virtual void generateMips() const = 0;
virtual void syncSampler() const = 0;
virtual Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const = 0;
virtual Size copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const final;
virtual void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {} // Only relevant for Variable Allocation textures
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
@ -205,7 +184,6 @@ protected:
Size size() const override { return 0; }
};
} }
#endif

View file

@ -0,0 +1,502 @@
//
// Created by Bradley Austin Davis on 2016/05/15
// Copyright 2013-2016 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 "GLTexture.h"
#include <QtCore/QThread>
#include <NumericalConstants.h>
#include "GLBackend.h"
#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f
#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f
#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024)
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define NO_BUFFER_WORK_SLEEP_TIME_MS 2
#define THREADED_TEXTURE_BUFFERING 1
static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB);
namespace gpu { namespace gl {
enum class MemoryPressureState
{
Idle,
Transfer,
Undersubscribed,
};
static MemoryPressureState _memoryPressureState{ MemoryPressureState::Idle };
template <typename T>
struct LessPairSecond {
bool operator()(const T& a, const T& b) { return a.second < b.second; }
};
using QueuePair = std::pair<TextureWeakPointer, float>;
// Contains a priority sorted list of textures on which work is to be done over many frames
// Uses a weak pointer to the texture to avoid keeping it in scope if the client stops using it
using WorkQueue = std::priority_queue<QueuePair, std::vector<QueuePair>, LessPairSecond<QueuePair>>;
using ImmediateQueuePair = std::pair<TexturePointer, float>;
// Contains a priority sorted list of textures on which work is to be done in the current frame
using ImmediateWorkQueue = std::priority_queue<ImmediateQueuePair, std::vector<ImmediateQueuePair>, LessPairSecond<ImmediateQueuePair>>;
// A map of weak texture pointers to queues of work to be done to transfer their data from the backing store to the GPU
using TransferMap = std::map<TextureWeakPointer, TransferQueue, std::owner_less<TextureWeakPointer>>;
class GLTextureTransferEngineDefault : public GLTextureTransferEngine {
using Parent = GLTextureTransferEngine;
public:
// Called once per frame by the GLBackend to manage texture memory
// Will deallocate textures if oversubscribed,
void manageMemory() override;
void shutdown() override;
protected:
class TextureBufferThread : public QThread {
public:
TextureBufferThread(GLTextureTransferEngineDefault& parent) : _parent(parent) { start(); }
protected:
void run() override {
while (!_parent._shutdown) {
if (!_parent.processActiveBufferQueue()) {
QThread::msleep(NO_BUFFER_WORK_SLEEP_TIME_MS);
}
}
}
GLTextureTransferEngineDefault& _parent;
};
using ActiveTransferJob = std::pair<TexturePointer, TransferJobPointer>;
using ActiveTransferQueue = std::list<ActiveTransferJob>;
void populateActiveBufferQueue();
bool processActiveBufferQueue();
void processTransferQueues();
void populateTransferQueue(const TexturePointer& texturePointer);
//void addToWorkQueue(const TexturePointer& texturePointer);
void updateMemoryPressure();
void processDemotes(size_t relief, const std::vector<TexturePointer>& strongTextures);
void processPromotes();
private:
std::atomic<bool> _shutdown{ false };
// Contains a priority sorted list of weak texture pointers that have been determined to be eligible for additional allocation
// While the memory state is 'undersubscribed', items will be removed from this list and processed, allocating additional memory
// per frame
WorkQueue _promoteQueue;
// This queue contains jobs that will buffer data from the texture backing store (ideally a memory mapped KTX file)
// to a CPU memory buffer. This queue is populated on the main GPU thread, and drained on a dedicated thread.
// When an item on the _activeBufferQueue is completed it is put into the _activeTransferQueue
ActiveTransferQueue _activeBufferQueue;
// This queue contains jobs that will upload data from a CPU buffer into a GPU. This queue is populated on the background
// thread that process the _activeBufferQueue and drained on the main GPU thread
ActiveTransferQueue _activeTransferQueue;
// Mutex protecting the _activeTransferQueue & _activeBufferQueue since they are each accessed both from the main GPU thread
// and the buffering thread
Mutex _bufferMutex;
// The buffering thread which drains the _activeBufferQueue and populates the _activeTransferQueue
TextureBufferThread* _transferThread{ nullptr };
// The amount of buffering work currently represented by the _activeBufferQueue
std::atomic<size_t> _queuedBufferSize{ 0 };
// This contains a map of all textures to queues of pending transfer jobs. While in the transfer state, this map is used to
// populate the _activeBufferQueue up to the limit specified in GLVariableAllocationTexture::MAX_BUFFER_SIZE
TransferMap _pendingTransfersMap;
};
}} // namespace gpu::gl
using namespace gpu;
using namespace gpu::gl;
void GLBackend::initTextureManagementStage() {
_textureManagement._transferEngine = std::make_shared<GLTextureTransferEngineDefault>();
}
void GLBackend::killTextureManagementStage() {
_textureManagement._transferEngine->shutdown();
_textureManagement._transferEngine.reset();
}
std::vector<TexturePointer> GLTextureTransferEngine::getAllTextures() {
std::vector<TexturePointer> result;
result.reserve(_registeredTextures.size());
std::remove_if(_registeredTextures.begin(), _registeredTextures.end(), [&](const std::weak_ptr<Texture>& weak)->bool {
auto strong = weak.lock();
bool strongResult = strong.operator bool();
if (strongResult) {
result.push_back(strong);
}
return strongResult;
});
return result;
}
void GLTextureTransferEngine::addMemoryManagedTexture(const TexturePointer& texturePointer) {
++_frameTexturesCreated;
_registeredTextures.push_back(texturePointer);
}
void GLTextureTransferEngineDefault::shutdown() {
_shutdown = true;
#if THREADED_TEXTURE_BUFFERING
if (_transferThread) {
_transferThread->wait();
delete _transferThread;
_transferThread = nullptr;
}
#endif
}
void GLTextureTransferEngineDefault::manageMemory() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
// reset the count used to limit the number of textures created per frame
resetFrameTextureCreated();
// Determine the current memory management state. It will be either idle (no work to do),
// undersubscribed (need to do more allocation) or transfer (need to upload content from the
// backing store to the GPU
updateMemoryPressure();
if (MemoryPressureState::Undersubscribed == _memoryPressureState) {
// If we're undersubscribed, we need to process some of the textures that can have additional allocation
processPromotes();
} else if (MemoryPressureState::Transfer == _memoryPressureState) {
// If we're in transfer mode we need to manage the buffering and upload queues
processTransferQueues();
}
}
// Each frame we will check if our memory pressure state has changed.
void GLTextureTransferEngineDefault::updateMemoryPressure() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage();
if (0 == allowedMemoryAllocation) {
allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY;
}
// Clear any defunct textures (weak pointers that no longer have a valid texture)
auto strongTextures = getAllTextures();
size_t totalVariableMemoryAllocation = 0;
size_t idealMemoryAllocation = 0;
bool canDemote = false;
bool canPromote = false;
bool hasTransfers = false;
for (const auto& texture : strongTextures) {
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
vartexture->sanityCheck();
// Track how much the texture thinks it should be using
idealMemoryAllocation += texture->evalTotalSize();
// Track how much we're actually using
totalVariableMemoryAllocation += gltexture->size();
if (vartexture->canDemote()) {
canDemote |= true;
}
if (vartexture->canPromote()) {
canPromote |= true;
}
if (vartexture->hasPendingTransfers()) {
hasTransfers |= true;
}
}
Backend::textureResourceIdealGPUMemSize.set(idealMemoryAllocation);
size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation;
float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
// If we're oversubscribed we need to demote textures IMMEDIATELY
if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
auto overPressure = pressure - OVERSUBSCRIBED_PRESSURE_VALUE;
size_t relief = (size_t)(overPressure * totalVariableMemoryAllocation);
processDemotes(relief, strongTextures);
return;
}
auto newState = MemoryPressureState::Idle;
if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) {
newState = MemoryPressureState::Undersubscribed;
} else if (hasTransfers) {
newState = MemoryPressureState::Transfer;
} else {
Lock lock(_bufferMutex);
if (!_activeBufferQueue.empty() || !_activeTransferQueue.empty() || !_pendingTransfersMap.empty()) {
newState = MemoryPressureState::Transfer;
}
}
// If we've changed state then we have to populate the appropriate structure with the work to be done
if (newState != _memoryPressureState) {
_memoryPressureState = newState;
_promoteQueue = WorkQueue();
_pendingTransfersMap.clear();
if (MemoryPressureState::Idle == _memoryPressureState) {
return;
}
// For each texture, if it's eligible for work in the current state, put it into the appropriate structure
for (const auto& texture : strongTextures) {
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
if (MemoryPressureState::Undersubscribed == _memoryPressureState && vargltexture->canPromote()) {
// Promote smallest first
_promoteQueue.push({ texture, 1.0f / (float)gltexture->size() });
} else if (MemoryPressureState::Transfer == _memoryPressureState && vargltexture->hasPendingTransfers()) {
populateTransferQueue(texture);
}
}
}
}
// Manage the _activeBufferQueue and _activeTransferQueue queues
void GLTextureTransferEngineDefault::processTransferQueues() {
#if THREADED_TEXTURE_BUFFERING
if (!_transferThread) {
_transferThread = new TextureBufferThread(*this);
}
#endif
// From the pendingTransferMap, queue jobs into the _activeBufferQueue
// Doing so will lock the weak texture pointer so that it can't be destroyed
// while the background thread is working.
//
// This will queue jobs until _queuedBufferSize can't be increased without exceeding
// GLVariableAllocationTexture::MAX_BUFFER_SIZE or there is no more work to be done
populateActiveBufferQueue();
#if !THREADED_TEXTURE_BUFFERING
processActiveBufferQueue();
#endif
// Take any tasks which have completed buffering and process them, uploading the buffered
// data to the GPU. Drains the _activeTransferQueue
{
ActiveTransferQueue activeTransferQueue;
{
Lock lock(_bufferMutex);
activeTransferQueue.swap(_activeTransferQueue);
}
while (!activeTransferQueue.empty()) {
const auto& activeTransferJob = activeTransferQueue.front();
const auto& texturePointer = activeTransferJob.first;
const auto& tranferJob = activeTransferJob.second;
tranferJob->transfer(texturePointer);
// The pop_front MUST be the last call since all of these varaibles in scope are
// references that will be invalid after the pop
activeTransferQueue.pop_front();
}
}
// If we have no more work in any of the structures, reset the memory state to idle to
// force reconstruction of the _pendingTransfersMap if necessary
{
Lock lock(_bufferMutex);
if (_activeTransferQueue.empty() && _activeBufferQueue.empty() && _pendingTransfersMap.empty()) {
_memoryPressureState = MemoryPressureState::Idle;
}
}
}
void GLTextureTransferEngineDefault::populateActiveBufferQueue() {
size_t queuedBufferSize = _queuedBufferSize;
static const auto& MAX_BUFFER_SIZE = GLVariableAllocationSupport::MAX_BUFFER_SIZE;
Q_ASSERT(queuedBufferSize <= MAX_BUFFER_SIZE);
size_t availableBufferSize = MAX_BUFFER_SIZE - queuedBufferSize;
// Queue up buffering jobs
ActiveTransferQueue newBufferJobs;
size_t newTransferSize{ 0 };
for (auto itr = _pendingTransfersMap.begin(); itr != _pendingTransfersMap.end(); ) {
const auto& weakTexture = itr->first;
const auto texture = weakTexture.lock();
// Texture no longer exists, remove from the transfer map and move on
if (!texture) {
itr = _pendingTransfersMap.erase(itr);
continue;
}
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
auto& textureTransferQueue = itr->second;
// Can't find any pending transfers, so move on
if (textureTransferQueue.empty()) {
if (vargltexture->hasPendingTransfers()) {
qWarning(gpugllogging) << "Texture has no transfer jobs, but has pending transfers";
}
itr = _pendingTransfersMap.erase(itr);
continue;
}
const auto& transferJob = textureTransferQueue.front();
const auto& transferSize = transferJob->size();
// If there's not enough space for the buffering, then break out of the loop
if (transferSize > availableBufferSize) {
break;
}
availableBufferSize -= transferSize;
Q_ASSERT(availableBufferSize <= MAX_BUFFER_SIZE);
Q_ASSERT(newTransferSize <= MAX_BUFFER_SIZE);
newTransferSize += transferSize;
Q_ASSERT(newTransferSize <= MAX_BUFFER_SIZE);
newBufferJobs.emplace_back(texture, transferJob);
textureTransferQueue.pop();
++itr;
}
{
Lock lock(_bufferMutex);
_activeBufferQueue.splice(_activeBufferQueue.end(), newBufferJobs);
Q_ASSERT(_queuedBufferSize <= MAX_BUFFER_SIZE);
_queuedBufferSize += newTransferSize;
Q_ASSERT(_queuedBufferSize <= MAX_BUFFER_SIZE);
}
}
bool GLTextureTransferEngineDefault::processActiveBufferQueue() {
ActiveTransferQueue activeBufferQueue;
{
Lock lock(_bufferMutex);
_activeBufferQueue.swap(activeBufferQueue);
}
if (activeBufferQueue.empty()) {
return false;
}
for (const auto& activeJob : activeBufferQueue) {
const auto& texture = activeJob.first;
const auto& transferJob = activeJob.second;
// Some jobs don't require buffering, but they pass through this queue to ensure that we don't re-order
// the buffering & transfer operations. All jobs in the queue must be processed in order.
if (!transferJob->bufferingRequired()) {
continue;
}
const auto& transferSize = transferJob->size();
transferJob->buffer(texture);
Q_ASSERT(_queuedBufferSize >= transferSize);
_queuedBufferSize -= transferSize;
}
{
Lock lock(_bufferMutex);
_activeTransferQueue.splice(_activeTransferQueue.end(), activeBufferQueue);
}
return true;
}
void GLTextureTransferEngineDefault::populateTransferQueue(const TexturePointer& texturePointer) {
TextureWeakPointer weakTexture = texturePointer;
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texturePointer);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
TransferJob::Queue pendingTransfers;
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
vargltexture->populateTransferQueue(pendingTransfers);
if (!pendingTransfers.empty()) {
_pendingTransfersMap[weakTexture] = pendingTransfers;
}
}
// From the queue of textures to be promited
void GLTextureTransferEngineDefault::processPromotes() {
// FIXME use max allocated memory per frame instead of promotion count
static const size_t MAX_ALLOCATED_BYTES_PER_FRAME = GLVariableAllocationSupport::MAX_BUFFER_SIZE;
static const size_t MAX_ALLOCATIONS_PER_FRAME = 8;
size_t allocatedBytes{ 0 };
size_t allocations{ 0 };
while (!_promoteQueue.empty()) {
// Grab the first item off the demote queue
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
auto entry = _promoteQueue.top();
_promoteQueue.pop();
auto texture = entry.first.lock();
if (!texture) {
continue;
}
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
auto originalSize = gltexture->size();
vartexture->promote();
auto allocationDelta = gltexture->size() - originalSize;
if (vartexture->canPromote()) {
// Promote smallest first
_promoteQueue.push({ texture, 1.0f / (float)gltexture->size() });
}
allocatedBytes += allocationDelta;
if (++allocations >= MAX_ALLOCATIONS_PER_FRAME) {
break;
}
if (allocatedBytes >= MAX_ALLOCATED_BYTES_PER_FRAME) {
break;
}
}
// Get the front of the work queue to perform work
if (_promoteQueue.empty()) {
// Force rebuild of work queue
_memoryPressureState = MemoryPressureState::Idle;
}
}
void GLTextureTransferEngineDefault::processDemotes(size_t reliefRequired, const std::vector<TexturePointer>& strongTextures) {
// Demote largest first
ImmediateWorkQueue demoteQueue;
for (const auto& texture : strongTextures) {
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
if (vargltexture->canDemote()) {
demoteQueue.push({ texture, (float)gltexture->size() });
}
}
size_t relieved = 0;
while (!demoteQueue.empty() && relieved < reliefRequired) {
{
const auto& target = demoteQueue.top();
const auto& texture = target.first;
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
auto oldSize = gltexture->size();
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
vargltexture->demote();
auto newSize = gltexture->size();
relieved += (oldSize - newSize);
}
demoteQueue.pop();
}
}
// FIXME hack for stats display
QString getTextureMemoryPressureModeString() {
switch (_memoryPressureState) {
case MemoryPressureState::Undersubscribed:
return "Undersubscribed";
case MemoryPressureState::Transfer:
return "Transfer";
case MemoryPressureState::Idle:
return "Idle";
}
Q_UNREACHABLE();
return "Unknown";
}

View file

@ -114,9 +114,9 @@ public:
void allocateStorage(uint16 allocatedMip);
void syncSampler() const override;
void promote() override;
void demote() override;
void populateTransferQueue() override;
size_t promote() override;
size_t demote() override;
void populateTransferQueue(TransferQueue& pendingTransfers) override;
Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
Size copyMipsFromTexture();

View file

@ -72,7 +72,7 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
case TextureUsageType::RESOURCE:
qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str();
object = new GL41ResourceTexture(shared_from_this(), texture);
GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer);
_textureManagement._transferEngine->addMemoryManagedTexture(texturePointer);
break;
default:
@ -86,7 +86,6 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
auto minAvailableMip = texture.minAvailableMipLevel();
if (minAvailableMip < varTex->_minAllocatedMip) {
varTex->_minAllocatedMip = minAvailableMip;
GL41VariableAllocationTexture::_memoryPressureStateStale = true;
}
}
}
@ -299,9 +298,7 @@ GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr
uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);
allocateStorage(allocatedMip);
_memoryPressureStateStale = true;
copyMipsFromTexture();
syncSampler();
}
@ -341,8 +338,7 @@ Size GL41VariableAllocationTexture::copyMipsFromTexture() {
amount += copyMipFaceFromTexture(sourceMip, targetMip, face);
}
}
incrementPopulatedSize(amount);
return amount;
}
@ -351,7 +347,6 @@ Size GL41VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, ui
withPreservedTexture([&] {
amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer);
});
incrementPopulatedSize(amountCopied);
return amountCopied;
}
@ -496,7 +491,7 @@ void GL41VariableAllocationTexture::copyTextureMipsInGPUMem(GLuint srcId, GLuint
});
}
void GL41VariableAllocationTexture::promote() {
size_t GL41VariableAllocationTexture::promote() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Q_ASSERT(_allocatedMip > 0);
@ -524,12 +519,11 @@ void GL41VariableAllocationTexture::promote() {
// update the memory usage
Backend::textureResourceGPUMemSize.update(oldSize, 0);
return (_size - oldSize);
// no change to Backend::textureResourcePopulatedGPUMemSize
populateTransferQueue();
}
void GL41VariableAllocationTexture::demote() {
size_t GL41VariableAllocationTexture::demote() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Q_ASSERT(_allocatedMip < _maxAllocatedMip);
auto oldId = _id;
@ -563,16 +557,16 @@ void GL41VariableAllocationTexture::demote() {
}
decrementPopulatedSize(amountUnpopulated);
}
populateTransferQueue();
return oldSize - _size;
}
void GL41VariableAllocationTexture::populateTransferQueue() {
void GL41VariableAllocationTexture::populateTransferQueue(TransferQueue& pendingTransfers) {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
if (_populatedMip <= _allocatedMip) {
return;
}
_pendingTransfers = TransferQueue();
const uint8_t maxFace = GLTexture::getFaceCount(_target);
uint16_t sourceMip = _populatedMip;
@ -588,7 +582,7 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
// If the mip is less than the max transfer size, then just do it in one transfer
if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
// Can the mip be transferred in one go
_pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face));
continue;
}
@ -605,14 +599,16 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
uint32_t lineOffset = 0;
while (lineOffset < lines) {
uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
_pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face, linesToCopy, lineOffset));
lineOffset += linesToCopy;
}
}
// queue up the sampler and populated mip change for after the transfer has completed
_pendingTransfers.emplace(new TransferJob(*this, [=] {
pendingTransfers.emplace(new TransferJob([=] {
_populatedMip = sourceMip;
incrementPopulatedSize(_gpuObject.evalMipSize(sourceMip));
sanityCheck();
syncSampler();
}));
} while (sourceMip != _allocatedMip);

View file

@ -187,9 +187,9 @@ public:
GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
void syncSampler() const override;
void promote() override;
void demote() override;
void populateTransferQueue() override;
size_t promote() override;
size_t demote() override;
void populateTransferQueue(TransferQueue& pendingTransfers) override;
void allocateStorage(uint16 mip);

View file

@ -28,7 +28,6 @@ using namespace gpu;
using namespace gpu::gl;
using namespace gpu::gl45;
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define FORCE_STRICT_TEXTURE 0
#define ENABLE_SPARSE_TEXTURE 0
@ -82,7 +81,8 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
#if !FORCE_STRICT_TEXTURE
case TextureUsageType::RESOURCE: {
if (GL45VariableAllocationTexture::_frameTexturesCreated < MAX_RESOURCE_TEXTURES_PER_FRAME) {
auto& transferEngine = _textureManagement._transferEngine;
if (transferEngine->allowCreate()) {
#if ENABLE_SPARSE_TEXTURE
if (isTextureManagementSparseEnabled() && GL45Texture::isSparseEligible(texture)) {
object = new GL45SparseResourceTexture(shared_from_this(), texture);
@ -92,7 +92,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
#else
object = new GL45ResourceTexture(shared_from_this(), texture);
#endif
GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer);
transferEngine->addMemoryManagedTexture(texturePointer);
} else {
auto fallback = texturePointer->getFallbackTexture();
if (fallback) {
@ -114,7 +114,6 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
auto minAvailableMip = texture.minAvailableMipLevel();
if (minAvailableMip < varTex->_minAllocatedMip) {
varTex->_minAllocatedMip = minAvailableMip;
GL45VariableAllocationTexture::_memoryPressureStateStale = true;
}
}
}
@ -124,6 +123,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
}
void GL45Backend::initTextureManagementStage() {
GLBackend::initTextureManagementStage();
// enable the Sparse Texture on gl45
_textureManagement._sparseCapable = true;

View file

@ -31,7 +31,6 @@ using GL45Texture = GL45Backend::GL45Texture;
using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture;
GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture) {
++_frameTexturesCreated;
Backend::textureResourceCount.increment();
}
@ -55,7 +54,6 @@ const GL45Texture::Bindless& GL45VariableAllocationTexture::getBindless() const
Size GL45VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {
Size amountCopied = 0;
amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer);
incrementPopulatedSize(amountCopied);
return amountCopied;
}
@ -82,7 +80,6 @@ void GL45VariableAllocationTexture::copyTextureMipsInGPUMem(GLuint srcId, GLuint
copyTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips);
}
// Managed size resource textures
using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
@ -104,7 +101,6 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend
uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);
allocateStorage(allocatedMip);
_memoryPressureStateStale = true;
copyMipsFromTexture();
syncSampler();
}
@ -134,6 +130,7 @@ Size GL45ResourceTexture::copyMipsFromTexture() {
amount += copyMipFaceFromTexture(sourceMip, targetMip, face);
}
}
incrementPopulatedSize(amount);
return amount;
}
@ -141,14 +138,14 @@ void GL45ResourceTexture::syncSampler() const {
Parent::syncSampler();
#if GPU_BINDLESS_TEXTURES
if (!isBindless()) {
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
}
#else
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
#endif
}
void GL45ResourceTexture::promote() {
size_t GL45ResourceTexture::promote() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Q_ASSERT(_allocatedMip > 0);
@ -171,7 +168,7 @@ void GL45ResourceTexture::promote() {
// allocate storage for new level
allocateStorage(targetAllocatedMip);
// copy pre-existing mips
copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip);
@ -191,11 +188,10 @@ void GL45ResourceTexture::promote() {
// update the memory usage
Backend::textureResourceGPUMemSize.update(oldSize, 0);
// no change to Backend::textureResourcePopulatedGPUMemSize
populateTransferQueue();
return (_size - oldSize);
}
void GL45ResourceTexture::demote() {
size_t GL45ResourceTexture::demote() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Q_ASSERT(_allocatedMip < _maxAllocatedMip);
auto oldId = _id;
@ -233,25 +229,25 @@ void GL45ResourceTexture::demote() {
// update the memory usage
Backend::textureResourceGPUMemSize.update(oldSize, 0);
// Demoting unpopulate the memory delta
// Demoting unpopulate the memory delta
if (oldPopulatedMip != _populatedMip) {
auto numPopulatedDemoted = _populatedMip - oldPopulatedMip;
Size amountUnpopulated = 0;
for (int i = 0; i < numPopulatedDemoted; i++) {
amountUnpopulated += _gpuObject.evalMipSize(oldPopulatedMip + i);
amountUnpopulated += _gpuObject.evalMipSize(oldPopulatedMip + i);
}
decrementPopulatedSize(amountUnpopulated);
decrementPopulatedSize(amountUnpopulated);
}
populateTransferQueue();
return (oldSize - _size);
}
void GL45ResourceTexture::populateTransferQueue() {
void GL45ResourceTexture::populateTransferQueue(TransferQueue& pendingTransfers) {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
sanityCheck();
if (_populatedMip <= _allocatedMip) {
return;
}
_pendingTransfers = TransferQueue();
const uint8_t maxFace = GLTexture::getFaceCount(_target);
uint16_t sourceMip = _populatedMip;
@ -267,16 +263,16 @@ void GL45ResourceTexture::populateTransferQueue() {
// If the mip is less than the max transfer size, then just do it in one transfer
if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
// Can the mip be transferred in one go
_pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face));
continue;
}
// break down the transfers into chunks so that no single transfer is
// break down the transfers into chunks so that no single transfer is
// consuming more than X bandwidth
// For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block
auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face);
const auto lines = mipDimensions.y;
const uint32_t BLOCK_NUM_LINES { 4 };
const uint32_t BLOCK_NUM_LINES{ 4 };
const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES;
auto bytesPerBlock = mipSize / numBlocks;
Q_ASSERT(0 == (mipSize % lines));
@ -284,14 +280,16 @@ void GL45ResourceTexture::populateTransferQueue() {
uint32_t lineOffset = 0;
while (lineOffset < lines) {
uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
_pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face, linesToCopy, lineOffset));
lineOffset += linesToCopy;
}
}
// queue up the sampler and populated mip change for after the transfer has completed
_pendingTransfers.emplace(new TransferJob(*this, [=] {
pendingTransfers.emplace(new TransferJob([=] {
_populatedMip = sourceMip;
incrementPopulatedSize(_gpuObject.evalMipSize(sourceMip));
sanityCheck();
syncSampler();
}));
} while (sourceMip != _allocatedMip);

View file

@ -105,9 +105,9 @@ public:
void allocateStorage(uint16 allocatedMip);
void syncSampler() const override;
void promote() override;
void demote() override;
void populateTransferQueue() override;
size_t promote() override;
size_t demote() override;
void populateTransferQueue(TransferJob::Queue& queue) override;
Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
Size copyMipsFromTexture();

View file

@ -90,7 +90,6 @@ GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texturePointer) {
auto minAvailableMip = texture.minAvailableMipLevel();
if (minAvailableMip < varTex->_minAllocatedMip) {
varTex->_minAllocatedMip = minAvailableMip;
GLESVariableAllocationTexture::_memoryPressureStateStale = true;
}
}
}
@ -361,7 +360,6 @@ GLESVariableAllocationTexture::GLESVariableAllocationTexture(const std::weak_ptr
uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);
allocateStorage(allocatedMip);
_memoryPressureStateStale = true;
copyMipsFromTexture();
syncSampler();
@ -403,8 +401,7 @@ Size GLESVariableAllocationTexture::copyMipsFromTexture() {
amount += copyMipFaceFromTexture(sourceMip, targetMip, face);
}
}
incrementPopulatedSize(amount);
return amount;
}
@ -413,7 +410,6 @@ Size GLESVariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, ui
withPreservedTexture([&] {
amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer);
});
incrementPopulatedSize(amountCopied);
return amountCopied;
}
@ -559,7 +555,7 @@ void GLESVariableAllocationTexture::copyTextureMipsInGPUMem(GLuint srcId, GLuint
});
}
void GLESVariableAllocationTexture::promote() {
size_t GLESVariableAllocationTexture::promote() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Q_ASSERT(_allocatedMip > 0);
@ -587,12 +583,11 @@ void GLESVariableAllocationTexture::promote() {
// update the memory usage
Backend::textureResourceGPUMemSize.update(oldSize, 0);
// no change to Backend::textureResourcePopulatedGPUMemSize
populateTransferQueue();
return _size - oldSize;
}
void GLESVariableAllocationTexture::demote() {
size_t GLESVariableAllocationTexture::demote() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Q_ASSERT(_allocatedMip < _maxAllocatedMip);
auto oldId = _id;
@ -626,16 +621,16 @@ void GLESVariableAllocationTexture::demote() {
}
decrementPopulatedSize(amountUnpopulated);
}
populateTransferQueue();
return oldSize - _size;
}
void GLESVariableAllocationTexture::populateTransferQueue() {
void GLESVariableAllocationTexture::populateTransferQueue(TransferJob::Queue& queue) {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
if (_populatedMip <= _allocatedMip) {
return;
}
_pendingTransfers = TransferQueue();
const uint8_t maxFace = GLTexture::getFaceCount(_target);
uint16_t sourceMip = _populatedMip;
@ -651,7 +646,7 @@ void GLESVariableAllocationTexture::populateTransferQueue() {
// If the mip is less than the max transfer size, then just do it in one transfer
if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
// Can the mip be transferred in one go
_pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
queue.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face));
continue;
}
@ -668,14 +663,16 @@ void GLESVariableAllocationTexture::populateTransferQueue() {
uint32_t lineOffset = 0;
while (lineOffset < lines) {
uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
_pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
queue.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face, linesToCopy, lineOffset));
lineOffset += linesToCopy;
}
}
// queue up the sampler and populated mip change for after the transfer has completed
_pendingTransfers.emplace(new TransferJob(*this, [=] {
queue.emplace(new TransferJob([=] {
_populatedMip = sourceMip;
incrementPopulatedSize(_gpuObject.evalMipSize(sourceMip));
sanityCheck();
syncSampler();
}));
} while (sourceMip != _allocatedMip);

View file

@ -270,6 +270,7 @@ ContextMetricCount Backend::texturePendingGPUTransferCount;
ContextMetricSize Backend::texturePendingGPUTransferMemSize;
ContextMetricSize Backend::textureResourcePopulatedGPUMemSize;
ContextMetricSize Backend::textureResourceIdealGPUMemSize;
Size Context::getFreeGPUMemSize() {
return Backend::freeGPUMemSize.getValue();
@ -329,3 +330,7 @@ Size Context::getTexturePendingGPUTransferMemSize() {
Size Context::getTextureResourcePopulatedGPUMemSize() {
return Backend::textureResourcePopulatedGPUMemSize.getValue();
}
Size Context::getTextureResourceIdealGPUMemSize() {
return Backend::textureResourceIdealGPUMemSize.getValue();
}

View file

@ -113,6 +113,7 @@ public:
static ContextMetricCount texturePendingGPUTransferCount;
static ContextMetricSize texturePendingGPUTransferMemSize;
static ContextMetricSize textureResourcePopulatedGPUMemSize;
static ContextMetricSize textureResourceIdealGPUMemSize;
protected:
@ -243,6 +244,7 @@ public:
static Size getTexturePendingGPUTransferMemSize();
static Size getTextureResourcePopulatedGPUMemSize();
static Size getTextureResourceIdealGPUMemSize();
protected:
Context(const Context& context);

View file

@ -36,6 +36,30 @@ namespace scriptable {
using ModelProviderPointer = std::shared_ptr<scriptable::ModelProvider>;
using WeakModelProviderPointer = std::weak_ptr<scriptable::ModelProvider>;
/**jsdoc
* @typedef {object} Graphics.Material
* @property {string} name
* @property {string} model
* @property {number} opacity
* @property {number} roughness
* @property {number} metallic
* @property {number} scattering
* @property {boolean} unlit
* @propety {Vec3} emissive
* @propety {Vec3} albedo
* @property {string} emissiveMap
* @property {string} albedoMap
* @property {string} opacityMap
* @property {string} metallicMap
* @property {string} specularMap
* @property {string} roughnessMap
* @property {string} glossMap
* @property {string} normalMap
* @property {string} bumpMap
* @property {string} occlusionMap
* @property {string} lightmapMap
* @property {string} scatteringMap
*/
class ScriptableMaterial {
public:
ScriptableMaterial() {}
@ -68,7 +92,7 @@ namespace scriptable {
/**jsdoc
* @typedef {object} Graphics.MaterialLayer
* @property {Material} material - This layer's material.
* @property {Graphics.Material} material - This layer's material.
* @property {number} priority - The priority of this layer. If multiple materials are applied to a mesh part, only the highest priority layer is used.
*/
class ScriptableMaterialLayer {

View file

@ -166,6 +166,17 @@ bool GraphicsScriptingInterface::updateMeshPart(scriptable::ScriptableMeshPointe
scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVariantMap& ifsMeshData) {
// TODO: this is bare-bones way for now to improvise a new mesh from the scripting side
// in the future we want to support a formal C++ structure data type here instead
/**jsdoc
* @typedef {object} Graphics.IFSData
* @property {string} [name=""] - mesh name (useful for debugging / debug prints).
* @property {string} [topology=""]
* @property {number[]} indices - vertex indices to use for the mesh faces.
* @property {Vec3[]} vertices - vertex positions (model space)
* @property {Vec3[]} [normals=[]] - vertex normals (normalized)
* @property {Vec3[]} [colors=[]] - vertex colors (normalized)
* @property {Vec2[]} [texCoords0=[]] - vertex texture coordinates (normalized)
*/
QString meshName = ifsMeshData.value("name").toString();
QString topologyName = ifsMeshData.value("topology").toString();
QVector<glm::uint32> indices = buffer_helpers::variantToVector<glm::uint32>(ifsMeshData.value("indices"));

View file

@ -46,10 +46,28 @@ public slots:
*/
scriptable::ScriptableModelPointer getModel(QUuid uuid);
/**jsdoc
* @function Graphics.updateModel
* @param {Uuid} id
* @param {Graphics.Model} model
* @returns {boolean}
*/
bool updateModel(QUuid uuid, const scriptable::ScriptableModelPointer& model);
/**jsdoc
* @function Graphics.canUpdateModel
* @param {Uuid} id
* @param {number} [meshIndex=-1]
* @param {number} [partNumber=-1]
* @returns {boolean}
*/
bool canUpdateModel(QUuid uuid, int meshIndex = -1, int partNumber = -1);
/**jsdoc
* @function Graphics.newModel
* @param {Graphics.Mesh[]} meshes
* @returns {Graphics.Model}
*/
scriptable::ScriptableModelPointer newModel(const scriptable::ScriptableMeshes& meshes);
/**jsdoc
@ -59,15 +77,6 @@ public slots:
* @param {Graphics.IFSData} ifsMeshData Index-Faced Set (IFS) arrays used to create the new mesh.
* @returns {Graphics.Mesh} the resulting Mesh / Mesh Part object
*/
/**jsdoc
* @typedef {object} Graphics.IFSData
* @property {string} [name] - mesh name (useful for debugging / debug prints).
* @property {number[]} indices - vertex indices to use for the mesh faces.
* @property {Vec3[]} vertices - vertex positions (model space)
* @property {Vec3[]} [normals] - vertex normals (normalized)
* @property {Vec3[]} [colors] - vertex colors (normalized)
* @property {Vec2[]} [texCoords0] - vertex texture coordinates (normalized)
*/
scriptable::ScriptableMeshPointer newMesh(const QVariantMap& ifsMeshData);
#ifdef SCRIPTABLE_MESH_TODO
@ -77,6 +86,11 @@ public slots:
bool updateMeshPart(scriptable::ScriptableMeshPointer mesh, scriptable::ScriptableMeshPartPointer part);
#endif
/**jsdoc
* @function Graphics.exportModelToOBJ
* @param {Graphics.Model} model
* @returns {string}
*/
QString exportModelToOBJ(const scriptable::ScriptableModel& in);
private:

View file

@ -36,6 +36,10 @@ namespace scriptable {
* @property {number} numIndices - Total number of vertex indices in the mesh.
* @property {number} numVertices - Total number of vertices in the Mesh.
* @property {number} numAttributes - Number of currently defined vertex attributes.
* @property {boolean} valid
* @property {boolean} strong
* @property {object} extents
* @property {object} bufferFormats
*/
class ScriptableMesh : public ScriptableMeshBase, QScriptable {
Q_OBJECT

View file

@ -12,7 +12,11 @@
namespace scriptable {
/**jsdoc
* @typedef {object} Graphics.MeshPart
* @property {boolean} valid
* @property {number} partIndex - The part index (within the containing Mesh).
* @property {number} firstVertexIndex
* @property {number} baseVertexIndex
* @property {number} lastVertexIndex
* @property {Graphics.Topology} topology - element interpretation (currently only 'triangles' is supported).
* @property {string[]} attributeNames - Vertex attribute names (color, normal, etc.)
* @property {number} numIndices - Number of vertex indices that this mesh part refers to.
@ -20,6 +24,8 @@ namespace scriptable {
* @property {number} numFaces - Number of faces represented by the mesh part (numIndices / numVerticesPerFace).
* @property {number} numVertices - Total number of vertices in the containing Mesh.
* @property {number} numAttributes - Number of currently defined vertex attributes.
* @property {object} extents
* @property {object} bufferFormats
*/
class ScriptableMeshPart : public QObject, QScriptable {

View file

@ -21,7 +21,7 @@ namespace scriptable {
* @property {Uuid} objectID - UUID of corresponding inworld object (if model is associated)
* @property {number} numMeshes - The number of submeshes contained in the model.
* @property {Graphics.Mesh[]} meshes - Array of submesh references.
* @property {Object.<string, Graphics.MaterialLayer[]>} materialLayers - Map of materials layer lists. You can look up a material layer list by mesh part number or by material name.
* @property {Object.<string,Graphics.MaterialLayer[]>} materialLayers - Map of materials layer lists. You can look up a material layer list by mesh part number or by material name.
* @property {string[]} materialNames - Array of all the material names used by the mesh parts of this model, in order (e.g. materialNames[0] is the name of the first mesh part's material).
*/

View file

@ -76,6 +76,23 @@ public:
// Access vertex position value
const Vec3& getPos(Index index) const { return _vertexBuffer.get<Vec3>(index); }
/**jsdoc
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>Points.</td></tr>
* <tr><td><code>1</code></td><td>Lines.</td></tr>
* <tr><td><code>2</code></td><td>Line strip.</td></tr>
* <tr><td><code>3</code></td><td>Triangles.</td></tr>
* <tr><td><code>4</code></td><td>Triangle strip.</td></tr>
* <tr><td><code>5</code></td><td>Quads.</td></tr>
* <tr><td><code>6</code></td><td>Quad strip.</td></tr>
* </tbody>
* </table>
* @typedef {number} Graphics.Topology
*/
enum Topology {
POINTS = 0,
LINES,

View file

@ -187,6 +187,13 @@ void TouchscreenVirtualPadDevice::InputDevice::update(float deltaTime, const con
_axisStateMap.clear();
}
bool TouchscreenVirtualPadDevice::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
auto& virtualPadManager = VirtualPad::Manager::instance();
virtualPadManager.requestHapticFeedback((int) duration);
return true;
}
void TouchscreenVirtualPadDevice::InputDevice::focusOutEvent() {
}

View file

@ -63,6 +63,8 @@ protected:
// Device functions
virtual controller::Input::NamedVector getAvailableInputs() const override;
virtual QString getDefaultMappingConfig() const override;
virtual bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override;
virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
virtual void focusOutEvent() override;

View file

@ -20,6 +20,13 @@
#include <vector>
#include <string>
/**jsdoc
* @namespace Midi
*
* @hifi-interface
* @hifi-client-entity
*/
class Midi : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -46,57 +53,135 @@ signals:
void midiReset();
public slots:
// Send Raw Midi Packet to all connected devices
/**jsdoc
* Send Raw MIDI packet to a particular device.
* @function Midi.sendRawDword
* @param {number} device - Integer device number.
* @param {number} raw - Integer (DWORD) raw MIDI message.
*/
Q_INVOKABLE void sendRawDword(int device, int raw);
/// Send Raw Midi message to selected device
/// @param {int} device: device number
/// @param {int} raw: raw midi message (DWORD)
// Send Midi Message to all connected devices
/**jsdoc
* Send MIDI message to a particular device.
* @function Midi.sendMidiMessage
* @param {number} device - Integer device number.
* @param {number} channel - Integer channel number.
* @param {number} type - 0x8 is note off, 0x9 is note on (if velocity=0, note off), etc.
* @param {number} note - MIDI note number.
* @param {number} velocity - Note velocity (0 means note off).
*/
Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity);
/// Send midi message to selected device/devices
/// @param {int} device: device number
/// @param {int} channel: channel number
/// @param {int} type: 0x8 is noteoff, 0x9 is noteon (if velocity=0, noteoff), etc
/// @param {int} note: midi note number
/// @param {int} velocity: note velocity (0 means noteoff)
// Send Midi Message to all connected devices
/**jsdoc
* Play a note on all connected devices.
* @function Midi.playMidiNote
* @param {number} status - 0x80 is note off, 0x90 is note on (if velocity=0, note off), etc.
* @param {number} note - MIDI note number.
* @param {number} velocity - Note velocity (0 means note off).
*/
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
/// play a note on all connected devices
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
/// @param {int} note: midi note number
/// @param {int} velocity: note velocity (0 means noteoff)
/// turn off all notes on all connected devices
/**jsdoc
* Turn off all notes on all connected devices.
* @function Midi.allNotesOff
*/
Q_INVOKABLE void allNotesOff();
/// clean up and re-discover attached devices
/**jsdoc
* Clean up and re-discover attached devices.
* @function Midi.resetDevices
*/
Q_INVOKABLE void resetDevices();
/// ask for a list of inputs/outputs
/**jsdoc
* Get a list of inputs/outputs.
* @function Midi.listMidiDevices
* @param {boolean} output
* @returns {string[]}
*/
Q_INVOKABLE QStringList listMidiDevices(bool output);
/// block an input/output by name
/**jsdoc
* Block an input/output by name.
* @function Midi.blockMidiDevice
* @param {string} name
* @param {boolean} output
*/
Q_INVOKABLE void blockMidiDevice(QString name, bool output);
/// unblock an input/output by name
/**jsdoc
* Unblock an input/output by name.
* @function Midi.unblockMidiDevice
* @param {string} name
* @param {boolean} output
*/
Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
/// repeat all incoming notes to all outputs (default disabled)
/**jsdoc
* Repeat all incoming notes to all outputs (default disabled).
* @function Midi.thruModeEnable
* @param {boolean} enable
*/
Q_INVOKABLE void thruModeEnable(bool enable);
/// broadcast on all unblocked devices
/**jsdoc
* Broadcast on all unblocked devices.
* @function Midi.broadcastEnable
* @param {boolean} enable
*/
Q_INVOKABLE void broadcastEnable(bool enable);
/// filter by event types
/**jsdoc
* @function Midi.typeNoteOffEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typeNoteOffEnable(bool enable);
/**jsdoc
* @function Midi.typeNoteOnEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typeNoteOnEnable(bool enable);
/**jsdoc
* @function Midi.typePolyKeyPressureEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typePolyKeyPressureEnable(bool enable);
/**jsdoc
* @function Midi.typeControlChangeEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typeControlChangeEnable(bool enable);
/**jsdoc
* @function Midi.typeProgramChangeEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typeProgramChangeEnable(bool enable);
/**jsdoc
* @function Midi.typeChanPressureEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typeChanPressureEnable(bool enable);
/**jsdoc
* @function Midi.typePitchBendEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typePitchBendEnable(bool enable);
/**jsdoc
* @function Midi.typeSystemMessageEnable
* @param {boolean} enable
*/
Q_INVOKABLE void typeSystemMessageEnable(bool enable);

View file

@ -32,6 +32,11 @@ const QString GET_PLACE = "/api/v1/places/%1";
/**jsdoc
* The location API provides facilities related to your current location in the metaverse.
*
* <h5>Getter/Setter</h5>
* <p>You can get and set your current metaverse address by directly reading a string value from and writing a string value to
* the <code>location</code> object. This is an alternative to using the <code>location.href</code> property or this object's
* functions.</p>
*
* @namespace location
*
* @hifi-interface

View file

@ -86,7 +86,6 @@ private:
/// Wrapper to expose resources to JS/QML
class ScriptableResource : public QObject {
/**jsdoc
* @constructor Resource
*
@ -98,12 +97,15 @@ class ScriptableResource : public QObject {
* @property {string} url - URL of this resource.
* @property {Resource.State} state - Current loading state.
*/
/**jsdoc
* @namespace Resource
* @variation 0
* @property {Resource.State} State
*/
Q_OBJECT
Q_PROPERTY(QUrl url READ getURL)
Q_PROPERTY(int state READ getState NOTIFY stateChanged)
public:
/**jsdoc
@ -115,7 +117,6 @@ public:
* @property {number} FINISHED - The resource has completely finished loading and is ready.
* @property {number} FAILED - Downloading the resource has failed.
*/
enum State {
QUEUED,
LOADING,

View file

@ -17,11 +17,30 @@
#include <DependencyManager.h>
/**jsdoc
* @namespace Resources
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*/
class ResourceScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
/**jsdoc
* @function Resources.overrideUrlPrefix
* @param {string} prefix
* @param {string} replacement
*/
Q_INVOKABLE void overrideUrlPrefix(const QString& prefix, const QString& replacement);
/**jsdoc
* @function Resources.restoreUrlPrefix
* @param {string} prefix
*/
Q_INVOKABLE void restoreUrlPrefix(const QString& prefix) {
overrideUrlPrefix(prefix, "");
}

View file

@ -29,13 +29,34 @@ public:
void addPacketStatsAndSendStatsPacket(QJsonObject statsObject);
public slots:
// JSDoc: Overridden in Agent.h.
/// threaded run of assignment
virtual void run() = 0;
/**jsdoc
* @function Agent.stop
* @deprecated This function is being removed from the API.
*/
Q_INVOKABLE virtual void stop() { setFinished(true); }
/**jsdoc
* @function Agent.sendStatsPacket
* @deprecated This function is being removed from the API.
*/
virtual void sendStatsPacket();
/**jsdoc
* @function Agent.clearQueuedCheckIns
* @deprecated This function is being removed from the API.
*/
void clearQueuedCheckIns() { _numQueuedCheckIns = 0; }
signals:
/**jsdoc
* @function Agent.finished
* @returns {Signal}
* @deprecated This function is being removed from the API.
*/
void finished();
protected:
@ -47,6 +68,10 @@ protected:
int _numQueuedCheckIns { 0 };
protected slots:
/**jsdoc
* @function Agent.domainSettingsRequestFailed
* @deprecated This function is being removed from the API.
*/
void domainSettingsRequestFailed();
private slots:

View file

@ -40,6 +40,15 @@ public:
virtual int getSteamVRBuildID() = 0;
};
/**jsdoc
* @namespace Steam
*
* @hifi-interface
* @hifi-client-entity
*
* @property {boolean} running - <em>Read-only.</em>
*/
class SteamScriptingInterface : public QObject {
Q_OBJECT
@ -49,7 +58,16 @@ public:
SteamScriptingInterface(QObject* parent, SteamClientPlugin* plugin) : QObject(parent), _plugin(plugin) {}
public slots:
/**jsdoc
* @function Steam.isRunning
* @returns {boolean}
*/
bool isRunning() const { return _plugin && _plugin->isRunning(); }
/**jsdoc
* @function Steam.openInviteOverlay
*/
void openInviteOverlay() const { if (_plugin) { _plugin->openInviteOverlay(); } }
private:

View file

@ -135,15 +135,29 @@ public:
PickQuery(const PickFilter& filter, const float maxDistance, const bool enabled);
/**jsdoc
* @namespace
* @augments Picks
*
* Enum for different types of Picks and Pointers.
*
* @typedef {enum} Picks.PickType
* @namespace PickType
* @variation 0
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} Ray Ray Picks intersect a ray with the nearest object in front of them, along a given direction.
* @property {number} Stylus Stylus Picks provide "tapping" functionality on/into flat surfaces.
*/
/**jsdoc
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>{@link PickType(0)|PickType.Ray}</code></td><td></td></tr>
* <tr><td><code>{@link PickType(0)|PickType.Stylus}</code></td><td></td></tr>
* </tbody>
* </table>
* @typedef {number} PickType
*/
enum PickType {
Ray = 0,
Stylus,

View file

@ -486,16 +486,14 @@ JitterSample::SampleSequence::SampleSequence(){
}
void JitterSample::configure(const Config& config) {
_freeze = config.freeze;
if (config.stop || _freeze) {
_freeze = config.stop || config.freeze;
if (config.freeze) {
auto pausedIndex = config.getIndex();
if (_sampleSequence.currentIndex != pausedIndex) {
_sampleSequence.currentIndex = pausedIndex;
}
} else {
if (_sampleSequence.currentIndex < 0) {
_sampleSequence.currentIndex = config.getIndex();
}
} else if (config.stop) {
_sampleSequence.currentIndex = -1;
}
_scale = config.scale;
}
@ -509,7 +507,12 @@ void JitterSample::run(const render::RenderContextPointer& renderContext, Output
current = -1;
}
}
jitter = _sampleSequence.offsets[(current < 0 ? SEQUENCE_LENGTH : current)];
jitter.x = 0.0f;
jitter.y = 0.0f;
if (current >= 0) {
jitter = _sampleSequence.offsets[current];
}
}

View file

@ -62,7 +62,7 @@ public:
};
using Config = JitterSampleConfig;
using Output = glm::vec2;
using Output = glm::vec2;
using JobModel = render::Job::ModelO<JitterSample, Output, Config>;
void configure(const Config& config);
@ -95,7 +95,7 @@ class AntialiasingConfig : public render::Job::Config {
Q_PROPERTY(bool debug MEMBER debug NOTIFY dirty)
Q_PROPERTY(float debugX MEMBER debugX NOTIFY dirty)
Q_PROPERTY(float debugFXAAX MEMBER debugFXAAX NOTIFY dirty)
Q_PROPERTY(bool fxaaOnOff READ debugFXAA WRITE setDebugFXAA NOTIFY dirty)
Q_PROPERTY(float debugShowVelocityThreshold MEMBER debugShowVelocityThreshold NOTIFY dirty)
Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty)
Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty)
@ -106,6 +106,10 @@ class AntialiasingConfig : public render::Job::Config {
public:
AntialiasingConfig() : render::Job::Config(true) {}
void setDebugFXAA(bool debug) { debugFXAAX = (debug ? 0.0f : 1.0f); emit dirty();}
bool debugFXAA() const { return (debugFXAAX == 0.0f ? true : false); }
float blend{ 0.25f };
float sharpen{ 0.05f };

Some files were not shown because too many files have changed in this diff Show more