Merge branch 'master' of github.com:highfidelity/hifi into serverless-domains

This commit is contained in:
Seth Alves 2018-03-21 11:11:43 -07:00
commit 4033af9b11
33 changed files with 411 additions and 134 deletions

View file

@ -1,4 +1,4 @@
# If we're running under the gradle build, HIFI_ANDROID will be set here, but
# If we're running under the gradle build, HIFI_ANDROID will be set here, but
# ANDROID will not be set until after the `project` statement. This is the *ONLY*
# place you need to use `HIFI_ANDROID` instead of `ANDROID`
if (WIN32 AND NOT HIFI_ANDROID)
@ -61,8 +61,6 @@ else()
endif()
option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF)
set(PLATFORM_QT_GL OpenGL)
if (USE_GLES)
@ -132,8 +130,8 @@ set_packaging_parameters()
# FIXME hack to work on the proper Android toolchain
if (ANDROID)
add_subdirectory(android/app)
return()
add_subdirectory(android/app)
return()
endif()
# add subdirectories for all targets
@ -148,16 +146,14 @@ if (BUILD_SERVER)
endif()
if (BUILD_CLIENT)
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
if (ANDROID)
add_subdirectory(gvr-interface)
set_target_properties(gvr-interface PROPERTIES FOLDER "Apps")
endif()
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
option(USE_SIXENSE "Build Interface with sixense library/plugin" OFF)
endif()
if (BUILD_CLIENT OR BUILD_SERVER)
add_subdirectory(plugins)
add_subdirectory(plugins)
endif()
# BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway

View file

@ -17,6 +17,8 @@ To produce an executable installer on Windows, the following are required:
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.

View file

@ -46,9 +46,18 @@ macro(GENERATE_INSTALLERS)
set(UNINSTALLER_HEADER_IMAGE "")
fix_path_for_nsis(${_UNINSTALLER_HEADER_BAD_PATH} UNINSTALLER_HEADER_IMAGE)
# grab the latest VC redist (2017) and add it to the installer, our NSIS template
# will call it during the install
install(CODE "file(DOWNLOAD https://go.microsoft.com/fwlink/?LinkId=746572 \"\${CMAKE_INSTALL_PREFIX}/vcredist_x64.exe\")")
# we use external libraries that still need the 120 (VS2013) redistributables
# so we include them as well until those external libraries are updated
# to use the redistributables that match what we build our applications for
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS
"C:/Windows/System32/msvcp120.dll"
"C:/Windows/System32/msvcr120.dll"
)
set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${INTERFACE_INSTALL_DIR})
set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT ${CLIENT_COMPONENT})
include(InstallRequiredSystemLibraries)
elseif (APPLE)
# produce a drag and drop DMG on OS X
set(CPACK_GENERATOR "DragNDrop")

View file

@ -39,7 +39,9 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> \"$<TARGET_FILE:${TARGET_NAME}>\""
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND}\
${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release>\
--no-compiler-runtime --no-opengl-sw --no-angle -no-system-d3d-compiler \"$<TARGET_FILE:${TARGET_NAME}>\""
)
set(QTAUDIO_PATH "$<TARGET_FILE_DIR:${TARGET_NAME}>/audio")

View file

@ -149,6 +149,8 @@ macro(SET_PACKAGING_PARAMETERS)
set(CLIENT_LAUNCH_NOW_REG_KEY "ClientLaunchAfterInstall")
set(SERVER_LAUNCH_NOW_REG_KEY "ServerLaunchAfterInstall")
set(CUSTOM_INSTALL_REG_KEY "CustomInstall")
set(CLIENT_ID_REG_KEY "ClientGUID")
set(GA_TRACKING_ID $ENV{GA_TRACKING_ID})
endif ()
# setup component categories for installer

View file

@ -41,6 +41,8 @@ set(CONSOLE_STARTUP_REG_KEY "@CONSOLE_STARTUP_REG_KEY@")
set(SERVER_LAUNCH_NOW_REG_KEY "@SERVER_LAUNCH_NOW_REG_KEY@")
set(CLIENT_LAUNCH_NOW_REG_KEY "@CLIENT_LAUNCH_NOW_REG_KEY@")
set(CUSTOM_INSTALL_REG_KEY "@CUSTOM_INSTALL_REG_KEY@")
set(GA_TRACKING_ID "@GA_TRACKING_ID@")
set(CLIENT_ID_REG_KEY "@CLIENT_ID_REG_KEY@")
set(INSTALLER_HEADER_IMAGE "@INSTALLER_HEADER_IMAGE@")
set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@")
set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@")

View file

@ -319,6 +319,78 @@ Function DownloadFile
FunctionEnd
!endif
!include NSISpcre.nsh
!insertmacro REMatches
Var CampaignName
!macro GetCampaignName RetVar
Call GetCampaignName
Pop ${RetVar}
!macroend
Function GetCampaignName
Push $0 ; Stash $0
; Parse filename out of the path
${RECaptureMatches} $0 "([^\\]*\\)*(.*)\.exe" $EXEPATH 0
${If} $0 == 2
Pop $0 ; Discard Path
Pop $0 ; Recover filename
; Parse campaign out of the filename
${RECaptureMatches} $0 "HighFidelity-([^-]*-)Beta-.*" $0 0
${If} $0 == 1
Pop $0 ; Recover campaign name
StrCpy $0 $0 -1 0 ; Remove trailing - and copy to _RetVar
${Else}
StrCpy $0 ""
${EndIf}
${Else}
StrCpy $0 ""
${EndIf}
Exch $0 ; Restore $0 and push result
FunctionEnd
!macro CreateGUID RetVar
System::Call 'ole32::CoCreateGuid(g .s)'
Pop ${RetVar}
; Strip opening and closing braces
StrCpy ${RetVar} ${RetVar} -1 1
!macroend
Var GAClientID
!macro InitGAClientID
; Generate a new GUID on every run for now
!insertmacro CreateGUID $GAClientID
!macroend
!macro GoogleAnalytics Category Action Label Value
${If} "@GA_TRACKING_ID@" != ""
Push $0
Push $1
StrCpy $0 "https://google-analytics.com/collect?v=1&tid=@GA_TRACKING_ID@"
StrCpy $0 "$0&cid=$GAClientID&t=event&ec=${Category}&ea=${Action}"
${If} "${Label}" != ""
StrCpy $0 "$0&el=${Label}"
${EndIf}
${If} "${Value}" != ""
StrCpy $0 "$0&ev=${Value}"
${EndIf}
GetTempFileName $1
inetc::get /SILENT $0 $1 /END
Delete $1
Pop $1
Pop $0
${EndIf}
!macroend
;--------------------------------
; Installation types
@ -342,28 +414,32 @@ SectionEnd
;--------------------------------
;Pages
!define MUI_CUSTOMFUNCTION_ABORT OnUserAbort
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomePre
!insertmacro MUI_PAGE_WELCOME
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageLicensePre
!insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@"
Page custom InstallTypesPage ReadInstallTypes
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageDirectoryPre
!insertmacro MUI_PAGE_DIRECTORY
;Start Menu Folder Page Configuration
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageStartMenuPre
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageComponentsPre
@CPACK_NSIS_PAGE_COMPONENTS@
Page custom PostInstallOptionsPage ReadPostInstallOptions
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
@ -452,8 +528,40 @@ Var CopyFromProductionCheckbox
Var ExpressInstallRadioButton
Var CustomInstallRadioButton
Var InstallTypeDialog
Var Express
Var CustomInstallTemporaryState
Var Express
!macro MaybeSkipPage
; Check if Express is set, if so, abort the post install options page
${If} $Express == "1"
Abort
${EndIf}
!macroend
Function OnUserAbort
!insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" ""
FunctionEnd
Function PageWelcomePre
!insertmacro GoogleAnalytics "Installer" "Welcome" "" ""
FunctionEnd
Function PageLicensePre
!insertmacro GoogleAnalytics "Installer" "License" "" ""
FunctionEnd
Function PageDirectoryPre
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Directory" "" ""
FunctionEnd
Function PageStartMenuPre
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "StartMenu" "" ""
FunctionEnd
Function PageComponentsPre
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Components" "" ""
FunctionEnd
Function PageInstallFilesPre
!insertmacro GoogleAnalytics "Installer" "Install" "" ""
FunctionEnd
!macro SetInstallOption Checkbox OptionName Default
; reads the value for the given install option to the registry
@ -472,6 +580,8 @@ Var CustomInstallTemporaryState
!macroend
Function InstallTypesPage
!insertmacro GoogleAnalytics "Installer" "Install Types" "" ""
!insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install"
nsDialogs::Create 1018
@ -523,14 +633,10 @@ Function ChangeCustomLabel
Pop $R1
FunctionEnd
Function AbortFunction
; Check if Express is set, if so, abort the post install options page
StrCmp $Express "1" 0 end
Abort
end:
FunctionEnd
Function PostInstallOptionsPage
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Post Install Options" "" ""
!insertmacro MUI_HEADER_TEXT "Setup Options" ""
nsDialogs::Create 1018
@ -540,11 +646,6 @@ Function PostInstallOptionsPage
Abort
${EndIf}
; Check if Express is set, if so, abort the post install options page
StrCmp $Express "1" 0 end
Abort
end:
StrCpy $CurrentOffset 0
StrCpy $OffsetUnits u
@ -837,9 +938,6 @@ Section "-Core installation"
Delete "$INSTDIR\ui_resources_200_percent.pak"
Delete "$INSTDIR\vccorlib120.dll"
Delete "$INSTDIR\version"
Delete "$INSTDIR\msvcr140.dll"
Delete "$INSTDIR\msvcp140.dll"
Delete "$INSTDIR\vcruntime140.dll"
Delete "$INSTDIR\xinput1_3.dll"
; Delete old desktop shortcuts before they were renamed during Sandbox rename
@ -858,11 +956,8 @@ Section "-Core installation"
; Rename the incorrectly cased Raleway font
Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml"
ExecWait "$INSTDIR\vcredist_x64.exe /install /q /norestart"
; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console)
RMDir /r "$INSTDIR\Interface"
Delete "$INSTDIR\vcredist_x64.exe"
;Use the entire tree produced by the INSTALL target. Keep the
;list of directories here in sync with the RMDir commands below.
@ -965,6 +1060,7 @@ Section "-Core installation"
; Handle whichever post install options were set
Call HandlePostInstallOptions
!insertmacro GoogleAnalytics "Installer" "Done" "" ""
SectionEnd
!include nsProcess.nsh
@ -979,7 +1075,7 @@ SectionEnd
${If} $R0 == 0
; the process is running, ask the user to close it
${If} "${displayName}" == "@CONSOLE_DISPLAY_NAME@"
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \
"${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the system tray and click Retry to continue." \
@ -992,6 +1088,8 @@ SectionEnd
/SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0
${EndIf}
!insertmacro GoogleAnalytics "Installer" "Abort" "${displayName} Running" ""
; If the user decided to cancel, stop the current installer/uninstaller
Abort
@ -1219,6 +1317,11 @@ Function .onInit
Quit
!endif
!insertmacro InitGAClientID
!insertmacro GetCampaignName $CampaignName
!insertmacro GoogleAnalytics "Installer" "Start" "$CampaignName" ""
; make sure none of the installed applications are still running
!insertmacro CheckForRunningApplications "installed" "Installer"
${nsProcess::Unload}

View file

@ -191,7 +191,11 @@ add_dependencies(${TARGET_NAME} resources)
if (WIN32)
# These are external plugins, but we need to do the 'add dependency' here so that their
# binary directories get added to the fixup path
add_dependency_external_projects(sixense)
if (USE_SIXENSE)
add_dependency_external_projects(sixense)
endif ()
add_dependency_external_projects(sdl2)
add_dependency_external_projects(OpenVR)
add_dependency_external_projects(neuron)
@ -328,13 +332,13 @@ if (APPLE)
else()
# copy the resources files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${RESOURCES_RCC}"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${RESOURCES_RCC}"
"$<TARGET_FILE_DIR:interface>"
# FIXME, the edit script code loads HTML from the scripts folder
# which in turn relies on CSS that refers to the fonts. In theory
# we should be able to modify the CSS to reference the QRC path to
# the ttf files, but doing so generates a CORS policy violation,
# we should be able to modify the CSS to reference the QRC path to
# the ttf files, but doing so generates a CORS policy violation,
# so we have to retain a copy of the fonts outside of the resources binary
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/resources/fonts"
@ -389,3 +393,6 @@ endif()
add_dependency_external_projects(GifCreator)
find_package(GifCreator REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS})
# tell CMake to exclude ui_console.h for policy CMP0071
set_property(SOURCE ui_console.h PROPERTY SKIP_AUTOMOC ON)

View file

@ -2734,6 +2734,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
@ -7448,10 +7449,35 @@ bool Application::isThrottleRendering() const {
}
bool Application::hasFocus() const {
if (_displayPlugin) {
return getActiveDisplayPlugin()->hasFocus();
bool result = (QApplication::activeWindow() != nullptr);
#if defined(Q_OS_WIN)
// On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
// take user focus away from their current window. So also check whether the application is the user's current foreground
// window.
result = result && (HWND)QApplication::activeWindow()->winId() == GetForegroundWindow();
#endif
return result;
}
void Application::setFocus() {
// Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
// flashes the taskbar icon.
auto window = qApp->getWindow();
window->activateWindow();
}
void Application::raise() {
auto windowState = qApp->getWindow()->windowState();
if (windowState & Qt::WindowMinimized) {
if (windowState & Qt::WindowMaximized) {
qApp->getWindow()->showMaximized();
} else if (windowState & Qt::WindowFullScreen) {
qApp->getWindow()->showFullScreen();
} else {
qApp->getWindow()->showNormal();
}
}
return (QApplication::activeWindow() != nullptr);
qApp->getWindow()->raise();
}
void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) {

View file

@ -161,6 +161,8 @@ public:
QRect getRecommendedHUDRect() const;
glm::vec2 getDeviceSize() const;
bool hasFocus() const;
void setFocus();
void raise();
void showCursor(const Cursor::Icon& cursor);

View file

@ -74,16 +74,14 @@ QScriptValue WindowScriptingInterface::hasFocus() {
void WindowScriptingInterface::setFocus() {
// It's forbidden to call focus() from another thread.
qApp->postLambdaEvent([] {
auto window = qApp->getWindow();
window->activateWindow();
window->setFocus();
qApp->setFocus();
});
}
void WindowScriptingInterface::raiseMainWindow() {
// It's forbidden to call raise() from another thread.
qApp->postLambdaEvent([] {
qApp->getWindow()->raise();
qApp->raise();
});
}

View file

@ -62,13 +62,14 @@ public slots:
QScriptValue hasFocus();
/**jsdoc
* Make the Interface window have focus.
* Make the Interface window have focus. On Windows, if Interface doesn't already have focus, the task bar icon flashes to
* indicate that Interface wants attention but focus isn't taken away from the application that the user is using.
* @function Window.setFocus
*/
void setFocus();
/**jsdoc
* Raise the Interface window if it is minimized, and give it focus.
* Raise the Interface window if it is minimized. If raised, the window gains focus.
* @function Window.raiseMainWindow
*/
void raiseMainWindow();

View file

@ -63,6 +63,19 @@ static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
const QString Web3DOverlay::TYPE = "web3d";
const QString Web3DOverlay::QML = "Web3DOverlay.qml";
static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) {
AbstractViewStateInterface::instance()->postLambdaEvent([surface] {
if (AbstractViewStateInterface::instance()->isAboutToQuit()) {
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
// if the application has already stopped its event loop, delete must be explicit
delete surface;
} else {
surface->deleteLater();
}
});
};
Web3DOverlay::Web3DOverlay() {
_touchDevice.setCapabilities(QTouchDevice::Position);
_touchDevice.setType(QTouchDevice::TouchScreen);
@ -75,7 +88,8 @@ Web3DOverlay::Web3DOverlay() {
connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface);
//need to be intialized before Tablet 1st open
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(_url);
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
@ -114,6 +128,7 @@ void Web3DOverlay::destroyWebSurface() {
if (!_webSurface) {
return;
}
QQuickItem* rootItem = _webSurface->getRootItem();
if (rootItem && rootItem->objectName() == "tabletRoot") {
@ -135,10 +150,15 @@ void Web3DOverlay::destroyWebSurface() {
QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
offscreenCache->release(QML, _webSurface);
// If the web surface was fetched out of the cache, release it back into the cache
if (_cachedWebSurface) {
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
offscreenCache->release(QML, _webSurface);
}
_cachedWebSurface = false;
}
_webSurface.reset();
}
@ -147,6 +167,8 @@ void Web3DOverlay::buildWebSurface() {
if (_webSurface) {
return;
}
// FIXME the context save here is most likely unecessary since the QML surfaces now render
// off the main thread, and all GL context work is done off the main thread (I *think*)
gl::withSavedContext([&] {
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
// and the current rendering load)
@ -156,10 +178,13 @@ void Web3DOverlay::buildWebSurface() {
if (isWebContent()) {
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getRootItem()->setProperty("url", _url);
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
} else {
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(_url);
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), qmlSurfaceDeleter);
_webSurface->load(_url);
_cachedWebSurface = false;
setupQmlSurface();
}
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition()));

View file

@ -88,6 +88,7 @@ private:
InputMode _inputMode { Touch };
QSharedPointer<OffscreenQmlSurface> _webSurface;
bool _cachedWebSurface{ false };
gpu::TexturePointer _texture;
QString _url;
QString _scriptURL;

View file

@ -21,10 +21,6 @@ glm::uvec2 NullDisplayPlugin::getRecommendedRenderSize() const {
return glm::uvec2(100, 100);
}
bool NullDisplayPlugin::hasFocus() const {
return false;
}
void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
if (frame) {
_gpuContext->consumeFrameUpdates(frame);

View file

@ -16,7 +16,6 @@ public:
grouping getGrouping() const override { return DEVELOPER; }
glm::uvec2 getRecommendedRenderSize() const override;
bool hasFocus() const override;
void submitFrame(const gpu::FramePointer& newFrame) override;
QImage getScreenshot(float aspectRatio = 0.0f) const override;
QImage getSecondaryCameraScreenshot() const override;

View file

@ -831,11 +831,6 @@ glm::uvec2 OpenGLDisplayPlugin::getSurfaceSize() const {
return result;
}
bool OpenGLDisplayPlugin::hasFocus() const {
auto window = _container->getPrimaryWidget();
return window ? window->hasFocus() : false;
}
void OpenGLDisplayPlugin::assertNotPresentThread() const {
Q_ASSERT(QThread::currentThread() != _presentThread);
}

View file

@ -98,8 +98,6 @@ protected:
virtual void compositePointer();
virtual void compositeExtra() {};
virtual bool hasFocus() const override;
// These functions must only be called on the presentation thread
virtual void customizeContext();
virtual void uncustomizeContext();

View file

@ -239,7 +239,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
}
bool WebEntityRenderer::hasWebSurface() {
return (bool)_webSurface;
return (bool)_webSurface && _webSurface->getRootItem();
}
bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
@ -303,7 +303,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
_fadeStartTime = usecTimestampNow();
_webSurface->resume();
return true;
return _webSurface->getRootItem();
}
void WebEntityRenderer::destroyWebSurface() {

View file

@ -157,7 +157,7 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u
}
if (usingUserData) {
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()));
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()), materialURLString);
// Since our material changed, the current name might not be valid anymore, so we need to update
setCurrentMaterialName(_currentMaterialName);

View file

@ -20,7 +20,7 @@ void NetworkMaterialResource::downloadFinished(const QByteArray& data) {
parsedMaterials.reset();
if (_url.toString().contains(".json")) {
parsedMaterials = parseJSONMaterials(QJsonDocument::fromJson(data));
parsedMaterials = parseJSONMaterials(QJsonDocument::fromJson(data), _url);
}
// TODO: parse other material types
@ -75,7 +75,7 @@ bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3&
* @property {number} materialVersion=1 - The version of the material. <em>Currently not used.</em>
* @property {Material|Material[]} materials - The details of the material or materials.
*/
NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON) {
NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON, const QUrl& baseUrl) {
ParsedMaterials toReturn;
if (!materialJSON.isNull() && materialJSON.isObject()) {
QJsonObject materialJSONObject = materialJSON.object();
@ -91,13 +91,13 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
QJsonArray materials = materialsValue.toArray();
for (auto material : materials) {
if (!material.isNull() && material.isObject()) {
auto parsedMaterial = parseJSONMaterial(material.toObject());
auto parsedMaterial = parseJSONMaterial(material.toObject(), baseUrl);
toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second;
toReturn.names.push_back(parsedMaterial.first);
}
}
} else if (materialsValue.isObject()) {
auto parsedMaterial = parseJSONMaterial(materialsValue.toObject());
auto parsedMaterial = parseJSONMaterial(materialsValue.toObject(), baseUrl);
toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second;
toReturn.names.push_back(parsedMaterial.first);
}
@ -138,7 +138,7 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
* @property {string} lightMap - URL of light map texture image. <em>Currently not used.</em>
*/
// Note: See MaterialEntityItem.h for default values used in practice.
std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) {
std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl) {
std::string name = "";
std::shared_ptr<NetworkMaterial> material = std::make_shared<NetworkMaterial>();
for (auto& key : materialJSON.keys()) {
@ -199,57 +199,58 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
} else if (key == "albedoMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
QString urlString = value.toString();
bool useAlphaChannel = false;
auto opacityMap = materialJSON.find("opacityMap");
if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == value.toString()) {
if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == urlString) {
useAlphaChannel = true;
}
material->setAlbedoMap(value.toString(), useAlphaChannel);
material->setAlbedoMap(baseUrl.resolved(urlString), useAlphaChannel);
}
} else if (key == "roughnessMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setRoughnessMap(value.toString(), false);
material->setRoughnessMap(baseUrl.resolved(value.toString()), false);
}
} else if (key == "glossMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setRoughnessMap(value.toString(), true);
material->setRoughnessMap(baseUrl.resolved(value.toString()), true);
}
} else if (key == "metallicMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setMetallicMap(value.toString(), false);
material->setMetallicMap(baseUrl.resolved(value.toString()), false);
}
} else if (key == "specularMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setMetallicMap(value.toString(), true);
material->setMetallicMap(baseUrl.resolved(value.toString()), true);
}
} else if (key == "normalMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setNormalMap(value.toString(), false);
material->setNormalMap(baseUrl.resolved(value.toString()), false);
}
} else if (key == "bumpMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setNormalMap(value.toString(), true);
material->setNormalMap(baseUrl.resolved(value.toString()), true);
}
} else if (key == "occlusionMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setOcclusionMap(value.toString());
material->setOcclusionMap(baseUrl.resolved(value.toString()));
}
} else if (key == "scatteringMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setScatteringMap(value.toString());
material->setScatteringMap(baseUrl.resolved(value.toString()));
}
} else if (key == "lightMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setLightmapMap(value.toString());
material->setLightmapMap(baseUrl.resolved(value.toString()));
}
}
}

View file

@ -37,8 +37,8 @@ public:
ParsedMaterials parsedMaterials;
static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON);
static std::pair<std::string, std::shared_ptr<NetworkMaterial>> parseJSONMaterial(const QJsonObject& materialJSON);
static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON, const QUrl& baseUrl);
static std::pair<std::string, std::shared_ptr<NetworkMaterial>> parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl);
private:
static bool parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB);

View file

@ -556,58 +556,58 @@ graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, im
return nullptr;
}
void NetworkMaterial::setAlbedoMap(const QString& url, bool useAlphaChannel) {
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
void NetworkMaterial::setAlbedoMap(const QUrl& url, bool useAlphaChannel) {
auto map = fetchTextureMap(url, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
if (map) {
map->setUseAlphaChannel(useAlphaChannel);
setTextureMap(MapChannel::ALBEDO_MAP, map);
}
}
void NetworkMaterial::setNormalMap(const QString& url, bool isBumpmap) {
auto map = fetchTextureMap(QUrl(url), isBumpmap ? image::TextureUsage::BUMP_TEXTURE : image::TextureUsage::NORMAL_TEXTURE, MapChannel::NORMAL_MAP);
void NetworkMaterial::setNormalMap(const QUrl& url, bool isBumpmap) {
auto map = fetchTextureMap(url, isBumpmap ? image::TextureUsage::BUMP_TEXTURE : image::TextureUsage::NORMAL_TEXTURE, MapChannel::NORMAL_MAP);
if (map) {
setTextureMap(MapChannel::NORMAL_MAP, map);
}
}
void NetworkMaterial::setRoughnessMap(const QString& url, bool isGloss) {
auto map = fetchTextureMap(QUrl(url), isGloss ? image::TextureUsage::GLOSS_TEXTURE : image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
void NetworkMaterial::setRoughnessMap(const QUrl& url, bool isGloss) {
auto map = fetchTextureMap(url, isGloss ? image::TextureUsage::GLOSS_TEXTURE : image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
if (map) {
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
}
}
void NetworkMaterial::setMetallicMap(const QString& url, bool isSpecular) {
auto map = fetchTextureMap(QUrl(url), isSpecular ? image::TextureUsage::SPECULAR_TEXTURE : image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
void NetworkMaterial::setMetallicMap(const QUrl& url, bool isSpecular) {
auto map = fetchTextureMap(url, isSpecular ? image::TextureUsage::SPECULAR_TEXTURE : image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
if (map) {
setTextureMap(MapChannel::METALLIC_MAP, map);
}
}
void NetworkMaterial::setOcclusionMap(const QString& url) {
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
void NetworkMaterial::setOcclusionMap(const QUrl& url) {
auto map = fetchTextureMap(url, image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
if (map) {
setTextureMap(MapChannel::OCCLUSION_MAP, map);
}
}
void NetworkMaterial::setEmissiveMap(const QString& url) {
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
void NetworkMaterial::setEmissiveMap(const QUrl& url) {
auto map = fetchTextureMap(url, image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
if (map) {
setTextureMap(MapChannel::EMISSIVE_MAP, map);
}
}
void NetworkMaterial::setScatteringMap(const QString& url) {
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP);
void NetworkMaterial::setScatteringMap(const QUrl& url) {
auto map = fetchTextureMap(url, image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP);
if (map) {
setTextureMap(MapChannel::SCATTERING_MAP, map);
}
}
void NetworkMaterial::setLightmapMap(const QString& url) {
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
void NetworkMaterial::setLightmapMap(const QUrl& url) {
auto map = fetchTextureMap(url, image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
if (map) {
//map->setTextureTransform(_lightmapTransform);
//map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y);

View file

@ -164,14 +164,14 @@ public:
NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl);
NetworkMaterial(const NetworkMaterial& material);
void setAlbedoMap(const QString& url, bool useAlphaChannel);
void setNormalMap(const QString& url, bool isBumpmap);
void setRoughnessMap(const QString& url, bool isGloss);
void setMetallicMap(const QString& url, bool isSpecular);
void setOcclusionMap(const QString& url);
void setEmissiveMap(const QString& url);
void setScatteringMap(const QString& url);
void setLightmapMap(const QString& url);
void setAlbedoMap(const QUrl& url, bool useAlphaChannel);
void setNormalMap(const QUrl& url, bool isBumpmap);
void setRoughnessMap(const QUrl& url, bool isGloss);
void setMetallicMap(const QUrl& url, bool isSpecular);
void setOcclusionMap(const QUrl& url);
void setEmissiveMap(const QUrl& url);
void setScatteringMap(const QUrl& url);
void setLightmapMap(const QUrl& url);
protected:
friend class Geometry;

View file

@ -140,9 +140,6 @@ public:
virtual void setContext(const gpu::ContextPointer& context) final { _gpuContext = context; }
virtual void submitFrame(const gpu::FramePointer& newFrame) = 0;
// Does the rendering surface have current focus?
virtual bool hasFocus() const = 0;
// The size of the rendering target (may be larger than the device size due to distortion)
virtual glm::uvec2 getRecommendedRenderSize() const = 0;

View file

@ -8,6 +8,9 @@ include_hifi_library_headers(audio)
include_hifi_library_headers(networking)
include_hifi_library_headers(octree)
# tell CMake to exclude qrc_fonts.cpp for policy CMP0071
set_property(SOURCE qrc_fonts.cpp PROPERTY SKIP_AUTOMOC ON)
if (NOT ANDROID)
target_nsight()
endif ()

View file

@ -47,6 +47,8 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr
}
}
const graphics::MaterialPointer MeshPartPayload::DEFAULT_MATERIAL = std::make_shared<graphics::Material>();
MeshPartPayload::MeshPartPayload(const std::shared_ptr<const graphics::Mesh>& mesh, int partIndex, graphics::MaterialPointer material) {
updateMeshPart(mesh, partIndex);
addMaterial(graphics::MaterialLayer(material, 0));
@ -99,7 +101,7 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShad
builder.withSubMetaCulled();
}
if (_drawMaterials.top().material) {
if (topMaterialExists()) {
auto matKey = _drawMaterials.top().material->getKey();
if (matKey.isTranslucent()) {
builder.withTransparent();
@ -119,7 +121,7 @@ Item::Bound MeshPartPayload::getBound() const {
ShapeKey MeshPartPayload::getShapeKey() const {
graphics::MaterialKey drawMaterialKey;
if (_drawMaterials.top().material) {
if (topMaterialExists()) {
drawMaterialKey = _drawMaterials.top().material->getKey();
}
@ -171,7 +173,7 @@ void MeshPartPayload::render(RenderArgs* args) {
bindMesh(batch);
// apply material properties
RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing);
RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing);
args->_details._materialSwitches++;
// Draw!
@ -356,7 +358,7 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCas
builder.withDeformed();
}
if (_drawMaterials.top().material) {
if (topMaterialExists()) {
auto matKey = _drawMaterials.top().material->getKey();
if (matKey.isTranslucent()) {
builder.withTransparent();
@ -387,7 +389,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe
}
graphics::MaterialKey drawMaterialKey;
if (_drawMaterials.top().material) {
if (topMaterialExists()) {
drawMaterialKey = _drawMaterials.top().material->getKey();
}
@ -469,7 +471,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
bindMesh(batch);
// apply material properties
RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing);
RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing);
args->_details._materialSwitches++;
// Draw!

View file

@ -65,15 +65,18 @@ public:
graphics::Mesh::Part _drawPart;
size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; }
size_t getMaterialTextureSize() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureSize() : 0; }
int getMaterialTextureCount() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureCount() : 0; }
bool hasTextureInfo() const { return _drawMaterials.top().material ? _drawMaterials.top().material->hasTextureInfo() : false; }
size_t getMaterialTextureSize() { return topMaterialExists() ? _drawMaterials.top().material->getTextureSize() : 0; }
int getMaterialTextureCount() { return topMaterialExists() ? _drawMaterials.top().material->getTextureCount() : 0; }
bool hasTextureInfo() const { return topMaterialExists() ? _drawMaterials.top().material->hasTextureInfo() : false; }
void addMaterial(graphics::MaterialLayer material);
void removeMaterial(graphics::MaterialPointer material);
protected:
static const graphics::MaterialPointer DEFAULT_MATERIAL;
render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() };
bool topMaterialExists() const { return !_drawMaterials.empty() && _drawMaterials.top().material; }
};
namespace render {

View file

@ -1604,7 +1604,8 @@ void Model::createVisibleRenderItemSet() {
int numParts = (int)mesh->getNumParts();
for (int partIndex = 0; partIndex < numParts; partIndex++) {
_modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
_modelMeshMaterialNames.push_back(getGeometry()->getShapeMaterial(shapeID)->getName());
auto material = getGeometry()->getShapeMaterial(shapeID);
_modelMeshMaterialNames.push_back(material ? material->getName() : "");
_modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i });
shapeID++;
}

View file

@ -20,8 +20,12 @@ if (NOT SERVER_ONLY AND NOT ANDROID)
add_subdirectory(${DIR})
set(DIR "oculusLegacy")
add_subdirectory(${DIR})
set(DIR "hifiSixense")
add_subdirectory(${DIR})
if (USE_SIXENSE)
set(DIR "hifiSixense")
add_subdirectory(${DIR})
endif()
set(DIR "hifiSpacemouse")
add_subdirectory(${DIR})
set(DIR "hifiNeuron")

View file

@ -19,7 +19,7 @@ if (WIN32 AND (NOT USE_GLES))
set(TARGET_NAME oculus)
setup_hifi_plugin(Multimedia)
link_hifi_libraries(
shared task gl gpu gpu-gl controllers ui qml
shared task gl gpu ${PLATFORM_GL_BACKEND} controllers ui qml
plugins ui-plugins display-plugins input-plugins
audio-client networking render-utils
${PLATFORM_GL_BACKEND}

View file

@ -0,0 +1,102 @@
// webSpawnTool.js
//
// Stress tests the rendering of web surfaces over time
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
SPAWNER = function (properties) {
properties = properties || {};
var RADIUS = properties.radius || 5.0; // Spawn within this radius (square)
var SPAWN_COUNT = properties.count || 10000; // number of entities to spawn
var SPAWN_LIMIT = properties.spawnLimit || 1;
var SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 2;
var SPAWN_LIFETIME = properties.lifetime || 10; // Entity timeout (when/if we crash, we need the entities to delete themselves)
function randomPositionXZ(center, radius) {
return {
x: center.x + (Math.random() * radius * 2.0) - radius,
y: center.y,
z: center.z + (Math.random() * radius * 2.0) - radius
};
}
function makeObject(properties) {
var overlay = Overlays.addOverlay("web3d", {
name: "Web",
url: "https://www.reddit.com/r/random",
localPosition: randomPositionXZ( { x: 0, y: 0, z: -1 }, 1),
localRotation: Quat.angleAxis(180, Vec3.Y_AXIS),
dimensions: {x: .8, y: .45, z: 0.1},
color: { red: 255, green: 255, blue: 255 },
alpha: 1.0,
showKeyboardFocusHighlight: false,
visible: true
});
var now = Date.now();
return {
destroy: function () {
Overlays.deleteOverlay(overlay)
},
getAge: function () {
return (Date.now() - now) / 1000.0;
}
};
}
var items = [];
var toCreate = 0;
var spawned = 0;
var spawnTimer = 0.0;
var keepAliveTimer = 0.0;
function clear () {
}
function create() {
toCreate = SPAWN_COUNT;
Script.update.connect(spawn);
}
function spawn(dt) {
if (toCreate <= 0) {
Script.update.disconnect(spawn);
print("Finished spawning");
}
else if ((spawnTimer -= dt) < 0.0){
spawnTimer = SPAWN_INTERVAL;
var n = Math.min(toCreate, SPAWN_LIMIT);
print("Spawning " + n + " items (" + (spawned += n) + ")");
toCreate -= n;
for (; n > 0; --n) {
items.push(makeObject());
}
}
}
function despawn() {
print("despawning");
items.forEach(function (item) {
item.destroy();
});
item = [];
}
function init () {
Script.update.disconnect(init);
Script.scriptEnding.connect(despawn);
clear();
create();
}
Script.update.connect(init);
};
SPAWNER();

View file

@ -14,7 +14,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
# link in the shared libraries
link_hifi_libraries(
shared task networking animation
ktx image octree gl gpu gpu-gl
ktx image octree gl gpu ${PLATFORM_GL_BACKEND}
render render-utils
graphics fbx model-networking graphics-scripting
entities entities-renderer audio avatars script-engine