mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 16:33:33 +02:00
fixed merge
This commit is contained in:
commit
73dd5c7f36
266 changed files with 5429 additions and 14875 deletions
.eslintrc.js
android/app
build.gradle
src/main
AndroidManifest.xml
assets
cpp
java
io/highfidelity/hifiinterface
GotoActivity.javaHifiUtils.javaHomeActivity.javaInterfaceActivity.javaMainActivity.javaPermissionChecker.java
QtPreloader
SplashActivity.javafragment
provider
task
view
org/qtproject/qt5/android/bindings
res
drawable
default_profile_avatar.xmldomain_bg.xmlhifi_header.xmlhifi_logo_header.xmlhifi_logo_splash.xmlic_clear.xmlic_search.xmlrounded_button.xmlrounded_edit.xml
font
layout
activity_goto.xmlactivity_home.xmlactivity_main.xmlactivity_splash.xmlcontent_home.xmldomain_view.xmlfragment_home.xmlfragment_login.xmlfragment_policy.xmlnavigation_header.xml
menu
values
assignment-client/src
interface
resources/qml
src
libraries
avatars-renderer/src/avatars-renderer
display-plugins/src/display-plugins
entities-renderer/src
gpu-gl-common/src/gpu/gl
gpu-gl/src/gpu
gl41
gl45
gpu-gles/src/gpu/gles
gpu/src/gpu
graphics/src/graphics
71
.eslintrc.js
71
.eslintrc.js
|
@ -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": ["/"]}}]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -104,6 +104,15 @@ dependencies {
|
|||
implementation 'com.android.support:design:26.1.0'
|
||||
implementation 'com.android.support:appcompat-v7:26.1.0'
|
||||
compile 'com.android.support:recyclerview-v7:26.1.0'
|
||||
compile 'com.android.support:cardview-v7:26.1.0'
|
||||
|
||||
compile 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||
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')
|
||||
}
|
||||
|
|
|
@ -39,21 +39,14 @@
|
|||
</activity>
|
||||
-->
|
||||
<activity
|
||||
android:name=".HomeActivity"
|
||||
android:label="@string/home"
|
||||
android:name=".MainActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".GotoActivity"
|
||||
android:label="@string/go_to"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar">
|
||||
</activity>
|
||||
<activity
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name=".InterfaceActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape"
|
||||
android:launchMode="singleTop"
|
||||
>
|
||||
|
||||
|
@ -75,6 +68,11 @@
|
|||
<meta-data
|
||||
android:name="preloaded_fonts"
|
||||
android:resource="@array/preloaded_fonts" />
|
||||
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar" />
|
||||
</application>
|
||||
|
||||
<uses-feature android:name="android.software.vr.mode" android:required="true"/>
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"hifi_domains" : [
|
||||
{
|
||||
"name": "Your last location",
|
||||
"url": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"name": "dev-mobile-master",
|
||||
"url": "hifi://dev-mobile-master",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"name": "dev-mobile",
|
||||
"url": "hifi://dev-mobile",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"name": "dev-welcome",
|
||||
"url": "hifi://dev-welcome",
|
||||
"thumbnail": ""
|
||||
}
|
||||
]
|
||||
}
|
121
android/app/src/main/assets/privacy_policy.html
Normal file
121
android/app/src/main/assets/privacy_policy.html
Normal 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>
|
|
@ -21,9 +21,12 @@
|
|||
|
||||
#include <AddressManager.h>
|
||||
#include "AndroidHelper.h"
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
QAndroidJniObject __activity;
|
||||
|
||||
QAndroidJniObject __interfaceActivity;
|
||||
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();
|
||||
|
@ -142,16 +145,22 @@ void unpackAndroidAssets() {
|
|||
extern "C" {
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
|
||||
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
|
||||
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
|
||||
__activity = QAndroidJniObject(instance);
|
||||
qRegisterMetaType<QAndroidJniObject>("QAndroidJniObject");
|
||||
__interfaceActivity = QAndroidJniObject(instance);
|
||||
auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler);
|
||||
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);
|
||||
__activity.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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -167,19 +176,12 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr
|
|||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
|
||||
qDebug() << "nativeOnPause";
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) {
|
||||
qDebug() << "nativeOnResume";
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
|
||||
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) {
|
||||
AndroidHelper::instance().goBackFromAndroidActivity();
|
||||
}
|
||||
|
||||
// HifiUtils
|
||||
|
@ -204,4 +206,86 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurren
|
|||
return env->NewStringUTF(str.toLatin1().data());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_protocolVersionSignature(JNIEnv *env, jobject instance) {
|
||||
return env->NewStringUTF(protocolVersionsSignatureBase64().toLatin1().data());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
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);
|
||||
QString password = QString(c_password);
|
||||
env->ReleaseStringUTFChars(username_, c_username);
|
||||
env->ReleaseStringUTFChars(password_, c_password);
|
||||
|
||||
auto accountManager = AndroidHelper::instance().getAccountManager();
|
||||
|
||||
__loginCompletedListener = QAndroidJniObject(instance);
|
||||
__usernameChangedListener = QAndroidJniObject(usernameChangedListener);
|
||||
|
||||
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
|
||||
jboolean jSuccess = (jboolean) true;
|
||||
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
|
||||
});
|
||||
|
||||
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
|
||||
jboolean jSuccess = (jboolean) false;
|
||||
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
|
||||
});
|
||||
|
||||
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
|
||||
Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env,
|
||||
jobject instance) {
|
||||
|
||||
__loadCompleteListener = QAndroidJniObject(instance);
|
||||
|
||||
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() {
|
||||
|
||||
__loadCompleteListener.callMethod<void>("onAppLoadedComplete", "()V");
|
||||
__interfaceActivity.callMethod<void>("onAppLoadedComplete", "()V");
|
||||
|
||||
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr,
|
||||
nullptr);
|
||||
});
|
||||
|
||||
}
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_highfidelity_hifiinterface_MainActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
|
||||
return AndroidHelper::instance().getAccountManager()->isLoggedIn();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_MainActivity_nativeLogout(JNIEnv *env, jobject instance) {
|
||||
AndroidHelper::instance().getAccountManager()->logout();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,94 +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.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
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()) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(urlString);
|
||||
} catch (URISyntaxException e) {
|
||||
return;
|
||||
}
|
||||
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
|
||||
urlString = "hifi://" + 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);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,16 @@
|
|||
package io.highfidelity.hifiinterface;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* Created by Gabriel Calero & Cristian Duarte on 4/13/18.
|
||||
*/
|
||||
|
||||
public class HifiUtils {
|
||||
|
||||
public static final String METAVERSE_BASE_URL = "https://metaverse.highfidelity.com";
|
||||
|
||||
private static HifiUtils instance;
|
||||
|
||||
private HifiUtils() {
|
||||
|
@ -18,6 +23,45 @@ public class HifiUtils {
|
|||
return instance;
|
||||
}
|
||||
|
||||
public String sanitizeHifiUrl(String urlString) {
|
||||
urlString = urlString.trim();
|
||||
if (!urlString.isEmpty()) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(urlString);
|
||||
} catch (URISyntaxException e) {
|
||||
return urlString;
|
||||
}
|
||||
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
|
||||
urlString = "hifi://" + urlString;
|
||||
}
|
||||
}
|
||||
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;
|
||||
try {
|
||||
uri = new URI(urlString);
|
||||
} catch (URISyntaxException e) {
|
||||
return urlString;
|
||||
}
|
||||
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
|
||||
urlString = baseUrl + urlString;
|
||||
}
|
||||
}
|
||||
return urlString;
|
||||
}
|
||||
|
||||
public native String getCurrentAddress();
|
||||
|
||||
public native String protocolVersionSignature();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
package io.highfidelity.hifiinterface;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.NavigationView;
|
||||
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.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabWidget;
|
||||
import android.widget.TextView;
|
||||
|
||||
import io.highfidelity.hifiinterface.QtPreloader.QtPreloader;
|
||||
import io.highfidelity.hifiinterface.view.DomainAdapter;
|
||||
|
||||
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
/**
|
||||
* 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 domainAdapter;
|
||||
private DrawerLayout mDrawerLayout;
|
||||
private ProgressDialog mDialog;
|
||||
private NavigationView mNavigationView;
|
||||
|
||||
@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);
|
||||
|
||||
TabHost tabs = (TabHost)findViewById(R.id.tabhost);
|
||||
tabs.setup();
|
||||
|
||||
TabHost.TabSpec spec=tabs.newTabSpec("featured");
|
||||
spec.setContent(R.id.featured);
|
||||
spec.setIndicator(getString(R.string.featured));
|
||||
tabs.addTab(spec);
|
||||
|
||||
spec = tabs.newTabSpec("popular");
|
||||
spec.setContent(R.id.popular);
|
||||
spec.setIndicator(getString(R.string.popular));
|
||||
tabs.addTab(spec);
|
||||
|
||||
spec = tabs.newTabSpec("bookmarks");
|
||||
spec.setContent(R.id.bookmarks);
|
||||
spec.setIndicator(getString(R.string.bookmarks));
|
||||
tabs.addTab(spec);
|
||||
|
||||
tabs.setCurrentTab(0);
|
||||
|
||||
TabWidget tabwidget=tabs.getTabWidget();
|
||||
for(int i=0; i<tabwidget.getChildCount(); i++){
|
||||
TextView tv=(TextView) tabwidget.getChildAt(i).findViewById(android.R.id.title);
|
||||
tv.setTextAppearance(R.style.TabText);
|
||||
}
|
||||
|
||||
|
||||
RecyclerView domainsView = findViewById(R.id.rvDomains);
|
||||
int numberOfColumns = 1;
|
||||
GridLayoutManager gridLayoutMgr = new GridLayoutManager(this, numberOfColumns);
|
||||
domainsView.setLayoutManager(gridLayoutMgr);
|
||||
domainAdapter = new DomainAdapter(this);
|
||||
domainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(View view, int position, DomainAdapter.Domain domain) {
|
||||
gotoDomain(domain.url);
|
||||
}
|
||||
});
|
||||
domainsView.setAdapter(domainAdapter);
|
||||
|
||||
SearchView searchView = findViewById(R.id.searchView);
|
||||
int searchPlateId = searchView.getContext().getResources().getIdentifier("android:id/search_plate", null, null);
|
||||
View searchPlate = searchView.findViewById(searchPlateId);
|
||||
if (searchPlate != null) {
|
||||
searchPlate.setBackgroundColor (Color.TRANSPARENT);
|
||||
int searchTextId = searchPlate.getContext ().getResources ().getIdentifier ("android:id/search_src_text", null, null);
|
||||
TextView searchTextView = searchView.findViewById(searchTextId);
|
||||
searchTextView.setTextAppearance(R.style.SearchText);
|
||||
}
|
||||
|
||||
if (getIntent() == null ||
|
||||
!getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) ||
|
||||
!getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) {
|
||||
preloadQt();
|
||||
showActivityIndicator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void gotoDomain(String domainUrl) {
|
||||
Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class);
|
||||
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
|
||||
HomeActivity.this.finish();
|
||||
if (getIntent() != null &&
|
||||
getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) &&
|
||||
getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void showActivityIndicator() {
|
||||
if (mDialog == null) {
|
||||
mDialog = new ProgressDialog(this);
|
||||
}
|
||||
mDialog.setMessage("Please wait...");
|
||||
mDialog.setCancelable(false);
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
private void cancelActivityIndicator() {
|
||||
if (mDialog != null) {
|
||||
mDialog.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private AsyncTask preloadTask;
|
||||
|
||||
private void preloadQt() {
|
||||
if (preloadTask == null) {
|
||||
preloadTask = new AsyncTask() {
|
||||
@Override
|
||||
protected Object doInBackground(Object[] objects) {
|
||||
new QtPreloader(HomeActivity.this).initQt();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cancelActivityIndicator();
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
};
|
||||
preloadTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@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() {
|
||||
cancelActivityIndicator();
|
||||
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;
|
||||
case R.id.action_settings:
|
||||
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
|
||||
public void onBackPressed() {
|
||||
finishAffinity();
|
||||
}
|
||||
}
|
|
@ -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,18 +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 nativeOnPause();
|
||||
//private native void nativeOnResume();
|
||||
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;
|
||||
|
@ -63,6 +62,7 @@ public class InterfaceActivity extends QtActivity {
|
|||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.isLoading = true;
|
||||
Intent intent = getIntent();
|
||||
if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) {
|
||||
intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
|
||||
|
@ -90,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);
|
||||
|
@ -103,40 +102,38 @@ 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
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
//nativeOnPause();
|
||||
nativeEnterBackground();
|
||||
//gvrApi.pauseTracking();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
nativeEnterForeground();
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
nativeEnterBackground();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//nativeOnResume();
|
||||
nativeEnterForeground();
|
||||
//gvrApi.resumeTracking();
|
||||
}
|
||||
|
||||
|
@ -194,14 +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);
|
||||
intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true);
|
||||
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;
|
||||
}
|
||||
|
@ -212,8 +211,18 @@ public class InterfaceActivity extends QtActivity {
|
|||
}
|
||||
}
|
||||
|
||||
public void onAppLoadedComplete() {
|
||||
super.isLoading = false;
|
||||
}
|
||||
|
||||
public void performHapticFeedback(int duration) {
|
||||
if (duration > 0) {
|
||||
mVibrator.vibrate(duration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
openAndroidActivity("Home");
|
||||
openAndroidActivity("Home", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -63,8 +63,7 @@ public class PermissionChecker extends Activity {
|
|||
}
|
||||
|
||||
private void launchActivityWithPermissions(){
|
||||
finish();
|
||||
Intent i = new Intent(this, HomeActivity.class);
|
||||
Intent i = new Intent(this, InterfaceActivity.class);
|
||||
startActivity(i);
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -1,315 +0,0 @@
|
|||
package io.highfidelity.hifiinterface.QtPreloader;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ComponentInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import org.qtproject.qt5.android.bindings.QtApplication;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
/**
|
||||
* Created by Gabriel Calero & Cristian Duarte on 3/22/18.
|
||||
*/
|
||||
|
||||
public class QtPreloader {
|
||||
|
||||
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
|
||||
private ComponentInfo m_contextInfo;
|
||||
private String[] m_qtLibs = null; // required qt libs
|
||||
private Context m_context;
|
||||
|
||||
private static final String DEX_PATH_KEY = "dex.path";
|
||||
private static final String LIB_PATH_KEY = "lib.path";
|
||||
private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
|
||||
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
||||
private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
|
||||
private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id";
|
||||
private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id";
|
||||
private static final String MAIN_LIBRARY_KEY = "main.library";
|
||||
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
public QtPreloader(Context context) {
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
public void initQt() {
|
||||
|
||||
try {
|
||||
m_contextInfo = m_context.getPackageManager().getActivityInfo(new ComponentName("io.highfidelity.hifiinterface", "io.highfidelity.hifiinterface.InterfaceActivity"),
|
||||
PackageManager.GET_META_DATA);
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) {
|
||||
int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id");
|
||||
m_qtLibs = m_context.getResources().getStringArray(resourceId);
|
||||
}
|
||||
ArrayList<String> libraryList = new ArrayList<>();
|
||||
String localPrefix = m_context.getApplicationInfo().dataDir + "/";
|
||||
String pluginsPrefix = localPrefix + "qt-reserved-files/";
|
||||
cleanOldCacheIfNecessary(localPrefix, pluginsPrefix);
|
||||
extractBundledPluginsAndImports(pluginsPrefix);
|
||||
|
||||
for (String lib : m_qtLibs) {
|
||||
libraryList.add(localPrefix + "lib/lib" + lib + ".so");
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) {
|
||||
String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":");
|
||||
for (String lib : extraLibs) {
|
||||
if (lib.length() > 0) {
|
||||
if (lib.startsWith("lib/")) {
|
||||
libraryList.add(localPrefix + lib);
|
||||
} else {
|
||||
libraryList.add(pluginsPrefix + lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bundle loaderParams = new Bundle();
|
||||
loaderParams.putString(DEX_PATH_KEY, new String());
|
||||
|
||||
loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
|
||||
|
||||
loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES
|
||||
+ "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml"
|
||||
+ "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports"
|
||||
+ "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins");
|
||||
|
||||
|
||||
// add all bundled Qt libs to loader params
|
||||
ArrayList<String> libs = new ArrayList<>();
|
||||
|
||||
String libName = m_contextInfo.metaData.getString("android.app.lib_name");
|
||||
loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
|
||||
loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs);
|
||||
|
||||
// load and start QtLoader class
|
||||
DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files
|
||||
m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written.
|
||||
loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
|
||||
m_context.getClassLoader()); // parent loader
|
||||
|
||||
Class<?> loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class
|
||||
Object qtLoader = loaderClass.newInstance(); // create an instance
|
||||
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
|
||||
contextClassName(),
|
||||
ClassLoader.class,
|
||||
Bundle.class);
|
||||
prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams);
|
||||
|
||||
// now load the application library so it's accessible from this class loader
|
||||
if (libName != null) {
|
||||
System.loadLibrary(libName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(QtApplication.QtTAG, "Error pre-loading HiFi Qt app", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String loaderClassName() {
|
||||
return "org.qtproject.qt5.android.QtActivityDelegate";
|
||||
}
|
||||
|
||||
protected Class<?> contextClassName() {
|
||||
return android.app.Activity.class;
|
||||
}
|
||||
|
||||
|
||||
private void deleteRecursively(File directory) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteRecursively(file);
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
directory.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) {
|
||||
File newCache = new File(localPrefix);
|
||||
if (!newCache.exists()) {
|
||||
{
|
||||
File oldPluginsCache = new File(oldLocalPrefix + "plugins/");
|
||||
if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) {
|
||||
deleteRecursively(oldPluginsCache);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
File oldImportsCache = new File(oldLocalPrefix + "imports/");
|
||||
if (oldImportsCache.exists() && oldImportsCache.isDirectory()) {
|
||||
deleteRecursively(oldImportsCache);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
File oldQmlCache = new File(oldLocalPrefix + "qml/");
|
||||
if (oldQmlCache.exists() && oldQmlCache.isDirectory()) {
|
||||
deleteRecursively(oldQmlCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private void copyFile(InputStream inputStream, OutputStream outputStream)
|
||||
throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
int count;
|
||||
while ((count = inputStream.read(buffer)) > 0) {
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAsset(String source, String destination)
|
||||
throws IOException {
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
AssetManager assetsManager = m_context.getAssets();
|
||||
InputStream inputStream = assetsManager.open(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private static void createBundledBinary(String source, String destination)
|
||||
throws IOException {
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
InputStream inputStream = new FileInputStream(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) {
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
long cacheVersion = 0;
|
||||
if (versionFile.exists() && versionFile.canRead()) {
|
||||
try {
|
||||
DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile));
|
||||
cacheVersion = inputStream.readLong();
|
||||
inputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheVersion != packageVersion) {
|
||||
deleteRecursively(new File(pluginsPrefix));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException {
|
||||
String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/";
|
||||
long packageVersion = -1;
|
||||
try {
|
||||
PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0);
|
||||
packageVersion = packageInfo.lastUpdateTime;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
File parentDirectory = versionFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
versionFile.createNewFile();
|
||||
|
||||
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile));
|
||||
outputStream.writeLong(packageVersion);
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY;
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String bundledImportBinary : list) {
|
||||
String[] split = bundledImportBinary.split(":");
|
||||
String sourceFileName = libsDir + split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
createBundledBinary(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY;
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String fileName : list) {
|
||||
String[] split = fileName.split(":");
|
||||
String sourceFileName = split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
copyAsset(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package io.highfidelity.hifiinterface;
|
||||
|
||||
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 {
|
||||
|
||||
private native void registerLoadCompleteListener();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_splash);
|
||||
registerLoadCompleteListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
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, MainActivity.class));
|
||||
SplashActivity.this.finish();
|
||||
}
|
||||
}
|
|
@ -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("");
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.highfidelity.hifiinterface.provider;
|
||||
|
||||
/**
|
||||
* Created by cduarte on 4/18/18.
|
||||
*/
|
||||
|
||||
public interface Callback<T> {
|
||||
public void callback(T t);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.highfidelity.hifiinterface.provider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.highfidelity.hifiinterface.view.DomainAdapter;
|
||||
|
||||
/**
|
||||
* Created by cduarte on 4/17/18.
|
||||
*/
|
||||
|
||||
public interface DomainProvider {
|
||||
|
||||
void retrieve(String filterText, DomainCallback domainCallback);
|
||||
|
||||
interface DomainCallback {
|
||||
void retrieveOk(List<DomainAdapter.Domain> domain);
|
||||
void retrieveError(Exception e, String message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
package io.highfidelity.hifiinterface.provider;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.MutableInt;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.highfidelity.hifiinterface.HifiUtils;
|
||||
import io.highfidelity.hifiinterface.view.DomainAdapter;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
/**
|
||||
* Created by cduarte on 4/17/18.
|
||||
*/
|
||||
|
||||
public class UserStoryDomainProvider implements DomainProvider {
|
||||
|
||||
public static final String BASE_URL = "https://metaverse.highfidelity.com/";
|
||||
|
||||
private static final String INCLUDE_ACTIONS_FOR_PLACES = "concurrency";
|
||||
private static final String INCLUDE_ACTIONS_FOR_FULL_SEARCH = "concurrency,announcements,snapshot";
|
||||
private static final int MAX_PAGES_TO_GET = 10;
|
||||
|
||||
private String mProtocol;
|
||||
private Retrofit mRetrofit;
|
||||
private UserStoryDomainProviderService mUserStoryDomainProviderService;
|
||||
|
||||
private boolean startedToGetFromAPI = false;
|
||||
private List<UserStory> allStories; // All retrieved stories from the API
|
||||
private List<DomainAdapter.Domain> suggestions; // Filtered places to show
|
||||
|
||||
public UserStoryDomainProvider(String protocol) {
|
||||
mRetrofit = new Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
mUserStoryDomainProviderService = mRetrofit.create(UserStoryDomainProviderService.class);
|
||||
mProtocol = protocol;
|
||||
allStories = new ArrayList<>();
|
||||
suggestions = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void fillDestinations(String filterText, DomainCallback domainCallback) {
|
||||
StoriesFilter filter = new StoriesFilter(filterText);
|
||||
final MutableInt counter = new MutableInt(0);
|
||||
allStories.clear();
|
||||
getUserStoryPage(1,
|
||||
e -> {
|
||||
allStories.subList(counter.value, allStories.size()).forEach(userStory -> {
|
||||
filter.filterOrAdd(userStory);
|
||||
});
|
||||
if (domainCallback != null) {
|
||||
domainCallback.retrieveOk(suggestions); //ended
|
||||
}
|
||||
},
|
||||
a -> {
|
||||
allStories.forEach(userStory -> {
|
||||
counter.value++;
|
||||
filter.filterOrAdd(userStory);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void handleError(String url, Throwable t, Callback<Exception> restOfPagesCallback) {
|
||||
restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t));
|
||||
}
|
||||
|
||||
private void getUserStoryPage(int pageNumber, Callback<Exception> restOfPagesCallback, Callback<Void> firstPageCallback) {
|
||||
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
|
||||
INCLUDE_ACTIONS_FOR_PLACES,
|
||||
"open",
|
||||
true,
|
||||
mProtocol,
|
||||
pageNumber);
|
||||
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
|
||||
userStories.enqueue(new retrofit2.Callback<UserStories>() {
|
||||
@Override
|
||||
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
|
||||
UserStories data = response.body();
|
||||
allStories.addAll(data.user_stories);
|
||||
if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) {
|
||||
if (pageNumber == 1 && firstPageCallback != null) {
|
||||
firstPageCallback.callback(null);
|
||||
}
|
||||
getUserStoryPage(pageNumber + 1, restOfPagesCallback, null);
|
||||
return;
|
||||
}
|
||||
restOfPagesCallback.callback(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<UserStories> call, Throwable t) {
|
||||
handleError(call.request().url().toString(), t, restOfPagesCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class StoriesFilter {
|
||||
String[] mWords = new String[]{};
|
||||
public StoriesFilter(String filterText) {
|
||||
mWords = filterText.toUpperCase().split("\\s+");
|
||||
}
|
||||
|
||||
private boolean matches(UserStory story) {
|
||||
if (mWords.length <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String word : mWords) {
|
||||
if (!story.searchText().contains(word)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addToSuggestions(UserStory story) {
|
||||
suggestions.add(story.toDomain());
|
||||
}
|
||||
|
||||
public void filterOrAdd(UserStory story) {
|
||||
if (matches(story)) {
|
||||
addToSuggestions(story);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void filterChoicesByText(String filterText, DomainCallback domainCallback) {
|
||||
suggestions.clear();
|
||||
StoriesFilter storiesFilter = new StoriesFilter(filterText);
|
||||
allStories.forEach(story -> {
|
||||
storiesFilter.filterOrAdd(story);
|
||||
});
|
||||
domainCallback.retrieveOk(suggestions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
|
||||
if (!startedToGetFromAPI) {
|
||||
startedToGetFromAPI = true;
|
||||
fillDestinations(filterText, domainCallback);
|
||||
} else {
|
||||
filterChoicesByText(filterText, domainCallback);
|
||||
}
|
||||
}
|
||||
|
||||
public void retrieveNot(DomainCallback domainCallback) {
|
||||
// TODO Call multiple pages
|
||||
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
|
||||
INCLUDE_ACTIONS_FOR_PLACES,
|
||||
"open",
|
||||
true,
|
||||
mProtocol,
|
||||
1);
|
||||
|
||||
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
|
||||
userStories.enqueue(new retrofit2.Callback<UserStories>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
|
||||
UserStories userStories = response.body();
|
||||
if (userStories == null) {
|
||||
domainCallback.retrieveOk(new ArrayList<>(0));
|
||||
}
|
||||
List<DomainAdapter.Domain> domains = new ArrayList<>(userStories.total_entries);
|
||||
userStories.user_stories.forEach(userStory -> {
|
||||
domains.add(userStory.toDomain());
|
||||
});
|
||||
domainCallback.retrieveOk(domains);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<UserStories> call, Throwable t) {
|
||||
domainCallback.retrieveError(new Exception(t), t.getMessage());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public interface UserStoryDomainProviderService {
|
||||
@GET("api/v1/user_stories")
|
||||
Call<UserStories> getUserStories(@Query("include_actions") String includeActions,
|
||||
@Query("restriction") String restriction,
|
||||
@Query("require_online") boolean requireOnline,
|
||||
@Query("protocol") String protocol,
|
||||
@Query("page") int pageNumber);
|
||||
}
|
||||
|
||||
class UserStory {
|
||||
public UserStory() {}
|
||||
String place_name;
|
||||
String path;
|
||||
String thumbnail_url;
|
||||
String place_id;
|
||||
String domain_id;
|
||||
private String searchText;
|
||||
|
||||
// New fields? tags, description
|
||||
|
||||
String searchText() {
|
||||
if (searchText == null) {
|
||||
searchText = place_name == null? "" : place_name.toUpperCase();
|
||||
}
|
||||
return searchText;
|
||||
}
|
||||
DomainAdapter.Domain toDomain() {
|
||||
// TODO Proper url creation (it can or can't have hifi
|
||||
// TODO Or use host value from api?
|
||||
String absoluteThumbnailUrl = HifiUtils.getInstance().absoluteHifiAssetUrl(thumbnail_url);
|
||||
DomainAdapter.Domain domain = new DomainAdapter.Domain(
|
||||
place_name,
|
||||
HifiUtils.getInstance().sanitizeHifiUrl(place_name) + "/" + path,
|
||||
absoluteThumbnailUrl
|
||||
);
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
class UserStories {
|
||||
String status;
|
||||
int current_page;
|
||||
int total_pages;
|
||||
int total_entries;
|
||||
List<UserStory> user_stories;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package io.highfidelity.hifiinterface.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -9,6 +10,8 @@ import android.view.ViewGroup;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
@ -19,69 +22,59 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
import io.highfidelity.hifiinterface.provider.DomainProvider;
|
||||
import io.highfidelity.hifiinterface.provider.UserStoryDomainProvider;
|
||||
|
||||
/**
|
||||
* Created by Gabriel Calero & Cristian Duarte on 3/20/18.
|
||||
*/
|
||||
public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder> {
|
||||
|
||||
private static final java.lang.String DOMAINS_FILE = "hifi_domains.json";
|
||||
private static final String TAG = "HiFi Interface";
|
||||
private Context mContext;
|
||||
private LayoutInflater mInflater;
|
||||
private ItemClickListener mClickListener;
|
||||
|
||||
public class Domain {
|
||||
public String name;
|
||||
public String url;
|
||||
public String thumbnail;
|
||||
Domain(String name, String url, String thumbnail) {
|
||||
this.name = name;
|
||||
this.thumbnail = thumbnail;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
private String mProtocol;
|
||||
private UserStoryDomainProvider domainProvider;
|
||||
private AdapterListener mAdapterListener;
|
||||
|
||||
// references to our domains
|
||||
private Domain[] mDomains = {};
|
||||
|
||||
public DomainAdapter(Context c) {
|
||||
public DomainAdapter(Context c, String protocol) {
|
||||
mContext = c;
|
||||
this.mInflater = LayoutInflater.from(mContext);
|
||||
loadDomains();
|
||||
mProtocol = protocol;
|
||||
domainProvider = new UserStoryDomainProvider(mProtocol);
|
||||
loadDomains("");
|
||||
}
|
||||
|
||||
private void loadDomains() {
|
||||
try {
|
||||
JSONObject json = new JSONObject(loadJSONFromAsset());
|
||||
JSONArray hifiDomains = json.getJSONArray("hifi_domains");
|
||||
List<Domain> domains = new ArrayList<>();
|
||||
for (int i = 0; i < hifiDomains.length(); i++) {
|
||||
JSONObject jDomain = hifiDomains.getJSONObject(i);
|
||||
public void setListener(AdapterListener adapterListener) {
|
||||
mAdapterListener = adapterListener;
|
||||
}
|
||||
|
||||
String domainName = jDomain.getString("name");
|
||||
String domainUrl = jDomain.getString("url");
|
||||
String domainThumb = jDomain.getString("thumbnail");
|
||||
|
||||
domains.add(new Domain(domainName, domainUrl, domainThumb));
|
||||
public void loadDomains(String filterText) {
|
||||
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
|
||||
@Override
|
||||
public void retrieveOk(List<Domain> domain) {
|
||||
mDomains = new Domain[domain.size()];
|
||||
mDomains = domain.toArray(mDomains);
|
||||
notifyDataSetChanged();
|
||||
if (mAdapterListener != null) {
|
||||
if (mDomains.length == 0) {
|
||||
mAdapterListener.onEmptyAdapter();
|
||||
} else {
|
||||
mAdapterListener.onNonEmptyAdapter();
|
||||
}
|
||||
}
|
||||
}
|
||||
mDomains = domains.toArray(mDomains);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error loading domains from local file", e);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Error loading domains from local file", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String loadJSONFromAsset() throws IOException {
|
||||
String json = null;
|
||||
InputStream is = mContext.getAssets().open(DOMAINS_FILE);
|
||||
int size = is.available();
|
||||
byte[] buffer = new byte[size];
|
||||
is.read(buffer);
|
||||
is.close();
|
||||
json = new String(buffer, "UTF-8");
|
||||
return json;
|
||||
@Override
|
||||
public void retrieveError(Exception e, String message) {
|
||||
Log.e("DOMAINS", message, e);
|
||||
if (mAdapterListener != null) mAdapterListener.onError(e, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,7 +87,10 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
|||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
// TODO
|
||||
//holder.thumbnail.setImageResource(mDomains[position].thumbnail);
|
||||
holder.mDomainName.setText(mDomains[position].name);
|
||||
Domain domain = mDomains[position];
|
||||
holder.mDomainName.setText(domain.name);
|
||||
Uri uri = Uri.parse(domain.thumbnail);
|
||||
Picasso.get().load(uri).into(holder.mThumbnail);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -128,4 +124,21 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
|||
public interface ItemClickListener {
|
||||
void onItemClick(View view, int position, Domain domain);
|
||||
}
|
||||
|
||||
public static class Domain {
|
||||
public String name;
|
||||
public String url;
|
||||
public String thumbnail;
|
||||
public Domain(String name, String url, String thumbnail) {
|
||||
this.name = name;
|
||||
this.thumbnail = thumbnail;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public interface AdapterListener {
|
||||
void onEmptyAdapter();
|
||||
void onNonEmptyAdapter();
|
||||
void onError(Exception e, String message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,8 @@ public class QtActivity extends Activity {
|
|||
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
|
||||
private QtActivityLoader m_loader = new QtActivityLoader(this);
|
||||
|
||||
public boolean isLoading;
|
||||
|
||||
public QtActivity() {
|
||||
}
|
||||
|
||||
|
@ -499,7 +501,11 @@ public class QtActivity extends Activity {
|
|||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
QtApplication.invokeDelegate();
|
||||
// GC: this trick allow us to show a splash activity until Qt app finishes
|
||||
// loading
|
||||
if (!isLoading) {
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -640,6 +646,7 @@ public class QtActivity extends Activity {
|
|||
super.onStop();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
|
|
17
android/app/src/main/res/drawable/default_profile_avatar.xml
Normal file
17
android/app/src/main/res/drawable/default_profile_avatar.xml
Normal 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>
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:top="1dp" android:left="12dp" android:bottom="6dp" android:right="12dp">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/backgroundDark" />
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
50
android/app/src/main/res/drawable/hifi_header.xml
Normal file
50
android/app/src/main/res/drawable/hifi_header.xml
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="350dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="350"
|
||||
android:viewportHeight="100">
|
||||
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M132.646,40.971 L132.646,62.955 L127.959,62.955 L127.959,53.361 L120.222,53.361 L120.222,62.955 L115.535,62.955 L115.535,40.971 L120.222,40.971 L120.222,49.453 L127.959,49.453 L127.959,40.971 L132.646,40.971 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M140.959,62.955 L136.271,62.955 L136.271,40.971 L140.959,40.971 L140.959,62.955 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M154.488,44.479 C153.383,44.479,152.454,44.568,151.658,44.79 C150.906,45.012,150.287,45.367,149.801,45.856 C149.315,46.345,149.006,47.055,148.784,47.899 C148.563,48.743,148.474,49.809,148.474,51.053 L148.474,53.051 C148.474,54.383,148.563,55.449,148.74,56.293 C148.917,57.137,149.226,57.803,149.668,58.247 C150.066,58.736,150.641,59.047,151.305,59.225 C151.968,59.403,152.808,59.492,153.781,59.492 C154.047,59.492,154.356,59.492,154.665,59.448 C154.976,59.448,155.284,59.403,155.638,59.358 L155.638,53.408 L152.898,53.408 L153.339,49.765 L160.06,49.765 L160.06,62.29 C159.22,62.601,158.16,62.823,156.876,63.001 C155.638,63.179,154.312,63.267,152.985,63.267 C151.305,63.267,149.89,63.045,148.741,62.646 C147.548,62.247,146.619,61.625,145.867,60.782 C145.116,59.937,144.585,58.916,144.231,57.628 C143.877,56.385,143.745,54.874,143.745,53.188 L143.745,50.921 C143.745,49.367,143.922,47.99,144.231,46.702 C144.585,45.414,145.16,44.348,145.956,43.46 C146.751,42.572,147.812,41.861,149.095,41.373 C150.377,40.884,152.013,40.618,153.958,40.618 C155.02,40.618,156.081,40.706,157.097,40.884 C158.115,41.062,158.954,41.284,159.618,41.506 L158.866,45.059 C158.291,44.881,157.628,44.748,156.921,44.615 C156.212,44.523,155.416,44.479,154.488,44.479 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M181.104,40.971 L181.104,62.955 L176.417,62.955 L176.417,53.361 L168.724,53.361 L168.724,62.955 L164.037,62.955 L164.037,40.971 L168.724,40.971 L168.724,49.453 L176.461,49.453 L176.461,40.971 L181.104,40.971 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M206.084,40.971 L205.555,44.79 L197.375,44.79 L197.375,49.365 L204.936,49.365 L204.405,53.183 L197.377,53.183 L197.377,62.954 L192.689,62.954 L192.689,40.971 L206.084,40.971 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M213.688,62.955 L209.001,62.955 L209.001,40.971 L213.688,40.971 L213.688,62.955 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M217.447,40.971 L224.609,40.971 C226.465,40.971,228.013,41.237,229.252,41.726 C230.491,42.215,231.463,42.925,232.215,43.769 C232.966,44.657,233.452,45.679,233.762,46.922 C234.073,48.166,234.203,49.498,234.203,50.92 L234.203,53.007 C234.203,54.473,234.071,55.805,233.762,57.049 C233.452,58.292,232.922,59.315,232.215,60.202 C231.463,61.09,230.49,61.756,229.252,62.245 C228.014,62.732,226.467,63,224.609,63 L217.491,63 L217.491,40.971 L217.447,40.971 L217.447,40.971 Z M222.09,59.137 L224.123,59.137 C225.008,59.137,225.76,59.047,226.467,58.825 C227.13,58.603,227.705,58.248,228.147,57.76 C228.589,57.272,228.899,56.606,229.121,55.805 C229.342,55.006,229.431,53.985,229.431,52.742 L229.431,51.098 C229.431,49.854,229.342,48.832,229.121,48.034 C228.899,47.235,228.591,46.569,228.147,46.08 C227.707,45.591,227.174,45.236,226.467,45.059 C225.803,44.881,225.008,44.748,224.123,44.748 L222.09,44.748 L222.09,59.137 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M250.871,40.971 L250.385,44.657 L241.942,44.657 L241.942,49.453 L249.899,49.453 L249.459,53.141 L241.985,53.141 L241.985,59.315 L250.918,59.315 L250.43,63 L237.254,63 L237.254,40.971 L250.871,40.971 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M258.609,40.971 L258.609,59.137 L267.496,59.137 L266.965,62.955 L253.922,62.955 L253.922,40.971 L258.609,40.971 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M275.232,62.955 L270.544,62.955 L270.544,40.971 L275.232,40.971 L275.232,62.955 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M295.482,40.971 L294.952,44.79 L289.027,44.79 L289.027,62.955 L284.339,62.955 L284.339,44.79 L277.884,44.79 L278.415,40.971 L295.482,40.971 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M306.58,49.543 L311.134,40.971 L315.953,40.971 L308.791,53.363 L308.791,62.956 L304.103,62.956 L304.103,53.363 L297.03,40.971 L302.159,40.971 L306.58,49.543 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M68.848,83.209 C64.427,83.209,60.094,82.32,56.07,80.633 C52.135,78.989,48.642,76.591,45.636,73.571 C42.63,70.551,40.242,66.999,38.606,63.091 C36.837,58.96,35.953,54.653,35.953,50.166 C35.953,45.725,36.837,41.372,38.517,37.33 C40.153,33.377,42.54,29.869,45.547,26.848 C48.553,23.828,52.09,21.43,55.981,19.786 C60.049,18.054,64.337,17.21,68.758,17.21 C73.179,17.21,77.513,18.098,81.536,19.786 C85.471,21.429,88.964,23.828,91.97,26.848 C94.977,29.869,97.365,33.422,98.999,37.33 C100.724,41.416,101.563,45.725,101.563,50.166 C101.563,54.606,100.681,58.959,99,63.001 C97.365,66.954,94.978,70.462,91.971,73.483 C88.965,76.503,85.428,78.901,81.537,80.544 C77.602,82.32,73.269,83.209,68.848,83.209 Z M68.848,20.584 C52.621,20.584,39.402,33.864,39.402,50.164 C39.402,66.465,52.621,79.744,68.848,79.744 S98.293,66.465,98.293,50.164 C98.293,33.864,85.074,20.584,68.848,20.584 Z" />
|
||||
<path
|
||||
android:fillColor="#00B4F0"
|
||||
android:pathData="M78.884,64.199 L78.884,41.014 C80.343,40.525,81.36,39.149,81.36,37.55 C81.36,35.551,79.725,33.908,77.735,33.908 C75.745,33.908,74.11,35.552,74.11,37.55 C74.11,39.105,75.038,40.393,76.409,40.97 L76.409,51.762 L61.376,44.745 L61.376,36.217 C62.835,35.729,63.852,34.352,63.852,32.753 C63.852,30.754,62.216,29.111,60.227,29.111 C58.238,29.111,56.602,30.755,56.602,32.753 C56.602,34.308,57.531,35.596,58.901,36.173 L58.901,59.491 C57.574,60.023,56.602,61.355,56.602,62.911 C56.602,64.91,58.238,66.553,60.227,66.553 C62.216,66.553,63.852,64.91,63.852,62.911 C63.852,61.312,62.835,59.935,61.376,59.447 L61.376,47.676 L76.409,54.694 L76.409,64.244 C75.083,64.775,74.11,66.109,74.11,67.663 C74.11,69.662,75.745,71.305,77.735,71.305 C79.725,71.305,81.36,69.662,81.36,67.663 C81.359,66.02,80.343,64.688,78.884,64.199 Z" />
|
||||
</vector>
|
14
android/app/src/main/res/drawable/hifi_logo_header.xml
Normal file
14
android/app/src/main/res/drawable/hifi_logo_header.xml
Normal 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>
|
50
android/app/src/main/res/drawable/hifi_logo_splash.xml
Normal file
50
android/app/src/main/res/drawable/hifi_logo_splash.xml
Normal 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>
|
12
android/app/src/main/res/drawable/ic_clear.xml
Normal file
12
android/app/src/main/res/drawable/ic_clear.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector android:height="24dp" android:viewportHeight="15.0"
|
||||
android:viewportWidth="15.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#404040" android:pathData="M7.5,7.5m-7.5,0a7.5,7.5 0,1 1,15 0a7.5,7.5 0,1 1,-15 0"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M4.683,4.583L10,10.27"
|
||||
android:strokeColor="#E3E3E3" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="bevel" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M10,4.73L4.683,10.417"
|
||||
android:strokeColor="#E3E3E3" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="bevel" android:strokeWidth="1"/>
|
||||
</vector>
|
4
android/app/src/main/res/drawable/ic_search.xml
Normal file
4
android/app/src/main/res/drawable/ic_search.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="24dp" android:viewportHeight="18.0"
|
||||
android:viewportWidth="18.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#F2F2F2" android:fillType="evenOdd" android:pathData="M12.5,11L11.7,11L11.4,10.7C12.4,9.6 13,8.1 13,6.5C13,2.9 10.1,0 6.5,0C2.9,0 0,2.9 0,6.5C0,10.1 2.9,13 6.5,13C8.1,13 9.6,12.4 10.7,11.4L11,11.7L11,12.5L16,17.5L17.5,16L12.5,11ZM6.5,11C4,11 2,9 2,6.5C2,4 4,2 6.5,2C9,2 11,4 11,6.5C11,9 9,11 6.5,11Z"/>
|
||||
</vector>
|
24
android/app/src/main/res/drawable/rounded_button.xml
Normal file
24
android/app/src/main/res/drawable/rounded_button.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:state_pressed="true" >
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton1" />
|
||||
<solid android:color="@color/colorButton1"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton1" />
|
||||
<solid android:color="@color/colorButton1"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton1" />
|
||||
<solid android:color="@color/colorButton1"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
7
android/app/src/main/res/drawable/rounded_edit.xml
Normal file
7
android/app/src/main/res/drawable/rounded_edit.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" android:padding="9dp">
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@android:color/black" />
|
||||
<solid android:color="@color/backgroundEditText"/>
|
||||
</shape>
|
7
android/app/src/main/res/font/raleway_italic.xml
Normal file
7
android/app/src/main/res/font/raleway_italic.xml
Normal 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&italic=1"
|
||||
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
|
||||
</font-family>
|
|
@ -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>
|
|
@ -1,38 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay"
|
||||
>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<include layout="@layout/content_home" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
|
||||
<android.support.design.widget.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:menu="@menu/menu_home"
|
||||
/>
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
70
android/app/src/main/res/layout/activity_main.xml
Normal file
70
android/app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay"
|
||||
>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
android:elevation="4dp"
|
||||
/>
|
||||
|
||||
<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 -->
|
||||
<android.support.design.widget.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
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_navigation"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:clickable="true"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/activity_horizontal_margin">
|
||||
<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: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>
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
15
android/app/src/main/res/layout/activity_splash.xml
Normal file
15
android/app/src/main/res/layout/activity_splash.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout 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_splash"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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>
|
|
@ -1,76 +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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:context="io.highfidelity.hifiinterface.HomeActivity"
|
||||
tools:showIn="@layout/activity_home">
|
||||
|
||||
<TabHost
|
||||
android:id="@+id/tabhost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TabWidget
|
||||
android:id="@android:id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundDark"/>
|
||||
|
||||
<SearchView
|
||||
android:id="@+id/searchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:background="@drawable/search_bg"
|
||||
android:gravity="right"
|
||||
android:layout_gravity="right"
|
||||
|
||||
/>
|
||||
<FrameLayout
|
||||
android:id="@android:id/tabcontent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/featured"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/rvDomains"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popular"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bookmarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</TabHost>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -1,74 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<android.support.v7.widget.CardView 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="113dp"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingRight="14dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:background="@drawable/domain_bg">
|
||||
<ImageView
|
||||
android:id="@+id/domainThumbnail"
|
||||
android:layout_height="@dimen/domainItemHeight"
|
||||
android:layout_marginStart="@dimen/domainMarginStart"
|
||||
android:layout_marginEnd="@dimen/domainMarginEnd"
|
||||
android:layout_marginTop="@dimen/domainMarginTop"
|
||||
android:layout_marginBottom="@dimen/domainMarginBottom"
|
||||
android:foreground="@drawable/rippleable"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:elevation="0dp"
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/item_corner_radius">
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:srcCompat="@android:drawable/ic_menu_gallery" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/domainName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:text=""
|
||||
android:textSize="21sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent" />
|
||||
<ImageView
|
||||
android:id="@+id/domainThumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
app:srcCompat="@android:drawable/ic_menu_gallery" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/amountOfPeople"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="7dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:text="10"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<TextView
|
||||
android:id="@+id/domainName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/domainNameHeight"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text=""
|
||||
android:textSize="21sp"
|
||||
android:textColor="@color/white_opaque"
|
||||
android:background="@color/black_060"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView2"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="13dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginBottom="3dp"
|
||||
app:srcCompat="@drawable/ic_person"
|
||||
app:layout_constraintBottom_toBottomOf="@id/amountOfPeople"
|
||||
app:layout_constraintLeft_toRightOf="@id/amountOfPeople"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/imageButtonBookmark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_bookmark"
|
||||
android:tint="@color/colorPrimary"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:padding="1dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/imageViewShare"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="9dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/imageButtonBookmark"
|
||||
app:layout_constraintLeft_toLeftOf="@id/imageButtonBookmark"
|
||||
app:layout_constraintRight_toRightOf="@id/imageButtonBookmark"
|
||||
android:background="@drawable/ic_share"
|
||||
/>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</android.support.v7.widget.CardView>
|
75
android/app/src/main/res/layout/fragment_home.xml
Normal file
75
android/app/src/main/res/layout/fragment_home.xml
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?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/contentHomeRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:background="@color/colorPrimary"
|
||||
tools:context="io.highfidelity.hifiinterface.MainActivity"
|
||||
tools:showIn="@layout/activity_home">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/searchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/searchEditHeight"
|
||||
app:layout_constraintTop_toTopOf="@id/contentHomeRoot"
|
||||
android:background="@drawable/search_bg"
|
||||
android:layout_marginTop="@dimen/searchEditAdditionalMarginTop"
|
||||
android:gravity="center_vertical|end"
|
||||
android:paddingStart="@dimen/searchEditPaddingStart"
|
||||
android:paddingEnd="@dimen/searchEditPaddingEnd"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:hint="@string/search_hint"
|
||||
android:textSize="@dimen/searchEditTextSize"
|
||||
android:inputType="textUri"
|
||||
android:imeOptions="actionGo"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_mag_icon"
|
||||
android:layout_width="@dimen/searchEditMagIconWidth"
|
||||
android:layout_height="@dimen/searchEditMagIconHeight"
|
||||
app:layout_constraintEnd_toEndOf="@id/searchView"
|
||||
app:layout_constraintTop_toTopOf="@id/searchView"
|
||||
app:layout_constraintBottom_toBottomOf="@id/searchView"
|
||||
android:layout_marginEnd="@dimen/searchEditMagMarginEnd"
|
||||
android:src="@drawable/ic_search"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_clear"
|
||||
android:layout_width="@dimen/searchEditClearIconWidth"
|
||||
android:layout_height="@dimen/searchEditClearIconHeight"
|
||||
app:layout_constraintEnd_toEndOf="@id/searchView"
|
||||
app:layout_constraintTop_toTopOf="@id/searchView"
|
||||
app:layout_constraintBottom_toBottomOf="@id/searchView"
|
||||
android:layout_marginEnd="@dimen/searchEditClearMarginEnd"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/ic_clear"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/searchNoResultsView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/SearchText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/searchView"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/search_no_results"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/rvDomains"
|
||||
app:layout_constraintTop_toBottomOf="@id/searchView"
|
||||
app:layout_constraintBottom_toBottomOf="@id/contentHomeRoot"
|
||||
app:layout_constraintStart_toStartOf="@id/contentHomeRoot"
|
||||
app:layout_constraintEnd_toEndOf="@id/contentHomeRoot"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
113
android/app/src/main/res/layout/fragment_login.xml
Normal file
113
android/app/src/main/res/layout/fragment_login.xml
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?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"
|
||||
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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textColor="@color/colorLoginError"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/username"
|
||||
app:layout_constraintLeft_toLeftOf="@id/username"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="46dp"
|
||||
android:layout_marginRight="46dp"
|
||||
android:background="@drawable/rounded_edit"
|
||||
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|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="35dp"
|
||||
android:layout_marginLeft="46dp"
|
||||
android:layout_marginRight="46dp"
|
||||
android:background="@drawable/rounded_edit"
|
||||
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|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="154dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
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"
|
||||
app:layout_goneMarginTop="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/forgotPassword"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:textSize="14dp"
|
||||
android:text="@string/forgot_password"
|
||||
android:textStyle="italic"
|
||||
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"/>
|
||||
|
||||
|
||||
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
39
android/app/src/main/res/layout/fragment_policy.xml
Normal file
39
android/app/src/main/res/layout/fragment_policy.xml
Normal 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>
|
63
android/app/src/main/res/layout/navigation_header.xml
Normal file
63
android/app/src/main/res/layout/navigation_header.xml
Normal 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>
|
|
@ -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>
|
8
android/app/src/main/res/menu/menu_navigation.xml
Normal file
8
android/app/src/main/res/menu/menu_navigation.xml
Normal 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>
|
|
@ -1,13 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="white_opaque">#ffffff</color>
|
||||
<color name="colorPrimary">#272727</color>
|
||||
<color name="colorPrimaryDark">#000000</color>
|
||||
<color name="colorPrimary">@color/backgroundLight</color>
|
||||
<color name="colorPrimaryDark">@color/backgroundDark</color>
|
||||
<color name="colorAccent">#54D7FD</color>
|
||||
<color name="backgroundEditText">#E3E3E3</color>
|
||||
<color name="editTextColor">#575757</color>
|
||||
<color name="tabs">#1EB5EC</color>
|
||||
<color name="colorButton1">#00B4EF</color>
|
||||
<color name="backgroundDark">#333333</color>
|
||||
<color name="backgroundLight">#4F4F4F</color>
|
||||
<color name="backgroundSearch">#33999999</color>
|
||||
<color name="editText">#212121</color>
|
||||
<color name="editTextHint">#9e9e9e</color>
|
||||
<color name="menuOption">#F2F2F2</color>
|
||||
<color name="colorLoginError">#FF7171</color>
|
||||
<color name="black_060">#99000000</color>
|
||||
<color name="statusbar_color">#292929</color>
|
||||
</resources>
|
||||
|
|
|
@ -11,4 +11,30 @@
|
|||
<dimen name="text_size_subtitle_material_toolbar">12dp</dimen>
|
||||
<dimen name="button_horizontal_margin">12dp</dimen>
|
||||
<dimen name="edit_text_padding">8dp</dimen>
|
||||
</resources>
|
||||
<dimen name="item_corner_radius">4dp</dimen>
|
||||
|
||||
<!-- Search (domains) screen dimensions -->
|
||||
<dimen name="searchEditHeight">47.5dp</dimen>
|
||||
<dimen name="searchEditAdditionalMarginTop">11dp</dimen>
|
||||
<dimen name="searchEditPaddingStart">24dp</dimen>
|
||||
<dimen name="searchEditPaddingEnd">51dp</dimen>
|
||||
<dimen name="searchEditTextSize">19sp</dimen>
|
||||
<dimen name="searchEditMagIconWidth">16dp</dimen>
|
||||
<dimen name="searchEditMagIconHeight">16dp</dimen>
|
||||
<dimen name="searchEditMagMarginEnd">22dp</dimen>
|
||||
<dimen name="searchEditClearIconWidth">16dp</dimen>
|
||||
<dimen name="searchEditClearIconHeight">16dp</dimen>
|
||||
<dimen name="searchEditClearMarginEnd">22dp</dimen>
|
||||
|
||||
<dimen name="domainItemHeight">163dp</dimen>
|
||||
<dimen name="domainMarginStart">14dp</dimen>
|
||||
<dimen name="domainMarginEnd">14dp</dimen>
|
||||
<dimen name="domainMarginTop">2dp</dimen>
|
||||
<dimen name="domainMarginBottom">6dp</dimen>
|
||||
<dimen name="domainNameHeight">64dp</dimen>
|
||||
|
||||
<dimen name="header_hifi_margin_top">56dp</dimen>
|
||||
<dimen name="header_hifi_height">101dp</dimen>
|
||||
<dimen name="header_hifi_width">425dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,9 +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\u00A0</string>
|
||||
<string name="password">Password\u00A0</string>
|
||||
<string name="login">Login</string>
|
||||
<string name="logout">Logout</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>
|
||||
|
|
|
@ -7,7 +7,14 @@
|
|||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.AppCompat.Translucent.NoActionBar" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
</style>
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
|
@ -35,7 +42,7 @@
|
|||
|
||||
<style name="SearchText">
|
||||
<item name="android:fontFamily">@font/raleway_light_italic</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
<item name="android:textSize">19sp</item>
|
||||
</style>
|
||||
|
||||
<!-- Overriding text size so it's not so big in portrait -->
|
||||
|
|
|
@ -75,6 +75,7 @@ public:
|
|||
public slots:
|
||||
/**jsdoc
|
||||
* @function Agent.run
|
||||
* @deprecated This function is being removed from the API.
|
||||
*/
|
||||
void run() override;
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -27,6 +27,9 @@ Original.CheckBox {
|
|||
property bool wrap: true;
|
||||
readonly property int checkSize: Math.max(boxSize - 8, 10)
|
||||
readonly property int checkRadius: 2
|
||||
property string labelFontFamily: "Raleway"
|
||||
property int labelFontSize: 14;
|
||||
property int labelFontWeight: Font.DemiBold;
|
||||
focusPolicy: Qt.ClickFocus
|
||||
hoverEnabled: true
|
||||
|
||||
|
@ -105,6 +108,9 @@ Original.CheckBox {
|
|||
contentItem: Label {
|
||||
text: checkBox.text
|
||||
color: checkBox.color
|
||||
font.family: checkBox.labelFontFamily;
|
||||
font.pixelSize: checkBox.labelFontSize;
|
||||
font.weight: checkBox.labelFontWeight;
|
||||
x: 2
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap
|
||||
|
|
|
@ -172,7 +172,7 @@ StackView {
|
|||
source: InputConfiguration.configurationLayout(box.currentText);
|
||||
onLoaded: {
|
||||
if (loader.item.hasOwnProperty("pluginName")) {
|
||||
if (box.currentText === "Vive") {
|
||||
if (box.currentText === "HTC Vive") {
|
||||
loader.item.pluginName = "OpenVR";
|
||||
} else {
|
||||
loader.item.pluginName = box.currentText;
|
||||
|
|
|
@ -10,11 +10,53 @@
|
|||
//
|
||||
#include "AndroidHelper.h"
|
||||
#include <QDebug>
|
||||
#include <AccountManager.h>
|
||||
|
||||
void AndroidHelper::requestActivity(const QString &activityName) {
|
||||
emit androidActivityRequested(activityName);
|
||||
AndroidHelper::AndroidHelper() {
|
||||
}
|
||||
|
||||
void AndroidHelper::goBackFromAndroidActivity() {
|
||||
emit backFromAndroidActivity();
|
||||
}
|
||||
AndroidHelper::~AndroidHelper() {
|
||||
workerThread.quit();
|
||||
workerThread.wait();
|
||||
}
|
||||
|
||||
void AndroidHelper::init() {
|
||||
workerThread.start();
|
||||
_accountManager = QSharedPointer<AccountManager>(new AccountManager, &QObject::deleteLater);
|
||||
_accountManager->setIsAgent(true);
|
||||
_accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL());
|
||||
_accountManager->setSessionID(DependencyManager::get<AccountManager>()->getSessionID());
|
||||
connect(_accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
|
||||
DependencyManager::get<AccountManager>()->setAccountInfo(AndroidHelper::instance().getAccountManager()->getAccountInfo());
|
||||
DependencyManager::get<AccountManager>()->setAuthURL(authURL);
|
||||
});
|
||||
|
||||
connect(_accountManager.data(), &AccountManager::logoutComplete, [] () {
|
||||
DependencyManager::get<AccountManager>()->logout();
|
||||
});
|
||||
_accountManager->moveToThread(&workerThread);
|
||||
}
|
||||
|
||||
void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene) {
|
||||
emit androidActivityRequested(activityName, backToScene);
|
||||
}
|
||||
|
||||
void AndroidHelper::notifyLoadComplete() {
|
||||
emit qtAppLoadComplete();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#define hifi_Android_Helper_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <AccountManager.h>
|
||||
|
||||
class AndroidHelper : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -21,17 +23,34 @@ public:
|
|||
static AndroidHelper instance;
|
||||
return instance;
|
||||
}
|
||||
void requestActivity(const QString &activityName);
|
||||
void goBackFromAndroidActivity();
|
||||
void init();
|
||||
void requestActivity(const QString &activityName, const bool backToScene);
|
||||
void notifyLoadComplete();
|
||||
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() {}
|
||||
AndroidHelper();
|
||||
~AndroidHelper();
|
||||
QSharedPointer<AccountManager> _accountManager;
|
||||
QThread workerThread;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -851,7 +851,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<Cursor::Manager>();
|
||||
DependencyManager::set<VirtualPad::Manager>();
|
||||
DependencyManager::set<DesktopPreviewProvider>();
|
||||
#if defined(Q_OS_ANDROID)
|
||||
DependencyManager::set<AccountManager>(); // use the default user agent getter
|
||||
#else
|
||||
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
|
||||
#endif
|
||||
DependencyManager::set<StatTracker>();
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
|
||||
DependencyManager::set<ScriptInitializerMixin, NativeScriptInitializers>();
|
||||
|
@ -1234,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());
|
||||
|
@ -2252,6 +2262,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_pendingRenderEvent = false;
|
||||
|
||||
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
void Application::updateVerboseLogging() {
|
||||
|
@ -2849,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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3212,7 +3236,8 @@ void Application::resizeGL() {
|
|||
// Set the desired FBO texture size. If it hasn't changed, this does nothing.
|
||||
// Otherwise, it must rebuild the FBOs
|
||||
uvec2 framebufferSize = displayPlugin->getRecommendedRenderSize();
|
||||
uvec2 renderSize = uvec2(vec2(framebufferSize) * getRenderResolutionScale());
|
||||
float renderResolutionScale = getRenderResolutionScale();
|
||||
uvec2 renderSize = uvec2(vec2(framebufferSize) * renderResolutionScale);
|
||||
if (_renderResolution != renderSize) {
|
||||
_renderResolution = renderSize;
|
||||
DependencyManager::get<FramebufferCache>()->setFrameBufferSize(fromGlm(renderSize));
|
||||
|
@ -3229,6 +3254,7 @@ void Application::resizeGL() {
|
|||
}
|
||||
|
||||
DependencyManager::get<OffscreenUi>()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
|
||||
displayPlugin->setRenderResolutionScale(renderResolutionScale);
|
||||
}
|
||||
|
||||
void Application::handleSandboxStatus(QNetworkReply* reply) {
|
||||
|
@ -3865,7 +3891,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
|
||||
|
@ -4187,7 +4213,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
|
|||
QUrl url(urlString);
|
||||
QString snapshotPath = url.toLocalFile();
|
||||
|
||||
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
|
||||
SnapshotMetaData* snapshotData = DependencyManager::get<Snapshot>()->parseSnapshotData(snapshotPath);
|
||||
if (snapshotData) {
|
||||
if (!snapshotData->getURL().toString().isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(snapshotData->getURL().toString());
|
||||
|
@ -6582,8 +6608,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());
|
||||
|
@ -7591,13 +7615,15 @@ void Application::loadAvatarBrowser() const {
|
|||
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
|
||||
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
|
||||
// Get a screenshot and save it
|
||||
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
|
||||
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
|
||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
|
||||
// If we're not doing an animated snapshot as well...
|
||||
if (!includeAnimated) {
|
||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||
if (!path.isEmpty()) {
|
||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||
}
|
||||
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
||||
// Get an animated GIF snapshot and save it
|
||||
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
|
||||
|
@ -7607,17 +7633,23 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
|
|||
|
||||
void Application::takeSecondaryCameraSnapshot(const QString& filename) {
|
||||
postLambdaEvent([filename, this] {
|
||||
QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
|
||||
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
|
||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true);
|
||||
});
|
||||
}
|
||||
|
||||
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
|
||||
postLambdaEvent([filename, cubemapOutputFormat, cameraPosition] {
|
||||
DependencyManager::get<Snapshot>()->save360Snapshot(cameraPosition, cubemapOutputFormat, filename);
|
||||
});
|
||||
}
|
||||
|
||||
void Application::shareSnapshot(const QString& path, const QUrl& href) {
|
||||
postLambdaEvent([path, href] {
|
||||
// not much to do here, everything is done in snapshot code...
|
||||
Snapshot::uploadSnapshot(path, href);
|
||||
DependencyManager::get<Snapshot>()->uploadSnapshot(path, href);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8252,29 +8284,22 @@ 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(),
|
||||
"stop", Qt::BlockingQueuedConnection);
|
||||
//GC: commenting it out until we fix it
|
||||
//getActiveDisplayPlugin()->deactivate();
|
||||
if (getActiveDisplayPlugin()->isActive()) {
|
||||
getActiveDisplayPlugin()->deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::enterForeground() {
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
||||
"start", Qt::BlockingQueuedConnection);
|
||||
//GC: commenting it out until we fix it
|
||||
/*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) {
|
||||
if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
|
||||
qWarning() << "Could not re-activate display plugin";
|
||||
}*/
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "Application_jni.cpp"
|
||||
#include "Application.moc"
|
||||
|
|
|
@ -282,6 +282,7 @@ public:
|
|||
|
||||
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
|
||||
void takeSecondaryCameraSnapshot(const QString& filename = QString());
|
||||
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename = QString());
|
||||
|
||||
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
||||
|
||||
|
@ -414,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);
|
||||
|
|
|
@ -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
|
|
@ -105,7 +105,7 @@ void Application::paintGL() {
|
|||
PerformanceTimer perfTimer("renderOverlay");
|
||||
// NOTE: There is no batch associated with this renderArgs
|
||||
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
|
||||
renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize());
|
||||
renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize() * getRenderResolutionScale());
|
||||
_applicationOverlay.renderOverlay(&renderArgs);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
@ -9,15 +9,11 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "SecondaryCamera.h"
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <gpu/Context.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "SecondaryCamera.h"
|
||||
#include <TextureCache.h>
|
||||
#include <gpu/Context.h>
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
|
||||
|
||||
|
@ -38,7 +34,6 @@ public:
|
|||
using JobModel = render::Job::ModelO<SecondaryCameraJob, RenderArgsPointer, Config>;
|
||||
SecondaryCameraJob() {
|
||||
_cachedArgsPointer = std::make_shared<RenderArgs>(_cachedArgs);
|
||||
_entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
_attachedEntityPropertyFlags += PROP_POSITION;
|
||||
_attachedEntityPropertyFlags += PROP_ROTATION;
|
||||
}
|
||||
|
@ -60,12 +55,16 @@ public:
|
|||
qWarning() << "ERROR: Cannot set mirror projection for SecondaryCamera without an attachedEntityId set.";
|
||||
return;
|
||||
}
|
||||
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_attachedEntityId,
|
||||
_attachedEntityPropertyFlags);
|
||||
glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition();
|
||||
glm::quat mirrorPropertiesRotation = entityProperties.getRotation();
|
||||
glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions();
|
||||
EntityItemPointer attachedEntity = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId);
|
||||
|
||||
if (!attachedEntity) {
|
||||
qWarning() << "ERROR: Cannot get EntityItemPointer for _attachedEntityId.";
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 mirrorPropertiesPosition = attachedEntity->getWorldPosition();
|
||||
glm::quat mirrorPropertiesRotation = attachedEntity->getWorldOrientation();
|
||||
glm::vec3 mirrorPropertiesDimensions = attachedEntity->getScaledDimensions();
|
||||
glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;
|
||||
|
||||
// setup mirror from world as inverse of world from mirror transformation using inverted x and z for mirrored image
|
||||
|
@ -120,10 +119,13 @@ public:
|
|||
setMirrorProjection(srcViewFrustum);
|
||||
} else {
|
||||
if (!_attachedEntityId.isNull()) {
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_attachedEntityId,
|
||||
_attachedEntityPropertyFlags);
|
||||
srcViewFrustum.setPosition(entityProperties.getPosition());
|
||||
srcViewFrustum.setOrientation(entityProperties.getRotation());
|
||||
EntityItemPointer attachedEntity = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId);
|
||||
if (!attachedEntity) {
|
||||
qWarning() << "ERROR: Cannot get EntityItemPointer for _attachedEntityId.";
|
||||
return;
|
||||
}
|
||||
srcViewFrustum.setPosition(attachedEntity->getWorldPosition());
|
||||
srcViewFrustum.setOrientation(attachedEntity->getWorldOrientation());
|
||||
} else {
|
||||
srcViewFrustum.setPosition(_position);
|
||||
srcViewFrustum.setOrientation(_orientation);
|
||||
|
@ -155,7 +157,6 @@ private:
|
|||
int _textureHeight;
|
||||
bool _mirrorProjection;
|
||||
EntityPropertyFlags _attachedEntityPropertyFlags;
|
||||
QSharedPointer<EntityScriptingInterface> _entityScriptingInterface;
|
||||
};
|
||||
|
||||
void SecondaryCameraJobConfig::setPosition(glm::vec3 pos) {
|
||||
|
@ -216,4 +217,4 @@ void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inp
|
|||
task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
|
||||
}
|
||||
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,17 @@ AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisi
|
|||
_type = MOTIONSTATE_TYPE_AVATAR;
|
||||
}
|
||||
|
||||
void AvatarMotionState::handleEasyChanges(uint32_t& flags) {
|
||||
ObjectMotionState::handleEasyChanges(flags);
|
||||
if (flags & Simulation::DIRTY_PHYSICS_ACTIVATION && !_body->isActive()) {
|
||||
_body->activate();
|
||||
}
|
||||
}
|
||||
|
||||
bool AvatarMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
|
||||
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||
}
|
||||
|
||||
AvatarMotionState::~AvatarMotionState() {
|
||||
assert(_avatar);
|
||||
_avatar = nullptr;
|
||||
|
@ -46,6 +57,9 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
|
|||
const btCollisionShape* AvatarMotionState::computeNewShape() {
|
||||
ShapeInfo shapeInfo;
|
||||
std::static_pointer_cast<Avatar>(_avatar)->computeShapeInfo(shapeInfo);
|
||||
glm::vec3 halfExtents = shapeInfo.getHalfExtents();
|
||||
halfExtents.y = 0.0f;
|
||||
_diameter = 2.0f * glm::length(halfExtents);
|
||||
return getShapeManager()->getShape(shapeInfo);
|
||||
}
|
||||
|
||||
|
@ -60,25 +74,31 @@ void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const {
|
|||
worldTrans.setRotation(glmToBullet(getObjectRotation()));
|
||||
if (_body) {
|
||||
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||
// HACK: The PhysicsEngine does not actually move OTHER avatars -- instead it slaves their local RigidBody to the transform
|
||||
// as specified by a remote simulation. However, to give the remote simulation time to respond to our own objects we tie
|
||||
// the other avatar's body to its true position with a simple spring. This is a HACK that will have to be improved later.
|
||||
const float SPRING_TIMESCALE = 0.5f;
|
||||
float tau = PHYSICS_ENGINE_FIXED_SUBSTEP / SPRING_TIMESCALE;
|
||||
btVector3 currentPosition = worldTrans.getOrigin();
|
||||
btVector3 targetPosition = glmToBullet(getObjectPosition());
|
||||
btTransform newTransform;
|
||||
newTransform.setOrigin((1.0f - tau) * currentPosition + tau * targetPosition);
|
||||
newTransform.setRotation(glmToBullet(getObjectRotation()));
|
||||
_body->setWorldTransform(newTransform);
|
||||
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
btVector3 offsetToTarget = glmToBullet(getObjectPosition()) - currentPosition;
|
||||
float distance = offsetToTarget.length();
|
||||
if ((1.0f - tau) * distance > _diameter) {
|
||||
// the avatar body is far from its target --> slam position
|
||||
btTransform newTransform;
|
||||
newTransform.setOrigin(currentPosition + offsetToTarget);
|
||||
newTransform.setRotation(glmToBullet(getObjectRotation()));
|
||||
_body->setWorldTransform(newTransform);
|
||||
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
|
||||
} else {
|
||||
// the avatar body is near its target --> slam velocity
|
||||
btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget;
|
||||
_body->setLinearVelocity(velocity);
|
||||
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
|
||||
}
|
||||
}
|
||||
|
||||
// These pure virtual methods must be implemented for each MotionState type
|
||||
|
@ -145,3 +165,8 @@ void AvatarMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
|
|||
mask = Physics::getDefaultCollisionMask(group);
|
||||
}
|
||||
|
||||
// virtual
|
||||
float AvatarMotionState::getMass() const {
|
||||
return std::static_pointer_cast<Avatar>(_avatar)->computeMass();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ class AvatarMotionState : public ObjectMotionState {
|
|||
public:
|
||||
AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape);
|
||||
|
||||
virtual void handleEasyChanges(uint32_t& flags) override;
|
||||
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
|
||||
|
||||
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
|
||||
|
||||
virtual uint32_t getIncomingDirtyFlags() override;
|
||||
|
@ -64,6 +67,8 @@ public:
|
|||
|
||||
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
||||
|
||||
virtual float getMass() const override;
|
||||
|
||||
friend class AvatarManager;
|
||||
friend class Avatar;
|
||||
|
||||
|
@ -76,6 +81,7 @@ protected:
|
|||
virtual const btCollisionShape* computeNewShape() override;
|
||||
|
||||
AvatarSharedPointer _avatar;
|
||||
float _diameter { 0.0f };
|
||||
|
||||
uint32_t _dirtyFlags;
|
||||
};
|
||||
|
|
|
@ -60,7 +60,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
// rotate the hips back to match the flying animation.
|
||||
|
||||
const float TILT_ANGLE = 0.523f;
|
||||
const glm::quat tiltRot = glm::angleAxis(TILT_ANGLE, transformVectorFast(avatarToSensorMat, -Vectors::UNIT_X));
|
||||
const glm::quat tiltRot = glm::angleAxis(TILT_ANGLE, glm::normalize(transformVectorFast(avatarToSensorMat, -Vectors::UNIT_X)));
|
||||
|
||||
glm::vec3 headPos;
|
||||
int headIndex = myAvatar->getJointIndex("Head");
|
||||
|
|
|
@ -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.
|
||||
|
@ -431,6 +450,10 @@ void WindowScriptingInterface::takeSecondaryCameraSnapshot(const QString& filena
|
|||
qApp->takeSecondaryCameraSnapshot(filename);
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
|
||||
qApp->takeSecondaryCamera360Snapshot(cameraPosition, cubemapOutputFormat, filename);
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
|
||||
qApp->shareSnapshot(path, href);
|
||||
}
|
||||
|
|
|
@ -370,6 +370,20 @@ public slots:
|
|||
*/
|
||||
void takeSecondaryCameraSnapshot(const QString& filename = QString());
|
||||
|
||||
/**jsdoc
|
||||
* 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".
|
||||
*
|
||||
* var filename = QString();
|
||||
*/
|
||||
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const QString& filename = QString());
|
||||
|
||||
/**jsdoc
|
||||
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
|
||||
* indicates whether or not a user connection was successfully made using the Web API.
|
||||
|
@ -508,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
|
||||
|
@ -578,6 +606,16 @@ signals:
|
|||
*/
|
||||
void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a still equirectangular snapshot has been taken by calling {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
|
||||
* @function Window.snapshot360Taken
|
||||
* @param {string} pathStillSnapshot - The path and name of the snapshot image file.
|
||||
* @param {boolean} notify - The value of the <code>notify</code> parameter that {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
|
||||
* was called with.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void snapshot360Taken(const QString& path360Snapshot, bool notify);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a snapshot submitted via {@link Window.shareSnapshot|shareSnapshot} is ready for sharing. The snapshot
|
||||
* may then be shared via the {@link Account.metaverseServerURL} Web API.
|
||||
|
|
|
@ -179,7 +179,7 @@ static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPT
|
|||
void ApplicationOverlay::buildFramebufferObject() {
|
||||
PROFILE_RANGE(app, __FUNCTION__);
|
||||
|
||||
auto uiSize = qApp->getUiSize();
|
||||
auto uiSize = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale());
|
||||
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {
|
||||
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("ApplicationOverlay"));
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -132,8 +132,8 @@ void setupPreferences() {
|
|||
// Snapshots
|
||||
static const QString SNAPSHOTS { "Snapshots" };
|
||||
{
|
||||
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
|
||||
auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); emit DependencyManager::get<Snapshot>()->snapshotLocationSet(value); };
|
||||
auto getter = []()->QString { return DependencyManager::get<Snapshot>()->_snapshotsLocation.get(); };
|
||||
auto setter = [](const QString& value) { DependencyManager::get<Snapshot>()->_snapshotsLocation.set(value); emit DependencyManager::get<Snapshot>()->snapshotLocationSet(value); };
|
||||
auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtNetwork/QHttpMultiPart>
|
||||
#include <QtGui/QImage>
|
||||
#include <QPainter>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
|
@ -31,20 +32,39 @@
|
|||
#include <NodeList.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <SecondaryCamera.h>
|
||||
#include <plugins/DisplayPlugin.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "display-plugins/CompositorHelper.h"
|
||||
#include "scripting/WindowScriptingInterface.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Snapshot.h"
|
||||
#include "SnapshotUploader.h"
|
||||
#include "ToneMappingEffect.h"
|
||||
|
||||
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
|
||||
// %1 <= username, %2 <= date and time, %3 <= current location
|
||||
const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2.jpg";
|
||||
|
||||
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
|
||||
const QString SNAPSHOTS_DIRECTORY = "Snapshots";
|
||||
|
||||
const QString URL = "highfidelity_url";
|
||||
static const int SNAPSHOT_360_TIMER_INTERVAL = 350;
|
||||
|
||||
Setting::Handle<QString> Snapshot::snapshotsLocation("snapshotsLocation");
|
||||
Snapshot::Snapshot() {
|
||||
_snapshotTimer.setSingleShot(false);
|
||||
_snapshotTimer.setTimerType(Qt::PreciseTimer);
|
||||
_snapshotTimer.setInterval(SNAPSHOT_360_TIMER_INTERVAL);
|
||||
connect(&_snapshotTimer, &QTimer::timeout, this, &Snapshot::takeNextSnapshot);
|
||||
|
||||
_snapshotIndex = 0;
|
||||
_oldEnabled = false;
|
||||
_oldAttachedEntityId = 0;
|
||||
_oldOrientation = 0;
|
||||
_oldvFoV = 0;
|
||||
_oldNearClipPlaneDistance = 0;
|
||||
_oldFarClipPlaneDistance = 0;
|
||||
}
|
||||
|
||||
SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
|
||||
|
||||
|
@ -78,14 +98,236 @@ QString Snapshot::saveSnapshot(QImage image, const QString& filename, const QStr
|
|||
|
||||
QFile* snapshotFile = savedFileForSnapshot(image, false, filename, pathname);
|
||||
|
||||
// we don't need the snapshot file, so close it, grab its filename and delete it
|
||||
snapshotFile->close();
|
||||
if (snapshotFile) {
|
||||
// we don't need the snapshot file, so close it, grab its filename and delete it
|
||||
snapshotFile->close();
|
||||
|
||||
QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
|
||||
QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
|
||||
|
||||
delete snapshotFile;
|
||||
delete snapshotFile;
|
||||
|
||||
return snapshotPath;
|
||||
return snapshotPath;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static const float CUBEMAP_SIDE_PIXEL_DIMENSION = 2048.0f;
|
||||
static const float SNAPSHOT_360_FOV = 90.0f;
|
||||
static const float SNAPSHOT_360_NEARCLIP = 0.3f;
|
||||
static const float SNAPSHOT_360_FARCLIP = 16384.0f;
|
||||
static const glm::quat CAMERA_ORIENTATION_DOWN(glm::quat(glm::radians(glm::vec3(-90.0f, 0.0f, 0.0f))));
|
||||
static const glm::quat CAMERA_ORIENTATION_FRONT(glm::quat(glm::radians(glm::vec3(0.0f, 0.0f, 0.0f))));
|
||||
static const glm::quat CAMERA_ORIENTATION_LEFT(glm::quat(glm::radians(glm::vec3(0.0f, 90.0f, 0.0f))));
|
||||
static const glm::quat CAMERA_ORIENTATION_BACK(glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))));
|
||||
static const glm::quat CAMERA_ORIENTATION_RIGHT(glm::quat(glm::radians(glm::vec3(0.0f, 270.0f, 0.0f))));
|
||||
static const glm::quat CAMERA_ORIENTATION_UP(glm::quat(glm::radians(glm::vec3(90.0f, 0.0f, 0.0f))));
|
||||
void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
|
||||
_snapshotFilename = filename;
|
||||
_cubemapOutputFormat = cubemapOutputFormat;
|
||||
SecondaryCameraJobConfig* secondaryCameraRenderConfig = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
||||
|
||||
// Save initial values of secondary camera render config
|
||||
_oldEnabled = secondaryCameraRenderConfig->isEnabled();
|
||||
_oldAttachedEntityId = secondaryCameraRenderConfig->property("attachedEntityId");
|
||||
_oldOrientation = secondaryCameraRenderConfig->property("orientation");
|
||||
_oldvFoV = secondaryCameraRenderConfig->property("vFoV");
|
||||
_oldNearClipPlaneDistance = secondaryCameraRenderConfig->property("nearClipPlaneDistance");
|
||||
_oldFarClipPlaneDistance = secondaryCameraRenderConfig->property("farClipPlaneDistance");
|
||||
|
||||
if (!_oldEnabled) {
|
||||
secondaryCameraRenderConfig->enableSecondaryCameraRenderConfigs(true);
|
||||
}
|
||||
|
||||
// Initialize some secondary camera render config options for 360 snapshot capture
|
||||
static_cast<ToneMappingConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(0);
|
||||
|
||||
secondaryCameraRenderConfig->resetSizeSpectatorCamera(static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION), static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION));
|
||||
secondaryCameraRenderConfig->setProperty("attachedEntityId", "");
|
||||
secondaryCameraRenderConfig->setPosition(cameraPosition);
|
||||
secondaryCameraRenderConfig->setProperty("vFoV", SNAPSHOT_360_FOV);
|
||||
secondaryCameraRenderConfig->setProperty("nearClipPlaneDistance", SNAPSHOT_360_NEARCLIP);
|
||||
secondaryCameraRenderConfig->setProperty("farClipPlaneDistance", SNAPSHOT_360_FARCLIP);
|
||||
|
||||
// Setup for Down Image capture
|
||||
secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN);
|
||||
|
||||
_snapshotIndex = 0;
|
||||
|
||||
_snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL);
|
||||
}
|
||||
|
||||
void Snapshot::takeNextSnapshot() {
|
||||
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
||||
|
||||
// Order is:
|
||||
// 0. Down
|
||||
// 1. Front
|
||||
// 2. Left
|
||||
// 3. Back
|
||||
// 4. Right
|
||||
// 5. Up
|
||||
if (_snapshotIndex < 6) {
|
||||
_imageArray[_snapshotIndex] = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
|
||||
}
|
||||
|
||||
if (_snapshotIndex == 0) {
|
||||
// Setup for Front Image capture
|
||||
config->setOrientation(CAMERA_ORIENTATION_FRONT);
|
||||
} else if (_snapshotIndex == 1) {
|
||||
// Setup for Left Image capture
|
||||
config->setOrientation(CAMERA_ORIENTATION_LEFT);
|
||||
} else if (_snapshotIndex == 2) {
|
||||
// Setup for Back Image capture
|
||||
config->setOrientation(CAMERA_ORIENTATION_BACK);
|
||||
} else if (_snapshotIndex == 3) {
|
||||
// Setup for Right Image capture
|
||||
config->setOrientation(CAMERA_ORIENTATION_RIGHT);
|
||||
} else if (_snapshotIndex == 4) {
|
||||
// Setup for Up Image capture
|
||||
config->setOrientation(CAMERA_ORIENTATION_UP);
|
||||
} else if (_snapshotIndex > 5) {
|
||||
_snapshotTimer.stop();
|
||||
|
||||
// Reset secondary camera render config
|
||||
static_cast<ToneMappingConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1);
|
||||
config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
|
||||
config->setProperty("attachedEntityId", _oldAttachedEntityId);
|
||||
config->setProperty("vFoV", _oldvFoV);
|
||||
config->setProperty("nearClipPlaneDistance", _oldNearClipPlaneDistance);
|
||||
config->setProperty("farClipPlaneDistance", _oldFarClipPlaneDistance);
|
||||
|
||||
if (!_oldEnabled) {
|
||||
config->enableSecondaryCameraRenderConfigs(false);
|
||||
}
|
||||
|
||||
// Process six QImages
|
||||
if (_cubemapOutputFormat) {
|
||||
QtConcurrent::run([this]() { convertToCubemap(); });
|
||||
} else {
|
||||
QtConcurrent::run([this]() { convertToEquirectangular(); });
|
||||
}
|
||||
}
|
||||
|
||||
_snapshotIndex++;
|
||||
}
|
||||
|
||||
void Snapshot::convertToCubemap() {
|
||||
float outputImageHeight = CUBEMAP_SIDE_PIXEL_DIMENSION * 3.0f;
|
||||
float outputImageWidth = CUBEMAP_SIDE_PIXEL_DIMENSION * 4.0f;
|
||||
|
||||
QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32);
|
||||
|
||||
QPainter painter(&outputImage);
|
||||
QPoint destPos;
|
||||
|
||||
// Paint DownImage
|
||||
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f);
|
||||
painter.drawImage(destPos, _imageArray[0]);
|
||||
|
||||
// Paint FrontImage
|
||||
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, CUBEMAP_SIDE_PIXEL_DIMENSION);
|
||||
painter.drawImage(destPos, _imageArray[1]);
|
||||
|
||||
// Paint LeftImage
|
||||
destPos = QPoint(0, CUBEMAP_SIDE_PIXEL_DIMENSION);
|
||||
painter.drawImage(destPos, _imageArray[2]);
|
||||
|
||||
// Paint BackImage
|
||||
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION * 3.0f, CUBEMAP_SIDE_PIXEL_DIMENSION);
|
||||
painter.drawImage(destPos, _imageArray[3]);
|
||||
|
||||
// Paint RightImage
|
||||
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f, CUBEMAP_SIDE_PIXEL_DIMENSION);
|
||||
painter.drawImage(destPos, _imageArray[4]);
|
||||
|
||||
// Paint UpImage
|
||||
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, 0);
|
||||
painter.drawImage(destPos, _imageArray[5]);
|
||||
|
||||
painter.end();
|
||||
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), true);
|
||||
}
|
||||
|
||||
void Snapshot::convertToEquirectangular() {
|
||||
// I got help from StackOverflow while writing this code:
|
||||
// https://stackoverflow.com/questions/34250742/converting-a-cubemap-into-equirectangular-panorama
|
||||
|
||||
int cubeFaceWidth = static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION);
|
||||
int cubeFaceHeight = static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION);
|
||||
float outputImageHeight = CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f;
|
||||
float outputImageWidth = outputImageHeight * 2.0f;
|
||||
QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32);
|
||||
outputImage.fill(0);
|
||||
QRgb sourceColorValue;
|
||||
float phi, theta;
|
||||
|
||||
for (int j = 0; j < outputImageHeight; j++) {
|
||||
theta = (1.0f - ((float)j / outputImageHeight)) * PI;
|
||||
|
||||
for (int i = 0; i < outputImageWidth; i++) {
|
||||
phi = ((float)i / outputImageWidth) * 2.0f * PI;
|
||||
|
||||
float x = glm::sin(phi) * glm::sin(theta) * -1.0f;
|
||||
float y = glm::cos(theta);
|
||||
float z = glm::cos(phi) * glm::sin(theta) * -1.0f;
|
||||
|
||||
float a = std::max(std::max(std::abs(x), std::abs(y)), std::abs(z));
|
||||
|
||||
float xa = x / a;
|
||||
float ya = y / a;
|
||||
float za = z / a;
|
||||
|
||||
// Pixel in the source images
|
||||
int xPixel, yPixel;
|
||||
QImage sourceImage;
|
||||
|
||||
if (xa == 1) {
|
||||
// Right image
|
||||
xPixel = (int)((((za + 1.0f) / 2.0f) - 1.0f) * cubeFaceWidth);
|
||||
yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
|
||||
sourceImage = _imageArray[4];
|
||||
} else if (xa == -1) {
|
||||
// Left image
|
||||
xPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceWidth);
|
||||
yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
|
||||
sourceImage = _imageArray[2];
|
||||
} else if (ya == 1) {
|
||||
// Down image
|
||||
xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
|
||||
yPixel = (int)((((za + 1.0f) / 2.0f) - 1.0f) * cubeFaceHeight);
|
||||
sourceImage = _imageArray[0];
|
||||
} else if (ya == -1) {
|
||||
// Up image
|
||||
xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
|
||||
yPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceHeight);
|
||||
sourceImage = _imageArray[5];
|
||||
} else if (za == 1) {
|
||||
// Front image
|
||||
xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
|
||||
yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
|
||||
sourceImage = _imageArray[1];
|
||||
} else if (za == -1) {
|
||||
// Back image
|
||||
xPixel = (int)((((xa + 1.0f) / 2.0f) - 1.0f) * cubeFaceWidth);
|
||||
yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
|
||||
sourceImage = _imageArray[3];
|
||||
} else {
|
||||
qDebug() << "Unknown face encountered when processing 360 Snapshot";
|
||||
xPixel = 0;
|
||||
yPixel = 0;
|
||||
}
|
||||
|
||||
xPixel = std::min(std::abs(xPixel), 2047);
|
||||
yPixel = std::min(std::abs(yPixel), 2047);
|
||||
|
||||
sourceColorValue = sourceImage.pixel(xPixel, yPixel);
|
||||
outputImage.setPixel(i, j, sourceColorValue);
|
||||
}
|
||||
}
|
||||
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), true);
|
||||
}
|
||||
|
||||
QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) {
|
||||
|
@ -123,12 +365,12 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt
|
|||
if (!userSelectedPathname.isNull()) {
|
||||
snapshotFullPath = userSelectedPathname;
|
||||
} else {
|
||||
snapshotFullPath = snapshotsLocation.get();
|
||||
snapshotFullPath = _snapshotsLocation.get();
|
||||
}
|
||||
|
||||
if (snapshotFullPath.isEmpty()) {
|
||||
snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Choose Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
||||
snapshotsLocation.set(snapshotFullPath);
|
||||
_snapshotsLocation.set(snapshotFullPath);
|
||||
}
|
||||
|
||||
if (!snapshotFullPath.isEmpty()) { // not cancelled
|
||||
|
@ -140,7 +382,27 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt
|
|||
snapshotFullPath.append(filename);
|
||||
|
||||
QFile* imageFile = new QFile(snapshotFullPath);
|
||||
imageFile->open(QIODevice::WriteOnly);
|
||||
while (!imageFile->open(QIODevice::WriteOnly)) {
|
||||
// It'd be better for the directory chooser to restore the cursor to its previous state
|
||||
// after choosing a directory, but if the user has entered this codepath,
|
||||
// something terrible has happened. Let's just show the user their cursor so they can get
|
||||
// out of this awful state.
|
||||
qApp->getApplicationCompositor().getReticleInterface()->setVisible(true);
|
||||
qApp->getApplicationCompositor().getReticleInterface()->setAllowMouseCapture(true);
|
||||
|
||||
snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Write Error - Choose New Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
||||
if (snapshotFullPath.isEmpty()) {
|
||||
return NULL;
|
||||
}
|
||||
_snapshotsLocation.set(snapshotFullPath);
|
||||
|
||||
if (!snapshotFullPath.endsWith(QDir::separator())) {
|
||||
snapshotFullPath.append(QDir::separator());
|
||||
}
|
||||
snapshotFullPath.append(filename);
|
||||
|
||||
imageFile = new QFile(snapshotFullPath);
|
||||
}
|
||||
|
||||
shot.save(imageFile, 0, IMAGE_QUALITY);
|
||||
imageFile->close();
|
||||
|
@ -210,9 +472,9 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) {
|
|||
}
|
||||
|
||||
QString Snapshot::getSnapshotsLocation() {
|
||||
return snapshotsLocation.get("");
|
||||
return _snapshotsLocation.get("");
|
||||
}
|
||||
|
||||
void Snapshot::setSnapshotsLocation(const QString& location) {
|
||||
snapshotsLocation.set(location);
|
||||
_snapshotsLocation.set(location);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <QString>
|
||||
#include <QStandardPaths>
|
||||
#include <QUrl>
|
||||
#include <QTimer>
|
||||
#include <QtGui/QImage>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include <DependencyManager.h>
|
||||
|
@ -46,12 +48,14 @@ class Snapshot : public QObject, public Dependency {
|
|||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
public:
|
||||
static QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString());
|
||||
static QTemporaryFile* saveTempSnapshot(QImage image);
|
||||
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
||||
Snapshot();
|
||||
QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString());
|
||||
void save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename);
|
||||
QTemporaryFile* saveTempSnapshot(QImage image);
|
||||
SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
||||
|
||||
static Setting::Handle<QString> snapshotsLocation;
|
||||
static void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
||||
Setting::Handle<QString> _snapshotsLocation{ "snapshotsLocation" };
|
||||
void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -76,11 +80,27 @@ public slots:
|
|||
*/
|
||||
Q_INVOKABLE void setSnapshotsLocation(const QString& location);
|
||||
|
||||
private slots:
|
||||
void takeNextSnapshot();
|
||||
|
||||
private:
|
||||
static QFile* savedFileForSnapshot(QImage& image,
|
||||
QFile* savedFileForSnapshot(QImage& image,
|
||||
bool isTemporary,
|
||||
const QString& userSelectedFilename = QString(),
|
||||
const QString& userSelectedPathname = QString());
|
||||
QString _snapshotFilename;
|
||||
bool _cubemapOutputFormat;
|
||||
QTimer _snapshotTimer;
|
||||
qint16 _snapshotIndex;
|
||||
bool _oldEnabled;
|
||||
QVariant _oldAttachedEntityId;
|
||||
QVariant _oldOrientation;
|
||||
QVariant _oldvFoV;
|
||||
QVariant _oldNearClipPlaneDistance;
|
||||
QVariant _oldFarClipPlaneDistance;
|
||||
QImage _imageArray[6];
|
||||
void convertToCubemap();
|
||||
void convertToEquirectangular();
|
||||
};
|
||||
|
||||
#endif // hifi_Snapshot_h
|
||||
|
|
|
@ -103,18 +103,40 @@ void SnapshotAnimated::captureFrames() {
|
|||
}
|
||||
}
|
||||
|
||||
void SnapshotAnimated::clearTempVariables() {
|
||||
// Clear out the frame and frame delay vectors.
|
||||
// Also release the memory not required to store the items.
|
||||
SnapshotAnimated::snapshotAnimatedFrameVector.clear();
|
||||
SnapshotAnimated::snapshotAnimatedFrameVector.squeeze();
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.clear();
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.squeeze();
|
||||
// Reset the current frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedTimestamp = 0;
|
||||
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
|
||||
}
|
||||
|
||||
void SnapshotAnimated::processFrames() {
|
||||
uint32_t width = SnapshotAnimated::snapshotAnimatedFrameVector[0].width();
|
||||
uint32_t height = SnapshotAnimated::snapshotAnimatedFrameVector[0].height();
|
||||
|
||||
// Create the GIF from the temporary files
|
||||
// Write out the header and beginning of the GIF file
|
||||
GifBegin(
|
||||
if (!GifBegin(
|
||||
&(SnapshotAnimated::snapshotAnimatedGifWriter),
|
||||
qPrintable(SnapshotAnimated::snapshotAnimatedPath),
|
||||
width,
|
||||
height,
|
||||
1); // "1" means "yes there is a delay" with this GifCreator library.
|
||||
1)) { // "1" means "yes there is a delay" with this GifCreator library.
|
||||
|
||||
// We should never, ever get here. If we do, that means that writing a still JPG to the filesystem
|
||||
// has succeeded, but that writing the tiny header to a GIF file in the same directory failed.
|
||||
// If that happens, we _could_ throw up the "Folder Chooser" dialog like we do for still JPG images,
|
||||
// but I have no way of testing whether or not that'll work or get properly exercised,
|
||||
// so I'm not going to bother for now.
|
||||
SnapshotAnimated::clearTempVariables();
|
||||
qDebug() << "Animated snapshot header failed to write - aborting GIF processing.";
|
||||
return;
|
||||
}
|
||||
for (int itr = 0; itr < SnapshotAnimated::snapshotAnimatedFrameVector.size(); itr++) {
|
||||
// Write each frame to the GIF
|
||||
GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter),
|
||||
|
@ -126,15 +148,7 @@ void SnapshotAnimated::processFrames() {
|
|||
// Write out the end of the GIF
|
||||
GifEnd(&(SnapshotAnimated::snapshotAnimatedGifWriter));
|
||||
|
||||
// Clear out the frame and frame delay vectors.
|
||||
// Also release the memory not required to store the items.
|
||||
SnapshotAnimated::snapshotAnimatedFrameVector.clear();
|
||||
SnapshotAnimated::snapshotAnimatedFrameVector.squeeze();
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.clear();
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.squeeze();
|
||||
// Reset the current frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedTimestamp = 0;
|
||||
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
|
||||
SnapshotAnimated::clearTempVariables();
|
||||
|
||||
// Update the "Share" dialog with the processed GIF.
|
||||
emit SnapshotAnimated::snapshotAnimatedDM->processingGifCompleted(SnapshotAnimated::snapshotAnimatedPath);
|
||||
|
|
|
@ -49,6 +49,7 @@ private:
|
|||
|
||||
static void captureFrames();
|
||||
static void processFrames();
|
||||
static void clearTempVariables();
|
||||
public:
|
||||
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
|
||||
static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -126,6 +126,7 @@ private: \
|
|||
* @property {number} gpuTextureResidentMemory - <em>Read-only.</em>
|
||||
* @property {number} gpuTextureFramebufferMemory - <em>Read-only.</em>
|
||||
* @property {number} gpuTextureResourceMemory - <em>Read-only.</em>
|
||||
* @property {number} gpuTextureResourceIdealMemory - <em>Read-only.</em>
|
||||
* @property {number} gpuTextureResourcePopulatedMemory - <em>Read-only.</em>
|
||||
* @property {number} gpuTextureExternalMemory - <em>Read-only.</em>
|
||||
* @property {string} gpuTextureMemoryPressureState - <em>Read-only.</em>
|
||||
|
@ -270,6 +271,7 @@ class Stats : public QQuickItem {
|
|||
STATS_PROPERTY(int, gpuTextureResidentMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureFramebufferMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureResourceMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureResourceIdealMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureResourcePopulatedMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureExternalMemory, 0)
|
||||
STATS_PROPERTY(QString, gpuTextureMemoryPressureState, QString())
|
||||
|
@ -918,6 +920,13 @@ signals:
|
|||
*/
|
||||
void gpuTextureResourceMemoryChanged();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the value of the <code>gpuTextureResourceIdealMemory</code> property changes.
|
||||
* @function Stats.gpuTextureResourceIdealMemoryChanged
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void gpuTextureResourceIdealMemoryChanged();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the value of the <code>gpuTextureResourcePopulatedMemory</code> property changes.
|
||||
* @function Stats.gpuTextureResourcePopulatedMemoryChanged
|
||||
|
|
|
@ -118,7 +118,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
|
||||
auto size = qApp->getUiSize();
|
||||
auto size = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale());
|
||||
int width = size.x;
|
||||
int height = size.y;
|
||||
mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, -1000, 1000);
|
||||
|
|
|
@ -60,7 +60,7 @@ public:
|
|||
bool intersects { false };
|
||||
OverlayID overlayID { UNKNOWN_OVERLAY_ID };
|
||||
float distance { 0 };
|
||||
BoxFace face;
|
||||
BoxFace face { UNKNOWN_FACE };
|
||||
glm::vec3 surfaceNormal;
|
||||
glm::vec3 intersection;
|
||||
QVariantMap extraInfo;
|
||||
|
|
|
@ -799,7 +799,6 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Avatar::simulateAttachments(float deltaTime) {
|
||||
assert(_attachmentModels.size() == _attachmentModelsTexturesLoaded.size());
|
||||
PerformanceTimer perfTimer("attachments");
|
||||
|
@ -1482,12 +1481,14 @@ void Avatar::updateDisplayNameAlpha(bool showDisplayName) {
|
|||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||
float uniformScale = getModelScale();
|
||||
shapeInfo.setCapsuleY(uniformScale * _skeletonModel->getBoundingCapsuleRadius(),
|
||||
0.5f * uniformScale * _skeletonModel->getBoundingCapsuleHeight());
|
||||
shapeInfo.setOffset(uniformScale * _skeletonModel->getBoundingCapsuleOffset());
|
||||
float radius = uniformScale * _skeletonModel->getBoundingCapsuleRadius();
|
||||
float height = uniformScale * _skeletonModel->getBoundingCapsuleHeight();
|
||||
shapeInfo.setCapsuleY(radius, 0.5f * height);
|
||||
|
||||
glm::vec3 offset = uniformScale * _skeletonModel->getBoundingCapsuleOffset();
|
||||
shapeInfo.setOffset(offset);
|
||||
}
|
||||
|
||||
void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
|
||||
|
@ -1510,9 +1511,8 @@ float Avatar::computeMass() {
|
|||
return _density * TWO_PI * radius * radius * (glm::length(end - start) + 2.0f * radius / 3.0f);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Avatar::rebuildCollisionShape() {
|
||||
addPhysicsFlags(Simulation::DIRTY_SHAPE);
|
||||
addPhysicsFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
|
||||
}
|
||||
|
||||
void Avatar::setPhysicsCallback(AvatarPhysicsCallback cb) {
|
||||
|
|
|
@ -28,69 +28,76 @@ void Basic2DWindowOpenGLDisplayPlugin::customizeContext() {
|
|||
qreal dpi = getFullscreenTarget()->physicalDotsPerInch();
|
||||
_virtualPadPixelSize = dpi * VirtualPad::Manager::BASE_DIAMETER_PIXELS / VirtualPad::Manager::DPI;
|
||||
|
||||
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png";
|
||||
auto image = QImage(iconPath);
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
|
||||
if (!_virtualPadStickTexture) {
|
||||
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png";
|
||||
auto image = QImage(iconPath);
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
|
||||
|
||||
_virtualPadStickTexture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
_virtualPadStickTexture->setSource("virtualPad stick");
|
||||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
_virtualPadStickTexture->setUsage(usage.build());
|
||||
_virtualPadStickTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
|
||||
_virtualPadStickTexture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||
_virtualPadStickTexture->setAutoGenerateMips(true);
|
||||
_virtualPadStickTexture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
_virtualPadStickTexture->setSource("virtualPad stick");
|
||||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
_virtualPadStickTexture->setUsage(usage.build());
|
||||
_virtualPadStickTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
|
||||
_virtualPadStickTexture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||
_virtualPadStickTexture->setAutoGenerateMips(true);
|
||||
}
|
||||
}
|
||||
|
||||
iconPath = PathUtils::resourcesPath() + "images/analog_stick_base.png";
|
||||
image = QImage(iconPath);
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
|
||||
if (!_virtualPadStickBaseTexture) {
|
||||
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick_base.png";
|
||||
auto image = QImage(iconPath);
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
|
||||
|
||||
_virtualPadStickBaseTexture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
_virtualPadStickBaseTexture->setSource("virtualPad base");
|
||||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
_virtualPadStickBaseTexture->setUsage(usage.build());
|
||||
_virtualPadStickBaseTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
|
||||
_virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||
_virtualPadStickBaseTexture->setAutoGenerateMips(true);
|
||||
_virtualPadStickBaseTexture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
_virtualPadStickBaseTexture->setSource("virtualPad base");
|
||||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
_virtualPadStickBaseTexture->setUsage(usage.build());
|
||||
_virtualPadStickBaseTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
|
||||
_virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||
_virtualPadStickBaseTexture->setAutoGenerateMips(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_virtualPadJumpBtnPixelSize = dpi * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI;
|
||||
iconPath = PathUtils::resourcesPath() + "images/fly.png";
|
||||
image = QImage(iconPath);
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio);
|
||||
image = image.mirrored();
|
||||
if (!_virtualPadJumpBtnTexture) {
|
||||
auto iconPath = PathUtils::resourcesPath() + "images/fly.png";
|
||||
auto image = QImage(iconPath);
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio);
|
||||
image = image.mirrored();
|
||||
|
||||
_virtualPadJumpBtnTexture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
_virtualPadJumpBtnTexture->setSource("virtualPad jump");
|
||||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
_virtualPadJumpBtnTexture->setUsage(usage.build());
|
||||
_virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
|
||||
_virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||
_virtualPadJumpBtnTexture->setAutoGenerateMips(true);
|
||||
_virtualPadJumpBtnTexture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
_virtualPadJumpBtnTexture->setSource("virtualPad jump");
|
||||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
_virtualPadJumpBtnTexture->setUsage(usage.build());
|
||||
_virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
|
||||
_virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||
_virtualPadJumpBtnTexture->setAutoGenerateMips(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Parent::customizeContext();
|
||||
|
@ -124,44 +131,32 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() {
|
|||
// render stick base
|
||||
auto stickBaseTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getFirstTouch(),
|
||||
_virtualPadPixelSize, _virtualPadPixelSize);
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
batch.setResourceTexture(0, _virtualPadStickBaseTexture);
|
||||
batch.resetViewTransform();
|
||||
batch.setModelTransform(stickBaseTransform);
|
||||
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
// render stick head
|
||||
auto stickTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getCurrentTouch(),
|
||||
_virtualPadPixelSize, _virtualPadPixelSize);
|
||||
_virtualPadPixelSize, _virtualPadPixelSize);
|
||||
auto jumpTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(),
|
||||
_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize);
|
||||
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.resetViewTransform();
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
batch.setResourceTexture(0, _virtualPadStickTexture);
|
||||
batch.resetViewTransform();
|
||||
batch.setModelTransform(stickTransform);
|
||||
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
|
||||
|
||||
batch.setResourceTexture(0, _virtualPadStickBaseTexture);
|
||||
batch.setModelTransform(stickBaseTransform);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) {
|
||||
// render stick head
|
||||
auto jumpTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(),
|
||||
_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize);
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
|
||||
batch.setResourceTexture(0, _virtualPadStickTexture);
|
||||
batch.setModelTransform(stickTransform);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
|
||||
if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) {
|
||||
batch.setResourceTexture(0, _virtualPadJumpBtnTexture);
|
||||
batch.resetViewTransform();
|
||||
batch.setModelTransform(jumpTransform);
|
||||
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
Parent::compositeExtra();
|
||||
|
|
|
@ -275,7 +275,7 @@ bool CompositorHelper::getReticleOverDesktop() const {
|
|||
// as being over the desktop.
|
||||
if (isHMD()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
|
||||
glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale();
|
||||
static const glm::vec2 minOverlayPosition;
|
||||
if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) ||
|
||||
glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) {
|
||||
|
@ -317,7 +317,7 @@ void CompositorHelper::sendFakeMouseEvent() {
|
|||
|
||||
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
|
||||
if (isHMD()) {
|
||||
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
|
||||
glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale();
|
||||
// FIXME don't allow negative mouseExtra
|
||||
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
|
||||
glm::vec2 minMouse = vec2(0) - mouseExtra;
|
||||
|
|
|
@ -885,7 +885,7 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
|
|||
}
|
||||
|
||||
void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
||||
auto renderSize = getRecommendedRenderSize();
|
||||
auto renderSize = glm::uvec2(glm::vec2(getRecommendedRenderSize()) * getRenderResolutionScale());
|
||||
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
|
||||
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
|
|||
updateAmbientLightFromEntity(entity);
|
||||
}
|
||||
|
||||
if (skyboxChanged) {
|
||||
if (skyboxChanged || _proceduralUserData != entity->getUserData()) {
|
||||
updateKeyBackgroundFromEntity(entity);
|
||||
}
|
||||
|
||||
|
@ -295,6 +295,10 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
|
|||
return true;
|
||||
}
|
||||
|
||||
if (entity->getUserData() != _proceduralUserData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (_typedEntity->getCompoundShapeURL() != _lastShapeURL) {
|
||||
return true;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
502
libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp
Normal file
502
libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp
Normal 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";
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -35,8 +35,11 @@ void main(void) {
|
|||
#ifdef PROCEDURAL
|
||||
|
||||
vec3 color = getSkyboxColor();
|
||||
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
|
||||
color = pow(color, vec3(2.2));
|
||||
// Protect from NaNs and negative values
|
||||
color = mix(color, vec3(0), isnan(color));
|
||||
color = max(color, vec3(0));
|
||||
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
|
||||
color = pow(color, vec3(2.2));
|
||||
_fragColor = vec4(color, 0.0);
|
||||
|
||||
// FIXME: scribe does not yet scrub out else statements
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue