diff --git a/assignment-client/src/entities/AssignmentParentFinder.cpp b/assignment-client/src/entities/AssignmentParentFinder.cpp index 294556383e..a0232daff4 100644 --- a/assignment-client/src/entities/AssignmentParentFinder.cpp +++ b/assignment-client/src/entities/AssignmentParentFinder.cpp @@ -11,7 +11,7 @@ #include "AssignmentParentFinder.h" -SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success) const { +SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { @@ -20,7 +20,11 @@ SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& } // search entities - parent = _tree->findEntityByEntityItemID(parentID); + if (entityTree) { + parent = entityTree->findByID(parentID); + } else { + parent = _tree->findEntityByEntityItemID(parentID); + } if (parent.expired()) { success = false; } else { diff --git a/assignment-client/src/entities/AssignmentParentFinder.h b/assignment-client/src/entities/AssignmentParentFinder.h index 9a776bc7dd..0c16143143 100644 --- a/assignment-client/src/entities/AssignmentParentFinder.h +++ b/assignment-client/src/entities/AssignmentParentFinder.h @@ -25,7 +25,7 @@ class AssignmentParentFinder : public SpatialParentFinder { public: AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { } virtual ~AssignmentParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const; protected: EntityTreePointer _tree; diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index a98745b404..5b8a689a9a 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -12,19 +12,16 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) # 0.5 public # URL http://static.oculus.com/sdk-downloads/ovr_sdk_win_0.5.0.1.zip # URL_MD5 d3fc4c02db9be5ff08af4ef4c97b32f9 -# 0.6 public -# URL http://static.oculus.com/sdk-downloads/0.6.0.1/Public/1435190862/ovr_sdk_win_0.6.0.1.zip -# URL_MD5 4b3ef825f9a1d6d3035c9f6820687da9 -# 0.8 public -# URL http://static.oculus.com/sdk-downloads/0.8.0.0/Public/1445451746/ovr_sdk_win_0.8.0.0.zip -# URL_MD5 54944b03b95149d6010f84eb701b9647 +# 1.3 public +# URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.3.0_public.zip +# URL_MD5 4d26faba0c1f35ff80bf674c96ed9259 if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://static.oculus.com/sdk-downloads/0.8.0.0/Public/1445451746/ovr_sdk_win_0.8.0.0.zip - URL_MD5 54944b03b95149d6010f84eb701b9647 + URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.3.0_public.zip + URL_MD5 a2dcf695e0f03a70fdd1ed7480585e82 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" @@ -33,14 +30,16 @@ if (WIN32) ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) - # FIXME need to account for different architectures - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE TYPE INTERNAL) + set(LIBOVR_DIR ${SOURCE_DIR}/OculusSDK/LibOVR) if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Windows/x64/Release/VS2013/LibOVR.lib CACHE TYPE INTERNAL) + set(LIBOVR_LIB_DIR ${LIBOVR_DIR}/Lib/Windows/x64/Release/VS2013 CACHE TYPE INTERNAL) else() - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Windows/Win32/Release/VS2013/LibOVR.lib CACHE TYPE INTERNAL) + set(LIBOVR_LIB_DIR ${LIBOVR_DIR}/Lib/Windows/Win32/Release/VS2013 CACHE TYPE INTERNAL) endif() + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${LIBOVR_LIB_DIR}/LibOVR.lib CACHE TYPE INTERNAL) + elseif(APPLE) ExternalProject_Add( diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index b4744aa172..8d1eca84d7 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -85,8 +85,8 @@ macro(GENERATE_INSTALLERS) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") - cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Client") - cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Server") + cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Interface") + cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Sandbox") include(CPack) endmacro() diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 92e3273f67..63e7a1a174 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -53,7 +53,7 @@ macro(SET_PACKAGING_PARAMETERS) set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) - set(CONSOLE_EXEC_NAME "Server Console.app") + set(CONSOLE_EXEC_NAME "Sandbox.app") set(CONSOLE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${CONSOLE_EXEC_NAME}") set(CONSOLE_APP_CONTENTS "${CONSOLE_INSTALL_APP_PATH}/Contents") @@ -84,12 +84,19 @@ macro(SET_PACKAGING_PARAMETERS) # shortcut names if (PRODUCTION_BUILD) - set(INTERFACE_SHORTCUT_NAME "High Fidelity") - set(CONSOLE_SHORTCUT_NAME "Server Console") + set(INTERFACE_SHORTCUT_NAME "Interface") + set(CONSOLE_SHORTCUT_NAME "Sandbox") else () - set(INTERFACE_SHORTCUT_NAME "High Fidelity - ${BUILD_VERSION}") - set(CONSOLE_SHORTCUT_NAME "Server Console - ${BUILD_VERSION}") + set(INTERFACE_SHORTCUT_NAME "Interface - ${BUILD_VERSION}") + set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION}") endif () + + set(INTERFACE_HF_SHORTCUT_NAME "High Fidelity ${INTERFACE_SHORTCUT_NAME}") + set(CONSOLE_HF_SHORTCUT_NAME "High Fidelity ${CONSOLE_SHORTCUT_NAME}") + + set(PRE_SANDBOX_INTERFACE_SHORTCUT_NAME "High Fidelity") + set(PRE_SANDBOX_CONSOLE_SHORTCUT_NAME "Server Console") + # check if we need to find signtool if (PRODUCTION_BUILD OR PR_BUILD) find_program(SIGNTOOL_EXECUTABLE signtool PATHS "C:/Program Files (x86)/Windows Kits/8.1" PATH_SUFFIXES "bin/x64") diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index 164e432706..d22ba1f5e1 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -10,10 +10,14 @@ # set(INTERFACE_SHORTCUT_NAME "@INTERFACE_SHORTCUT_NAME@") +set(INTERFACE_HF_SHORTCUT_NAME "@INTERFACE_HF_SHORTCUT_NAME@") set(INTERFACE_WIN_EXEC_NAME "@INTERFACE_EXEC_PREFIX@.exe") set(CONSOLE_INSTALL_SUBDIR "@CONSOLE_INSTALL_DIR@") set(CONSOLE_SHORTCUT_NAME "@CONSOLE_SHORTCUT_NAME@") +set(CONSOLE_HF_SHORTCUT_NAME "@CONSOLE_HF_SHORTCUT_NAME@") set(CONSOLE_WIN_EXEC_NAME "@CONSOLE_EXEC_NAME@") +set(PRE_SANDBOX_INTERFACE_SHORTCUT_NAME "@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@") +set(PRE_SANDBOX_CONSOLE_SHORTCUT_NAME "@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@") set(DS_EXEC_NAME "@DS_EXEC_NAME@") set(AC_EXEC_NAME "@AC_EXEC_NAME@") set(HIGH_FIDELITY_PROTOCOL "@HIGH_FIDELITY_PROTOCOL@") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index ca26be6ab0..0e30d8aa2a 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -387,7 +387,7 @@ Function PostInstallOptionsPage StrCpy $OffsetUnits u ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@" + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_HF_SHORTCUT_NAME@" Pop $DesktopClientCheckbox IntOp $CurrentOffset $CurrentOffset + 15 @@ -396,7 +396,7 @@ Function PostInstallOptionsPage ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@" + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" Pop $DesktopServerCheckbox ; set the checkbox state depending on what is present in the registry @@ -404,7 +404,7 @@ Function PostInstallOptionsPage IntOp $CurrentOffset $CurrentOffset + 15 - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup" + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" Pop $ServerStartupCheckbox ; set the checkbox state depending on what is present in the registry @@ -414,9 +414,9 @@ Function PostInstallOptionsPage ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console after install" + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install" ${Else} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity after install" + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" ${EndIf} Pop $LaunchNowCheckbox @@ -465,10 +465,10 @@ Function ReadPostInstallOptions ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ; check if the user asked for a desktop shortcut to Server Console + ; check if the user asked for a desktop shortcut to Sandbox ${NSD_GetState} $DesktopServerCheckbox $DesktopServerState - ; check if the user asked to have Server Console launched every startup + ; check if the user asked to have Sandbox launched every startup ${NSD_GetState} $ServerStartupCheckbox $ServerStartupState ${EndIf} @@ -485,7 +485,7 @@ Function HandlePostInstallOptions ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ; check if the user asked for a desktop shortcut to High Fidelity ${If} $DesktopClientState == ${BST_CHECKED} - CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" + CreateShortCut "$DESKTOP\@INTERFACE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" !insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES ${Else} !insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO @@ -494,23 +494,23 @@ Function HandlePostInstallOptions ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ; check if the user asked for a desktop shortcut to Server Console + ; check if the user asked for a desktop shortcut to Sandbox ${If} $DesktopServerState == ${BST_CHECKED} - CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES ${Else} !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} - ; check if the user asked to have Server Console launched every startup + ; check if the user asked to have Sandbox launched every startup ${If} $ServerStartupState == ${BST_CHECKED} ; in case we added a shortcut in the global context, pull that now SetShellVarContext all - Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" + Delete "$SMSTARTUP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk" ; make a startup shortcut in this user's current context SetShellVarContext current - CreateShortCut "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + CreateShortCut "$SMSTARTUP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" ; reset the shell var context back SetShellVarContext all @@ -589,6 +589,19 @@ Section "-Core installation" Delete "$INSTDIR\version" Delete "$INSTDIR\xinput1_3.dll" + ; Delete old desktop shortcuts before they were renamed during Sandbox rename + Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk" + Delete "$DESKTOP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk" + + ; Delete old Start Menu shortcuts before Sandbox rename + Delete "$SMPROGRAMS\$STARTMENU_FOLDER\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk" + Delete "$SMPROGRAMS\$STARTMENU_FOLDER\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk" + + ; Delete old startup item for Server Console before Sandbox rename + SetShellVarContext current + Delete "$SMSTARTUP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk" + SetShellVarContext all + ; Rename the incorrectly cased Raleway font Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml" @@ -724,8 +737,8 @@ SectionEnd !macroend !macro CheckForRunningApplications action prompter - !insertmacro PromptForRunningApplication "@INTERFACE_WIN_EXEC_NAME@" "High Fidelity client" ${action} ${prompter} - !insertmacro PromptForRunningApplication "@CONSOLE_WIN_EXEC_NAME@" "Server Console" ${action} ${prompter} + !insertmacro PromptForRunningApplication "@INTERFACE_WIN_EXEC_NAME@" "@CONSOLE_SHORTCUT_NAME@" ${action} ${prompter} + !insertmacro PromptForRunningApplication "@CONSOLE_WIN_EXEC_NAME@" "@INTERFACE_SHORTCUT_NAME@" ${action} ${prompter} !insertmacro PromptForRunningApplication "@DS_EXEC_NAME@" "Domain Server" ${action} ${prompter} !insertmacro PromptForRunningApplication "@AC_EXEC_NAME@" "Assignment Client" ${action} ${prompter} !macroend @@ -869,12 +882,12 @@ Section "Uninstall" Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" Delete "$SMPROGRAMS\$MUI_TEMP\@INTERFACE_SHORTCUT_NAME@.lnk" Delete "$SMPROGRAMS\$MUI_TEMP\@CONSOLE_SHORTCUT_NAME@.lnk" - Delete "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" - Delete "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" + Delete "$DESKTOP\@INTERFACE_HF_SHORTCUT_NAME@.lnk" + Delete "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" ; if it exists, delete the startup shortcut for the current user SetShellVarContext current - Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" + Delete "$SMSTARTUP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" SetShellVarContext all @CPACK_NSIS_DELETE_ICONS@ diff --git a/examples/entityScripts/changeColorOnEnterLeave.js b/examples/entityScripts/changeColorOnEnterLeave.js new file mode 100644 index 0000000000..909fa6e814 --- /dev/null +++ b/examples/entityScripts/changeColorOnEnterLeave.js @@ -0,0 +1,26 @@ +// +// changeColorOnEnterLeave.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 3/31/16. +// Copyright 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 +// + +(function(){ + function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + this.enterEntity = function(myID) { + print("enterEntity() myID:" + myID); + Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} }); + }; + + this.leaveEntity = function(myID) { + print("leaveEntity() myID:" + myID); + Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} }); + }; +}) \ No newline at end of file diff --git a/examples/selectAudioDevice.js b/examples/selectAudioDevice.js index b1da219eb5..c86d034adb 100644 --- a/examples/selectAudioDevice.js +++ b/examples/selectAudioDevice.js @@ -120,21 +120,25 @@ function menuItemEvent(menuItem) { if (menuItem.endsWith(" for Output")) { var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Output"); print("output audio selection..." + selectedDevice); + Menu.menuItemEvent.disconnect(menuItemEvent); Menu.setIsOptionChecked(selectedOutputMenu, false); selectedOutputMenu = menuItem; Menu.setIsOptionChecked(selectedOutputMenu, true); if (AudioDevice.setOutputDevice(selectedDevice)) { Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice); } + Menu.menuItemEvent.connect(menuItemEvent); } else if (menuItem.endsWith(" for Input")) { var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Input"); print("input audio selection..." + selectedDevice); + Menu.menuItemEvent.disconnect(menuItemEvent); Menu.setIsOptionChecked(selectedInputMenu, false); selectedInputMenu = menuItem; Menu.setIsOptionChecked(selectedInputMenu, true); if (AudioDevice.setInputDevice(selectedDevice)) { Settings.setValue(INPUT_DEVICE_SETTING, selectedDevice); } + Menu.menuItemEvent.connect(menuItemEvent); } } } diff --git a/examples/tests/cube_texture.png b/examples/tests/cube_texture.png new file mode 100644 index 0000000000..ed65d8d6cb Binary files /dev/null and b/examples/tests/cube_texture.png differ diff --git a/examples/tests/textureStress.fs b/examples/tests/textureStress.fs new file mode 100644 index 0000000000..cb59c9984f --- /dev/null +++ b/examples/tests/textureStress.fs @@ -0,0 +1,41 @@ +float aspect(vec2 v) { + return v.x / v.y; +} + +vec3 aspectCorrectedTexture() { + vec2 uv = _position.xy; + uv += 0.5; + uv.y = 1.0 - uv.y; + + float targetAspect = iWorldScale.x / iWorldScale.y; + float sourceAspect = aspect(iChannelResolution[0].xy); + float aspectCorrection = sourceAspect / targetAspect; + if (aspectCorrection > 1.0) { + float offset = aspectCorrection - 1.0; + float halfOffset = offset / 2.0; + uv.y -= halfOffset; + uv.y *= aspectCorrection; + } else { + float offset = 1.0 - aspectCorrection; + float halfOffset = offset / 2.0; + uv.x -= halfOffset; + uv.x /= aspectCorrection; + } + + if (any(lessThan(uv, vec2(0.0)))) { + return vec3(0.0); + } + + if (any(greaterThan(uv, vec2(1.0)))) { + return vec3(0.0); + } + + vec4 color = texture(iChannel0, uv); + return color.rgb * max(0.5, sourceAspect) * max(0.9, fract(iWorldPosition.x)); +} + +float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) { + specular = aspectCorrectedTexture(); + return 1.0; +} + diff --git a/examples/tests/textureStress.js b/examples/tests/textureStress.js new file mode 100644 index 0000000000..daf6eb41ec --- /dev/null +++ b/examples/tests/textureStress.js @@ -0,0 +1,67 @@ +Script.include("https://s3.amazonaws.com/DreamingContent/scripts/Austin.js"); + +var ENTITY_SPAWN_LIMIT = 500; +var ENTITY_LIFETIME = 600; +var RADIUS = 1.0; // Spawn within this radius (square) +var TEST_ENTITY_NAME = "EntitySpawnTest"; + +var entities = []; +var textureIndex = 0; +var texture = Script.resolvePath('cube_texture.png'); +var shader = Script.resolvePath('textureStress.fs'); +var qml = Script.resolvePath('textureStress.qml'); +qmlWindow = new OverlayWindow({ + title: 'Test Qml', + source: qml, + height: 240, + width: 320, + toolWindow: false, + visible: true +}); + +function deleteItems(count) { + if (!count) { + var ids = Entities.findEntities(MyAvatar.position, 50); + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id, ["name"]); + if (properties.name === TEST_ENTITY_NAME) { + Entities.deleteEntity(id); + } + }, this); + entities = []; + return; + } else { + // FIXME... implement + } +} + +function createItems(count) { + for (var i = 0; i < count; ++i) { + var newEntity = Entities.addEntity({ + type: "Box", + name: TEST_ENTITY_NAME, + position: AUSTIN.avatarRelativePosition(AUSTIN.randomPositionXZ({ x: 0, y: 0, z: -2 }, RADIUS)), + color: { r: 255, g: 255, b: 255 }, + dimensions: AUSTIN.randomDimensions(), + lifetime: ENTITY_LIFETIME, + userData: JSON.stringify({ + ProceduralEntity: { + version: 2, + shaderUrl: shader, + channels: [ texture + "?" + textureIndex++ ] + } + }) + }); + entities.push(newEntity); + } +} + +qmlWindow.fromQml.connect(function(message){ + print(message); + if (message[0] === "create") { + var count = message[1] || 1; + createItems(message[1] || 1); + } else if (message[0] === "delete") { + deleteItems(message[1]); + } +}); diff --git a/examples/tests/textureStress.qml b/examples/tests/textureStress.qml new file mode 100644 index 0000000000..1a8b994475 --- /dev/null +++ b/examples/tests/textureStress.qml @@ -0,0 +1,69 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Rectangle { + id: root + width: parent ? parent.width : 100 + height: parent ? parent.height : 100 + + signal sendToScript(var message); + + Text { + id: label + text: "GPU Texture Usage: " + } + Text { + id: usage + anchors.left: label.right + anchors.leftMargin: 8 + text: "N/A" + Timer { + repeat: true + running: true + interval: 500 + onTriggered: { + usage.text = Render.getConfig("Stats")["textureGPUMemoryUsage"]; + } + } + } + + Column { + + + anchors { left: parent.left; right: parent.right; top: label.bottom; topMargin: 8; bottom: parent.bottom } + spacing: 8 + + Button { + text: "Add 1" + onClicked: root.sendToScript(["create", 1]); + } + Button { + text: "Add 10" + onClicked: root.sendToScript(["create", 10]); + } + Button { + text: "Add 100" + onClicked: root.sendToScript(["create", 100]); + } + /* + Button { + text: "Delete 1" + onClicked: root.sendToScript(["delete", 1]); + } + Button { + text: "Delete 10" + onClicked: root.sendToScript(["delete", 10]); + } + Button { + text: "Delete 100" + onClicked: root.sendToScript(["delete", 100]); + } + */ + Button { + text: "Delete All" + onClicked: root.sendToScript(["delete", 0]); + } + } +} + + diff --git a/examples/utilities/tools/render/BG.qml b/examples/utilities/render/BG.qml similarity index 100% rename from examples/utilities/tools/render/BG.qml rename to examples/utilities/render/BG.qml diff --git a/examples/utilities/tools/render/ConfigSlider.qml b/examples/utilities/render/configSlider/ConfigSlider.qml similarity index 100% rename from examples/utilities/tools/render/ConfigSlider.qml rename to examples/utilities/render/configSlider/ConfigSlider.qml diff --git a/examples/utilities/render/configSlider/qmldir b/examples/utilities/render/configSlider/qmldir new file mode 100644 index 0000000000..6680ec9638 --- /dev/null +++ b/examples/utilities/render/configSlider/qmldir @@ -0,0 +1 @@ +ConfigSlider 1.0 ConfigSlider.qml \ No newline at end of file diff --git a/examples/utilities/render/culling.qml b/examples/utilities/render/culling.qml new file mode 100644 index 0000000000..e3f5e67bbe --- /dev/null +++ b/examples/utilities/render/culling.qml @@ -0,0 +1,114 @@ +// +// culling.qml +// examples/utilities/render +// +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "configSlider" + +Column { + id: root + spacing: 8 + property var sceneOctree: Render.getConfig("DrawSceneOctree"); + property var itemSelection: Render.getConfig("DrawItemSelection"); + + Component.onCompleted: { + sceneOctree.enabled = true; + itemSelection.enabled = true; + sceneOctree.showVisibleCells = false; + sceneOctree.showEmptyCells = false; + itemSelection.showInsideItems = false; + itemSelection.showInsideSubcellItems = false; + itemSelection.showPartialItems = false; + itemSelection.showPartialSubcellItems = false; + } + Component.onDestruction: { + sceneOctree.enabled = false; + itemSelection.enabled = false; + Render.getConfig("FetchSceneSelection").freezeFrustum = false; + Render.getConfig("CullSceneSelection").freezeFrustum = false; + } + + GroupBox { + title: "Culling" + Row { + spacing: 8 + Column { + spacing: 8 + + CheckBox { + text: "Freeze Culling Frustum" + checked: false + onCheckedChanged: { + Render.getConfig("FetchSceneSelection").freezeFrustum = checked; + Render.getConfig("CullSceneSelection").freezeFrustum = checked; + } + } + Label { + text: "Octree" + } + CheckBox { + text: "Visible Cells" + checked: root.sceneOctree.showVisibleCells + onCheckedChanged: { root.sceneOctree.showVisibleCells = checked } + } + CheckBox { + text: "Empty Cells" + checked: false + onCheckedChanged: { root.sceneOctree.showEmptyCells = checked } + } + } + Column { + spacing: 8 + + Label { + text: "Frustum Items" + } + CheckBox { + text: "Inside Items" + checked: false + onCheckedChanged: { root.itemSelection.showInsideItems = checked } + } + CheckBox { + text: "Inside Sub-cell Items" + checked: false + onCheckedChanged: { root.itemSelection.showInsideSubcellItems = checked } + } + CheckBox { + text: "Partial Items" + checked: false + onCheckedChanged: { root.itemSelection.showPartialItems = checked } + } + CheckBox { + text: "Partial Sub-cell Items" + checked: false + onCheckedChanged: { root.itemSelection.showPartialSubcellItems = checked } + } + } + } + } + + GroupBox { + title: "Render Items" + + Column{ + Repeater { + model: [ "Opaque:DrawOpaqueDeferred", "Transparent:DrawTransparentDeferred", "Light:DrawLight", + "Opaque Overlays:DrawOverlay3DOpaque", "Transparent Overlays:DrawOverlay3DTransparent" ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: true + config: Render.getConfig(modelData.split(":")[1]) + property: "maxDrawn" + max: config.numDrawn + min: -1 + } + } + } + } +} diff --git a/examples/utilities/tools/render/debug.js b/examples/utilities/render/debug.js similarity index 100% rename from examples/utilities/tools/render/debug.js rename to examples/utilities/render/debug.js diff --git a/examples/utilities/tools/render/debugBG.js b/examples/utilities/render/debugBG.js similarity index 100% rename from examples/utilities/tools/render/debugBG.js rename to examples/utilities/render/debugBG.js diff --git a/examples/utilities/tools/render/debugFramebuffer.js b/examples/utilities/render/debugFramebuffer.js similarity index 100% rename from examples/utilities/tools/render/debugFramebuffer.js rename to examples/utilities/render/debugFramebuffer.js diff --git a/examples/utilities/render/debugRender.js b/examples/utilities/render/debugRender.js new file mode 100644 index 0000000000..788c7cb4a0 --- /dev/null +++ b/examples/utilities/render/debugRender.js @@ -0,0 +1,21 @@ +// +// debugRender.js +// examples/utilities/render +// +// Sam Gateau, created on 3/22/2016. +// Copyright 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 +// + +// Set up the qml ui +var qml = Script.resolvePath('culling.qml'); +var window = new OverlayWindow({ + title: 'Render Draws', + source: qml, + width: 300, + height: 200 +}); +window.setPosition(200, 50); +window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/examples/utilities/tools/render/framebuffer.qml b/examples/utilities/render/framebuffer.qml similarity index 100% rename from examples/utilities/tools/render/framebuffer.qml rename to examples/utilities/render/framebuffer.qml diff --git a/examples/utilities/tools/render/main.qml b/examples/utilities/render/main.qml similarity index 99% rename from examples/utilities/tools/render/main.qml rename to examples/utilities/render/main.qml index 22f263b2d0..aecd566207 100644 --- a/examples/utilities/tools/render/main.qml +++ b/examples/utilities/render/main.qml @@ -10,6 +10,7 @@ // import QtQuick 2.5 import QtQuick.Controls 1.4 +import "configSlider" Column { id: root diff --git a/examples/utilities/tools/render/plotperf/PlotPerf.qml b/examples/utilities/render/plotperf/PlotPerf.qml similarity index 67% rename from examples/utilities/tools/render/plotperf/PlotPerf.qml rename to examples/utilities/render/plotperf/PlotPerf.qml index 0e100e4e72..179707c0f1 100644 --- a/examples/utilities/tools/render/plotperf/PlotPerf.qml +++ b/examples/utilities/render/plotperf/PlotPerf.qml @@ -1,6 +1,6 @@ // // PlotPerf.qml -// examples/utilities/tools/render +// examples/utilities/render/plotperf // // Created by Sam Gateau on 3//2016 // Copyright 2016 High Fidelity, Inc. @@ -15,40 +15,64 @@ Item { id: root width: parent.width height: 100 + + // The title of the graph property string title - property var config - property string parameters - // THis is my hack to get the name of the first property and assign it to a trigger var in order to get + // THe object used as the default source object for the prop plots + property var object + + // THis is my hack to get a property and assign it to a trigger var in order to get // a signal called whenever the value changed - property var trigger: config[parameters.split(":")[3].split("-")[0]] + property var trigger + + // Plots is an array of plot descriptor + // a default plot descriptor expects the following object: + // prop: [ { + // object: {} // Optional: this is the object from which the prop will be fetched, + // if nothing than the object from root is used + // prop:"bufferCPUCount", // Needed the name of the property from the object to feed the plot + // label: "CPU", // Optional: Label as displayed on the plot + // color: "#00B4EF" // Optional: Color of the curve + // unit: "km/h" // Optional: Unit added to the value displayed, if nothing then the default unit is used + // scale: 1 // Optional: Extra scaling used to represent the value, this scale is combined with the global scale. + // }, + property var plots + + // Default value scale used to define the max value of the chart + property var valueScale: 1 + + // Default value unit appended to the value displayed + property var valueUnit: "" + + // Default number of digits displayed + property var valueNumDigits: 0 + - property var inputs: parameters.split(":") - property var valueScale: +inputs[0] - property var valueUnit: inputs[1] - property var valueNumDigits: inputs[2] - property var input_VALUE_OFFSET: 3 property var valueMax : 1 property var _values : new Array() property var tick : 0 function createValues() { - if (inputs.length > input_VALUE_OFFSET) { - for (var i = input_VALUE_OFFSET; i < inputs.length; i++) { - var varProps = inputs[i].split("-") - _values.push( { - value: varProps[0], - valueMax: 1, - numSamplesConstantMax: 0, - valueHistory: new Array(), - label: varProps[1], - color: varProps[2], - scale: (varProps.length > 3 ? varProps[3] : 1), - unit: (varProps.length > 4 ? varProps[4] : valueUnit) - }) - } - } + print("trigger is: " + JSON.stringify(trigger)) + if (Array.isArray(plots)) { + for (var i =0; i < plots.length; i++) { + var plot = plots[i]; + print(" a pnew Plot:" + JSON.stringify(plot)); + _values.push( { + object: (plot["object"] !== undefined ? plot["object"] : root.object), + value: plot["prop"], + valueMax: 1, + numSamplesConstantMax: 0, + valueHistory: new Array(), + label: (plot["label"] !== undefined ? plot["label"] : ""), + color: (plot["color"] !== undefined ? plot["color"] : "white"), + scale: (plot["scale"] !== undefined ? plot["scale"] : 1), + unit: (plot["unit"] !== undefined ? plot["unit"] : valueUnit) + }) + } + } print("in creator" + JSON.stringify(_values)); } @@ -69,7 +93,8 @@ Item { var currentValueMax = 0 for (var i = 0; i < _values.length; i++) { - var currentVal = config[_values[i].value] * _values[i].scale; + var currentVal = _values[i].object[_values[i].value] * _values[i].scale; + _values[i].valueHistory.push(currentVal) _values[i].numSamplesConstantMax++; diff --git a/examples/utilities/tools/render/plotperf/qmldir b/examples/utilities/render/plotperf/qmldir similarity index 100% rename from examples/utilities/tools/render/plotperf/qmldir rename to examples/utilities/render/plotperf/qmldir diff --git a/examples/utilities/tools/render/renderStats.js b/examples/utilities/render/renderStats.js similarity index 100% rename from examples/utilities/tools/render/renderStats.js rename to examples/utilities/render/renderStats.js diff --git a/examples/utilities/render/stats.qml b/examples/utilities/render/stats.qml new file mode 100644 index 0000000000..0e51cb8834 --- /dev/null +++ b/examples/utilities/render/stats.qml @@ -0,0 +1,195 @@ +// +// stats.qml +// examples/utilities/render +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "plotperf" + +Item { + id: statsUI + anchors.fill:parent + + Column { + id: stats + spacing: 8 + anchors.fill:parent + + property var config: Render.getConfig("Stats") + + function evalEvenHeight() { + // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? + return (height - spacing * (children.length - 1)) / children.length + } + + PlotPerf { + title: "Num Buffers" + height: parent.evalEvenHeight() + object: stats.config + trigger: stats.config["bufferCPUCount"] + plots: [ + { + prop: "bufferCPUCount", + label: "CPU", + color: "#00B4EF" + }, + { + prop: "bufferGPUCount", + label: "GPU", + color: "#1AC567" + } + ] + } + PlotPerf { + title: "gpu::Buffer Memory" + height: parent.evalEvenHeight() + object: stats.config + trigger: stats.config["bufferCPUMemoryUsage"] + valueScale: 1048576 + valueUnit: "Mb" + valueNumDigits: "1" + plots: [ + { + prop: "bufferCPUMemoryUsage", + label: "CPU", + color: "#00B4EF" + }, + { + prop: "bufferGPUMemoryUsage", + label: "GPU", + color: "#1AC567" + } + ] + } + PlotPerf { + title: "Num Textures" + height: parent.evalEvenHeight() + object: stats.config + trigger: stats.config["textureCPUCount"] + plots: [ + { + prop: "textureCPUCount", + label: "CPU", + color: "#00B4EF" + }, + { + prop: "textureGPUCount", + label: "GPU", + color: "#1AC567" + }, + { + prop: "frameTextureCount", + label: "Frame", + color: "#E2334D" + } + ] + } + PlotPerf { + title: "gpu::Texture Memory" + height: parent.evalEvenHeight() + object: stats.config + trigger: stats.config["textureCPUMemoryUsage"] + valueScale: 1048576 + valueUnit: "Mb" + valueNumDigits: "1" + plots: [ + { + prop: "textureCPUMemoryUsage", + label: "CPU", + color: "#00B4EF" + }, + { + prop: "textureGPUMemoryUsage", + label: "GPU", + color: "#1AC567" + } + ] + } + + PlotPerf { + title: "Triangles" + height: parent.evalEvenHeight() + object: stats.config + trigger: stats.config["frameTriangleCount"] + valueScale: 1000 + valueUnit: "K" + plots: [ + { + prop: "frameTriangleCount", + label: "Triangles", + color: "#1AC567" + }, + { + prop: "frameTriangleRate", + label: "rate", + color: "#E2334D", + scale: 0.001, + unit: "MT/s" + } + ] + } + PlotPerf { + title: "Drawcalls" + height: parent.evalEvenHeight() + object: stats.config + trigger: stats.config["frameDrawcallCount"] + plots: [ + { + prop: "frameAPIDrawcallCount", + label: "API Drawcalls", + color: "#00B4EF" + }, + { + prop: "frameDrawcallCount", + label: "GPU Drawcalls", + color: "#1AC567" + }, + { + prop: "frameDrawcallRate", + label: "rate", + color: "#E2334D", + scale: 0.001, + unit: "K/s" + } + ] + } + + property var drawOpaqueConfig: Render.getConfig("DrawOpaqueDeferred") + property var drawTransparentConfig: Render.getConfig("DrawTransparentDeferred") + property var drawLightConfig: Render.getConfig("DrawLight") + + PlotPerf { + title: "Items" + height: parent.evalEvenHeight() + object: parent.drawOpaqueConfig + trigger: Render.getConfig("DrawOpaqueDeferred")["numDrawn"] + plots: [ + { + object: Render.getConfig("DrawOpaqueDeferred"), + prop: "numDrawn", + label: "Opaques", + color: "#1AC567" + }, + { + object: Render.getConfig("DrawTransparentDeferred"), + prop: "numDrawn", + label: "Translucents", + color: "#00B4EF" + }, + { + object: Render.getConfig("DrawLight"), + prop: "numDrawn", + label: "Lights", + color: "#E2334D" + } + ] + } + } + +} diff --git a/examples/utilities/tools/debugRenderCulling.js b/examples/utilities/tools/debugRenderCulling.js deleted file mode 100644 index dbc5f07e0d..0000000000 --- a/examples/utilities/tools/debugRenderCulling.js +++ /dev/null @@ -1,99 +0,0 @@ -// -// debugRenderOctree.js -// examples/utilities/tools -// -// Sam Gateau -// Copyright 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 -// - -Script.include("cookies.js"); - -var panel = new Panel(10, 300); -var drawOctree = Render.RenderDeferredTask.DrawSceneOctree; -Render.RenderDeferredTask.DrawSceneOctree.enabled = true; -Render.RenderDeferredTask.DrawItemSelection.enabled = true; - -panel.newCheckbox("Show Octree Cells", - function(value) { Render.RenderDeferredTask.DrawSceneOctree.showVisibleCells = value; }, - function() { return (Render.RenderDeferredTask.DrawSceneOctree.showVisibleCells); }, - function(value) { return (value); } -); -panel.newCheckbox("Show Empty Cells", - function(value) { Render.RenderDeferredTask.DrawSceneOctree.showEmptyCells = value; }, - function() { return (Render.RenderDeferredTask.DrawSceneOctree.showEmptyCells); }, - function(value) { return (value); } -); -panel.newCheckbox("Freeze Frustum", - function(value) { Render.RenderDeferredTask.FetchSceneSelection.freezeFrustum = value; Render.RenderDeferredTask.CullSceneSelection.freezeFrustum = value; }, - function() { return (Render.RenderDeferredTask.FetchSceneSelection.freezeFrustum); }, - function(value) { return (value); } -); -panel.newCheckbox("Show Inside Items", - function(value) { Render.RenderDeferredTask.DrawItemSelection.showInsideItems = value; }, - function() { return (Render.RenderDeferredTask.DrawItemSelection.showInsideItems); }, - function(value) { return (value); } -); - -panel.newCheckbox("Show Inside Subcell Items", - function(value) { Render.RenderDeferredTask.DrawItemSelection.showInsideSubcellItems = value; }, - function() { return (Render.RenderDeferredTask.DrawItemSelection.showInsideSubcellItems); }, - function(value) { return (value); } -); - -panel.newCheckbox("Show Partial Items", - function(value) { Render.RenderDeferredTask.DrawItemSelection.showPartialItems = value; }, - function() { return (Render.RenderDeferredTask.DrawItemSelection.showPartialItems); }, - function(value) { return (value); } -); - -panel.newCheckbox("Show Partial Subcell Items", - function(value) { Render.RenderDeferredTask.DrawItemSelection.showPartialSubcellItems = value; }, - function() { return (Render.RenderDeferredTask.DrawItemSelection.showPartialSubcellItems); }, - function(value) { return (value); } -); - -/* -panel.newSlider('Cells Free / Allocated', -1, 1, - function(value) { value; }, // setter - function() { return Render.RenderDeferredTask.DrawSceneOctree.numFreeCells; }, // getter - function(value) { return value; }); - -this.update = function () { - var numFree = Render.RenderDeferredTask.DrawSceneOctree.numFreeCells; - var numAlloc = Render.RenderDeferredTask.DrawSceneOctree.numAllocatedCells; - var title = [ - ' ' + name, - numFree + ' / ' + numAlloc - ].join('\t'); - - widget.editTitle({ text: title }); - slider.setMaxValue(numAlloc); -}; -*/ -function mouseMoveEvent(event) { - panel.mouseMoveEvent(event); -} - -function mousePressEvent(event) { - panel.mousePressEvent(event); -} - -function mouseReleaseEvent(event) { - panel.mouseReleaseEvent(event); -} - -Controller.mouseMoveEvent.connect(mouseMoveEvent); -Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); - -function scriptEnding() { - panel.destroy(); - Render.RenderDeferredTask.DrawSceneOctree.enabled = false; - Render.RenderDeferredTask.DrawItemSelection.enabled = false; -} -Script.scriptEnding.connect(scriptEnding); - - diff --git a/examples/utilities/tools/render/stats.qml b/examples/utilities/tools/render/stats.qml deleted file mode 100644 index aacc896444..0000000000 --- a/examples/utilities/tools/render/stats.qml +++ /dev/null @@ -1,69 +0,0 @@ -// -// stats.qml -// examples/utilities/tools/render -// -// Created by Zach Pomerantz on 2/8/2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html -// -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "plotperf" - -Item { - id: statsUI - anchors.fill:parent - - Column { - id: stats - spacing: 8 - anchors.fill:parent - - property var config: Render.getConfig("Stats") - - function evalEvenHeight() { - // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? - return (height - spacing * (children.length - 1)) / children.length - } - - PlotPerf { - title: "Num Buffers" - config: stats.config - height: parent.evalEvenHeight() - parameters: "1::0:bufferCPUCount-CPU-#00B4EF:bufferGPUCount-GPU-#1AC567" - } - PlotPerf { - title: "gpu::Buffer Memory" - config: stats.config - height: parent.evalEvenHeight() - parameters: "1048576:Mb:1:bufferCPUMemoryUsage-CPU-#00B4EF:bufferGPUMemoryUsage-GPU-#1AC567" - } - - PlotPerf { - title: "Num Textures" - config: stats.config - height: parent.evalEvenHeight() - parameters: "1::0:textureCPUCount-CPU-#00B4EF:textureGPUCount-GPU-#1AC567:frameTextureCount-Frame-#E2334D" - } - PlotPerf { - title: "gpu::Texture Memory" - config: stats.config - height: parent.evalEvenHeight() - parameters: "1048576:Mb:1:textureCPUMemoryUsage-CPU-#00B4EF:textureGPUMemoryUsage-GPU-#1AC567" - } - PlotPerf { - title: "Drawcalls" - config: stats.config - height: parent.evalEvenHeight() - parameters: "1::0:frameDrawcallCount-frame-#E2334D:frameDrawcallRate-rate-#1AC567-0.001-K/s" - } - PlotPerf { - title: "Triangles" - config: stats.config - height: parent.evalEvenHeight() - parameters: "1000:K:0:frameTriangleCount-frame-#E2334D:frameTriangleRate-rate-#1AC567-0.001-MT/s" - } - } -} diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index d7fba12f26..a512d5a049 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -33,7 +34,8 @@ IceServer::IceServer(int argc, char* argv[]) : _id(QUuid::createUuid()), _serverSocket(), _activePeers(), - _httpManager(QHostAddress::AnyIPv4, ICE_SERVER_MONITORING_PORT, QString("%1/web/").arg(QCoreApplication::applicationDirPath()), this) + _httpManager(QHostAddress::AnyIPv4, ICE_SERVER_MONITORING_PORT, QString("%1/web/").arg(QCoreApplication::applicationDirPath()), this), + _lastInactiveCheckTimestamp(QDateTime::currentMSecsSinceEpoch()) { // start the ice-server socket qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT; @@ -68,8 +70,6 @@ bool IceServer::packetVersionMatch(const udt::Packet& packet) { void IceServer::processPacket(std::unique_ptr packet) { - _lastPacketTimestamp = QDateTime::currentMSecsSinceEpoch(); - auto nlPacket = NLPacket::fromBase(std::move(packet)); // make sure that this packet at least looks like something we can read @@ -201,8 +201,8 @@ bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& pla void IceServer::requestDomainPublicKey(const QUuid& domainID) { // send a request to the metaverse API for the public key for this domain - QNetworkAccessManager* manager = new QNetworkAccessManager { this }; - connect(manager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished); + auto& networkAccessManager = NetworkAccessManager::getInstance(); + connect(&networkAccessManager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished); QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL }; QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID)); @@ -213,7 +213,7 @@ void IceServer::requestDomainPublicKey(const QUuid& domainID) { qDebug() << "Requesting public key for domain with ID" << domainID; - manager->get(publicKeyRequest); + networkAccessManager.get(publicKeyRequest); } void IceServer::publicKeyReplyFinished(QNetworkReply* reply) { @@ -281,6 +281,8 @@ void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSoc void IceServer::clearInactivePeers() { NetworkPeerHash::iterator peerItem = _activePeers.begin(); + _lastInactiveCheckTimestamp = QDateTime::currentMSecsSinceEpoch(); + while (peerItem != _activePeers.end()) { SharedNetworkPeer peer = peerItem.value(); @@ -309,11 +311,14 @@ bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, b const quint64 MAX_PACKET_GAP_MS_FOR_STUCK_SOCKET = 10 * 1000; - int statusNumber = (QDateTime::currentMSecsSinceEpoch() - _lastPacketTimestamp > MAX_PACKET_GAP_MS_FOR_STUCK_SOCKET) - ? 1 : 0; + auto sinceLastInactiveCheck = QDateTime::currentMSecsSinceEpoch() - _lastInactiveCheckTimestamp; + int statusNumber = (sinceLastInactiveCheck > MAX_PACKET_GAP_MS_FOR_STUCK_SOCKET) ? 1 : 0; connection->respond(HTTPConnection::StatusCode200, QByteArray::number(statusNumber)); + + return true; } } - return true; + + return false; } diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 6cc33fd8fc..7d1d05324c 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -58,7 +58,7 @@ private: using DomainPublicKeyHash = std::unordered_map; DomainPublicKeyHash _domainPublicKeys; - quint64 _lastPacketTimestamp; + quint64 _lastInactiveCheckTimestamp; }; #endif // hifi_IceServer_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c6669303a8..ffc9b0333c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -224,7 +224,6 @@ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStanda static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/script.js"); #endif -const QString DEFAULT_SCRIPTS_JS_URL = "http://s3.amazonaws.com/hifi-public/scripts/defaultScripts.js"; Setting::Handle maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS); const QHash Application::_acceptedExtensions { @@ -598,8 +597,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : audioThread->setObjectName("Audio Thread"); auto audioIO = DependencyManager::get(); - audioIO->setPositionGetter([this]{ return getMyAvatar()->getPositionForAudio(); }); - audioIO->setOrientationGetter([this]{ return getMyAvatar()->getOrientationForAudio(); }); + audioIO->setPositionGetter([]{ + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + + return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; + }); + audioIO->setOrientationGetter([]{ + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + + return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; + }); audioIO->moveToThread(audioThread); recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { @@ -1480,11 +1489,15 @@ void Application::paintGL() { // FIXME not needed anymore? _offscreenContext->makeCurrent(); - displayPlugin->updateHeadPose(_frameCount); + displayPlugin->beginFrameRender(_frameCount); // update the avatar with a fresh HMD pose getMyAvatar()->updateFromHMDSensorMatrix(getHMDSensorPose()); + // update sensorToWorldMatrix for camera and hand controllers + getMyAvatar()->updateSensorToWorldMatrix(); + + auto lodManager = DependencyManager::get(); @@ -1997,6 +2010,12 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_Y: + if (isShifted && isMeta) { + getActiveDisplayPlugin()->cycleDebugOutput(); + } + break; + case Qt::Key_B: if (isMeta) { auto offscreenUi = DependencyManager::get(); @@ -2562,11 +2581,6 @@ void Application::idle(uint64_t now) { return; // bail early, nothing to do here. } - checkChangeCursor(); - - Stats::getInstance()->updateStats(); - AvatarInputs::getInstance()->update(); - // These tasks need to be done on our first idle, because we don't want the showing of // overlay subwindows to do a showDesktop() until after the first time through static bool firstIdle = true; @@ -2615,6 +2629,11 @@ void Application::idle(uint64_t now) { // We're going to execute idle processing, so restart the last idle timer _lastTimeUpdated.start(); + checkChangeCursor(); + + Stats::getInstance()->updateStats(); + AvatarInputs::getInstance()->update(); + { static uint64_t lastIdleStart{ now }; uint64_t idleStartToStartDuration = now - lastIdleStart; @@ -2790,43 +2809,50 @@ void Application::calibrateEyeTracker5Points() { } #endif -bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { - QVector entities; +bool Application::exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset) { + QHash entities; auto entityTree = getEntities()->getTree(); auto exportTree = std::make_shared(); exportTree->createRootElement(); glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE); - for (auto entityID : entityIDs) { + for (auto entityID : entityIDs) { // Gather entities and properties. auto entityItem = entityTree->findEntityByEntityItemID(entityID); if (!entityItem) { + qCWarning(interfaceapp) << "Skipping export of" << entityID << "that is not in scene."; continue; } - auto properties = entityItem->getProperties(); - auto position = properties.getPosition(); - - root.x = glm::min(root.x, position.x); - root.y = glm::min(root.y, position.y); - root.z = glm::min(root.z, position.z); - - entities << entityItem; + if (!givenOffset) { + EntityItemID parentID = entityItem->getParentID(); + if (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID)) { + auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties. + root.x = glm::min(root.x, position.x); + root.y = glm::min(root.y, position.y); + root.z = glm::min(root.z, position.z); + } + } + entities[entityID] = entityItem; } if (entities.size() == 0) { return false; } - for (auto entityItem : entities) { - auto properties = entityItem->getProperties(); - - properties.setPosition(properties.getPosition() - root); - exportTree->addEntity(entityItem->getEntityItemID(), properties); + if (givenOffset) { + root = *givenOffset; + } + for (EntityItemPointer& entityDatum : entities) { + auto properties = entityDatum->getProperties(); + EntityItemID parentID = properties.getParentID(); + if (parentID.isInvalidID()) { + properties.setPosition(properties.getPosition() - root); + } else if (!entities.contains(parentID)) { + entityDatum->globalizeProperties(properties, "Parent %3 of %2 %1 is not selected for export.", -root); + } // else valid parent -- don't offset + exportTree->addEntity(entityDatum->getEntityItemID(), properties); } - - // remap IDs on export so that we aren't publishing the IDs of entities in our domain - exportTree->remapIDs(); exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); @@ -2836,33 +2862,14 @@ bool Application::exportEntities(const QString& filename, const QVector entities; - getEntities()->getTree()->findEntities(AACube(glm::vec3(x, y, z), scale), entities); - - if (entities.size() > 0) { - glm::vec3 root(x, y, z); - auto exportTree = std::make_shared(); - exportTree->createRootElement(); - - for (int i = 0; i < entities.size(); i++) { - EntityItemProperties properties = entities.at(i)->getProperties(); - EntityItemID id = entities.at(i)->getEntityItemID(); - properties.setPosition(properties.getPosition() - root); - exportTree->addEntity(id, properties); - } - - // remap IDs on export so that we aren't publishing the IDs of entities in our domain - exportTree->remapIDs(); - - exportTree->writeToSVOFile(filename.toLocal8Bit().constData()); - } else { - qCDebug(interfaceapp) << "No models were selected"; - return false; + QVector ids; + getEntities()->getTree()->findEntities(AACube(offset, scale), entities); + foreach(EntityItemPointer entity, entities) { + ids << entity->getEntityItemID(); } - - // restore the main window's active state - _window->activateWindow(); - return true; + return exportEntities(filename, ids, &offset); } void Application::loadSettings() { @@ -2895,7 +2902,6 @@ bool Application::importEntities(const QString& urlOrFilename) { bool success = _entityClipboard->readFromURL(urlOrFilename); if (success) { - _entityClipboard->remapIDs(); _entityClipboard->reaverageOctreeElements(); } return success; @@ -2978,6 +2984,11 @@ void Application::updateLOD() { } } +void Application::pushPreRenderLambda(void* key, std::function func) { + std::unique_lock guard(_preRenderLambdasLock); + _preRenderLambdas[key] = func; +} + // Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone. // (Maybe this code should be moved there?) // The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition(). @@ -3346,9 +3357,10 @@ void Application::update(float deltaTime) { } { PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); - PerformanceTimer perfTimer("havestChanges"); + PerformanceTimer perfTimer("harvestChanges"); if (_physicsEngine->hasOutgoingChanges()) { getEntities()->getTree()->withWriteLock([&] { + PerformanceTimer perfTimer("handleOutgoingChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges(); _entitySimulation.handleOutgoingChanges(outgoingChanges, Physics::getSessionUUID()); avatarManager->handleOutgoingChanges(outgoingChanges); @@ -3364,6 +3376,7 @@ void Application::update(float deltaTime) { // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) _entitySimulation.handleCollisionEvents(collisionEvents); + // NOTE: the getEntities()->update() call below will wait for lock // and will simulate entity motion (the EntityTree has been given an EntitySimulation). getEntities()->update(); // update the models... @@ -3387,9 +3400,6 @@ void Application::update(float deltaTime) { qApp->updateMyAvatarLookAtPosition(); - // update sensorToWorldMatrix for camera and hand controllers - myAvatar->updateSensorToWorldMatrix(); - { PROFILE_RANGE_EX("MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount()); avatarManager->updateMyAvatar(deltaTime); @@ -3454,6 +3464,16 @@ void Application::update(float deltaTime) { QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); } } + + { + PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0); + + std::unique_lock guard(_preRenderLambdasLock); + for (auto& iter : _preRenderLambdas) { + iter.second(); + } + _preRenderLambdas.clear(); + } } diff --git a/interface/src/Application.h b/interface/src/Application.h index d21e647bc7..31c865ad90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -211,6 +211,8 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } + virtual void pushPreRenderLambda(void* key, std::function func) override; + const QRect& getMirrorViewRect() const { return _mirrorViewRect; } void updateMyAvatarLookAtPosition(); @@ -233,7 +235,7 @@ signals: public slots: QVector pasteEntities(float x, float y, float z); - bool exportEntities(const QString& filename, const QVector& entityIDs); + bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool importEntities(const QString& url); @@ -510,6 +512,9 @@ private: bool _cursorNeedsChanging { false }; QThread* _deadlockWatchdogThread; + + std::map> _preRenderLambdas; + std::mutex _preRenderLambdasLock; }; #endif // hifi_Application_h diff --git a/interface/src/InterfaceParentFinder.cpp b/interface/src/InterfaceParentFinder.cpp index de4b0ce38c..b1db63debd 100644 --- a/interface/src/InterfaceParentFinder.cpp +++ b/interface/src/InterfaceParentFinder.cpp @@ -16,7 +16,7 @@ #include "InterfaceParentFinder.h" -SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success) const { +SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { @@ -25,9 +25,13 @@ SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& s } // search entities - EntityTreeRenderer* treeRenderer = qApp->getEntities(); - EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr; - parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr; + if (entityTree) { + parent = entityTree->findByID(parentID); + } else { + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr; + parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr; + } if (!parent.expired()) { success = true; return parent; diff --git a/interface/src/InterfaceParentFinder.h b/interface/src/InterfaceParentFinder.h index db579c2144..a2e9fb50e4 100644 --- a/interface/src/InterfaceParentFinder.h +++ b/interface/src/InterfaceParentFinder.h @@ -21,7 +21,7 @@ class InterfaceParentFinder : public SpatialParentFinder { public: InterfaceParentFinder() { } virtual ~InterfaceParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const; }; #endif // hifi_InterfaceParentFinder_h diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 056b5b76c4..3417011051 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -859,7 +859,11 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); - _skeletonModel->setURL(_skeletonModelURL); + if (QThread::currentThread() == thread()) { + _skeletonModel->setURL(_skeletonModelURL); + } else { + QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", Qt::QueuedConnection, Q_ARG(QUrl, _skeletonModelURL)); + } } // create new model, can return an instance of a SoftAttachmentModel rather then Model diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 38babc4ef0..6aa6f57e07 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -373,32 +373,34 @@ void MyAvatar::simulate(float deltaTime) { EntityTreeRenderer* entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { - auto now = usecTimestampNow(); - EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); - MovingEntitiesOperator moveOperator(entityTree); - forEachDescendant([&](SpatiallyNestablePointer object) { - // if the queryBox has changed, tell the entity-server - if (object->computePuffedQueryAACube() && object->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast(object); - bool success; - AACube newCube = entity->getQueryAACube(success); - if (success) { - moveOperator.addEntityToMoveList(entity, newCube); - } - if (packetSender) { - EntityItemProperties properties = entity->getProperties(); - properties.setQueryAACubeDirty(); - properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); - entity->setLastBroadcast(usecTimestampNow()); + entityTree->withWriteLock([&] { + auto now = usecTimestampNow(); + EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); + MovingEntitiesOperator moveOperator(entityTree); + forEachDescendant([&](SpatiallyNestablePointer object) { + // if the queryBox has changed, tell the entity-server + if (object->computePuffedQueryAACube() && object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + bool success; + AACube newCube = entity->getQueryAACube(success); + if (success) { + moveOperator.addEntityToMoveList(entity, newCube); + } + if (packetSender) { + EntityItemProperties properties = entity->getProperties(); + properties.setQueryAACubeDirty(); + properties.setLastEdited(now); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + entity->setLastBroadcast(usecTimestampNow()); + } } + }); + // also update the position of children in our local octree + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + entityTree->recurseTreeWithOperator(&moveOperator); } }); - // also update the position of children in our local octree - if (moveOperator.hasMovingEntities()) { - PerformanceTimer perfTimer("recurseTreeWithOperator"); - entityTree->recurseTreeWithOperator(&moveOperator); - } } } diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 5949b3318a..7bf1547a3c 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -97,3 +97,11 @@ bool HMDScriptingInterface::isMounted() const{ auto displayPlugin = qApp->getActiveDisplayPlugin(); return (displayPlugin->isHmd() && displayPlugin->isDisplayVisible()); } + +QString HMDScriptingInterface::preferredAudioInput() const { + return qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice(); +} + +QString HMDScriptingInterface::preferredAudioOutput() const { + return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 0057fe4eb9..d4c7b7cc0e 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -34,6 +34,8 @@ public: Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const; Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; + Q_INVOKABLE QString preferredAudioInput() const; + Q_INVOKABLE QString preferredAudioOutput() const; public: HMDScriptingInterface(); diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 010698800b..adf08934f0 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -18,22 +18,22 @@ QString const ModelOverlay::TYPE = "model"; ModelOverlay::ModelOverlay() - : _model(std::make_shared()), + : _model(std::make_shared(std::make_shared())), _modelTextures(QVariantMap()), _updateModel(false) { - _model.init(); + _model->init(); _isLoaded = false; } ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : Volume3DOverlay(modelOverlay), - _model(std::make_shared()), + _model(std::make_shared(std::make_shared())), _modelTextures(QVariantMap()), _url(modelOverlay->_url), _updateModel(false) { - _model.init(); + _model->init(); if (_url.isValid()) { _updateModel = true; _isLoaded = false; @@ -44,27 +44,27 @@ void ModelOverlay::update(float deltatime) { if (_updateModel) { _updateModel = false; - _model.setSnapModelToCenter(true); - _model.setScale(getDimensions()); - _model.setRotation(getRotation()); - _model.setTranslation(getPosition()); - _model.setURL(_url); - _model.simulate(deltatime, true); + _model->setSnapModelToCenter(true); + _model->setScale(getDimensions()); + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + _model->setURL(_url); + _model->simulate(deltatime, true); } else { - _model.simulate(deltatime); + _model->simulate(deltatime); } - _isLoaded = _model.isActive(); + _isLoaded = _model->isActive(); } bool ModelOverlay::addToScene(Overlay::Pointer overlay, std::shared_ptr scene, render::PendingChanges& pendingChanges) { Volume3DOverlay::addToScene(overlay, scene, pendingChanges); - _model.addToScene(scene, pendingChanges); + _model->addToScene(scene, pendingChanges); return true; } void ModelOverlay::removeFromScene(Overlay::Pointer overlay, std::shared_ptr scene, render::PendingChanges& pendingChanges) { Volume3DOverlay::removeFromScene(overlay, scene, pendingChanges); - _model.removeFromScene(scene, pendingChanges); + _model->removeFromScene(scene, pendingChanges); } void ModelOverlay::render(RenderArgs* args) { @@ -73,9 +73,9 @@ void ModelOverlay::render(RenderArgs* args) { // fix them up in the scene render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; - if (_model.needsFixupInScene()) { - _model.removeFromScene(scene, pendingChanges); - _model.addToScene(scene, pendingChanges); + if (_model->needsFixupInScene()) { + _model->removeFromScene(scene, pendingChanges); + _model->addToScene(scene, pendingChanges); } scene->enqueuePendingChanges(pendingChanges); @@ -100,7 +100,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) { setDimensions(scale); } else { - _model.setScaleToFit(true, getDimensions()); + _model->setScaleToFit(true, getDimensions()); _updateModel = true; } } @@ -120,7 +120,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { QUrl newTextureURL = textureMap[key].toUrl(); qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL; - QMetaObject::invokeMethod(&_model, "setTextureWithNameToURL", Qt::AutoConnection, + QMetaObject::invokeMethod(_model.get(), "setTextureWithNameToURL", Qt::AutoConnection, Q_ARG(const QString&, key), Q_ARG(const QUrl&, newTextureURL)); @@ -134,7 +134,7 @@ QVariant ModelOverlay::getProperty(const QString& property) { return _url.toString(); } if (property == "dimensions" || property == "scale" || property == "size") { - return vec3toVariant(_model.getScaleToFitDimensions()); + return vec3toVariant(_model->getScaleToFitDimensions()); } if (property == "textures") { if (_modelTextures.size() > 0) { @@ -155,13 +155,13 @@ bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& float& distance, BoxFace& face, glm::vec3& surfaceNormal) { QString subMeshNameTemp; - return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp); + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) { - return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); } ModelOverlay* ModelOverlay::createClone() const { diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 0b67f7ed37..36ff75cb6a 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -41,11 +41,11 @@ public: private: - Model _model; + ModelPointer _model; QVariantMap _modelTextures; QUrl _url; bool _updateModel; }; -#endif // hifi_ModelOverlay_h \ No newline at end of file +#endif // hifi_ModelOverlay_h diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index ca666443fa..02d3f8873e 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -36,7 +36,7 @@ AnimationPointer AnimationCache::getAnimation(const QUrl& url) { QSharedPointer AnimationCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new Animation(url), &Resource::allReferencesCleared); + return QSharedPointer(new Animation(url), &Resource::deleter); } Animation::Animation(const QUrl& url) : Resource(url) {} diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a2b664d064..6a8f190808 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1056,7 +1056,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm // limit rotation const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); + if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) { + deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); + } // directly set absolutePose rotation _internalPoseSet._absolutePoses[index].rot = deltaQuat * headQuat; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 1f9a02d8ab..3a27b9304b 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -221,6 +221,8 @@ public: void setEnableInverseKinematics(bool enable); + const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } + protected: bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); } void updateAnimationStateHandlers(); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 7e01196dc7..0c7a79e2a3 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -50,6 +50,9 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100; +static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; }; +static const auto DEFAULT_ORIENTATION_GETTER = [] { return Quaternions::IDENTITY; }; + Setting::Handle dynamicJitterBuffers("dynamicJitterBuffers", DEFAULT_DYNAMIC_JITTER_BUFFERS); Setting::Handle maxFramesOverDesired("maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED); Setting::Handle staticDesiredJitterBufferFrames("staticDesiredJitterBufferFrames", @@ -103,7 +106,9 @@ AudioClient::AudioClient() : _outgoingAvatarAudioSequenceNumber(0), _audioOutputIODevice(_receivedAudioStream, this), _stats(&_receivedAudioStream), - _inputGate() + _inputGate(), + _positionGetter(DEFAULT_POSITION_GETTER), + _orientationGetter(DEFAULT_ORIENTATION_GETTER) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL); @@ -170,6 +175,50 @@ int numDestinationSamplesRequired(const QAudioFormat& sourceFormat, const QAudio return (numSourceSamples * ratio) + 0.5f; } +#ifdef Q_OS_WIN +QString friendlyNameForAudioDevice(IMMDevice* pEndpoint) { + QString deviceName; + IPropertyStore* pPropertyStore; + pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore); + pEndpoint->Release(); + pEndpoint = NULL; + PROPVARIANT pv; + PropVariantInit(&pv); + HRESULT hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); + pPropertyStore->Release(); + pPropertyStore = NULL; + deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal); + if (!IsWindows8OrGreater()) { + // Windows 7 provides only the 31 first characters of the device name. + const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31; + deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN); + } + PropVariantClear(&pv); + return deviceName; +} + +QString AudioClient::friendlyNameForAudioDevice(wchar_t* guid) { + QString deviceName; + HRESULT hr = S_OK; + CoInitialize(NULL); + IMMDeviceEnumerator* pMMDeviceEnumerator = NULL; + CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator); + IMMDevice* pEndpoint; + hr = pMMDeviceEnumerator->GetDevice(guid, &pEndpoint); + if (hr == E_NOTFOUND) { + printf("Audio Error: device not found\n"); + deviceName = QString("NONE"); + } else { + deviceName = ::friendlyNameForAudioDevice(pEndpoint); + } + pMMDeviceEnumerator->Release(); + pMMDeviceEnumerator = NULL; + CoUninitialize(); + return deviceName; +} + +#endif + QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #ifdef __APPLE__ if (QAudioDeviceInfo::availableDevices(mode).size() > 1) { @@ -243,23 +292,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { printf("Audio Error: device not found\n"); deviceName = QString("NONE"); } else { - IPropertyStore* pPropertyStore; - pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore); - pEndpoint->Release(); - pEndpoint = NULL; - PROPVARIANT pv; - PropVariantInit(&pv); - hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); - pPropertyStore->Release(); - pPropertyStore = NULL; - deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal); - if (!IsWindows8OrGreater()) { - // Windows 7 provides only the 31 first characters of the device name. - const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31; - deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN); - } - qCDebug(audioclient) << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName; - PropVariantClear(&pv); + deviceName = friendlyNameForAudioDevice(pEndpoint); } pMMDeviceEnumerator->Release(); pMMDeviceEnumerator = NULL; @@ -550,7 +583,7 @@ void AudioClient::configureReverb() { p.wetDryMix = 100.0f; p.preDelay = 0.0f; p.earlyGain = -96.0f; // disable ER - p.lateGain -= 12.0f; // quieter than listener reverb + p.lateGain += _reverbOptions->getWetDryMix() * (24.0f/100.0f) - 24.0f; // -0dB to -24dB, based on wetDryMix p.lateMixLeft = 0.0f; p.lateMixRight = 0.0f; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index d3145629ee..3a14c878f6 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -126,6 +127,10 @@ public: static const float CALLBACK_ACCELERATOR_RATIO; +#ifdef Q_OS_WIN + static QString friendlyNameForAudioDevice(wchar_t* guid); +#endif + public slots: void start(); void stop(); diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index a7af1bdda2..abcdb2da7c 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -36,5 +36,5 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) { QSharedPointer SoundCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { qCDebug(audio) << "Requesting sound at" << url.toString(); - return QSharedPointer(new Sound(url), &Resource::allReferencesCleared); + return QSharedPointer(new Sound(url), &Resource::deleter); } diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 2b0613321e..f101ba6c51 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -81,6 +81,9 @@ namespace controller { // Triggers LT, RT, + // Grips (Oculus touch squeeze) + LG, + RG, NUM_STANDARD_AXES, LZ = LT, RZ = RT diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 8049e2d5a5..e5d98d18f7 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -552,9 +552,9 @@ float OpenGLDisplayPlugin::presentRate() { { Lock lock(_mutex); result = _usecsPerFrame.getAverage(); - result = 1.0f / result; - result *= USECS_PER_SECOND; } + result = 1.0f / result; + result *= USECS_PER_SECOND; return result; } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index b9628deb6c..8c4862ee3d 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -17,9 +17,9 @@ #include #include #include +#include #define THREADED_PRESENT 1 -#include class OpenGLDisplayPlugin : public DisplayPlugin { protected: diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 5be3f0d96a..505fa004ab 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -69,10 +69,11 @@ void HmdDisplayPlugin::compositeOverlay() { // set the alpha Uniform(*_program, _alphaUniform).Set(overlayAlpha); + auto eyePoses = _currentPresentFrameInfo.eyePoses; _sphereSection->Use(); for_each_eye([&](Eye eye) { eyeViewport(eye); - auto modelView = glm::inverse(_currentRenderEyePoses[eye]); // *glm::translate(mat4(), vec3(0, 0, -1)); + auto modelView = glm::inverse(eyePoses[eye]); // *glm::translate(mat4(), vec3(0, 0, -1)); auto mvp = _eyeProjections[eye] * modelView; Uniform(*_program, _mvpUniform).Set(mvp); _sphereSection->Draw(); @@ -95,10 +96,10 @@ void HmdDisplayPlugin::compositePointer() { // Mouse pointer _plane->Use(); // Reconstruct the headpose from the eye poses - auto headPosition = (vec3(_currentRenderEyePoses[Left][3]) + vec3(_currentRenderEyePoses[Right][3])) / 2.0f; + auto headPosition = vec3(_currentPresentFrameInfo.headPose[3]); for_each_eye([&](Eye eye) { eyeViewport(eye); - auto reticleTransform = compositorHelper->getReticleTransform(_currentRenderEyePoses[eye], headPosition); + auto reticleTransform = compositorHelper->getReticleTransform(_currentPresentFrameInfo.eyePoses[eye], headPosition); auto mvp = _eyeProjections[eye] * reticleTransform; Uniform(*_program, _mvpUniform).Set(mvp); _plane->Draw(); @@ -160,15 +161,28 @@ void HmdDisplayPlugin::internalPresent() { void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { Lock lock(_mutex); - _renderEyePoses[frameIndex][eye] = pose; + FrameInfo& frame = _frameInfos[frameIndex]; + frame.eyePoses[eye] = pose; } void HmdDisplayPlugin::updateFrameData() { + // Check if we have old frame data to discard + { + Lock lock(_mutex); + auto itr = _frameInfos.find(_currentRenderFrameIndex); + if (itr != _frameInfos.end()) { + _frameInfos.erase(itr); + } + } + Parent::updateFrameData(); - Lock lock(_mutex); - _currentRenderEyePoses = _renderEyePoses[_currentRenderFrameIndex]; + + { + Lock lock(_mutex); + _currentPresentFrameInfo = _frameInfos[_currentRenderFrameIndex]; + } } glm::mat4 HmdDisplayPlugin::getHeadPose() const { - return _headPoseCache.get(); + return _currentRenderFrameInfo.get().headPose; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 080a44bc66..899dd6636a 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -28,6 +28,16 @@ public: virtual glm::mat4 getHeadPose() const override; + using EyePoses = std::array; + + struct FrameInfo { + EyePoses eyePoses; + glm::mat4 headPose; + double sensorSampleTime { 0 }; + double predictedDisplayTime { 0 }; + }; + + protected: virtual void hmdPresent() = 0; virtual bool isHmdMounted() const = 0; @@ -46,10 +56,10 @@ protected: glm::mat4 _cullingProjection; glm::uvec2 _renderTargetSize; float _ipd { 0.064f }; - using EyePoses = std::array; - QMap _renderEyePoses; - EyePoses _currentRenderEyePoses; - ThreadSafeValueCache _headPoseCache { glm::mat4() }; + + QMap _frameInfos; + FrameInfo _currentPresentFrameInfo; + ThreadSafeValueCache _currentRenderFrameInfo; private: bool _enablePreview { false }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 65ac5197c8..edc7f7d754 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -130,6 +130,7 @@ void EntityTreeRenderer::setTree(OctreePointer newTree) { } void EntityTreeRenderer::update() { + PerformanceTimer perfTimer("ETRupdate"); if (_tree && !_shuttingDown) { EntityTreePointer tree = std::static_pointer_cast(_tree); tree->update(); @@ -159,12 +160,14 @@ void EntityTreeRenderer::update() { bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector* entitiesContainingAvatar) { bool didUpdate = false; - float radius = 1.0f; // for now, assume 1 meter radius + float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later QVector foundEntities; // find the entities near us // don't let someone else change our tree while we search _tree->withReadLock([&] { + + // FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster std::static_pointer_cast(_tree)->findEntities(avatarPosition, radius, foundEntities); // Whenever you're in an intersection between zones, we will always choose the smallest zone. @@ -173,36 +176,37 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& _bestZoneVolume = std::numeric_limits::max(); // create a list of entities that actually contain the avatar's position - foreach(EntityItemPointer entity, foundEntities) { - if (entity->contains(avatarPosition)) { - if (entitiesContainingAvatar) { - *entitiesContainingAvatar << entity->getEntityItemID(); - } + for (auto& entity : foundEntities) { + auto isZone = entity->getType() == EntityTypes::Zone; + auto hasScript = !entity->getScript().isEmpty(); - // if this entity is a zone, use this time to determine the bestZone - if (entity->getType() == EntityTypes::Zone) { - if (!entity->getVisible()) { - #ifdef WANT_DEBUG - qCDebug(entitiesrenderer) << "not visible"; - #endif - } else { + // only consider entities that are zones or have scripts, all other entities can + // be ignored because they can have events fired on them. + // FIXME - this could be optimized further by determining if the script is loaded + // and if it has either an enterEntity or leaveEntity method + if (isZone || hasScript) { + // now check to see if the point contains our entity, this can be expensive if + // the entity has a collision hull + if (entity->contains(avatarPosition)) { + if (entitiesContainingAvatar) { + *entitiesContainingAvatar << entity->getEntityItemID(); + } + + // if this entity is a zone and visible, determine if it is the bestZone + if (isZone && entity->getVisible()) { float entityVolumeEstimate = entity->getVolumeEstimate(); if (entityVolumeEstimate < _bestZoneVolume) { _bestZoneVolume = entityVolumeEstimate; _bestZone = std::dynamic_pointer_cast(entity); - } - else if (entityVolumeEstimate == _bestZoneVolume) { + } else if (entityVolumeEstimate == _bestZoneVolume) { + // in the case of the volume being equal, we will use the + // EntityItemID to deterministically pick one entity over the other if (!_bestZone) { _bestZoneVolume = entityVolumeEstimate; _bestZone = std::dynamic_pointer_cast(entity); - } - else { - // in the case of the volume being equal, we will use the - // EntityItemID to deterministically pick one entity over the other - if (entity->getEntityItemID() < _bestZone->getEntityItemID()) { - _bestZoneVolume = entityVolumeEstimate; - _bestZone = std::dynamic_pointer_cast(entity); - } + } else if (entity->getEntityItemID() < _bestZone->getEntityItemID()) { + _bestZoneVolume = entityVolumeEstimate; + _bestZone = std::dynamic_pointer_cast(entity); } } } @@ -217,13 +221,24 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& }); return didUpdate; } + bool EntityTreeRenderer::checkEnterLeaveEntities() { + PerformanceTimer perfTimer("checkEnterLeaveEntities"); + auto now = usecTimestampNow(); bool didUpdate = false; if (_tree && !_shuttingDown) { glm::vec3 avatarPosition = _viewState->getAvatarPosition(); - if (avatarPosition != _lastAvatarPosition) { + // we want to check our enter/leave state if we've moved a significant amount, or + // if some amount of time has elapsed since we last checked. We check the time + // elapsed because zones or entities might have been created "around us" while we've + // been stationary + auto movedEnough = glm::distance(avatarPosition, _lastAvatarPosition) > ZONE_CHECK_DISTANCE; + auto enoughTimeElapsed = (now - _lastZoneCheck) > ZONE_CHECK_INTERVAL; + + if (movedEnough || enoughTimeElapsed) { + _lastZoneCheck = now; QVector entitiesContainingAvatar; didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, &entitiesContainingAvatar); @@ -248,8 +263,6 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { } _currentEntitiesInside = entitiesContainingAvatar; _lastAvatarPosition = avatarPosition; - } else { - didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, nullptr); } } return didUpdate; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 095723dc9a..0fd5adc3f0 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -178,6 +178,10 @@ private: std::shared_ptr _bestZone; float _bestZoneVolume; + quint64 _lastZoneCheck { 0 }; + const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz + const float ZONE_CHECK_DISTANCE = 0.001f; + glm::vec3 _previousKeyLightColor; float _previousKeyLightIntensity; float _previousKeyLightAmbientIntensity; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e2db598d40..a9dcb3883c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -105,7 +105,7 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) { // If textures are unset, revert to original textures - if (textures == "") { + if (textures.isEmpty()) { return _originalTextures; } @@ -116,11 +116,19 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) { QJsonParseError error; QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); + // If textures are invalid, revert to original textures if (error.error != QJsonParseError::NoError) { - qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures; + qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << textures; return _originalTextures; } - return texturesJson.object().toVariantMap(); + + QVariantMap texturesMap = texturesJson.toVariant().toMap(); + // If textures are unset, revert to original textures + if (texturesMap.isEmpty()) { + return _originalTextures; + } + + return texturesJson.toVariant().toMap(); } void RenderableModelEntityItem::remapTextures() { @@ -132,21 +140,25 @@ void RenderableModelEntityItem::remapTextures() { return; // nothing to do if the model has not yet loaded } - auto& geometry = _model->getGeometry()->getGeometry(); - if (!_originalTexturesRead) { - _originalTextures = geometry->getTextures(); + _originalTextures = _model->getTextures(); _originalTexturesRead = true; // Default to _originalTextures to avoid remapping immediately and lagging on load _currentTextures = _originalTextures; } - auto textures = parseTexturesToMap(_textures); + auto textures = getTextures(); + if (textures == _lastTextures) { + return; + } - if (textures != _currentTextures) { - geometry->setTextures(textures); - _currentTextures = textures; + _lastTextures = textures; + auto newTextures = parseTexturesToMap(textures); + + if (newTextures != _currentTextures) { + _model->setTextures(newTextures); + _currentTextures = newTextures; } } @@ -359,41 +371,16 @@ void RenderableModelEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::Model); if (hasModel()) { - if (_model) { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - - // check to see if when we added our models to the scene they were ready, if they were not ready, then - // fix them up in the scene - bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; - if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { - _showCollisionHull = shouldShowCollisionHull; - render::PendingChanges pendingChanges; - - _model->removeFromScene(scene, pendingChanges); - - render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(getThisPointer(), statusGetters); - _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); - - scene->enqueuePendingChanges(pendingChanges); - } - - // FIXME: this seems like it could be optimized if we tracked our last known visible state in - // the renderable item. As it stands now the model checks it's visible/invisible state - // so most of the time we don't do anything in this function. - _model->setVisibleInScene(getVisible(), scene); - } - - - remapTextures(); + // Prepare the current frame { - // float alpha = getLocalRenderAlpha(); - if (!_model || _needsModelReload) { // TODO: this getModel() appears to be about 3% of model render time. We should optimize PerformanceTimer perfTimer("getModel"); EntityTreeRenderer* renderer = static_cast(args->_renderer); getModel(renderer); + + // Remap textures immediately after loading to avoid flicker + remapTextures(); } if (_model) { @@ -424,15 +411,40 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } }); updateModelBounds(); + } + } - // Check if the URL has changed - // Do this last as the getModel is queued for the next frame, - // and we need to keep state directing the model to reinitialize - auto& currentURL = getParsedModelURL(); - if (currentURL != _model->getURL()) { - // Defer setting the url to the render thread - getModel(_myRenderer); - } + // Enqueue updates for the next frame + if (_model) { + + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + + // FIXME: this seems like it could be optimized if we tracked our last known visible state in + // the renderable item. As it stands now the model checks it's visible/invisible state + // so most of the time we don't do anything in this function. + _model->setVisibleInScene(getVisible(), scene); + + // Remap textures for the next frame to avoid flicker + remapTextures(); + + // check to see if when we added our models to the scene they were ready, if they were not ready, then + // fix them up in the scene + bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; + if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { + _showCollisionHull = shouldShowCollisionHull; + render::PendingChanges pendingChanges; + + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(getThisPointer(), statusGetters); + _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); + + scene->enqueuePendingChanges(pendingChanges); + } + + auto& currentURL = getParsedModelURL(); + if (currentURL != _model->getURL()) { + // Defer setting the url to the render thread + getModel(_myRenderer); } } } else { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index e88b239e41..6d40a80950 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -90,6 +90,7 @@ private: bool _needsInitialSimulation = true; bool _needsModelReload = true; EntityTreeRenderer* _myRenderer = nullptr; + QString _lastTextures; QVariantMap _currentTextures; QVariantMap _originalTextures; bool _originalTexturesRead = false; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index ce3faeb196..855fd16408 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -55,8 +55,8 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { qWarning() << "Too many concurrent web views to create new view"; return false; } - qDebug() << "Building web surface"; + ++_currentWebCount; // Save the original GL context, because creating a QML surface will create a new context QOpenGLContext * currentContext = QOpenGLContext::currentContext(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bbc94e9910..431d638063 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1010,6 +1010,10 @@ EntityTreePointer EntityItem::getTree() const { return tree; } +SpatialParentTree* EntityItem::getParentTree() const { + return getTree().get(); +} + bool EntityItem::wantTerseEditLogging() const { EntityTreePointer tree = getTree(); return tree ? tree->wantTerseEditLogging() : false; @@ -1978,3 +1982,25 @@ void EntityItem::dimensionsChanged() { requiresRecalcBoxes(); SpatiallyNestable::dimensionsChanged(); // Do what you have to do } + +void EntityItem::globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate, const glm::vec3& offset) const { + bool success; + auto globalPosition = getPosition(success); + if (success) { + properties.setPosition(globalPosition + offset); + properties.setRotation(getRotation()); + properties.setDimensions(getDimensions()); + // Should we do velocities and accelerations, too? This could end up being quite involved, which is why the method exists. + } else { + properties.setPosition(getQueryAACube().calcCenter() + offset); // best we can do + } + if (!messageTemplate.isEmpty()) { + QString name = properties.getName(); + if (name.isEmpty()) { + name = EntityTypes::getEntityTypeName(properties.getType()); + } + qCWarning(entities) << messageTemplate.arg(getEntityItemID().toString()).arg(name).arg(properties.getParentID().toString()); + } + QUuid empty; + properties.setParentID(empty); +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 577bb406bc..622f78b2d3 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -86,6 +86,8 @@ public: /// returns true if something changed virtual bool setProperties(const EntityItemProperties& properties); + // Update properties with empty parent id and globalized/absolute values (applying offset), and apply (non-empty) log template to args id, name-or-type, parent id. + void globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate = QString(), const glm::vec3& offset = glm::vec3(0.0f)) const; /// Override this in your derived class if you'd like to be informed when something about the state of the entity /// has changed. This will be called with properties change or when new data is loaded from a stream @@ -359,6 +361,7 @@ public: void setPhysicsInfo(void* data) { _physicsInfo = data; } EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; + virtual SpatialParentTree* getParentTree() const; bool wantTerseEditLogging() const; glm::mat4 getEntityToWorldMatrix() const; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index af1c2e71aa..5292e12ae8 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -24,7 +24,6 @@ #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" #include "LogHandler.h" -#include "RemapIDOperator.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; @@ -1317,42 +1316,59 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen float x, float y, float z) { SendEntitiesOperationArgs args; args.packetSender = packetSender; - args.localTree = localTree; + args.ourTree = this; + args.otherTree = localTree; args.root = glm::vec3(x, y, z); - QVector newEntityIDs; - args.newEntityIDs = &newEntityIDs; + // If this is called repeatedly (e.g., multiple pastes with the same data), the new elements will clash unless we use new identifiers. + // We need to keep a map so that we can map parent identifiers correctly. + QHash map; + args.map = ↦ recurseTreeWithOperation(sendEntitiesOperation, &args); packetSender->releaseQueuedMessages(); - return newEntityIDs; + return map.values().toVector(); } bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) { SendEntitiesOperationArgs* args = static_cast(extraData); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); - entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - EntityItemID newID(QUuid::createUuid()); - args->newEntityIDs->append(newID); - EntityItemProperties properties = entityItem->getProperties(); - properties.setPosition(properties.getPosition() + args->root); + std::function getMapped = [&](EntityItemPointer& item) -> const EntityItemID { + EntityItemID oldID = item->getEntityItemID(); + if (args->map->contains(oldID)) { // Already been handled (e.g., as a parent of somebody that we've processed). + return args->map->value(oldID); + } + EntityItemID newID = QUuid::createUuid(); + EntityItemProperties properties = item->getProperties(); + EntityItemID oldParentID = properties.getParentID(); + if (oldParentID.isInvalidID()) { // no parent + properties.setPosition(properties.getPosition() + args->root); + } else { + EntityItemPointer parentEntity = args->ourTree->findEntityByEntityItemID(oldParentID); + if (parentEntity) { // map the parent + // Warning: (non-tail) recursion of getMapped could blow the call stack if the parent hierarchy is VERY deep. + properties.setParentID(getMapped(parentEntity)); + // But do not add root offset in this case. + } else { // Should not happen, but let's try to be helpful... + item->globalizeProperties(properties, "Cannot find %3 parent of %2 %1", args->root); + } + } properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity // queue the packet to send to the server args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); // also update the local tree instantly (note: this is not our tree, but an alternate tree) - if (args->localTree) { - args->localTree->withWriteLock([&] { - args->localTree->addEntity(newID, properties); + if (args->otherTree) { + args->otherTree->withWriteLock([&] { + args->otherTree->addEntity(newID, properties); }); } - }); - return true; -} + args->map->insert(oldID, newID); + return newID; + }; -void EntityTree::remapIDs() { - RemapIDOperator theOperator; - recurseTreeWithOperator(&theOperator); + entityTreeElement->forEachEntity(getMapped); + return true; } bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, @@ -1393,7 +1409,6 @@ bool EntityTree::readFromMap(QVariantMap& map) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); } } - return true; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 0b85c5f32f..5c5f5b4eb9 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -16,6 +16,7 @@ #include #include +#include class EntityTree; typedef std::shared_ptr EntityTreePointer; @@ -46,13 +47,14 @@ public: class SendEntitiesOperationArgs { public: glm::vec3 root; - EntityTreePointer localTree; + EntityTree* ourTree; + EntityTreePointer otherTree; EntityEditPacketSender* packetSender; - QVector* newEntityIDs; + QHash* map; }; -class EntityTree : public Octree { +class EntityTree : public Octree, public SpatialParentTree { Q_OBJECT public: EntityTree(bool shouldReaverage = false); @@ -125,6 +127,7 @@ public: EntityItemPointer findClosestEntity(glm::vec3 position, float targetRadius); EntityItemPointer findEntityByID(const QUuid& id); EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID); + virtual SpatiallyNestablePointer findByID(const QUuid& id) { return findEntityByID(id); } EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID @@ -200,8 +203,6 @@ public: bool wantTerseEditLogging() const { return _wantTerseEditLogging; } void setWantTerseEditLogging(bool value) { _wantTerseEditLogging = value; } - void remapIDs(); - virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) override; virtual bool readFromMap(QVariantMap& entityDescription) override; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index bef4406f71..e5511c0b25 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -42,6 +42,17 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( _color[0] = _color[1] = _color[2] = 0; } +const QString ModelEntityItem::getTextures() const { + QReadLocker locker(&_texturesLock); + auto textures = _textures; + return textures; +} + +void ModelEntityItem::setTextures(const QString& textures) { + QWriteLocker locker(&_texturesLock); + _textures = textures; +} + EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index bce27f1cca..d0e0909b27 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -110,8 +110,8 @@ public: float getAnimationFPS() const { return _animationLoop.getFPS(); } static const QString DEFAULT_TEXTURES; - const QString& getTextures() const { return _textures; } - void setTextures(const QString& textures) { _textures = textures; } + const QString getTextures() const; + void setTextures(const QString& textures); virtual bool shouldBePhysical() const; @@ -159,7 +159,9 @@ protected: AnimationPropertyGroup _animationProperties; AnimationLoop _animationLoop; + mutable QReadWriteLock _texturesLock; QString _textures; + ShapeType _shapeType = SHAPE_TYPE_NONE; // used on client side diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 4b798cbcd9..02a86ab952 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -561,6 +561,12 @@ bool ParticleEffectEntityItem::needsToCallUpdate() const { void ParticleEffectEntityItem::update(const quint64& now) { + // we check for 'now' in the past in case users set their clock backward + if (now < _lastSimulated) { + _lastSimulated = now; + return; + } + float deltaTime = (float)(now - _lastSimulated) / (float)USECS_PER_SECOND; _lastSimulated = now; diff --git a/libraries/entities/src/RemapIDOperator.cpp b/libraries/entities/src/RemapIDOperator.cpp deleted file mode 100644 index eee6e49a1c..0000000000 --- a/libraries/entities/src/RemapIDOperator.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// RemapIDOperator.cpp -// libraries/entities/src -// -// Created by Seth Alves on 2015-12-6. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#include "EntityTree.h" -#include "RemapIDOperator.h" - -QUuid RemapIDOperator::remap(const QUuid& oldID) { - if (oldID.isNull()) { - return oldID; - } - if (!_oldToNew.contains(oldID)) { - _oldToNew[oldID] = QUuid::createUuid(); - } - return _oldToNew[oldID]; -} - -bool RemapIDOperator::postRecursion(OctreeElementPointer element) { - EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); - entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - entityItem->setID(remap(entityItem->getID())); - entityItem->setParentID(remap(entityItem->getParentID())); - }); - return true; -} diff --git a/libraries/entities/src/RemapIDOperator.h b/libraries/entities/src/RemapIDOperator.h deleted file mode 100644 index 439aec28fc..0000000000 --- a/libraries/entities/src/RemapIDOperator.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// RemapIDOperator.h -// libraries/entities/src -// -// Created by Seth Alves on 2015-12-6. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_RemapIDOperator_h -#define hifi_RemapIDOperator_h - -#include "Octree.h" - -// this will change all the IDs in an EntityTree. Parent/Child relationships are maintained. - -class RemapIDOperator : public RecurseOctreeOperator { -public: - RemapIDOperator() : RecurseOctreeOperator() {} - ~RemapIDOperator() {} - virtual bool preRecursion(OctreeElementPointer element) { return true; } - virtual bool postRecursion(OctreeElementPointer element); -private: - QUuid remap(const QUuid& oldID); - QHash _oldToNew; -}; - -#endif // hifi_RemapIDOperator_h diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 31bbc84cb2..8e5579f90b 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -38,11 +38,13 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { _context->setShareContext(sharedContext); } _context->setFormat(getDefaultOpenGLSurfaceFormat()); + if (_context->create()) { _offscreenSurface->setFormat(_context->format()); _offscreenSurface->create(); - return true; + return _offscreenSurface->isValid(); } + qWarning("Failed to create OffscreenGLCanvas context"); return false; } diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 818b3c6ca8..1d7824f789 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -70,7 +70,7 @@ public: virtual bool event(QEvent *e) override; protected: - class Queue : public QQueue { + class Queue : private QQueue { public: void add(QEvent::Type type); QEvent* take(); @@ -134,10 +134,11 @@ QEvent* OffscreenQmlRenderThread::Queue::take() { } OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) { + qDebug() << "Building QML Renderer"; if (!_canvas.create(shareContext)) { - static const char* error = "Failed to create OffscreenGLCanvas"; - qWarning() << error; - throw error; + qWarning("Failed to create OffscreenGLCanvas"); + _quit = true; + return; }; _renderControl = new QMyQuickRenderControl(); @@ -160,6 +161,8 @@ OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, } void OffscreenQmlRenderThread::run() { + qDebug() << "Starting QML Renderer thread"; + while (!_quit) { QEvent* e = _queue.take(); event(e); @@ -208,12 +211,14 @@ void OffscreenQmlRenderThread::setupFbo() { } void OffscreenQmlRenderThread::init() { + qDebug() << "Initializing QML Renderer"; + connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender); connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate); if (!_canvas.makeCurrent()) { - // Failed to make GL context current, this OffscreenQmlSurface is basically dead qWarning("Failed to make context current on QML Renderer Thread"); + _quit = true; return; } @@ -360,6 +365,8 @@ void OffscreenQmlSurface::onAboutToQuit() { } void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { + qDebug() << "Building QML surface"; + _renderer = new OffscreenQmlRenderThread(this, shareContext); _renderer->moveToThread(_renderer); _renderer->setObjectName("QML Renderer Thread"); diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index b898bddef9..7f442895a5 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -35,6 +35,7 @@ public: int _RSNumTextureBounded = 0; + int _DSNumAPIDrawcalls = 0; int _DSNumDrawcalls = 0; int _DSNumTriangles = 0; diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index e847ad1a42..b5b6437ed8 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -326,6 +326,7 @@ void GLBackend::do_draw(Batch& batch, size_t paramOffset) { glDrawArrays(mode, startVertex, numVertices); _stats._DSNumTriangles += numVertices / 3; _stats._DSNumDrawcalls++; + _stats._DSNumAPIDrawcalls++; (void)CHECK_GL_ERROR(); } @@ -344,6 +345,7 @@ void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { glDrawElements(mode, numIndices, glType, indexBufferByteOffset); _stats._DSNumTriangles += numIndices / 3; _stats._DSNumDrawcalls++; + _stats._DSNumAPIDrawcalls++; (void) CHECK_GL_ERROR(); } @@ -358,6 +360,7 @@ void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); _stats._DSNumTriangles += (numInstances * numVertices) / 3; _stats._DSNumDrawcalls += numInstances; + _stats._DSNumAPIDrawcalls++; (void) CHECK_GL_ERROR(); } @@ -383,6 +386,7 @@ void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { #endif _stats._DSNumTriangles += (numInstances * numIndices) / 3; _stats._DSNumDrawcalls += numInstances; + _stats._DSNumAPIDrawcalls++; (void)CHECK_GL_ERROR(); } @@ -395,6 +399,8 @@ void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); _stats._DSNumDrawcalls += commandCount; + _stats._DSNumAPIDrawcalls++; + #else // FIXME implement the slow path #endif @@ -410,7 +416,7 @@ void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); _stats._DSNumDrawcalls += commandCount; - + _stats._DSNumAPIDrawcalls++; #else // FIXME implement the slow path #endif diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index d4efe7fe99..f5abacd279 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -158,7 +158,6 @@ public: ~GLState(); // The state commands to reset to default, - // WARNING depending on the order of the State::Field enum static const Commands _resetStateCommands; friend class GLBackend; diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu/src/gpu/GLBackendState.cpp index 64bd87c876..5ef77773e8 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu/src/gpu/GLBackendState.cpp @@ -35,6 +35,7 @@ const GLBackend::GLState::Commands makeResetStateCommands(); const GLBackend::GLState::Commands GLBackend::GLState::_resetStateCommands = makeResetStateCommands(); +// NOTE: This must stay in sync with the ordering of the State::Field enum const GLBackend::GLState::Commands makeResetStateCommands() { // Since State::DEFAULT is a static defined in another .cpp the initialisation order is random // and we have a 50/50 chance that State::DEFAULT is not yet initialized. @@ -69,9 +70,9 @@ const GLBackend::GLState::Commands makeResetStateCommands() { CommandPointer(stencilCommand), CommandPointer(stencilCommand), - std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), - std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), + + std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), @@ -609,6 +610,8 @@ void GLBackend::do_setStateDepthBias(Vec2 bias) { glDisable(GL_POLYGON_OFFSET_LINE); glDisable(GL_POLYGON_OFFSET_POINT); } + (void) CHECK_GL_ERROR(); + _pipeline._stateCache.depthBias = bias.x; _pipeline._stateCache.depthBiasSlopeScale = bias.y; } @@ -689,6 +692,7 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); } (void) CHECK_GL_ERROR(); + _pipeline._stateCache.alphaToCoverageEnable = enable; } } @@ -702,6 +706,7 @@ void GLBackend::do_setStateSampleMask(uint32 mask) { glEnable(GL_SAMPLE_MASK); glSampleMaski(0, mask); } + (void) CHECK_GL_ERROR(); #endif _pipeline._stateCache.sampleMask = mask; } @@ -742,10 +747,10 @@ void GLBackend::do_setStateBlend(State::BlendFunction function) { glBlendFuncSeparate(BLEND_ARGS[function.getSourceColor()], BLEND_ARGS[function.getDestinationColor()], BLEND_ARGS[function.getSourceAlpha()], BLEND_ARGS[function.getDestinationAlpha()]); - (void) CHECK_GL_ERROR(); } else { glDisable(GL_BLEND); } + (void) CHECK_GL_ERROR(); _pipeline._stateCache.blendFunction = function; } @@ -757,6 +762,7 @@ void GLBackend::do_setStateColorWriteMask(uint32 mask) { mask & State::ColorMask::WRITE_GREEN, mask & State::ColorMask::WRITE_BLUE, mask & State::ColorMask::WRITE_ALPHA ); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.colorWriteMask = mask; } @@ -764,7 +770,6 @@ void GLBackend::do_setStateColorWriteMask(uint32 mask) { void GLBackend::do_setStateBlendFactor(Batch& batch, size_t paramOffset) { - Vec4 factor(batch._params[paramOffset + 0]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 2]._float, diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index 09714b5542..3fc7906285 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -225,6 +225,12 @@ public: } break; + case gpu::R11G11B10: + texel.format = GL_RGB; + // the type should be float + texel.internalFormat = GL_R11F_G11F_B10F; + break; + case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it texel.internalFormat = GL_DEPTH_COMPONENT; @@ -302,11 +308,6 @@ public: case gpu::SRGBA: texel.internalFormat = GL_SRGB; // standard 2.2 gamma correction color break; - case gpu::R11G11B10: { - // the type should be float - texel.internalFormat = GL_R11F_G11F_B10F; - break; - } default: qCDebug(gpulogging) << "Unknown combination of texel format"; } diff --git a/libraries/gpu/src/gpu/State.h b/libraries/gpu/src/gpu/State.h index 7e32a7280a..385edec277 100755 --- a/libraries/gpu/src/gpu/State.h +++ b/libraries/gpu/src/gpu/State.h @@ -345,6 +345,7 @@ public: uint8 getColorWriteMask() const { return _values.colorWriteMask; } // All the possible fields + // NOTE: If you change this, you must update GLBackend::GLState::_resetStateCommands enum Field { FILL_MODE, CULL_MODE, @@ -364,6 +365,7 @@ public: STENCIL_TEST_BACK, SAMPLE_MASK, + ALPHA_TO_COVERAGE_ENABLE, BLEND_FUNCTION, diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index df93cd76a5..ad0fae577c 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -285,7 +285,7 @@ Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 widt } // Here the Texture has been fully defined from the gpu point of view (size and format) - _defined = true; + _defined = true; } else { _stamp++; } @@ -457,6 +457,14 @@ uint32 Texture::getStoredMipSize(uint16 level) const { return 0; } +gpu::Resource::Size Texture::getStoredSize() const { + auto size = 0; + for (int level = 0; level < evalNumMips(); ++level) { + size += getStoredMipSize(level); + } + return size; +} + uint16 Texture::evalNumSamplesUsed(uint16 numSamplesTried) { uint16 sample = numSamplesTried; if (numSamplesTried <= 1) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 80fbc867e3..1a8885f075 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -288,9 +288,12 @@ public: Stamp getStamp() const { return _stamp; } Stamp getDataStamp() const { return _storage->getStamp(); } - // The size in bytes of data stored in the texture + // The theoretical size in bytes of data stored in the texture Size getSize() const { return _size; } + // The actual size in bytes of data stored in the texture + Size getStoredSize() const; + // Resize, unless auto mips mode would destroy all the sub mips Size resize1D(uint16 width, uint16 numSamples); Size resize2D(uint16 width, uint16 height, uint16 numSamples); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2a6f33b964..4120062308 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -66,16 +66,15 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { GeometryExtra extra{ mapping, textureBaseUrl }; // Get the raw GeometryResource, not the wrapped NetworkGeometry - _geometryResource = modelCache->getResource(url, QUrl(), true, &extra).staticCast(); + _geometryResource = modelCache->getResource(url, QUrl(), false, &extra).staticCast(); + // Avoid caching nested resources - their references will be held by the parent + _geometryResource->_isCacheable = false; if (_geometryResource->isLoaded()) { onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty()); } else { connect(_geometryResource.data(), &Resource::finished, this, &GeometryMappingResource::onGeometryMappingLoaded); } - - // Avoid caching nested resources - their references will be held by the parent - _geometryResource->_isCacheable = false; } } @@ -86,6 +85,10 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) { _meshes = _geometryResource->_meshes; _materials = _geometryResource->_materials; } + + // Avoid holding onto extra references + _geometryResource.reset(); + finishedLoading(success); } @@ -157,7 +160,7 @@ class GeometryDefinitionResource : public GeometryResource { Q_OBJECT public: GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) : - GeometryResource(url), _mapping(mapping), _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {} + GeometryResource(url, textureBaseUrl.isValid() ? textureBaseUrl : url), _mapping(mapping) {} virtual void downloadFinished(const QByteArray& data) override; @@ -166,7 +169,6 @@ protected: private: QVariantHash _mapping; - QUrl _textureBaseUrl; }; void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { @@ -220,13 +222,20 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl); } - return QSharedPointer(resource, &Resource::allReferencesCleared); + return QSharedPointer(resource, &Resource::deleter); } std::shared_ptr ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { GeometryExtra geometryExtra = { mapping, textureBaseUrl }; GeometryResource::Pointer resource = getResource(url, QUrl(), true, &geometryExtra).staticCast(); - return std::make_shared(resource); + if (resource) { + if (resource->isLoaded() && !resource->hasTextures()) { + resource->setTextures(); + } + return std::make_shared(resource); + } else { + return NetworkGeometry::Pointer(); + } } const QVariantMap Geometry::getTextures() const { @@ -270,6 +279,9 @@ void Geometry::setTextures(const QVariantMap& textureMap) { material->setTextures(textureMap); _areTexturesLoaded = false; + + // If we only use cached textures, they should all be loaded + areTexturesLoaded(); } } } else { @@ -279,8 +291,6 @@ void Geometry::setTextures(const QVariantMap& textureMap) { bool Geometry::areTexturesLoaded() const { if (!_areTexturesLoaded) { - _hasTransparentTextures = false; - for (auto& material : _materials) { // Check if material textures are loaded if (std::any_of(material->_textures.cbegin(), material->_textures.cend(), @@ -293,8 +303,6 @@ bool Geometry::areTexturesLoaded() const { const auto albedoTexture = material->_textures[NetworkMaterial::MapChannel::ALBEDO_MAP]; if (albedoTexture.texture && albedoTexture.texture->getGPUTexture()) { material->resetOpacityMap(); - - _hasTransparentTextures |= material->getKey().isTranslucent(); } } @@ -313,6 +321,21 @@ const std::shared_ptr Geometry::getShapeMaterial(int shap return nullptr; } +void GeometryResource::deleter() { + resetTextures(); + Resource::deleter(); +} + +void GeometryResource::setTextures() { + for (const FBXMaterial& material : _geometry->materials) { + _materials.push_back(std::make_shared(material, _textureBaseUrl)); + } +} + +void GeometryResource::resetTextures() { + _materials.clear(); +} + NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) { connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished); connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index dad7883a6a..45f78f4f19 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -74,9 +74,6 @@ public: void setTextures(const QVariantMap& textureMap); virtual bool areTexturesLoaded() const; - // Returns true if any albedo texture has a non-masking alpha channel. - // This can only be known after areTexturesLoaded(). - bool hasTransparentTextures() const { return _hasTransparentTextures; } protected: friend class GeometryMappingResource; @@ -91,7 +88,6 @@ protected: private: mutable bool _areTexturesLoaded { false }; - mutable bool _hasTransparentTextures { false }; }; /// A geometry loaded from the network. @@ -99,15 +95,24 @@ class GeometryResource : public Resource, public Geometry { public: using Pointer = QSharedPointer; - GeometryResource(const QUrl& url) : Resource(url) {} + GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) : Resource(url) {} virtual bool areTexturesLoaded() const { return isLoaded() && Geometry::areTexturesLoaded(); } + virtual void deleter() override; + protected: + friend class ModelCache; friend class GeometryMappingResource; - virtual bool isCacheable() const override { return _loaded && _isCacheable; } + // Geometries may not hold onto textures while cached - that is for the texture cache + bool hasTextures() const { return !_materials.empty(); } + void setTextures(); + void resetTextures(); + QUrl _textureBaseUrl; + + virtual bool isCacheable() const override { return _loaded && _isCacheable; } bool _isCacheable { true }; }; diff --git a/libraries/model-networking/src/model-networking/ShaderCache.cpp b/libraries/model-networking/src/model-networking/ShaderCache.cpp index 7e52052c30..3e14a99f87 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.cpp +++ b/libraries/model-networking/src/model-networking/ShaderCache.cpp @@ -28,6 +28,6 @@ NetworkShaderPointer ShaderCache::getShader(const QUrl& url) { } QSharedPointer ShaderCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkShader(url, delayLoad), &Resource::allReferencesCleared); + return QSharedPointer(new NetworkShader(url, delayLoad), &Resource::deleter); } diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 28f4882b86..3ba36dc2da 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -166,12 +166,11 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path) { return texture; } - QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { const TextureExtra* textureExtra = static_cast(extra); return QSharedPointer(new NetworkTexture(url, textureExtra->type, textureExtra->content), - &Resource::allReferencesCleared); + &Resource::deleter); } NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) : @@ -339,10 +338,13 @@ void NetworkTexture::setImage(void* voidTexture, int originalWidth, if (gpuTexture) { _width = gpuTexture->getWidth(); _height = gpuTexture->getHeight(); + setBytes(gpuTexture->getStoredSize()); } else { + // FIXME: If !gpuTexture, we failed to load! _width = _height = 0; + qWarning() << "Texture did not load"; } - + finishedLoading(true); emit networkTextureCreated(qWeakPointerCast (_self)); diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index df86ba2a8f..2bb6cfa436 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -111,7 +111,7 @@ Box Mesh::evalPartBound(int partNum) const { return box; } -Box Mesh::evalPartBounds(int partStart, int partEnd, Boxes& bounds) const { +Box Mesh::evalPartsBound(int partStart, int partEnd) const { Box totalBound; auto part = _partBuffer.cbegin() + partStart; auto partItEnd = _partBuffer.cbegin() + partEnd; diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index f9d9b0eeb4..a3e95c68c0 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -108,9 +108,9 @@ public: // evaluate the bounding box of A part Box evalPartBound(int partNum) const; - // evaluate the bounding boxes of the parts in the range [start, end[ and fill the bounds parameter - // the returned box is the bounding box of ALL the evaluated part bounds. - Box evalPartBounds(int partStart, int partEnd, Boxes& bounds) const; + // evaluate the bounding boxes of the parts in the range [start, end] + // the returned box is the bounding box of ALL the evaluated parts bound. + Box evalPartsBound(int partStart, int partEnd) const; static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast(topo); } diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 8cb1f31090..446009ac23 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -228,7 +228,8 @@ bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end nodeList->sendPacket(std::move(packet), *assetServer); - _pendingRequests[assetServer][messageID] = { callback, progressCallback }; + _pendingRequests[assetServer][messageID] = { QSharedPointer(), callback, progressCallback }; + return true; } else { @@ -326,6 +327,9 @@ void AssetClient::handleAssetGetReply(QSharedPointer message, S if (requestIt != messageCallbackMap.end()) { auto& callbacks = requestIt->second; + // Store message in case we need to disconnect from it later. + callbacks.message = message; + if (message->isComplete()) { callbacks.completeCallback(true, error, message->readAll()); } else { @@ -550,6 +554,12 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) { auto messageMapIt = _pendingRequests.find(node); if (messageMapIt != _pendingRequests.end()) { for (const auto& value : messageMapIt->second) { + auto& message = value.second.message; + if (message) { + // Disconnect from all signals emitting from the pending message + disconnect(message.data(), nullptr, this, nullptr); + } + value.second.completeCallback(false, AssetServerError::NoError, QByteArray()); } messageMapIt->second.clear(); diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index e46c8c6524..f1890377dc 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -86,14 +86,15 @@ private: ReceivedAssetCallback callback, ProgressCallback progressCallback); bool uploadAsset(const QByteArray& data, UploadResultCallback callback); - struct GetAssetCallbacks { + struct GetAssetRequestData { + QSharedPointer message; ReceivedAssetCallback completeCallback; ProgressCallback progressCallback; }; static MessageID _currentID; std::unordered_map> _pendingMappingRequests; - std::unordered_map> _pendingRequests; + std::unordered_map> _pendingRequests; std::unordered_map> _pendingInfoRequests; std::unordered_map> _pendingUploads; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 9738233c85..b5f5ca7c25 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -117,22 +117,22 @@ void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) { } void ResourceCache::addUnusedResource(const QSharedPointer& resource) { - if (resource->getBytesTotal() > _unusedResourcesMaxSize) { - // If it doesn't fit anyway, let's leave whatever is already in the cache. + // If it doesn't fit or its size is unknown, leave the cache alone. + if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) { resource->setCache(nullptr); return; } - reserveUnusedResource(resource->getBytesTotal()); + reserveUnusedResource(resource->getBytes()); resource->setLRUKey(++_lastLRUKey); _unusedResources.insert(resource->getLRUKey(), resource); - _unusedResourcesSize += resource->getBytesTotal(); + _unusedResourcesSize += resource->getBytes(); } void ResourceCache::removeUnusedResource(const QSharedPointer& resource) { if (_unusedResources.contains(resource->getLRUKey())) { _unusedResources.remove(resource->getLRUKey()); - _unusedResourcesSize -= resource->getBytesTotal(); + _unusedResourcesSize -= resource->getBytes(); } } @@ -142,7 +142,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { // unload the oldest resource QMap >::iterator it = _unusedResources.begin(); - _unusedResourcesSize -= it.value()->getBytesTotal(); + _unusedResourcesSize -= it.value()->getBytes(); it.value()->setCache(nullptr); _unusedResources.erase(it); } @@ -399,7 +399,7 @@ void Resource::makeRequest() { connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress); connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished); - _bytesReceived = _bytesTotal = 0; + _bytesReceived = _bytesTotal = _bytes = 0; _request->send(); } @@ -412,6 +412,8 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota void Resource::handleReplyFinished() { Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished"); + _bytes = _bytesTotal; + if (!_request || _request != sender()) { // This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted. qWarning(networking) << "Received signal Resource::handleReplyFinished from ResourceRequest that is not the current" diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 7f4d86393b..f674c96a1e 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -181,6 +181,9 @@ public: /// For loading resources, returns the number of total bytes (<= zero if unknown). qint64 getBytesTotal() const { return _bytesTotal; } + /// For loaded resources, returns the number of actual bytes (defaults to total bytes if not explicitly set). + qint64 getBytes() const { return _bytes; } + /// For loading resources, returns the load progress. float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; } @@ -191,7 +194,7 @@ public: void setCache(ResourceCache* cache) { _cache = cache; } - Q_INVOKABLE void allReferencesCleared(); + virtual void deleter() { allReferencesCleared(); } const QUrl& getURL() const { return _url; } @@ -222,10 +225,15 @@ protected: /// This should be overridden by subclasses that need to process the data once it is downloaded. virtual void downloadFinished(const QByteArray& data) { finishedLoading(true); } + /// Called when the download is finished and processed, sets the number of actual bytes. + void setBytes(qint64 bytes) { _bytes = bytes; } + /// Called when the download is finished and processed. /// This should be called by subclasses that override downloadFinished to mark the end of processing. Q_INVOKABLE void finishedLoading(bool success); + Q_INVOKABLE void allReferencesCleared(); + QUrl _url; QUrl _activeUrl; bool _startedLoading = false; @@ -253,6 +261,7 @@ private: QTimer* _replyTimer = nullptr; qint64 _bytesReceived = 0; qint64 _bytesTotal = 0; + qint64 _bytes = 0; int _attempts = 0; }; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index f4f28176c7..b855e08ff1 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -74,6 +74,9 @@ public: /// whether the HMD is being worn virtual bool isDisplayVisible() const { return false; } + virtual QString getPreferredAudioInDevice() const { return QString(); } + virtual QString getPreferredAudioOutDevice() const { return QString(); } + // Rendering support /** @@ -122,7 +125,7 @@ public: } // will query the underlying hmd api to compute the most recent head pose - virtual void updateHeadPose(uint32_t frameIndex) {} + virtual void beginFrameRender(uint32_t frameIndex) {} // returns a copy of the most recent head pose, computed via updateHeadPose virtual glm::mat4 getHeadPose() const { @@ -142,6 +145,8 @@ public: virtual float presentRate() { return -1.0f; } uint32_t presentCount() const { return _presentedFrameIndex; } + virtual void cycleDebugOutput() {} + static const QString& MENU_PATH(); signals: diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index 37c15b0ca4..145ecfc7b9 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -36,6 +36,6 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { } QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared); + return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::deleter); } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 815cb45423..7c7c263562 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -46,6 +46,8 @@ public: virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; + virtual void pushPreRenderLambda(void* key, std::function func) = 0; + // FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface static AbstractViewStateInterface* instance(); static void setInstance(AbstractViewStateInterface* instance); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index f3fa4a43b2..ac24b09c40 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -435,8 +435,9 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, // we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working... + const float OVER_CONSERVATIVE_SCALE = 1.1f; if ((eyeHalfPlaneDistance > -nearRadius) && - (glm::distance(eyePoint, glm::vec3(light->getPosition())) < expandedRadius + nearRadius)) { + (glm::distance(eyePoint, glm::vec3(light->getPosition())) < (expandedRadius * OVER_CONSERVATIVE_SCALE) + nearRadius)) { coneParam.w = 0.0f; batch._glUniform4fv(_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); @@ -452,6 +453,7 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo batch.setProjectionTransform( projMats[side]); batch.setViewTransform(viewTransforms[side]); } else { + light->setShowContour(false); coneParam.w = 1.0f; batch._glUniform4fv(_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); diff --git a/libraries/render-utils/src/FramebufferCache.cpp b/libraries/render-utils/src/FramebufferCache.cpp index 7948dfcefe..22bfbfd869 100644 --- a/libraries/render-utils/src/FramebufferCache.cpp +++ b/libraries/render-utils/src/FramebufferCache.cpp @@ -97,7 +97,7 @@ void FramebufferCache::createPrimaryFramebuffer() { // FIXME: Decide on the proper one, let s stick to R11G11B10 for now //_lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, defaultSampler)); - _lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::R11G11B10), width, height, defaultSampler)); + _lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler)); //_lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC4, gpu::HALF, gpu::RGBA), width, height, defaultSampler)); _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _lightingFramebuffer->setRenderBuffer(0, _lightingTexture); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 854630abf0..d295b07caf 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -58,7 +58,6 @@ void MeshPartPayload::updateMeshPart(const std::shared_ptr& d auto vertexFormat = _drawMesh->getVertexFormat(); _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); _drawPart = _drawMesh->getPartBuffer().get(partIndex); - _localBound = _drawMesh->evalPartBound(partIndex); } } @@ -352,7 +351,23 @@ void ModelMeshPartPayload::initCache() { void ModelMeshPartPayload::notifyLocationChanged() { - _model->_needsUpdateClusterMatrices = true; + +} + +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector& clusterMatrices) { + ModelMeshPartPayload::updateTransform(transform, offsetTransform); + + if (clusterMatrices.size() > 0) { + _worldBound = AABox(); + for (auto& clusterMatrix : clusterMatrices) { + AABox clusterBound = _localBound; + clusterBound.transform(clusterMatrix); + _worldBound += clusterBound; + } + + // clusterMatrix has world rotation but not world translation. + _worldBound.translate(transform.getTranslation()); + } } ItemKey ModelMeshPartPayload::getKey() const { @@ -377,12 +392,6 @@ ItemKey ModelMeshPartPayload::getKey() const { return builder.build(); } -Item::Bound ModelMeshPartPayload::getBound() const { - // NOTE: we can't cache this bounds because we need to handle the case of a moving - // entity or mesh part. - return _model->getPartBounds(_meshIndex, _partIndex, _transform.getTranslation(), _transform.getRotation()); -} - ShapeKey ModelMeshPartPayload::getShapeKey() const { assert(_model->isLoaded()); const FBXGeometry& geometry = _model->getFBXGeometry(); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 9f73a46a93..41869ec7e3 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -74,15 +74,15 @@ namespace render { class ModelMeshPartPayload : public MeshPartPayload { public: ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); - + typedef render::Payload Payload; typedef Payload::DataPointer Pointer; void notifyLocationChanged() override; + void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector& clusterMatrices); // Render Item interface render::ItemKey getKey() const override; - render::Item::Bound getBound() const override; render::ShapeKey getShapeKey() const override; // shape interface void render(RenderArgs* args) const override; @@ -101,4 +101,11 @@ public: bool _isBlendShaped{ false }; }; +namespace render { + template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload); + template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload); + template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args); +} + #endif // hifi_MeshPartPayload_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 545d239549..3a5928f3d1 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -76,14 +76,9 @@ AbstractViewStateInterface* Model::_viewState = NULL; bool Model::needsFixupInScene() const { if (readyToAddToScene()) { - // Once textures are loaded, fixup if they are now transparent - if (_needsUpdateTransparentTextures && _geometry->getGeometry()->areTexturesLoaded()) { - _needsUpdateTransparentTextures = false; - bool hasTransparentTextures = _geometry->getGeometry()->hasTransparentTextures(); - if (_hasTransparentTextures != hasTransparentTextures) { - _hasTransparentTextures = hasTransparentTextures; - return true; - } + if (_needsUpdateTextures && _geometry->getGeometry()->areTexturesLoaded()) { + _needsUpdateTextures = false; + return true; } if (!_readyWhenAdded) { return true; @@ -130,25 +125,64 @@ void Model::setOffset(const glm::vec3& offset) { } void Model::enqueueLocationChange() { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - Transform transform; - transform.setTranslation(_translation); - transform.setRotation(_rotation); + _needsUpdateClusterMatrices = true; - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); + // queue up this work for later processing, at the end of update and just before rendering. + // the application will ensure only the last lambda is actually invoked. + void* key = (void*)this; + std::weak_ptr weakSelf = shared_from_this(); + AbstractViewStateInterface::instance()->pushPreRenderLambda(key, [weakSelf]() { - render::PendingChanges pendingChanges; - foreach (auto itemID, _renderItems.keys()) { - pendingChanges.updateItem(itemID, [transform, offset](MeshPartPayload& data) { - data.updateTransform(transform, offset); - data.notifyLocationChanged(); - }); - } + // do nothing, if the model has already been destroyed. + auto self = weakSelf.lock(); + if (!self) { + return; + } - scene->enqueuePendingChanges(pendingChanges); + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + + Transform modelTransform; + modelTransform.setScale(self->_scale); + modelTransform.setTranslation(self->_translation); + modelTransform.setRotation(self->_rotation); + + Transform modelMeshOffset; + if (self->isLoaded()) { + // includes model offset and unitScale. + modelMeshOffset = Transform(self->_rig->getGeometryToRigTransform()); + } else { + modelMeshOffset.postTranslate(self->_offset); + } + + // only apply offset only, collision mesh does not share the same unit scale as the FBX file's mesh. + Transform collisionMeshOffset; + collisionMeshOffset.postTranslate(self->_offset); + + render::PendingChanges pendingChanges; + foreach (auto itemID, self->_modelMeshRenderItems.keys()) { + pendingChanges.updateItem(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) { + // Ensure the model geometry was not reset between frames + if (data._model->isLoaded()) { + // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. + data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation()); + + // update the model transform and bounding box for this render item. + const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); + data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices); + } + }); + } + + foreach (auto itemID, self->_collisionRenderItems.keys()) { + pendingChanges.updateItem(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { + // update the model transform for this render item. + data.updateTransform(modelTransform, collisionMeshOffset); + }); + } + + scene->enqueuePendingChanges(pendingChanges); + }); } void Model::initJointTransforms() { @@ -497,39 +531,16 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr scen _isVisible = newValue; render::PendingChanges pendingChanges; - foreach (auto item, _renderItems.keys()) { - pendingChanges.resetItem(item, _renderItems[item]); + foreach (auto item, _modelMeshRenderItems.keys()) { + pendingChanges.resetItem(item, _modelMeshRenderItems[item]); + } + foreach (auto item, _collisionRenderItems.keys()) { + pendingChanges.resetItem(item, _collisionRenderItems[item]); } scene->enqueuePendingChanges(pendingChanges); } } - -bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, bool showCollisionHull) { - - if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { - _showCollisionHull = showCollisionHull; - segregateMeshGroups(); - } - - bool somethingAdded = false; - - foreach (auto renderItem, _renderItemsSet) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [](MeshPartPayload& data) { - data.notifyLocationChanged(); - }); - _renderItems.insert(item, renderPayload); - somethingAdded = true; - } - - _readyWhenAdded = readyToAddToScene(); - - return somethingAdded; -} - bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters, @@ -541,16 +552,48 @@ bool Model::addToScene(std::shared_ptr scene, bool somethingAdded = false; - foreach (auto renderItem, _renderItemsSet) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - renderPayload->addStatusGetters(statusGetters); - pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [](MeshPartPayload& data) { - data.notifyLocationChanged(); - }); - _renderItems.insert(item, renderPayload); - somethingAdded = true; + if (_modelMeshRenderItems.size()) { + for (auto item : _modelMeshRenderItems.keys()) { + pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { + data.notifyLocationChanged(); + }); + } + } else { + for (auto renderItem : _modelMeshRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + if (statusGetters.size()) { + renderPayload->addStatusGetters(statusGetters); + } + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { + data.notifyLocationChanged(); + }); + _modelMeshRenderItems.insert(item, renderPayload); + somethingAdded = true; + } + } + + if (_collisionRenderItems.size()) { + for (auto item : _collisionRenderItems.keys()) { + pendingChanges.updateItem(item, [](MeshPartPayload& data) { + data.notifyLocationChanged(); + }); + } + } else { + for (auto renderItem : _collisionRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + if (statusGetters.size()) { + renderPayload->addStatusGetters(statusGetters); + } + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [](MeshPartPayload& data) { + data.notifyLocationChanged(); + }); + _collisionRenderItems.insert(item, renderPayload); + somethingAdded = true; + } } _readyWhenAdded = readyToAddToScene(); @@ -559,11 +602,16 @@ bool Model::addToScene(std::shared_ptr scene, } void Model::removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges) { - foreach (auto item, _renderItems.keys()) { + foreach (auto item, _modelMeshRenderItems.keys()) { pendingChanges.removeItem(item); } - _renderItems.clear(); - _renderItemsSet.clear(); + _modelMeshRenderItems.clear(); + _modelMeshRenderItemsSet.clear(); + foreach (auto item, _collisionRenderItems.keys()) { + pendingChanges.removeItem(item); + } + _collisionRenderItems.clear(); + _collisionRenderItemsSet.clear(); _meshGroupsKnown = false; _readyWhenAdded = false; } @@ -721,6 +769,13 @@ int Model::getLastFreeJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; } +void Model::setTextures(const QVariantMap& textures) { + if (isLoaded()) { + _needsUpdateTextures = true; + _geometry->getGeometry()->setTextures(textures); + } +} + void Model::setURL(const QUrl& url) { // don't recreate the geometry if it's the same URL if (_url == url && _geometry && _geometry->getURL() == url) { @@ -737,8 +792,7 @@ void Model::setURL(const QUrl& url) { } _needsReload = true; - _needsUpdateTransparentTextures = true; - _hasTransparentTextures = false; + _needsUpdateTextures = true; _meshGroupsKnown = false; invalidCalculatedMeshBoxes(); deleteGeometry(); @@ -993,7 +1047,7 @@ void Model::simulateInternal(float deltaTime) { void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { PerformanceTimer perfTimer("Model::updateClusterMatrices"); - if (!_needsUpdateClusterMatrices) { + if (!_needsUpdateClusterMatrices || !isLoaded()) { return; } _needsUpdateClusterMatrices = false; @@ -1175,10 +1229,14 @@ void Model::segregateMeshGroups() { return; } - Q_ASSERT(_renderItems.isEmpty()); // We should not have any existing renderItems if we enter this section of code - Q_ASSERT(_renderItemsSet.isEmpty()); // We should not have any existing renderItemsSet if we enter this section of code + // We should not have any existing renderItems if we enter this section of code + Q_ASSERT(_modelMeshRenderItems.isEmpty()); + Q_ASSERT(_modelMeshRenderItemsSet.isEmpty()); + Q_ASSERT(_collisionRenderItems.isEmpty()); + Q_ASSERT(_collisionRenderItemsSet.isEmpty()); - _renderItemsSet.clear(); + _modelMeshRenderItemsSet.clear(); + _collisionRenderItemsSet.clear(); Transform transform; transform.setTranslation(_translation); @@ -1204,9 +1262,9 @@ void Model::segregateMeshGroups() { _collisionHullMaterial->setMetallic(0.02f); _collisionHullMaterial->setRoughness(0.5f); } - _renderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterial, transform, offset); + _collisionRenderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterial, transform, offset); } else { - _renderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); + _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); } shapeID++; @@ -1229,13 +1287,21 @@ bool Model::initWhenReady(render::ScenePointer scene) { offset.setScale(_scale); offset.postTranslate(_offset); - foreach (auto renderItem, _renderItemsSet) { + foreach (auto renderItem, _modelMeshRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + _modelMeshRenderItems.insert(item, renderPayload); + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [transform, offset](MeshPartPayload& data) { + data.notifyLocationChanged(); + }); + } + foreach (auto renderItem, _collisionRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); - _renderItems.insert(item, renderPayload); + _collisionRenderItems.insert(item, renderPayload); pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [transform,offset](MeshPartPayload& data) { - data.updateTransform(transform, offset); + pendingChanges.updateItem(item, [transform, offset](MeshPartPayload& data) { data.notifyLocationChanged(); }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 785b902b3a..239d0e7c28 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -44,6 +44,7 @@ namespace render { typedef unsigned int ItemID; } class MeshPartPayload; +class ModelMeshPartPayload; class ModelRenderLocations; inline uint qHash(const std::shared_ptr& a, uint seed) { @@ -86,7 +87,10 @@ public: bool initWhenReady(render::ScenePointer scene); bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, - bool showCollisionHull = false); + bool showCollisionHull = false) { + auto getters = render::Item::Status::Getters(0); + return addToScene(scene, pendingChanges, getters, showCollisionHull); + } bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters, @@ -128,6 +132,9 @@ public: /// Returns a reference to the shared collision geometry. const NetworkGeometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } + const QVariantMap getTextures() const { assert(isLoaded()); return _geometry->getGeometry()->getTextures(); } + void setTextures(const QVariantMap& textures); + /// Provided as a convenience, will crash if !isLoaded() // And so that getGeometry() isn't chained everywhere const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return getGeometry()->getGeometry()->getGeometry(); } @@ -375,14 +382,17 @@ protected: bool _renderCollisionHull; - QSet> _renderItemsSet; - QMap _renderItems; + QSet> _collisionRenderItemsSet; + QMap _collisionRenderItems; + + QSet> _modelMeshRenderItemsSet; + QMap _modelMeshRenderItems; + bool _readyWhenAdded { false }; bool _needsReload { true }; bool _needsUpdateClusterMatrices { true }; - mutable bool _needsUpdateTransparentTextures { true }; - mutable bool _hasTransparentTextures { false }; bool _showCollisionHull { false }; + mutable bool _needsUpdateTextures { true }; friend class ModelMeshPartPayload; RigPointer _rig; diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 0125ef79fe..9fb6802992 100755 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -32,9 +32,10 @@ public: class RenderDeferred { public: + using JobModel = render::Job::Model; + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); - using JobModel = render::Job::Model; }; class DrawConfig : public render::Job::Config { diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index 69f8e836aa..8355dcf91b 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -25,6 +25,7 @@ in vec4 _texCoord0; out vec4 _fragColor; void main(void) { + DeferredTransform deferredTransform = getDeferredTransform(); // Grab the fragment data from the uv diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index 60fc0bb2c4..530b7accac 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -20,17 +20,14 @@ namespace render { class DrawSceneOctreeConfig : public Job::Config { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty()) - Q_PROPERTY(bool showVisibleCells MEMBER showVisibleCells WRITE setShowVisibleCells) - Q_PROPERTY(bool showEmptyCells MEMBER showEmptyCells WRITE setShowEmptyCells) + Q_PROPERTY(bool showVisibleCells READ getShowVisibleCells WRITE setShowVisibleCells NOTIFY dirty()) + Q_PROPERTY(bool showEmptyCells READ getShowEmptyCells WRITE setShowEmptyCells NOTIFY dirty()) Q_PROPERTY(int numAllocatedCells READ getNumAllocatedCells) Q_PROPERTY(int numFreeCells READ getNumFreeCells) public: DrawSceneOctreeConfig() : Job::Config(false) {} - - bool showVisibleCells{ true }; - bool showEmptyCells{ false }; int numAllocatedCells{ 0 }; int numFreeCells{ 0 }; @@ -38,6 +35,12 @@ namespace render { int getNumAllocatedCells() const { return numAllocatedCells; } int getNumFreeCells() const { return numFreeCells; } + bool showVisibleCells{ true }; + bool showEmptyCells{ false }; + + bool getShowVisibleCells() { return showVisibleCells; } + bool getShowEmptyCells() { return showEmptyCells; } + public slots: void setShowVisibleCells(bool show) { showVisibleCells = show; emit dirty(); } void setShowEmptyCells(bool show) { showEmptyCells = show; emit dirty(); } @@ -79,10 +82,10 @@ namespace render { class DrawItemSelectionConfig : public Job::Config { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty()) - Q_PROPERTY(bool showInsideItems MEMBER showInsideItems WRITE setShowInsideItems) - Q_PROPERTY(bool showInsideSubcellItems MEMBER showInsideSubcellItems WRITE setShowInsideSubcellItems) - Q_PROPERTY(bool showPartialItems MEMBER showPartialItems WRITE setShowPartialItems) - Q_PROPERTY(bool showPartialSubcellItems MEMBER showPartialSubcellItems WRITE setShowPartialSubcellItems) + Q_PROPERTY(bool showInsideItems READ getShowInsideItems WRITE setShowInsideItems NOTIFY dirty()) + Q_PROPERTY(bool showInsideSubcellItems READ getShowInsideSubcellItems WRITE setShowInsideSubcellItems NOTIFY dirty()) + Q_PROPERTY(bool showPartialItems READ getShowPartialItems WRITE setShowPartialItems NOTIFY dirty()) + Q_PROPERTY(bool showPartialSubcellItems READ getShowPartialSubcellItems WRITE setShowPartialSubcellItems NOTIFY dirty()) public: DrawItemSelectionConfig() : Job::Config(false) {} @@ -92,7 +95,12 @@ namespace render { bool showPartialItems{ true }; bool showPartialSubcellItems{ true }; - public slots: + bool getShowInsideItems() const { return showInsideItems; }; + bool getShowInsideSubcellItems() const { return showInsideSubcellItems; }; + bool getShowPartialItems() const { return showPartialItems; }; + bool getShowPartialSubcellItems() const { return showPartialSubcellItems; }; + + public slots: void setShowInsideItems(bool show) { showInsideItems = show; emit dirty(); } void setShowInsideSubcellItems(bool show) { showInsideSubcellItems = show; emit dirty(); } void setShowPartialItems(bool show) { showPartialItems = show; emit dirty(); } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 733f7d9e9c..08ff97fd17 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -20,12 +20,16 @@ using namespace render; -void render::renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { +void render::renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems) { auto& scene = sceneContext->_scene; RenderArgs* args = renderContext->args; - for (const auto& itemDetails : inItems) { - auto& item = scene->getItem(itemDetails.id); + int numItemsToDraw = (int)inItems.size(); + if (maxDrawnItems != -1) { + numItemsToDraw = glm::min(numItemsToDraw, maxDrawnItems); + } + for (auto i = 0; i < numItemsToDraw; ++i) { + auto& item = scene->getItem(inItems[i].id); item.render(args); } } @@ -69,7 +73,10 @@ void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContext // render lights gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; - renderItems(sceneContext, renderContext, inLights); + renderItems(sceneContext, renderContext, inLights, _maxDrawn); args->_batch = nullptr; }); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->setNumDrawn((int)inLights.size()); } diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index fc2ab9682f..8a0f951028 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -16,15 +16,37 @@ namespace render { -void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems); +void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems = -1); void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1); + + +class DrawLightConfig : public Job::Config { + Q_OBJECT + Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) + Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) +public: + int getNumDrawn() { return numDrawn; } + void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); } + + int maxDrawn{ -1 }; +signals: + void numDrawnChanged(); + void dirty(); + +protected: + int numDrawn{ 0 }; +}; + class DrawLight { public: - using JobModel = Job::ModelI; + using Config = DrawLightConfig; + using JobModel = Job::ModelI; + void configure(const Config& config) { _maxDrawn = config.maxDrawn; } void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights); protected: + int _maxDrawn; // initialized by Config }; } diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp index ee96e0cd86..794f278fcf 100644 --- a/libraries/render/src/render/EngineStats.cpp +++ b/libraries/render/src/render/EngineStats.cpp @@ -36,6 +36,7 @@ void EngineStats::run(const SceneContextPointer& sceneContext, const RenderConte gpu::ContextStats gpuStats(_gpuStats); renderContext->args->_context->getStats(_gpuStats); + config->frameAPIDrawcallCount = _gpuStats._DSNumAPIDrawcalls - gpuStats._DSNumAPIDrawcalls; config->frameDrawcallCount = _gpuStats._DSNumDrawcalls - gpuStats._DSNumDrawcalls; config->frameDrawcallRate = config->frameDrawcallCount * frequency; diff --git a/libraries/render/src/render/EngineStats.h b/libraries/render/src/render/EngineStats.h index 478d94855e..4a57724644 100644 --- a/libraries/render/src/render/EngineStats.h +++ b/libraries/render/src/render/EngineStats.h @@ -34,6 +34,7 @@ namespace render { Q_PROPERTY(qint64 textureCPUMemoryUsage MEMBER textureCPUMemoryUsage NOTIFY dirty) Q_PROPERTY(qint64 textureGPUMemoryUsage MEMBER textureGPUMemoryUsage NOTIFY dirty) + Q_PROPERTY(quint32 frameAPIDrawcallCount MEMBER frameAPIDrawcallCount NOTIFY dirty) Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY dirty) Q_PROPERTY(quint32 frameDrawcallRate MEMBER frameDrawcallRate NOTIFY dirty) @@ -57,6 +58,7 @@ namespace render { qint64 textureCPUMemoryUsage{ 0 }; qint64 textureGPUMemoryUsage{ 0 }; + quint32 frameAPIDrawcallCount{ 0 }; quint32 frameDrawcallCount{ 0 }; quint32 frameDrawcallRate{ 0 }; diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 79f4b2a269..e091b4842c 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -16,8 +16,13 @@ using namespace render; void PendingChanges::resetItem(ItemID id, const PayloadPointer& payload) { - _resetItems.push_back(id); - _resetPayloads.push_back(payload); + if (payload) { + _resetItems.push_back(id); + _resetPayloads.push_back(payload); + } else { + qDebug() << "WARNING: PendingChanges::resetItem with a null payload!"; + removeItem(id); + } } void PendingChanges::removeItem(ItemID id) { diff --git a/libraries/script-engine/src/BatchLoader.cpp b/libraries/script-engine/src/BatchLoader.cpp index ad3f422813..f3e6242216 100644 --- a/libraries/script-engine/src/BatchLoader.cpp +++ b/libraries/script-engine/src/BatchLoader.cpp @@ -18,6 +18,7 @@ #include #include #include "ResourceManager.h" +#include "ScriptEngines.h" BatchLoader::BatchLoader(const QList& urls) : QObject(), @@ -34,8 +35,9 @@ void BatchLoader::start() { } _started = true; - - for (const auto& url : _urls) { + + for (const auto& rawURL : _urls) { + QUrl url = expandScriptUrl(normalizeScriptURL(rawURL)); auto request = ResourceManager::createResourceRequest(this, url); if (!request) { _data.insert(url, QString()); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 3f403b3677..95ca4ab4f8 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -220,11 +220,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { return; } - _fileNameString = scriptURL.toString(); + QUrl url = expandScriptUrl(normalizeScriptURL(scriptURL)); + _fileNameString = url.toString(); _isReloading = reload; - QUrl url(scriptURL); - bool isPending; auto scriptCache = DependencyManager::get(); scriptCache->getScript(url, this, isPending, reload); @@ -234,7 +233,7 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { _scriptContents = scriptContents; if (_wantSignals) { - emit scriptLoaded(_fileNameString); + emit scriptLoaded(url.toString()); } } @@ -673,11 +672,14 @@ void ScriptEngine::run() { } qint64 now = usecTimestampNow(); - float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; - if (!_isFinished) { - if (_wantSignals) { - emit update(deltaTime); + // we check for 'now' in the past in case people set their clock back + if (lastUpdate < now) { + float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; + if (!_isFinished) { + if (_wantSignals) { + emit update(deltaTime); + } } } lastUpdate = now; @@ -845,7 +847,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const { QUrl url(include); // first lets check to see if it's already a full URL if (!url.scheme().isEmpty()) { - return url; + return expandScriptUrl(normalizeScriptURL(url)); } // we apparently weren't a fully qualified url, so, let's assume we're relative @@ -862,7 +864,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const { } // at this point we should have a legitimate fully qualified URL for our parent - url = parentURL.resolved(url); + url = expandScriptUrl(normalizeScriptURL(parentURL.resolved(url))); return url; } diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index f95209d880..f22d048661 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -13,13 +13,14 @@ #include #include +#include #include "ScriptEngine.h" #include "ScriptEngineLogging.h" #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) -#define __LOC__ __FILE__ "("__STR1__(__LINE__)") : Warning Msg: " +#define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning Msg: " #ifndef __APPLE__ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); @@ -41,25 +42,53 @@ ScriptEngines::ScriptEngines() _scriptsModelFilter.setDynamicSortFilter(true); } -QString normalizeScriptUrl(const QString& rawScriptUrl) { - if (!rawScriptUrl.startsWith("http:") && !rawScriptUrl.startsWith("https:") && !rawScriptUrl.startsWith("atp:")) { -#ifdef Q_OS_LINUX - if (rawScriptUrl.startsWith("file:")) { - return rawScriptUrl; - } - return QUrl::fromLocalFile(rawScriptUrl).toString(); -#else - if (rawScriptUrl.startsWith("file:")) { - return rawScriptUrl.toLower(); - } - // Force lowercase on file scripts because of drive letter weirdness. - return QUrl::fromLocalFile(rawScriptUrl).toString().toLower(); -#endif +QUrl normalizeScriptURL(const QUrl& rawScriptURL) { + if (rawScriptURL.scheme() == "file") { + QUrl fullNormal = rawScriptURL; + QUrl defaultScriptLoc = defaultScriptsLocation(); + #ifdef Q_OS_LINUX + #else + // Force lowercase on file scripts because of drive letter weirdness. + if (rawScriptURL.isLocalFile()) { + fullNormal.setPath(fullNormal.path().toLower()); + } + #endif + // if this url is something "beneath" the default script url, replace the local path with ~ + if (fullNormal.scheme() == defaultScriptLoc.scheme() && + fullNormal.host() == defaultScriptLoc.host() && + fullNormal.path().startsWith(defaultScriptLoc.path())) { + fullNormal.setPath("/~/" + fullNormal.path().mid(defaultScriptLoc.path().size())); + } + return fullNormal; + } else if (rawScriptURL.scheme() == "http" || rawScriptURL.scheme() == "https" || rawScriptURL.scheme() == "atp") { + return rawScriptURL; + } else { + // don't accidently support gopher + return QUrl(""); } - return QUrl(rawScriptUrl).toString(); } +QUrl expandScriptUrl(const QUrl& normalizedScriptURL) { + if (normalizedScriptURL.scheme() == "http" || + normalizedScriptURL.scheme() == "https" || + normalizedScriptURL.scheme() == "atp") { + return normalizedScriptURL; + } else if (normalizedScriptURL.scheme() == "file") { + if (normalizedScriptURL.path().startsWith("/~/")) { + QUrl url = normalizedScriptURL; + QStringList splitPath = url.path().split("/"); + QUrl defaultScriptsLoc = defaultScriptsLocation(); + url.setPath(defaultScriptsLoc.path() + "/" + splitPath.mid(2).join("/")); // 2 to skip the slashes in /~/ + return url; + } + return normalizedScriptURL; + } else { + return QUrl(""); + } +} + + QObject* scriptsModel(); void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) { @@ -192,19 +221,25 @@ QVariantList ScriptEngines::getLocal() { } QVariantList ScriptEngines::getRunning() { - const int WINDOWS_DRIVE_LETTER_SIZE = 1; QVariantList result; auto runningScripts = getRunningScripts(); foreach(const QString& runningScript, runningScripts) { QUrl runningScriptURL = QUrl(runningScript); - if (runningScriptURL.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { + if (!runningScriptURL.isValid()) { runningScriptURL = QUrl::fromLocalFile(runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded))); } QVariantMap resultNode; resultNode.insert("name", runningScriptURL.fileName()); - resultNode.insert("url", runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded))); + QUrl displayURL = expandScriptUrl(QUrl(runningScriptURL)); + QString displayURLString; + if (displayURL.isLocalFile()) { + displayURLString = displayURL.toLocalFile(); + } else { + displayURLString = displayURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded)); + } + resultNode.insert("url", displayURLString); // The path contains the exact path/URL of the script, which also is used in the stopScript function. - resultNode.insert("path", runningScript); + resultNode.insert("path", normalizeScriptURL(runningScript).toString()); resultNode.insert("local", runningScriptURL.isLocalFile()); result.append(resultNode); } @@ -213,10 +248,11 @@ QVariantList ScriptEngines::getRunning() { static const QString SETTINGS_KEY = "Settings"; -static const QString DEFAULT_SCRIPTS_JS_URL = "http://s3.amazonaws.com/hifi-public/scripts/defaultScripts.js"; void ScriptEngines::loadDefaultScripts() { - loadScript(DEFAULT_SCRIPTS_JS_URL); + QUrl defaultScriptsLoc = defaultScriptsLocation(); + defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "/scripts/defaultScripts.js"); + loadScript(defaultScriptsLoc.toString()); } void ScriptEngines::loadOneScript(const QString& scriptFilename) { @@ -265,7 +301,7 @@ void ScriptEngines::saveScripts() { for (auto it = runningScripts.begin(); it != runningScripts.end(); ++it) { if (getScriptEngine(*it)->isUserLoaded()) { settings.setArrayIndex(i); - settings.setValue("script", *it); + settings.setValue("script", normalizeScriptURL(*it).toString()); ++i; } } @@ -307,11 +343,16 @@ void ScriptEngines::stopAllScripts(bool restart) { } } -bool ScriptEngines::stopScript(const QString& rawScriptUrl, bool restart) { +bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { bool stoppedScript = false; { + QUrl scriptURL = normalizeScriptURL(QUrl(rawScriptURL)); + if (!scriptURL.isValid()) { + scriptURL = normalizeScriptURL(QUrl::fromLocalFile(rawScriptURL)); + } + const QString scriptURLString = scriptURL.toString(); + QReadLocker lock(&_scriptEnginesHashLock); - const QString scriptURLString = normalizeScriptUrl(rawScriptUrl); if (_scriptEnginesHash.contains(scriptURLString)) { ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURLString]; if (restart) { @@ -344,18 +385,30 @@ void ScriptEngines::reloadAllScripts() { stopAllScripts(true); } -ScriptEngine* ScriptEngines::loadScript(const QString& scriptFilename, bool isUserLoaded, bool loadScriptFromEditor, bool activateMainWindow, bool reload) { +ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserLoaded, bool loadScriptFromEditor, + bool activateMainWindow, bool reload) { if (thread() != QThread::currentThread()) { ScriptEngine* result { nullptr }; QMetaObject::invokeMethod(this, "loadScript", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ScriptEngine*, result), - Q_ARG(QString, scriptFilename), + Q_ARG(QUrl, scriptFilename), Q_ARG(bool, isUserLoaded), Q_ARG(bool, loadScriptFromEditor), Q_ARG(bool, activateMainWindow), Q_ARG(bool, reload)); return result; } - QUrl scriptUrl(scriptFilename); + QUrl scriptUrl; + if (!scriptFilename.isValid() || + (scriptFilename.scheme() != "http" && + scriptFilename.scheme() != "https" && + scriptFilename.scheme() != "atp" && + scriptFilename.scheme() != "file")) { + // deal with a "url" like c:/something + scriptUrl = normalizeScriptURL(QUrl::fromLocalFile(scriptFilename.toString())); + } else { + scriptUrl = normalizeScriptURL(scriptFilename); + } + auto scriptEngine = getScriptEngine(scriptUrl.toString()); if (scriptEngine) { return scriptEngine; @@ -368,7 +421,7 @@ ScriptEngine* ScriptEngines::loadScript(const QString& scriptFilename, bool isUs }, Qt::QueuedConnection); - if (scriptFilename.isNull()) { + if (scriptFilename.isEmpty()) { launchScriptEngine(scriptEngine); } else { // connect to the appropriate signals of this script engine @@ -382,11 +435,11 @@ ScriptEngine* ScriptEngines::loadScript(const QString& scriptFilename, bool isUs return scriptEngine; } -ScriptEngine* ScriptEngines::getScriptEngine(const QString& rawScriptUrl) { +ScriptEngine* ScriptEngines::getScriptEngine(const QString& rawScriptURL) { ScriptEngine* result = nullptr; { QReadLocker lock(&_scriptEnginesHashLock); - const QString scriptURLString = normalizeScriptUrl(rawScriptUrl); + const QString scriptURLString = normalizeScriptURL(QUrl(rawScriptURL)).toString(); auto it = _scriptEnginesHash.find(scriptURLString); if (it != _scriptEnginesHash.end()) { result = it.value(); @@ -396,15 +449,17 @@ ScriptEngine* ScriptEngines::getScriptEngine(const QString& rawScriptUrl) { } // FIXME - change to new version of ScriptCache loading notification -void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptUrl) { - UserActivityLogger::getInstance().loadedScript(rawScriptUrl); +void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) { + UserActivityLogger::getInstance().loadedScript(rawScriptURL); ScriptEngine* scriptEngine = qobject_cast(sender()); launchScriptEngine(scriptEngine); { QWriteLocker lock(&_scriptEnginesHashLock); - const QString scriptURLString = normalizeScriptUrl(rawScriptUrl); + QUrl url = QUrl(rawScriptURL); + QUrl normalized = normalizeScriptURL(url); + const QString scriptURLString = normalized.toString(); _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine); } emit scriptCountChanged(); @@ -427,11 +482,11 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) { } -void ScriptEngines::onScriptFinished(const QString& rawScriptUrl, ScriptEngine* engine) { +void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEngine* engine) { bool removed = false; { QWriteLocker lock(&_scriptEnginesHashLock); - const QString scriptURLString = normalizeScriptUrl(rawScriptUrl); + const QString scriptURLString = normalizeScriptURL(QUrl(rawScriptURL)).toString(); for (auto it = _scriptEnginesHash.find(scriptURLString); it != _scriptEnginesHash.end(); ++it) { if (it.value() == engine) { _scriptEnginesHash.erase(it); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index df60d6ff63..a0d914ec64 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -51,7 +51,7 @@ public: ScriptsModelFilter* scriptsModelFilter() { return &_scriptsModelFilter; }; Q_INVOKABLE void loadOneScript(const QString& scriptFilename); - Q_INVOKABLE ScriptEngine* loadScript(const QString& scriptFilename = QString(), + Q_INVOKABLE ScriptEngine* loadScript(const QUrl& scriptFilename = QString(), bool isUserLoaded = true, bool loadScriptFromEditor = false, bool activateMainWindow = false, bool reload = false); Q_INVOKABLE bool stopScript(const QString& scriptHash, bool restart = false); @@ -96,4 +96,7 @@ protected: ScriptsModelFilter _scriptsModelFilter; }; +QUrl normalizeScriptURL(const QUrl& rawScriptURL); +QUrl expandScriptUrl(const QUrl& normalizedScriptURL); + #endif // hifi_ScriptEngine_h diff --git a/libraries/script-engine/src/ScriptsModel.cpp b/libraries/script-engine/src/ScriptsModel.cpp index 37b2551f39..0c58966e81 100644 --- a/libraries/script-engine/src/ScriptsModel.cpp +++ b/libraries/script-engine/src/ScriptsModel.cpp @@ -13,21 +13,19 @@ #include #include #include +#include #include +#include #include "ScriptEngine.h" #include "ScriptEngines.h" #include "ScriptEngineLogging.h" #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) -#define __LOC__ __FILE__ "("__STR1__(__LINE__)") : Warning Msg: " +#define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning Msg: " - -static const QString S3_URL = "http://s3.amazonaws.com/hifi-public"; -static const QString PUBLIC_URL = "http://public.highfidelity.io"; static const QString MODELS_LOCATION = "scripts/"; - static const QString PREFIX_PARAMETER_NAME = "prefix"; static const QString MARKER_PARAMETER_NAME = "marker"; static const QString IS_TRUNCATED_NAME = "IsTruncated"; @@ -63,7 +61,7 @@ ScriptsModel::ScriptsModel(QObject* parent) : connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles); reloadLocalFiles(); - reloadRemoteFiles(); + reloadDefaultFiles(); } ScriptsModel::~ScriptsModel() { @@ -140,36 +138,56 @@ void ScriptsModel::updateScriptsLocation(const QString& newPath) { reloadLocalFiles(); } -void ScriptsModel::reloadRemoteFiles() { +void ScriptsModel::reloadDefaultFiles() { if (!_loadingScripts) { _loadingScripts = true; for (int i = _treeNodes.size() - 1; i >= 0; i--) { TreeNodeBase* node = _treeNodes.at(i); if (node->getType() == TREE_NODE_TYPE_SCRIPT && - static_cast(node)->getOrigin() == SCRIPT_ORIGIN_REMOTE) + static_cast(node)->getOrigin() == SCRIPT_ORIGIN_DEFAULT) { delete node; _treeNodes.removeAt(i); } } - requestRemoteFiles(); + requestDefaultFiles(); } } -void ScriptsModel::requestRemoteFiles(QString marker) { - QUrl url(S3_URL); - QUrlQuery query; - query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION); - if (!marker.isEmpty()) { - query.addQueryItem(MARKER_PARAMETER_NAME, marker); - } - url.setQuery(query); +void ScriptsModel::requestDefaultFiles(QString marker) { + QUrl url(defaultScriptsLocation()); - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(request); - connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); + if (url.isLocalFile()) { + // if the url indicates a local directory, use QDirIterator + // QString localDir = url.toLocalFile() + "/scripts"; + QString localDir = expandScriptUrl(url).toLocalFile() + "/scripts"; + int localDirPartCount = localDir.split("/").size(); + #ifdef Q_OS_WIN + localDirPartCount++; // one for the drive letter + #endif + QDirIterator it(localDir, QStringList() << "*.js", QDir::Files, QDirIterator::Subdirectories); + while (it.hasNext()) { + QUrl jsFullPath = QUrl::fromLocalFile(it.next()); + QString jsPartialPath = jsFullPath.path().split("/").mid(localDirPartCount).join("/"); + jsFullPath = normalizeScriptURL(jsFullPath); + _treeNodes.append(new TreeNodeScript(jsPartialPath, jsFullPath.toString(), SCRIPT_ORIGIN_DEFAULT)); + } + _loadingScripts = false; + } else { + // the url indicates http(s), use QNetworkRequest + QUrlQuery query; + query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION); + if (!marker.isEmpty()) { + query.addQueryItem(MARKER_PARAMETER_NAME, marker); + } + url.setQuery(query); + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(request); + connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); + } } void ScriptsModel::downloadFinished() { @@ -182,8 +200,10 @@ void ScriptsModel::downloadFinished() { if (!data.isEmpty()) { finished = parseXML(data); } else { - qCDebug(scriptengine) << "Error: Received no data when loading remote scripts"; + qCDebug(scriptengine) << "Error: Received no data when loading default scripts"; } + } else { + qDebug() << "Error: when loading default scripts --" << reply->error(); } reply->deleteLater(); @@ -218,7 +238,11 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) { xml.readNext(); lastKey = xml.text().toString(); if (jsRegex.exactMatch(xml.text().toString())) { - _treeNodes.append(new TreeNodeScript(lastKey.mid(MODELS_LOCATION.length()), S3_URL + "/" + lastKey, SCRIPT_ORIGIN_REMOTE)); + QString localPath = lastKey.split("/").mid(1).join("/"); + QUrl fullPath = defaultScriptsLocation(); + fullPath.setPath(fullPath.path() + "/" + lastKey); + const QString fullPathStr = normalizeScriptURL(fullPath).toString(); + _treeNodes.append(new TreeNodeScript(localPath, fullPathStr, SCRIPT_ORIGIN_DEFAULT)); } } xml.readNext(); @@ -231,12 +255,12 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) { // Error handling if (xml.hasError()) { - qCDebug(scriptengine) << "Error loading remote scripts: " << xml.errorString(); + qCDebug(scriptengine) << "Error loading default scripts: " << xml.errorString(); return true; } if (truncated) { - requestRemoteFiles(lastKey); + requestDefaultFiles(lastKey); } // If this request was not truncated, we are done. @@ -261,7 +285,9 @@ void ScriptsModel::reloadLocalFiles() { const QFileInfoList localFiles = _localDirectory.entryInfoList(); for (int i = 0; i < localFiles.size(); i++) { QFileInfo file = localFiles[i]; - _treeNodes.append(new TreeNodeScript(file.fileName(), file.absoluteFilePath(), SCRIPT_ORIGIN_LOCAL)); + QString fileName = file.fileName(); + QUrl absPath = normalizeScriptURL(QUrl::fromLocalFile(file.absoluteFilePath())); + _treeNodes.append(new TreeNodeScript(fileName, absPath.toString(), SCRIPT_ORIGIN_LOCAL)); } rebuildTree(); endResetModel(); @@ -283,6 +309,9 @@ void ScriptsModel::rebuildTree() { QString hash; QStringList pathList = script->getLocalPath().split(tr("/")); pathList.removeLast(); + if (pathList.isEmpty()) { + continue; + } QStringList::const_iterator pathIterator; for (pathIterator = pathList.constBegin(); pathIterator != pathList.constEnd(); ++pathIterator) { hash.append(*pathIterator + "/"); diff --git a/libraries/script-engine/src/ScriptsModel.h b/libraries/script-engine/src/ScriptsModel.h index df9716d43b..e1902f4b23 100644 --- a/libraries/script-engine/src/ScriptsModel.h +++ b/libraries/script-engine/src/ScriptsModel.h @@ -21,7 +21,7 @@ class TreeNodeFolder; enum ScriptOrigin { SCRIPT_ORIGIN_LOCAL, - SCRIPT_ORIGIN_REMOTE + SCRIPT_ORIGIN_DEFAULT }; enum TreeNodeType { @@ -84,10 +84,10 @@ protected slots: void updateScriptsLocation(const QString& newPath); void downloadFinished(); void reloadLocalFiles(); - void reloadRemoteFiles(); + void reloadDefaultFiles(); protected: - void requestRemoteFiles(QString marker = QString()); + void requestDefaultFiles(QString marker = QString()); bool parseXML(QByteArray xmlFile); void rebuildTree(); diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 0b453e6f36..a3afa220c9 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -17,6 +17,8 @@ #include "GeometryUtil.h" #include "NumericalConstants.h" +const glm::vec3 AABox::INFINITY_VECTOR(std::numeric_limits::infinity()); + AABox::AABox(const AACube& other) : _corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) { } @@ -34,7 +36,7 @@ AABox::AABox(const glm::vec3& corner, const glm::vec3& dimensions) : _corner(corner), _scale(dimensions) { }; -AABox::AABox() : _corner(std::numeric_limits::infinity()), _scale(0.0f) { +AABox::AABox() : _corner(INFINITY_VECTOR), _scale(0.0f) { }; glm::vec3 AABox::calcCenter() const { @@ -475,8 +477,15 @@ AABox AABox::clamp(float min, float max) const { } AABox& AABox::operator += (const glm::vec3& point) { - _corner = glm::min(_corner, point); - _scale = glm::max(_scale, point - _corner); + + if (isInvalid()) { + _corner = glm::min(_corner, point); + } else { + glm::vec3 maximum(_corner + _scale); + _corner = glm::min(_corner, point); + maximum = glm::max(maximum, point); + _scale = maximum - _corner; + } return (*this); } @@ -484,17 +493,31 @@ AABox& AABox::operator += (const glm::vec3& point) { AABox& AABox::operator += (const AABox& box) { if (!box.isInvalid()) { (*this) += box._corner; - _scale = glm::max(_scale, box.calcTopFarLeft() - _corner); + (*this) += box.calcTopFarLeft(); } return (*this); } +void AABox::embiggen(float scale) { + _corner += scale * (-0.5f * _scale); + _scale *= scale; +} + +void AABox::embiggen(const glm::vec3& scale) { + _corner += scale * (-0.5f * _scale); + _scale *= scale; +} + +void AABox::scale(float scale) { + _corner *= scale; + _scale *= scale; +} + void AABox::scale(const glm::vec3& scale) { _corner *= scale; _scale *= scale; } - void AABox::rotate(const glm::quat& rotation) { auto minimum = _corner; auto maximum = _corner + _scale; @@ -544,3 +567,47 @@ void AABox::transform(const Transform& transform) { rotate(transform.getRotation()); translate(transform.getTranslation()); } + +void AABox::transform(const glm::mat4& matrix) { + auto minimum = _corner; + auto maximum = _corner + _scale; + + glm::vec3 bottomLeftNear(minimum.x, minimum.y, minimum.z); + glm::vec3 bottomRightNear(maximum.x, minimum.y, minimum.z); + glm::vec3 bottomLeftFar(minimum.x, minimum.y, maximum.z); + glm::vec3 bottomRightFar(maximum.x, minimum.y, maximum.z); + glm::vec3 topLeftNear(minimum.x, maximum.y, minimum.z); + glm::vec3 topRightNear(maximum.x, maximum.y, minimum.z); + glm::vec3 topLeftFar(minimum.x, maximum.y, maximum.z); + glm::vec3 topRightFar(maximum.x, maximum.y, maximum.z); + + glm::vec3 bottomLeftNearTransformed = transformPoint(matrix, bottomLeftNear); + glm::vec3 bottomRightNearTransformed = transformPoint(matrix, bottomRightNear); + glm::vec3 bottomLeftFarTransformed = transformPoint(matrix, bottomLeftFar); + glm::vec3 bottomRightFarTransformed = transformPoint(matrix, bottomRightFar); + glm::vec3 topLeftNearTransformed = transformPoint(matrix, topLeftNear); + glm::vec3 topRightNearTransformed = transformPoint(matrix, topRightNear); + glm::vec3 topLeftFarTransformed = transformPoint(matrix, topLeftFar); + glm::vec3 topRightFarTransformed = transformPoint(matrix, topRightFar); + + minimum = glm::min(bottomLeftNearTransformed, + glm::min(bottomRightNearTransformed, + glm::min(bottomLeftFarTransformed, + glm::min(bottomRightFarTransformed, + glm::min(topLeftNearTransformed, + glm::min(topRightNearTransformed, + glm::min(topLeftFarTransformed, + topRightFarTransformed))))))); + + maximum = glm::max(bottomLeftNearTransformed, + glm::max(bottomRightNearTransformed, + glm::max(bottomLeftFarTransformed, + glm::max(bottomRightFarTransformed, + glm::max(topLeftNearTransformed, + glm::max(topRightNearTransformed, + glm::max(topLeftFarTransformed, + topRightFarTransformed))))))); + + _corner = minimum; + _scale = maximum - minimum; +} diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index ec06c60121..30bde2d717 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -58,7 +58,6 @@ public: const glm::vec3& getMinimumPoint() const { return _corner; } glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); } - bool contains(const glm::vec3& point) const; bool contains(const AABox& otherBox) const; bool touches(const AABox& otherBox) const; @@ -93,10 +92,19 @@ public: void scale(float scale); void scale(const glm::vec3& scale); + /// make the AABox bigger (scale about it's center) + void embiggen(float scale); + void embiggen(const glm::vec3& scale); + // Transform the extents with transform void transform(const Transform& transform); - bool isInvalid() const { return _corner == glm::vec3(std::numeric_limits::infinity()); } + // Transform the extents with matrix + void transform(const glm::mat4& matrix); + + static const glm::vec3 INFINITY_VECTOR; + + bool isInvalid() const { return _corner == INFINITY_VECTOR; } private: glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const; diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 79e83e9b40..410a95a4d5 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "PathUtils.h" @@ -53,3 +54,14 @@ QString findMostRecentFileExtension(const QString& originalFileName, QVector possibleExtensions); QString findMostRecentFileExtension(const QString& originalFileName, QVector possibleExtensions); +QUrl defaultScriptsLocation(); + #endif // hifi_PathUtils_h diff --git a/libraries/shared/src/PortableHighResolutionClock.cpp b/libraries/shared/src/PortableHighResolutionClock.cpp index 6e096a7ce1..55dd61707c 100644 --- a/libraries/shared/src/PortableHighResolutionClock.cpp +++ b/libraries/shared/src/PortableHighResolutionClock.cpp @@ -24,7 +24,7 @@ namespace { win_high_resolution_clock::time_point win_high_resolution_clock::now() { LARGE_INTEGER count; QueryPerformanceCounter(&count); - return time_point(duration(count.QuadPart * static_cast(period::den) / g_Frequency)); + return time_point(duration(static_cast((double) count.QuadPart * (double) period::den / (double)g_Frequency))); } #endif diff --git a/libraries/shared/src/SpatialParentFinder.h b/libraries/shared/src/SpatialParentFinder.h index 9b49490fa5..aae7d9f040 100644 --- a/libraries/shared/src/SpatialParentFinder.h +++ b/libraries/shared/src/SpatialParentFinder.h @@ -19,6 +19,10 @@ class SpatiallyNestable; using SpatiallyNestableWeakPointer = std::weak_ptr; using SpatiallyNestablePointer = std::shared_ptr; +class SpatialParentTree { +public: + virtual SpatiallyNestablePointer findByID(const QUuid& id) { return nullptr; } +}; class SpatialParentFinder : public Dependency { @@ -31,7 +35,7 @@ public: SpatialParentFinder() { } virtual ~SpatialParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const = 0; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const = 0; }; #endif // hifi_SpatialParentFinder_h diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 55da4822ef..13bf5d9054 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -105,7 +105,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons success = false; return nullptr; } - _parent = parentFinder->find(parentID, success); + _parent = parentFinder->find(parentID, success, getParentTree()); if (!success) { return nullptr; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index a65c7c7f2c..379f2facd7 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -140,6 +140,7 @@ public: bool isDead() const { return _isDead; } bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; } + virtual SpatialParentTree* getParentTree() const { return nullptr; } protected: const NestableType _nestableType; // EntityItem or an AvatarData diff --git a/libraries/shared/src/ThreadSafeValueCache.h b/libraries/shared/src/ThreadSafeValueCache.h index e4e78ca3d7..37a1258aa1 100644 --- a/libraries/shared/src/ThreadSafeValueCache.h +++ b/libraries/shared/src/ThreadSafeValueCache.h @@ -23,6 +23,7 @@ template class ThreadSafeValueCache { public: + ThreadSafeValueCache() {} ThreadSafeValueCache(const T& v) : _value { v } {} // returns atomic copy of the cached value. diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index d18ada1f5a..66667a0b2a 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -122,13 +122,18 @@ void QmlWindowClass::initQml(QVariantMap properties) { object->setProperty(SOURCE_PROPERTY, _source); // Forward messages received from QML on to the script - connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection); + connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); }); } Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); } +void QmlWindowClass::qmlToScript(const QVariant& message) { + QJSValue js = qvariant_cast(message); + emit fromQml(js.toVariant()); +} + void QmlWindowClass::sendToQml(const QVariant& message) { // Forward messages received from the script on to QML QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 242f9b0dd4..e1865b133a 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -63,6 +63,7 @@ signals: protected slots: void hasClosed(); + void qmlToScript(const QVariant& message); protected: static QVariantMap parseArguments(QScriptContext* context); diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index e5cbffda21..a91690ecdd 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -12,8 +12,8 @@ if (WIN32) add_definitions(-DGLEW_STATIC) set(TARGET_NAME oculus) - setup_hifi_plugin() - link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins) + setup_hifi_plugin(Multimedia) + link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins audio-client networking) include_hifi_library_headers(octree) @@ -21,5 +21,6 @@ if (WIN32) find_package(LibOVR REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES}) + target_link_libraries(${TARGET_NAME} Winmm.lib) endif() \ No newline at end of file diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index e23d8cade6..52eb70134d 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -12,14 +12,19 @@ #include "OculusHelpers.h" void OculusBaseDisplayPlugin::resetSensors() { - ovr_RecenterPose(_session); + ovr_RecenterTrackingOrigin(_session); } -void OculusBaseDisplayPlugin::updateHeadPose(uint32_t frameIndex) { - auto displayTime = ovr_GetPredictedDisplayTime(_session, frameIndex); - auto trackingState = ovr_GetTrackingState(_session, displayTime, true); - mat4 headPose = toGlm(trackingState.HeadPose.ThePose); - _headPoseCache.set(headPose); +void OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + FrameInfo frame; + frame.sensorSampleTime = ovr_GetTimeInSeconds();; + frame.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, frameIndex); + auto trackingState = ovr_GetTrackingState(_session, frame.predictedDisplayTime, ovrTrue); + frame.headPose = toGlm(trackingState.HeadPose.ThePose); + + _currentRenderFrameInfo.set(frame); + Lock lock(_mutex); + _frameInfos[frameIndex] = frame; } bool OculusBaseDisplayPlugin::isSupported() const { @@ -42,36 +47,30 @@ bool OculusBaseDisplayPlugin::internalActivate() { _hmdDesc = ovr_GetHmdDesc(_session); - _ipd = ovr_GetFloat(_session, OVR_KEY_IPD, _ipd); - glm::uvec2 eyeSizes[2]; _viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; + _ipd = 0; ovr_for_each_eye([&](ovrEyeType eye) { _eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye]; ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_session, eye, _eyeFovs[eye]); ovrMatrix4f ovrPerspectiveProjection = - ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL); _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); - _eyeOffsets[eye] = glm::translate(mat4(), toGlm(erd.HmdToEyeViewOffset)); + _eyeOffsets[eye] = glm::translate(mat4(), toGlm(erd.HmdToEyeOffset)); eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_session, eye, erd.Fov, 1.0f)); - _viewScaleDesc.HmdToEyeViewOffset[eye] = erd.HmdToEyeViewOffset; + _viewScaleDesc.HmdToEyeOffset[eye] = erd.HmdToEyeOffset; + _ipd += glm::abs(glm::length(toGlm(erd.HmdToEyeOffset))); }); auto combinedFov = _eyeFovs[0]; combinedFov.LeftTan = combinedFov.RightTan = std::max(combinedFov.LeftTan, combinedFov.RightTan); - _cullingProjection = toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded)); + _cullingProjection = toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL)); _renderTargetSize = uvec2( eyeSizes[0].x + eyeSizes[1].x, std::max(eyeSizes[0].y, eyeSizes[1].y)); - if (!OVR_SUCCESS(ovr_ConfigureTracking(_session, - ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { - logWarning("Failed to attach to sensor device"); - } - - // Parent class relies on our _session intialization, so it must come after that. memset(&_sceneLayer, 0, sizeof(ovrLayerEyeFov)); _sceneLayer.Header.Type = ovrLayerType_EyeFov; _sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index d21b0561bc..71f876a82a 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -20,7 +20,9 @@ public: // Stereo specific methods virtual void resetSensors() override final; - virtual void updateHeadPose(uint32_t frameIndex) override; + virtual void beginFrameRender(uint32_t frameIndex) override; + float getTargetFrameRate() override { return _hmdDesc.DisplayRefreshRate; } + protected: void customizeContext() override; @@ -28,9 +30,8 @@ protected: void internalDeactivate() override; protected: - ovrSession _session; + ovrSession _session { nullptr }; ovrGraphicsLuid _luid; - float _ipd{ OVR_DEFAULT_IPD }; ovrEyeRenderDesc _eyeRenderDescs[2]; ovrFovPort _eyeFovs[2]; ovrHmdDesc _hmdDesc; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp new file mode 100644 index 0000000000..f11ec18b10 --- /dev/null +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -0,0 +1,219 @@ +// +// OculusControllerManager.cpp +// input-plugins/src/input-plugins +// +// Created by Bradley Austin Davis 2016/03/04. +// 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 "OculusControllerManager.h" + +#include + +#include +#include +#include + +#include +#include + +#include "OculusHelpers.h" + +Q_DECLARE_LOGGING_CATEGORY(oculus) + + +static const QString MENU_PARENT = "Avatar"; +static const QString MENU_NAME = "Oculus Touch Controllers"; +static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; + +const QString OculusControllerManager::NAME = "Oculus"; + +bool OculusControllerManager::isSupported() const { + return oculusAvailable(); +} + +bool OculusControllerManager::activate() { + InputPlugin::activate(); + if (!_session) { + _session = acquireOculusSession(); + } + Q_ASSERT(_session); + + // register with UserInputMapper + auto userInputMapper = DependencyManager::get(); + if (_remote) { + userInputMapper->registerDevice(_remote); + } + if (_touch) { + userInputMapper->registerDevice(_touch); + } + return true; +} + +void OculusControllerManager::deactivate() { + InputPlugin::deactivate(); + + if (_session) { + releaseOculusSession(); + _session = nullptr; + } + + // unregister with UserInputMapper + auto userInputMapper = DependencyManager::get(); + if (_touch) { + userInputMapper->removeDevice(_touch->getDeviceID()); + } + if (_remote) { + userInputMapper->removeDevice(_remote->getDeviceID()); + } +} + +void OculusControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { + PerformanceTimer perfTimer("OculusControllerManager::TouchDevice::update"); + + if (!OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { + qCWarning(oculus) << "Unable to read oculus input state"; + return; + } + + if (_touch) { + _touch->update(deltaTime, inputCalibrationData, jointsCaptured); + } + if (_remote) { + _remote->update(deltaTime, inputCalibrationData, jointsCaptured); + } +} + +void OculusControllerManager::pluginFocusOutEvent() { + if (_touch) { + _touch->focusOutEvent(); + } + if (_remote) { + _remote->focusOutEvent(); + } +} + +using namespace controller; + +static const std::vector> BUTTON_MAP { { + { ovrButton_X, X }, + { ovrButton_Y, Y }, + { ovrButton_A, A }, + { ovrButton_B, B }, + { ovrButton_LThumb, LS }, + { ovrButton_RThumb, RS }, + { ovrButton_LShoulder, LB }, + { ovrButton_RShoulder, RB }, +} }; + +static const std::vector> TOUCH_MAP { { + { ovrTouch_X, LEFT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_A, RIGHT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_LIndexTrigger, LEFT_PRIMARY_INDEX_TOUCH }, + { ovrTouch_RIndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH }, + { ovrTouch_LThumb, LS_TOUCH }, + { ovrTouch_RThumb, RS_TOUCH }, + { ovrTouch_LThumbUp, LEFT_THUMB_UP }, + { ovrTouch_RThumbUp, RIGHT_THUMB_UP }, + { ovrTouch_LIndexPointing, LEFT_INDEX_POINT }, + { ovrTouch_RIndexPointing, RIGHT_INDEX_POINT }, +} }; + +void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { + _poseStateMap.clear(); + _buttonPressedMap.clear(); + + if (!jointsCaptured) { + int numTrackedControllers = 0; + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + auto tracking = ovr_GetTrackingState(_parent._session, 0, false); + ovr_for_each_hand([&](ovrHandType hand) { + ++numTrackedControllers; + if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]); + } + }); + } + using namespace controller; + // Axes + const auto& inputState = _parent._inputState; + _axisStateMap[LX] = inputState.Thumbstick[ovrHand_Left].x; + _axisStateMap[LY] = inputState.Thumbstick[ovrHand_Left].y; + _axisStateMap[LT] = inputState.IndexTrigger[ovrHand_Left]; + _axisStateMap[LG] = inputState.HandTrigger[ovrHand_Left]; + + _axisStateMap[RX] = inputState.Thumbstick[ovrHand_Right].x; + _axisStateMap[RY] = inputState.Thumbstick[ovrHand_Right].y; + _axisStateMap[RT] = inputState.IndexTrigger[ovrHand_Right]; + _axisStateMap[RG] = inputState.HandTrigger[ovrHand_Right]; + + // Buttons + for (const auto& pair : BUTTON_MAP) { + if (inputState.Buttons & pair.first) { + _buttonPressedMap.insert(pair.second); + } + } + // Touches + for (const auto& pair : TOUCH_MAP) { + if (inputState.Touches & pair.first) { + _buttonPressedMap.insert(pair.second); + } + } +} + +void OculusControllerManager::TouchDevice::focusOutEvent() { + _axisStateMap.clear(); + _buttonPressedMap.clear(); +}; + +void OculusControllerManager::TouchDevice::handlePose(float deltaTime, + const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, + const ovrPoseStatef& handPose) { + auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND; + auto& pose = _poseStateMap[poseId]; + pose.translation = toGlm(handPose.ThePose.Position); + pose.rotation = toGlm(handPose.ThePose.Orientation); + pose.angularVelocity = toGlm(handPose.AngularVelocity); + pose.velocity = toGlm(handPose.LinearVelocity); +} + +controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailableInputs() const { + using namespace controller; + QVector availableInputs{ + // Trackpad analogs + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(RX, "RX"), + makePair(RY, "RY"), + // trigger analogs + makePair(LT, "LT"), + makePair(RT, "RT"), + + makePair(LB, "LB"), + makePair(RB, "RB"), + + makePair(LS, "LS"), + makePair(RS, "RS"), + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), + + makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), + makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), + makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), + makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), + }; + return availableInputs; +} + +QString OculusControllerManager::TouchDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/touch.json"; + return MAPPING_JSON; +} + + + diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h new file mode 100644 index 0000000000..fc10dcc73d --- /dev/null +++ b/plugins/oculus/src/OculusControllerManager.h @@ -0,0 +1,81 @@ +// +// Created by Bradley Austin Davis on 2016/03/04 +// 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 +// + +#ifndef hifi__OculusControllerManager +#define hifi__OculusControllerManager + +#include +#include + +#include + +#include +#include + +#include + +class OculusControllerManager : public InputPlugin { + Q_OBJECT +public: + // Plugin functions + bool isSupported() const override; + bool isJointController() const override { return true; } + const QString& getName() const override { return NAME; } + + bool activate() override; + void deactivate() override; + + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + +private: + class OculusInputDevice : public controller::InputDevice { + public: + OculusInputDevice(OculusControllerManager& parent, const QString& name) : controller::InputDevice(name), _parent(parent) {} + + OculusControllerManager& _parent; + friend class OculusControllerManager; + }; + + class RemoteDevice : public OculusInputDevice { + public: + using Pointer = std::shared_ptr; + RemoteDevice(OculusControllerManager& parent) : OculusInputDevice(parent, "Oculus Remote") {} + + controller::Input::NamedVector getAvailableInputs() const override; + QString getDefaultMappingConfig() const override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void focusOutEvent() override; + + friend class OculusControllerManager; + }; + + class TouchDevice : public OculusInputDevice { + public: + using Pointer = std::shared_ptr; + TouchDevice(OculusControllerManager& parent) : OculusInputDevice(parent, "Oculus Touch") {} + + controller::Input::NamedVector getAvailableInputs() const override; + QString getDefaultMappingConfig() const override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void focusOutEvent() override; + + private: + void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose); + int _trackedControllers { 0 }; + friend class OculusControllerManager; + }; + + ovrSession _session { nullptr }; + ovrInputState _inputState {}; + RemoteDevice::Pointer _remote; + TouchDevice::Pointer _touch; + static const QString NAME; +}; + +#endif // hifi__OculusControllerManager diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 8c3a676c61..a53643ba21 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -6,10 +6,34 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OculusDisplayPlugin.h" + +// Odd ordering of header is required to avoid 'macro redinition warnings' +#include + +#include + #include + #include "OculusHelpers.h" const QString OculusDisplayPlugin::NAME("Oculus Rift"); +static ovrPerfHudMode currentDebugMode = ovrPerfHud_Off; + +bool OculusDisplayPlugin::internalActivate() { + bool result = Parent::internalActivate(); + currentDebugMode = ovrPerfHud_Off; + if (result && _session) { + ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode); + } + return result; +} + +void OculusDisplayPlugin::cycleDebugOutput() { + if (_session) { + currentDebugMode = static_cast((currentDebugMode + 1) % ovrPerfHud_Count); + ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode); + } +} void OculusDisplayPlugin::customizeContext() { Parent::customizeContext(); @@ -48,12 +72,6 @@ void blit(const SrcFbo& srcFbo, const DstFbo& dstFbo) { }); } -void OculusDisplayPlugin::updateFrameData() { - Parent::updateFrameData(); - _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentRenderEyePoses[Left]); - _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentRenderEyePoses[Right]); -} - void OculusDisplayPlugin::hmdPresent() { PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex) @@ -63,12 +81,38 @@ void OculusDisplayPlugin::hmdPresent() { } blit(_compositeFramebuffer, _sceneFbo); + _sceneFbo->Commit(); { + _sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime; + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.headPose); + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.headPose); ovrLayerHeader* layers = &_sceneLayer.Header; ovrResult result = ovr_SubmitFrame(_session, _currentRenderFrameIndex, &_viewScaleDesc, &layers, 1); if (!OVR_SUCCESS(result)) { logWarning("Failed to present"); } } - _sceneFbo->Increment(); } + +bool OculusDisplayPlugin::isHmdMounted() const { + ovrSessionStatus status; + return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) && + (ovrFalse != status.HmdMounted)); +} + +QString OculusDisplayPlugin::getPreferredAudioInDevice() const { + WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE]; + if (!OVR_SUCCESS(ovr_GetAudioDeviceInGuidStr(buffer))) { + return QString(); + } + return AudioClient::friendlyNameForAudioDevice(buffer); +} + +QString OculusDisplayPlugin::getPreferredAudioOutDevice() const { + WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE]; + if (!OVR_SUCCESS(ovr_GetAudioDeviceOutGuidStr(buffer))) { + return QString(); + } + return AudioClient::friendlyNameForAudioDevice(buffer); +} + diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index 9b6b922f69..d6cd6f6f3d 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -12,22 +12,21 @@ struct SwapFramebufferWrapper; using SwapFboPtr = QSharedPointer; -const float TARGET_RATE_Oculus = 75.0f; - class OculusDisplayPlugin : public OculusBaseDisplayPlugin { using Parent = OculusBaseDisplayPlugin; public: const QString& getName() const override { return NAME; } - float getTargetFrameRate() override { return TARGET_RATE_Oculus; } + QString getPreferredAudioInDevice() const override; + QString getPreferredAudioOutDevice() const override; protected: + bool internalActivate() override; void hmdPresent() override; - // FIXME update with Oculus API call once it's available in the SDK - bool isHmdMounted() const override { return true; } + bool isHmdMounted() const override; void customizeContext() override; void uncustomizeContext() override; - void updateFrameData() override; + void cycleDebugOutput() override; private: static const QString NAME; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 170be05952..c9e702ecd0 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -9,7 +9,10 @@ #include "OculusHelpers.h" #include + #include +#include +#include using Mutex = std::mutex; using Lock = std::unique_lock; @@ -38,9 +41,23 @@ void logFatal(const char* what) { qFatal(error.c_str()); } +static const QString OCULUS_RUNTIME_PATH { "C:\\Program Files (x86)\\Oculus\\Support\\oculus-runtime" }; +static const QString GOOD_OCULUS_RUNTIME_FILE { OCULUS_RUNTIME_PATH + "\\LibOVRRT64_1.dll" }; + bool oculusAvailable() { ovrDetectResult detect = ovr_Detect(0); - return (detect.IsOculusServiceRunning && detect.IsOculusHMDConnected); + if (!detect.IsOculusServiceRunning || !detect.IsOculusHMDConnected) { + return false; + } + + // HACK Explicitly check for the presence of the 1.0 runtime DLL, and fail if it + // doesn't exist + if (!QFile(GOOD_OCULUS_RUNTIME_FILE).exists()) { + qCWarning(oculus) << "Oculus Runtime detected, but no 1.x DLL present: \"" + GOOD_OCULUS_RUNTIME_FILE + "\""; + return false; + } + + return true; } ovrSession acquireOculusSession() { @@ -98,9 +115,9 @@ SwapFramebufferWrapper::~SwapFramebufferWrapper() { destroyColor(); } -void SwapFramebufferWrapper::Increment() { - ++color->CurrentIndex; - color->CurrentIndex %= color->TextureCount; +void SwapFramebufferWrapper::Commit() { + auto result = ovr_CommitTextureSwapChain(_session, color); + Q_ASSERT(OVR_SUCCESS(result)); } void SwapFramebufferWrapper::Resize(const uvec2 & size) { @@ -114,7 +131,7 @@ void SwapFramebufferWrapper::Resize(const uvec2 & size) { void SwapFramebufferWrapper::destroyColor() { if (color) { - ovr_DestroySwapTextureSet(_session, color); + ovr_DestroyTextureSwapChain(_session, color); color = nullptr; } } @@ -122,13 +139,30 @@ void SwapFramebufferWrapper::destroyColor() { void SwapFramebufferWrapper::initColor() { destroyColor(); - if (!OVR_SUCCESS(ovr_CreateSwapTextureSetGL(_session, GL_SRGB8_ALPHA8, size.x, size.y, &color))) { + ovrTextureSwapChainDesc desc = {}; + desc.Type = ovrTexture_2D; + desc.ArraySize = 1; + desc.Width = size.x; + desc.Height = size.y; + desc.MipLevels = 1; + desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; + desc.SampleCount = 1; + desc.StaticImage = ovrFalse; + + ovrResult result = ovr_CreateTextureSwapChainGL(_session, &desc, &color); + if (!OVR_SUCCESS(result)) { logFatal("Failed to create swap textures"); } - for (int i = 0; i < color->TextureCount; ++i) { - ovrGLTexture& ovrTex = (ovrGLTexture&)color->Textures[i]; - glBindTexture(GL_TEXTURE_2D, ovrTex.OGL.TexId); + int length = 0; + result = ovr_GetTextureSwapChainLength(_session, color, &length); + if (!OVR_SUCCESS(result) || !length) { + qFatal("Unable to count swap chain textures"); + } + for (int i = 0; i < length; ++i) { + GLuint chainTexId; + ovr_GetTextureSwapChainBufferGL(_session, color, i, &chainTexId); + glBindTexture(GL_TEXTURE_2D, chainTexId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -141,8 +175,11 @@ void SwapFramebufferWrapper::initDone() { } void SwapFramebufferWrapper::onBind(oglplus::Framebuffer::Target target) { - ovrGLTexture& tex = (ovrGLTexture&)(color->Textures[color->CurrentIndex]); - glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex.OGL.TexId, 0); + int curIndex; + ovr_GetTextureSwapChainCurrentIndex(_session, color, &curIndex); + GLuint curTexId; + ovr_GetTextureSwapChainBufferGL(_session, color, curIndex, &curTexId); + glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, curTexId, 0); } void SwapFramebufferWrapper::onUnbind(oglplus::Framebuffer::Target target) { diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index e10e058ad2..2f13c45466 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -111,10 +111,10 @@ inline ovrPosef ovrPoseFromGlm(const glm::mat4 & m) { // then submit it and increment to the next texture. // The Oculus SDK manages the creation and destruction of // the textures -struct SwapFramebufferWrapper : public FramebufferWrapper { +struct SwapFramebufferWrapper : public FramebufferWrapper { SwapFramebufferWrapper(const ovrSession& session); ~SwapFramebufferWrapper(); - void Increment(); + void Commit(); void Resize(const uvec2 & size); protected: void initColor() override final; diff --git a/plugins/oculus/src/OculusProvider.cpp b/plugins/oculus/src/OculusProvider.cpp index be708db932..e723fa839a 100644 --- a/plugins/oculus/src/OculusProvider.cpp +++ b/plugins/oculus/src/OculusProvider.cpp @@ -18,6 +18,7 @@ #include "OculusDisplayPlugin.h" #include "OculusDebugDisplayPlugin.h" +#include "OculusControllerManager.h" class OculusProvider : public QObject, public DisplayProvider, InputProvider { @@ -51,8 +52,6 @@ public: } virtual InputPluginList getInputPlugins() override { - // FIXME pending full oculus input API and hardware -#if 0 static std::once_flag once; std::call_once(once, [&] { InputPluginPointer plugin(new OculusControllerManager()); @@ -60,7 +59,6 @@ public: _inputPlugins.push_back(plugin); } }); -#endif return _inputPlugins; } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 396f55b932..7b9dcc0b7d 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -35,10 +35,14 @@ void OculusLegacyDisplayPlugin::resetSensors() { ovrHmd_RecenterPose(_hmd); } -void OculusLegacyDisplayPlugin::updateHeadPose(uint32_t frameIndex) { +void OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + FrameInfo frame; + frame.predictedDisplayTime = frame.sensorSampleTime = ovr_GetTimeInSeconds(); + _trackingState = ovrHmd_GetTrackingState(_hmd, frame.predictedDisplayTime); + frame.headPose = toGlm(_trackingState.HeadPose.ThePose); + _currentRenderFrameInfo.set(frame); Lock lock(_mutex); - _trackingState = ovrHmd_GetTrackingState(_hmd, ovr_GetTimeInSeconds()); - _headPoseCache.set(toGlm(_trackingState.HeadPose.ThePose)); + _frameInfos[frameIndex] = frame; } bool OculusLegacyDisplayPlugin::isSupported() const { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 187c0681e9..2710ab1335 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -26,7 +26,7 @@ public: // Stereo specific methods virtual void resetSensors() override; - virtual void updateHeadPose(uint32_t frameIndex) override; + virtual void beginFrameRender(uint32_t frameIndex) override; virtual float getTargetFrameRate() override; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index c4d8b252f0..f968ae440e 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -121,22 +121,23 @@ void OpenVrDisplayPlugin::resetSensors() { _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); } -void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) { +void OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { - float displayFrequency = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float); - float frameDuration = 1.f / displayFrequency; - float vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); + double displayFrequency = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float); + double frameDuration = 1.f / displayFrequency; + double vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); + FrameInfo frame; #if THREADED_PRESENT // 3 frames of prediction + vsyncToPhotons = 44ms total - const float NUM_PREDICTION_FRAMES = 3.0f; - float predictedSecondsFromNow = NUM_PREDICTION_FRAMES * frameDuration + vsyncToPhotons; + const double NUM_PREDICTION_FRAMES = 3.0f; + frame.predictedDisplayTime = NUM_PREDICTION_FRAMES * frameDuration + vsyncToPhotons; #else - float predictedSecondsFromNow = frameDuration + vsyncToPhotons; + frame.predictedDisplayTime = frameDuration + vsyncToPhotons; #endif vr::TrackedDevicePose_t predictedTrackedDevicePose[vr::k_unMaxTrackedDeviceCount]; - _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, predictedSecondsFromNow, predictedTrackedDevicePose, vr::k_unMaxTrackedDeviceCount); + _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, frame.predictedDisplayTime, predictedTrackedDevicePose, vr::k_unMaxTrackedDeviceCount); // copy and process predictedTrackedDevicePoses for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { @@ -145,8 +146,11 @@ void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) { _trackedDeviceLinearVelocities[i] = transformVectorFast(_sensorResetMat, toGlm(_trackedDevicePose[i].vVelocity)); _trackedDeviceAngularVelocities[i] = transformVectorFast(_sensorResetMat, toGlm(_trackedDevicePose[i].vAngularVelocity)); } + frame.headPose = _trackedDevicePoseMat4[0]; + _currentRenderFrameInfo.set(frame); - _headPoseCache.set(_trackedDevicePoseMat4[0]); + Lock lock(_mutex); + _frameInfos[frameIndex] = frame; } void OpenVrDisplayPlugin::hmdPresent() { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 022af5b06d..0e1e7c5267 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -27,7 +27,7 @@ public: // Stereo specific methods virtual void resetSensors() override; - virtual void updateHeadPose(uint32_t frameIndex) override; + virtual void beginFrameRender(uint32_t frameIndex) override; protected: bool internalActivate() override; diff --git a/server-console/.gitignore b/server-console/.gitignore index 1624823e80..b293c73eb5 100644 --- a/server-console/.gitignore +++ b/server-console/.gitignore @@ -1,5 +1,6 @@ Server\ Console-*/ server-console-*/ +Sandbox-*/ electron-packager/ npm-debug.log logs/ diff --git a/server-console/CMakeLists.txt b/server-console/CMakeLists.txt index 9a96b07442..1c6e40c582 100644 --- a/server-console/CMakeLists.txt +++ b/server-console/CMakeLists.txt @@ -23,7 +23,7 @@ add_dependencies(${TARGET_NAME} assignment-client domain-server) # set the packaged console folder depending on platform, so we can copy it if (APPLE) - set(PACKAGED_CONSOLE_FOLDER "Server\\ Console-darwin-x64/${CONSOLE_EXEC_NAME}") + set(PACKAGED_CONSOLE_FOLDER "Sandbox-darwin-x64/${CONSOLE_EXEC_NAME}") elseif (WIN32) set(PACKAGED_CONSOLE_FOLDER "server-console-win32-x64") elseif (UNIX) diff --git a/server-console/packager.js b/server-console/packager.js index bf8ddd68d4..657098d2f4 100644 --- a/server-console/packager.js +++ b/server-console/packager.js @@ -23,12 +23,12 @@ var options = { arch: "x64", platform: platform, icon: "resources/" + iconName, - ignore: "logs|(S|s)erver(\\s|-)(C|c)onsole-\\S+|electron-packager|README.md|CMakeLists.txt|packager.js|.gitignore" + ignore: "logs|(S|s)erver(\\s|-)(C|c)onsole-\\S+|(S|s)andbox-\\S+|electron-packager|README.md|CMakeLists.txt|packager.js|.gitignore" } const EXEC_NAME = "server-console"; -const SHORT_NAME = "Server Console"; -const FULL_NAME = "High Fidelity Server Console"; +const SHORT_NAME = "Sandbox"; +const FULL_NAME = "High Fidelity Sandbox"; // setup per OS options if (osType == "Darwin") { diff --git a/server-console/src/images/console-hf-logo-2x.png b/server-console/src/images/console-hf-logo-2x.png index 4c0afd6777..420fb18fde 100644 Binary files a/server-console/src/images/console-hf-logo-2x.png and b/server-console/src/images/console-hf-logo-2x.png differ diff --git a/server-console/src/main.js b/server-console/src/main.js index 18cffdb81b..26ec2dd536 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -111,8 +111,8 @@ function shutdown() { dialog.showMessageBox({ type: 'question', buttons: ['Yes', 'No'], - title: 'Stopping Server Console', - message: 'Quitting will stop your Server Console and your Home domain will no longer be running.\nDo you wish to continue?' + title: 'Stopping High Fidelity Sandbox', + message: 'Quitting will stop your Sandbox and your Home domain will no longer be running.\nDo you wish to continue?' }, shutdownCallback); } else { shutdownCallback(0); @@ -212,7 +212,7 @@ var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) }); if (shouldQuit) { - console.warn("Another instance of the Server Console is already running - this instance will quit."); + console.warn("Another instance of the Sandbox is already running - this instance will quit."); app.quit(); return; } @@ -237,7 +237,7 @@ function binaryMissingMessage(displayName, executableName, required) { var message = "The " + displayName + " executable was not found.\n"; if (required) { - message += "It is required for the Server Console to run.\n\n"; + message += "It is required for the High Fidelity Sandbox to run.\n\n"; } else { message += "\n"; } @@ -250,7 +250,7 @@ function binaryMissingMessage(displayName, executableName, required) { message += paths.join("\n"); } else { message += "It is expected to be found beside this executable.\n"; - message += "You may need to re-install the Server Console."; + message += "You may need to re-install the High Fidelity Sandbox."; } return message; @@ -347,134 +347,6 @@ function goHomeClicked() { } } -function stackManagerBasePath() { - var dataPath = 'High Fidelity/Stack Manager/resources'; - - if (process.platform == "win32") { - return path.resolve(osHomeDir(), 'AppData/Local', dataPath); - } else if (process.platform == "darwin") { - return path.resolve(osHomeDir(), 'Library/Application Support', dataPath); - } else { - return "" - } -} - -function isStackManagerContentPresent() { - var modelsPath = path.resolve(stackManagerBasePath(), 'models.json.gz'); - - try { - var stats = fs.lstatSync(modelsPath); - - if (stats.isFile()) { - console.log("Stack Manager entities file discovered at " + modelsPath) - // we found a content file - return true; - } - } catch (e) { - console.log("Stack Manager entities file not found at " + modelsPath); - } -} - -function promptToMigrateContent() { - dialog.showMessageBox({ - type: 'question', - buttons: ['Yes', 'No'], - title: 'Migrate Content', - message: 'Are you sure?\n\nThis will stop your home server and replace everything in your home with your content from Stack Manager.' - }, function(index) { - if (index == 0) { - if (homeServer.state != ProcessGroupStates.STOPPED) { - var stopThenMigrateCallback = function(processGroup) { - if (isShuttingDown) { - homeServer.removeListener('state-update', stopThenMigrateCallback); - } else if (processGroup.state == ProcessGroupStates.STOPPED) { - performContentMigration(); - - homeServer.removeListener('state-update', stopThenMigrateCallback); - } - }; - - homeServer.on('state-update', stopThenMigrateCallback); - homeServer.stop(); - - } else { - performContentMigration(); - } - } - }); -} - -function performContentMigration() { - // check if there is a models file to migrate - var modelsPath = path.resolve(stackManagerBasePath(), 'models.json.gz'); - - try { - var stats = fs.lstatSync(modelsPath); - } catch (e) { - // no entities file - dialog.showMessageBox({ - type: 'info', - buttons: ['OK'], - title: 'Models File Not Found', - message: 'There is no models file at ' + modelsPath + '\n\nStack Manager content migration can not proceed.' - }, null); - - return; - } - - function showMigrationCompletionDialog(copyError) { - if (!copyError) { - // show message for successful migration - dialog.showMessageBox({ - type: 'info', - buttons: ['OK'], - title: 'Migration Complete', - message: 'Your Stack Manager content has been migrated.\n\nYour home server will now be restarted.' - }, null); - } else { - // show error message for copy fail - dialog.showMessageBox({ - type: 'info', - buttons: ['OK'], - title: 'Migration Failed', - message: 'There was an error copying your Stack Manager content: ' + copyError + '\n\nPlease try again.' - }, null); - } - } - - // we have a models file, try and copy it - var newModelsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'entities/models.json.gz') - console.log("Copying Stack Manager entity file from " + modelsPath + " to " + newModelsPath); - - try { - fs.copySync(modelsPath, newModelsPath); - - // check if there are any assets to copy - var oldAssetsPath = path.resolve(stackManagerBasePath(), 'assets'); - - var assets = fs.readdirSync(oldAssetsPath); - - if (assets.length > 0) { - // assume this means the directory is not empty - // and that we should copy it - var newAssetsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'assets'); - - console.log("Copying Stack Manager assets from " + oldAssetsPath + " to " + newAssetsPath); - - // attempt to copy the assets folder - fs.copySync(oldAssetsPath, newAssetsPath, { - preserveTimestamps: true - }); - } - - showMigrationCompletionDialog(null); - } catch (error) { - showMigrationCompletionDialog(error); - } - - homeServer.start(); -} - var logWindow = null; var labels = { @@ -530,12 +402,6 @@ var labels = { shell.openExternal('http://localhost:40100/settings/?action=share') } }, - migrateContent: { - label: 'Migrate Stack Manager Content', - click: function() { - promptToMigrateContent(); - } - }, shuttingDown: { label: "Shutting down...", enabled: false @@ -569,13 +435,6 @@ function buildMenuArray(serverState) { menuArray.push(labels.share); menuArray.push(separator); menuArray.push(labels.quit); - - var foundStackManagerContent = isStackManagerContentPresent(); - if (foundStackManagerContent) { - // add a separator and the stack manager content migration option - menuArray.splice(menuArray.length - 1, 0, labels.migrateContent, separator); - } - } @@ -724,7 +583,7 @@ function maybeShowSplash() { var window = new BrowserWindow({ icon: appIcon, width: 1600 * zoomFactor, - height: 737 * zoomFactor, + height: 650 * zoomFactor, center: true, frame: true, useContentSize: true, @@ -770,7 +629,7 @@ app.on('ready', function() { // Create tray icon tray = new Tray(trayIcons[ProcessGroupStates.STOPPED]); - tray.setToolTip('High Fidelity Server Console'); + tray.setToolTip('High Fidelity Sandbox'); tray.on('click', function() { tray.popUpContextMenu(tray.menu); diff --git a/server-console/src/splash.css b/server-console/src/splash.css index bd3a06cbe3..5a911e3872 100644 --- a/server-console/src/splash.css +++ b/server-console/src/splash.css @@ -122,11 +122,6 @@ h2 { #main-content { height: 350px; - border-bottom: 2px solid #F5F6F6; -} - -#existing-resources-area { - padding-top: 20px; } .footer { @@ -143,4 +138,4 @@ h2 { input[type="checkbox"] { -webkit-transform: scale(1.4); display: inline-block; -} \ No newline at end of file +} diff --git a/server-console/src/splash.html b/server-console/src/splash.html index 30868ebb6f..b9947bcbaa 100644 --- a/server-console/src/splash.html +++ b/server-console/src/splash.html @@ -23,7 +23,7 @@ High Fidelity is now installed and your Home domain is ready for you to explore.

-You can make your home yours by uploading your own models and scripts, and adding items from the Market. +You can make your home yours by uploading your own models and scripts.

@@ -67,12 +67,6 @@ You can make your home yours by uploading your own models and scripts, and addin -
-

-

Your existing Stack Manager content is safe.

- Server Console comes with demo content but does not overwrite your data. See our guide to importing content previously managed with Stack Manager. -

-