diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 6dc4899d29..f51455e9d4 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -46,6 +46,8 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) set(SHADER_TARGET ${SHADER_TARGET}_frag.h) endif() + set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}") + # Target dependant Custom rule on the SHADER_FILE if (APPLE) set(GLPROFILE MAC_GL) @@ -87,10 +89,14 @@ macro(AUTOSCRIBE_SHADER_LIB) file(GLOB_RECURSE SHADER_INCLUDE_FILES src/*.slh) file(GLOB_RECURSE SHADER_SOURCE_FILES src/*.slv src/*.slf) + #make the shader folder + set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders/${TARGET_NAME}") + file(MAKE_DIRECTORY ${SHADERS_DIR}) + #message(${SHADER_INCLUDE_FILES}) foreach(SHADER_FILE ${SHADER_SOURCE_FILES}) AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES}) - file(TO_CMAKE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE) + file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE) list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE}) endforeach() #message(${AUTOSCRIBE_SHADER_SRC}) @@ -105,4 +111,7 @@ macro(AUTOSCRIBE_SHADER_LIB) list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${SHADER_SOURCE_FILES}) list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${AUTOSCRIBE_SHADER_SRC}) + # Link library shaders, if they exist + include_directories("${SHADERS_DIR}") + endmacro() diff --git a/cmake/macros/LinkHifiLibraries.cmake b/cmake/macros/LinkHifiLibraries.cmake index ed37256b10..3767dc7131 100644 --- a/cmake/macros/LinkHifiLibraries.cmake +++ b/cmake/macros/LinkHifiLibraries.cmake @@ -19,20 +19,14 @@ macro(LINK_HIFI_LIBRARIES) endif () include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src") + include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}/shaders") add_dependencies(${TARGET_NAME} ${HIFI_LIBRARY}) # link the actual library - it is static so don't bubble it up target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY}) - - # ask the library what its include dependencies are and link them - get_target_property(LINKED_TARGET_DEPENDENCY_INCLUDES ${HIFI_LIBRARY} DEPENDENCY_INCLUDES) - - if(LINKED_TARGET_DEPENDENCY_INCLUDES) - list(APPEND ${TARGET_NAME}_DEPENDENCY_INCLUDES ${LINKED_TARGET_DEPENDENCY_INCLUDES}) - endif() endforeach() setup_memory_debugger() -endmacro(LINK_HIFI_LIBRARIES) \ No newline at end of file +endmacro(LINK_HIFI_LIBRARIES) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 7c229075fd..1101a08acb 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -1237,7 +1237,7 @@
- +
diff --git a/examples/tests/rapidProceduralChange/rapidProceduralChangeTest.js b/examples/tests/rapidProceduralChange/rapidProceduralChangeTest.js new file mode 100644 index 0000000000..6897a1b70f --- /dev/null +++ b/examples/tests/rapidProceduralChange/rapidProceduralChangeTest.js @@ -0,0 +1,91 @@ +// rapidProceduralChangeTest.js +// examples/tests/rapidProceduralChange +// +// Created by Eric Levin on 3/9/2016. +// Copyright 2016 High Fidelity, Inc. +// +// This test creates primitives with fragment shaders and rapidly updates its uniforms, as well as a skybox. +// For the test to pass: +// - The primitives (cube and sphere) should update at rate of update loop, cycling through red values. +// - The skymap should do the same, although its periodicity may be different. +// +// Under the hood, the primitives are driven by a uniform, while the skymap is driven by a timer. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var orientation = Camera.getOrientation(); +orientation = Quat.safeEulerAngles(orientation); +orientation.x = 0; +orientation = Quat.fromVec3Degrees(orientation); + +var centerUp = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation))); +centerUp.y += 0.5; +var centerDown = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation))); +centerDown.y -= 0.5; + +var ENTITY_SHADER_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/shaders/uniformTest.fs"; +var SKYBOX_SHADER_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/shaders/timerTest.fs"; + +var entityData = { + ProceduralEntity: { + shaderUrl: ENTITY_SHADER_URL, + uniforms: { red: 0.0 } + } +}; +var skyboxData = { + ProceduralEntity: { + shaderUrl: SKYBOX_SHADER_URL, + uniforms: { red: 0.0 } + } +}; + +var testBox = Entities.addEntity({ + type: "Box", + dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + position: centerUp, + userData: JSON.stringify(entityData) +}); +var testSphere = Entities.addEntity({ + type: "Sphere", + dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + position: centerDown, + userData: JSON.stringify(entityData) +}); +var testZone = Entities.addEntity({ + type: "Zone", + dimensions: { x: 50, y: 50, z: 50 }, + position: MyAvatar.position, + userData: JSON.stringify(skyboxData), + backgroundMode: "skybox", + skybox: { url: "http://kyoub.googlecode.com/svn/trunk/KYouB/textures/skybox_test.png" } +}); + + +var currentTime = 0; + +function update(deltaTime) { + var red = (Math.sin(currentTime) + 1) / 2; + entityData.ProceduralEntity.uniforms.red = red; + skyboxData.ProceduralEntity.uniforms.red = red; + entityEdit = { userData: JSON.stringify(entityData) }; + skyboxEdit = { userData: JSON.stringify(skyboxData) }; + + Entities.editEntity(testBox, entityEdit); + Entities.editEntity(testSphere, entityEdit); + Entities.editEntity(testZone, skyboxEdit); + + currentTime += deltaTime; +} + +Script.update.connect(update); + +Script.scriptEnding.connect(cleanup); + +function cleanup() { + Entities.deleteEntity(testBox); + Entities.deleteEntity(testSphere); + Entities.deleteEntity(testZone); +} + diff --git a/examples/tests/rapidProceduralChange/timerTest.fs b/examples/tests/rapidProceduralChange/timerTest.fs new file mode 100644 index 0000000000..e0fcf9d1d6 --- /dev/null +++ b/examples/tests/rapidProceduralChange/timerTest.fs @@ -0,0 +1,21 @@ +// +// timerTest.fs +// examples/tests/rapidProceduralChange +// +// Created by Eric Levin on 3/9/16. +// Copyright 2016 High Fidelity, Inc. +// +// This fragment shader is designed to test the rapid changing of a uniform on the timer. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +uniform float red; + +vec3 getSkyboxColor() { + float blue = red; + blue = (cos(iGlobalTime) + 1) / 2; + return vec3(1.0, 0.0, blue); +} + diff --git a/examples/tests/rapidProceduralChange/uniformTest.fs b/examples/tests/rapidProceduralChange/uniformTest.fs new file mode 100644 index 0000000000..fe0ad6a1ea --- /dev/null +++ b/examples/tests/rapidProceduralChange/uniformTest.fs @@ -0,0 +1,27 @@ +// +// uniformTest.fs +// examples/tests/rapidProceduralChange +// +// Created by Eric Levin on 3/9/16. +// Copyright 2016 High Fidelity, Inc. +// +// This fragment shader is designed to test the rapid changing of a uniform. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +uniform float red; + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + fragColor = vec4(red, 0.0, 1.0, 1.0); +} + +vec4 getProceduralColor() { + vec4 result; + vec2 position = _position.xz; + position += 0.5; + mainImage(result, position * iWorldScale.xz); + return result; +} + diff --git a/examples/tests/skybox/px.fs b/examples/tests/skybox/px.fs new file mode 100644 index 0000000000..8b6bdc0e5c --- /dev/null +++ b/examples/tests/skybox/px.fs @@ -0,0 +1,18 @@ +// +// px.fs +// examples/tests/skybox +// +// Created by Zach Pomerantz on 3/10/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 + + +vec3 getSkyboxColor() { + float red = (cos(iGlobalTime) + 1) / 2; + vec3 color = vec3(red, 1.0, 1.0); + + return color; +} + diff --git a/examples/tests/skybox/px_rgba.fs b/examples/tests/skybox/px_rgba.fs new file mode 100644 index 0000000000..59bac7d963 --- /dev/null +++ b/examples/tests/skybox/px_rgba.fs @@ -0,0 +1,20 @@ +// +// px_rgba.fs +// examples/tests/skybox +// +// Created by Zach Pomerantz on 3/10/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 + + +vec3 getSkyboxColor() { + float red = (cos(iGlobalTime) + 1) / 2; + vec3 color = vec3(red, 1.0, 1.0); + + color *= skybox.color.rgb; + + return color; +} + diff --git a/examples/tests/skybox/px_tex.fs b/examples/tests/skybox/px_tex.fs new file mode 100644 index 0000000000..9d9da7ba10 --- /dev/null +++ b/examples/tests/skybox/px_tex.fs @@ -0,0 +1,22 @@ +// +// px_rgba.fs +// examples/tests/skybox +// +// Created by Zach Pomerantz on 3/10/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 + + +vec3 getSkyboxColor() { + float red = (cos(iGlobalTime) + 1) / 2; + vec3 color = vec3(red, 1.0, 1.0); + + vec3 coord = normalize(_normal); + vec3 texel = texture(cubeMap, coord).rgb; + color *= texel; + + return color; +} + diff --git a/examples/tests/skybox/px_tex_rgba.fs b/examples/tests/skybox/px_tex_rgba.fs new file mode 100644 index 0000000000..d44a32e33e --- /dev/null +++ b/examples/tests/skybox/px_tex_rgba.fs @@ -0,0 +1,24 @@ +// +// px_rgba.fs +// examples/tests/skybox +// +// Created by Zach Pomerantz on 3/10/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 + + +vec3 getSkyboxColor() { + float red = (cos(iGlobalTime) + 1) / 2; + vec3 color = vec3(red, 1.0, 1.0); + + vec3 coord = normalize(_normal); + vec3 texel = texture(cubeMap, coord).rgb; + color *= texel; + + color *= skybox.color.rgb; + + return color; +} + diff --git a/examples/tests/skybox/skyboxTest.js b/examples/tests/skybox/skyboxTest.js new file mode 100644 index 0000000000..76d80ab153 --- /dev/null +++ b/examples/tests/skybox/skyboxTest.js @@ -0,0 +1,83 @@ +// skyboxTest.js +// examples/tests/skybox +// +// Created by Zach Pomerantz on 3/10/2016. +// Copyright 2016 High Fidelity, Inc. +// +// This test cycles through different variations on the skybox with a mouseclick. +// For the test to pass, you should observe the following cycle: +// - Procedural skybox (no texture, no color) +// - Procedural skybox (no texture, with color) +// - Procedural skybox (with texture, no color) +// - Procedural skybox (with texture, with color) +// - Color skybox (no texture) +// - Color skybox (with texture) +// - Texture skybox (no color) +// +// As you run the test, descriptions of the expected rendered skybox will appear as overlays. +// +// NOTE: This does not test uniforms/textures applied to a procedural shader through userData. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var PX_URL = Script.resolvePath('px.fs'); +var PX_RGBA_URL = Script.resolvePath('px_rgba.fs'); +var PX_TEX_URL = Script.resolvePath('px_tex.fs'); +var PX_TEX_RGBA_URL = Script.resolvePath('px_tex_rgba.fs'); + +var TEX_URL = 'https://hifi-public.s3.amazonaws.com/alan/Playa/Skies/Test-Sky_out.png'; +var NO_TEX = ''; + +var COLOR = { red: 255, green: 0, blue: 255 }; +var NO_COLOR = { red: 0, green: 0, blue: 0 }; + +var data = { ProceduralEntity: { shaderUrl: PX_URL } }; + +var zone = Entities.addEntity({ + type: 'Zone', + dimensions: { x: 50, y: 50, z: 50 }, + position: MyAvatar.position, + backgroundMode: 'skybox' +}); +var text = Overlays.addOverlay('text', { + text: 'Click this box to advance tests; note that red value cycling means white->light blue', + x: Window.innerWidth / 2 - 250, y: Window.innerHeight / 2 - 25, + width: 500, height: 50 +}); + +print('Zone:', zone); +print('Text:', text); + +var edits = [ + ['Red value should cycle', getEdit(PX_URL, NO_TEX, NO_COLOR)], + ['Red value should cycle, no green', getEdit(PX_RGBA_URL, NO_TEX, COLOR)], + ['Red value should cycle, each face tinted differently', getEdit(PX_TEX_URL, TEX_URL, NO_COLOR)], + ['Red value should cycle, each face tinted differently, no green', getEdit(PX_TEX_RGBA_URL, TEX_URL, COLOR)], + ['No green', getEdit(null, NO_TEX, COLOR)], + ['Each face colored differently, no green', getEdit(null, TEX_URL, COLOR)], + ['Each face colored differently', getEdit(null, TEX_URL, NO_COLOR)], +]; + +Controller.mousePressEvent.connect(function(e) { if (Overlays.getOverlayAtPoint(e) === text) next(); }); + +Script.scriptEnding.connect(function() { + Overlays.deleteOverlay(text); + Entities.deleteEntity(zone); +}); + +var i = 0; +function next() { + var edit = edits[i]; + Overlays.editOverlay(text, { text: edit[0] }); + Entities.editEntity(zone, edit[1]); + i++; + i %= edits.length; +} + +function getEdit(px, url, color) { + return { userData: px ? getUserData(px) : '', backgroundMode: 'skybox', skybox: { url: url, color: color } } +} +function getUserData(px) { return JSON.stringify({ ProceduralEntity: { shaderUrl: px } }); } + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2e4cb1db0f..9bd4b00246 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3758,19 +3758,19 @@ namespace render { switch (backgroundMode) { case model::SunSkyStage::SKY_BOX: { auto skybox = skyStage->getSkybox(); - if (skybox && skybox->getCubemap() && skybox->getCubemap()->isDefined()) { + if (skybox) { PerformanceTimer perfTimer("skybox"); skybox->render(batch, *(args->_viewFrustum)); break; } - // If no skybox texture is available, render the SKY_DOME while it loads } - // fall through to next case + + // Fall through: if no skybox is available, render the SKY_DOME case model::SunSkyStage::SKY_DOME: { if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { PerformanceTimer perfTimer("stars"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "Application::payloadRender() ... stars..."); + "Application::payloadRender() ... My god, it's full of stars..."); // should be the first rendering pass - w/o depth buffer / lighting static const float alpha = 1.0f; @@ -3778,6 +3778,7 @@ namespace render { } } break; + case model::SunSkyStage::NO_BACKGROUND: default: // this line intentionally left blank @@ -4881,7 +4882,10 @@ void Application::updateDisplayMode() { } } emit activeDisplayPluginChanged(); - resetSensors(); + + // reset the avatar, to set head and hand palms back to a resonable default pose. + getMyAvatar()->reset(false); + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ab1b7238df..42401010b0 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -464,6 +464,8 @@ Menu::Menu() { avatar, SLOT(setUseAnimPreAndPostRotations(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true, avatar, SLOT(setEnableInverseKinematics(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSensorToWorldMatrix, 0, false, + avatar, SLOT(setEnableDebugDrawSensorToWorldMatrix(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::KeyboardMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu()), diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7130dd59e8..bc718bf78e 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -144,6 +144,7 @@ namespace MenuOption { const QString RenderResolutionHalf = "1/2"; const QString RenderResolutionThird = "1/3"; const QString RenderResolutionQuarter = "1/4"; + const QString RenderSensorToWorldMatrix = "Show SensorToWorld Matrix"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString ResetSensors = "Reset Sensors"; const QString RunningScripts = "Running Scripts..."; diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 3305cc4f3b..a0283f0efd 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -22,11 +22,11 @@ #include #include -#include -#include +#include +#include -#include -#include +#include +#include //static const float TILT = 0.23f; static const float TILT = 0.0f; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8be8b601a4..9302c3b47d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -438,6 +438,10 @@ void MyAvatar::updateSensorToWorldMatrix() { lateUpdatePalms(); + if (_enableDebugDrawSensorToWorldMatrix) { + DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), extractTranslation(_sensorToWorldMatrix), glm::vec4(1)); + } + _sensorToWorldMatrixCache.set(_sensorToWorldMatrix); } @@ -695,12 +699,21 @@ void MyAvatar::setEnableDebugDrawPosition(bool isEnabled) { void MyAvatar::setEnableDebugDrawHandControllers(bool isEnabled) { _enableDebugDrawHandControllers = isEnabled; + if (!isEnabled) { DebugDraw::getInstance().removeMarker("leftHandController"); DebugDraw::getInstance().removeMarker("rightHandController"); } } +void MyAvatar::setEnableDebugDrawSensorToWorldMatrix(bool isEnabled) { + _enableDebugDrawSensorToWorldMatrix = isEnabled; + + if (!isEnabled) { + DebugDraw::getInstance().removeMarker("sensorToWorldMatrix"); + } +} + void MyAvatar::setEnableMeshVisible(bool isEnabled) { render::ScenePointer scene = qApp->getMain3DScene(); _skeletonModel.setVisibleInScene(isEnabled, scene); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2301e37a5b..f834a627b2 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -278,6 +278,7 @@ public slots: void setEnableDebugDrawAnimPose(bool isEnabled); void setEnableDebugDrawPosition(bool isEnabled); void setEnableDebugDrawHandControllers(bool isEnabled); + void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled); bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); } void setEnableMeshVisible(bool isEnabled); void setUseAnimPreAndPostRotations(bool isEnabled); @@ -442,6 +443,7 @@ private: bool _enableDebugDrawDefaultPose { false }; bool _enableDebugDrawAnimPose { false }; bool _enableDebugDrawHandControllers { false }; + bool _enableDebugDrawSensorToWorldMatrix { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index c711560b53..ac47c55eca 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -164,7 +164,7 @@ void Stats::updateStats(bool force) { MyAvatar* myAvatar = avatarManager->getMyAvatar(); glm::vec3 avatarPos = myAvatar->getPosition(); STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); - STAT_UPDATE_FLOAT(speed, glm::length(myAvatar->getVelocity()), 0.1f); + STAT_UPDATE_FLOAT(speed, glm::length(myAvatar->getVelocity()), 0.01f); STAT_UPDATE_FLOAT(yaw, myAvatar->getBodyYaw(), 0.1f); if (_expanded || force) { SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 0cba089256..94533137ad 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -13,6 +13,7 @@ #include #include +#include "Application.h" const float DEFAULT_LINE_WIDTH = 1.0f; @@ -38,15 +39,17 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _drawInFront(base3DOverlay->_drawInFront) { } - void Base3DOverlay::setProperties(const QVariantMap& properties) { Overlay::setProperties(properties); + bool needRenderItemUpdate = false; + auto drawInFront = properties["drawInFront"]; if (drawInFront.isValid()) { bool value = drawInFront.toBool(); setDrawInFront(value); + needRenderItemUpdate = true; } auto position = properties["position"]; @@ -60,16 +63,19 @@ void Base3DOverlay::setProperties(const QVariantMap& properties) { } if (position.isValid()) { setPosition(vec3FromVariant(position)); + needRenderItemUpdate = true; } if (properties["lineWidth"].isValid()) { setLineWidth(properties["lineWidth"].toFloat()); + needRenderItemUpdate = true; } auto rotation = properties["rotation"]; if (rotation.isValid()) { setRotation(quatFromVariant(rotation)); + needRenderItemUpdate = true; } if (properties["isSolid"].isValid()) { @@ -100,6 +106,17 @@ void Base3DOverlay::setProperties(const QVariantMap& properties) { if (properties["ignoreRayIntersection"].isValid()) { setIgnoreRayIntersection(properties["ignoreRayIntersection"].toBool()); } + + // Communicate changes to the renderItem if needed + if (needRenderItemUpdate) { + auto itemID = getRenderItemID(); + if (render::Item::isValidID(itemID)) { + render::ScenePointer scene = qApp->getMain3DScene(); + render::PendingChanges pendingChanges; + pendingChanges.updateItem(itemID); + scene->enqueuePendingChanges(pendingChanges); + } + } } QVariant Base3DOverlay::getProperty(const QString& property) { diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index f003b0316e..1179fbaa50 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -233,19 +233,6 @@ bool Overlays::editOverlay(unsigned int id, const QVariant& properties) { if (thisOverlay) { thisOverlay->setProperties(properties.toMap()); - if (thisOverlay->is3D()) { - auto itemID = thisOverlay->getRenderItemID(); - if (render::Item::isValidID(itemID)) { - render::ScenePointer scene = qApp->getMain3DScene(); - const render::Item& item = scene->getItem(itemID); - if (item.getKey() != render::payloadGetKey(thisOverlay)) { - render::PendingChanges pendingChanges; - pendingChanges.updateItem(itemID); - scene->enqueuePendingChanges(pendingChanges); - } - } - } - return true; } return false; diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index 27d03b5e09..eb909de993 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -51,27 +51,24 @@ void QmlOverlay::buildQmlElement(const QUrl& url) { } QmlOverlay::~QmlOverlay() { - if (_qmlElement) { - _qmlElement->deleteLater(); - _qmlElement = nullptr; - } + _qmlElement.reset(); } void QmlOverlay::setProperties(const QVariantMap& properties) { Overlay2D::setProperties(properties); auto bounds = _bounds; - std::weak_ptr weakQmlElement; - DependencyManager::get()->executeOnUiThread([=] { + std::weak_ptr weakQmlElement = _qmlElement; + DependencyManager::get()->executeOnUiThread([weakQmlElement, bounds, properties] { // check to see if qmlElement still exists auto qmlElement = weakQmlElement.lock(); if (qmlElement) { - _qmlElement->setX(bounds.left()); - _qmlElement->setY(bounds.top()); - _qmlElement->setWidth(bounds.width()); - _qmlElement->setHeight(bounds.height()); + qmlElement->setX(bounds.left()); + qmlElement->setY(bounds.top()); + qmlElement->setWidth(bounds.width()); + qmlElement->setHeight(bounds.height()); + QMetaObject::invokeMethod(qmlElement.get(), "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties)); } }); - QMetaObject::invokeMethod(_qmlElement.get(), "updatePropertiesFromScript", Q_ARG(QVariant, properties)); } void QmlOverlay::render(RenderArgs* args) { diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index f6e9d8dbbc..860ed6ba37 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -414,13 +415,39 @@ void CompositorHelper::updateTooltips() { } static const float FADE_DURATION = 500.0f; +static const float FADE_IN_ALPHA = 1.0f; +static const float FADE_OUT_ALPHA = 0.0f; + +void CompositorHelper::startFadeFailsafe(float endValue) { + _fadeStarted = usecTimestampNow(); + _fadeFailsafeEndValue = endValue; + + const int SLIGHT_DELAY = 10; + QTimer::singleShot(FADE_DURATION + SLIGHT_DELAY, [this]{ + checkFadeFailsafe(); + }); +} + +void CompositorHelper::checkFadeFailsafe() { + auto elapsedInFade = usecTimestampNow() - _fadeStarted; + if (elapsedInFade > FADE_DURATION) { + setAlpha(_fadeFailsafeEndValue); + } +} + void CompositorHelper::fadeIn() { _fadeInAlpha = true; _alphaPropertyAnimation->setDuration(FADE_DURATION); _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(1.0f); + _alphaPropertyAnimation->setEndValue(FADE_IN_ALPHA); _alphaPropertyAnimation->start(); + + // Sometimes, this "QPropertyAnimation" fails to complete the animation, and we end up with a partially faded + // state. So we will also have this fail-safe, where we record the timestamp of the fadeRequest, and the target + // value of the fade, and if after that time we still haven't faded all the way, we will kick it to the final + // fade value + startFadeFailsafe(FADE_IN_ALPHA); } void CompositorHelper::fadeOut() { @@ -428,8 +455,9 @@ void CompositorHelper::fadeOut() { _alphaPropertyAnimation->setDuration(FADE_DURATION); _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(0.0f); + _alphaPropertyAnimation->setEndValue(FADE_OUT_ALPHA); _alphaPropertyAnimation->start(); + startFadeFailsafe(FADE_OUT_ALPHA); } void CompositorHelper::toggle() { diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 55364be85b..ebc0171752 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -145,6 +145,11 @@ private: float _fadeInAlpha { true }; float _oculusUIRadius { 1.0f }; + quint64 _fadeStarted { 0 }; + float _fadeFailsafeEndValue { 1.0f }; + void checkFadeFailsafe(); + void startFadeFailsafe(float endValue); + int _reticleQuad; int _previousBorderWidth { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 7af97678a7..1c175b778c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -296,6 +296,9 @@ void OpenGLDisplayPlugin::customizeContext() { if (uniform.Name() == "mvp") { _mvpUniform = uniform.Index(); } + if (uniform.Name() == "alpha") { + _alphaUniform = uniform.Index(); + } uniforms.Next(); } @@ -406,33 +409,53 @@ void OpenGLDisplayPlugin::updateFramerate() { void OpenGLDisplayPlugin::compositeOverlay() { using namespace oglplus; - // Overlay draw - if (isStereo()) { - Uniform(*_program, _mvpUniform).Set(mat4()); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - drawUnitQuad(); - }); - } else { + + auto compositorHelper = DependencyManager::get(); + + // check the alpha + auto overlayAlpha = compositorHelper->getAlpha(); + if (overlayAlpha > 0.0f) { + // set the alpha + Uniform(*_program, _alphaUniform).Set(overlayAlpha); + // Overlay draw - Uniform(*_program, _mvpUniform).Set(mat4()); - drawUnitQuad(); + if (isStereo()) { + Uniform(*_program, _mvpUniform).Set(mat4()); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + drawUnitQuad(); + }); + } else { + // Overlay draw + Uniform(*_program, _mvpUniform).Set(mat4()); + drawUnitQuad(); + } } + Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositePointer() { using namespace oglplus; auto compositorHelper = DependencyManager::get(); - Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); - if (isStereo()) { - for_each_eye([&](Eye eye) { - eyeViewport(eye); + + // check the alpha + auto overlayAlpha = compositorHelper->getAlpha(); + if (overlayAlpha > 0.0f) { + // set the alpha + Uniform(*_program, _alphaUniform).Set(overlayAlpha); + + Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); + if (isStereo()) { + for_each_eye([&](Eye eye) { + eyeViewport(eye); + drawUnitQuad(); + }); + } else { drawUnitQuad(); - }); - } else { - drawUnitQuad(); + } } Uniform(*_program, _mvpUniform).Set(mat4()); + Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositeLayers() { diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 576d6d8eff..ace866a946 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -86,6 +86,7 @@ protected: ProgramPtr _program; int32_t _mvpUniform { -1 }; + int32_t _alphaUniform { -1 }; ShapeWrapperPtr _plane; mutable Mutex _mutex; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 92bfef0dc8..e3d92d5761 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -62,30 +62,50 @@ void HmdDisplayPlugin::uncustomizeContext() { void HmdDisplayPlugin::compositeOverlay() { using namespace oglplus; - _sphereSection->Use(); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - auto modelView = glm::inverse(_currentRenderEyePoses[eye]); // *glm::translate(mat4(), vec3(0, 0, -1)); - auto mvp = _eyeProjections[eye] * modelView; - Uniform(*_program, _mvpUniform).Set(mvp); - _sphereSection->Draw(); - }); + auto compositorHelper = DependencyManager::get(); + + // check the alpha + auto overlayAlpha = compositorHelper->getAlpha(); + if (overlayAlpha > 0.0f) { + // set the alpha + Uniform(*_program, _alphaUniform).Set(overlayAlpha); + + _sphereSection->Use(); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto modelView = glm::inverse(_currentRenderEyePoses[eye]); // *glm::translate(mat4(), vec3(0, 0, -1)); + auto mvp = _eyeProjections[eye] * modelView; + Uniform(*_program, _mvpUniform).Set(mvp); + _sphereSection->Draw(); + }); + } + Uniform(*_program, _alphaUniform).Set(1.0); } void HmdDisplayPlugin::compositePointer() { - //Mouse Pointer + using namespace oglplus; + auto compositorHelper = DependencyManager::get(); - _plane->Use(); - // Reconstruct the headpose from the eye poses - auto headPosition = (vec3(_currentRenderEyePoses[Left][3]) + vec3(_currentRenderEyePoses[Right][3])) / 2.0f; - for_each_eye([&](Eye eye) { - using namespace oglplus; - eyeViewport(eye); - auto reticleTransform = compositorHelper->getReticleTransform(_currentRenderEyePoses[eye], headPosition); - auto mvp = _eyeProjections[eye] * reticleTransform; - Uniform(*_program, _mvpUniform).Set(mvp); - _plane->Draw(); - }); + + // check the alpha + auto overlayAlpha = compositorHelper->getAlpha(); + if (overlayAlpha > 0.0f) { + // set the alpha + Uniform(*_program, _alphaUniform).Set(overlayAlpha); + + // Mouse pointer + _plane->Use(); + // Reconstruct the headpose from the eye poses + auto headPosition = (vec3(_currentRenderEyePoses[Left][3]) + vec3(_currentRenderEyePoses[Right][3])) / 2.0f; + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto reticleTransform = compositorHelper->getReticleTransform(_currentRenderEyePoses[eye], headPosition); + auto mvp = _eyeProjections[eye] * reticleTransform; + Uniform(*_program, _mvpUniform).Set(mvp); + _plane->Draw(); + }); + } + Uniform(*_program, _alphaUniform).Set(1.0); } void HmdDisplayPlugin::internalPresent() { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 44c3146b41..39111dd3eb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -140,8 +140,8 @@ void EntityTreeRenderer::update() { // If we haven't already updated and previously attempted to load a texture, // check if the texture loaded and apply it if (!updated && ( - (_pendingSkyboxTexture && _skyboxTexture && _skyboxTexture->isLoaded()) || - (_pendingAmbientTexture && _ambientTexture && _ambientTexture->isLoaded()))) { + (_pendingSkyboxTexture && (!_skyboxTexture || _skyboxTexture->isLoaded())) || + (_pendingAmbientTexture && (!_ambientTexture && _ambientTexture->isLoaded())))) { applyZonePropertiesToScene(_bestZone); } @@ -158,6 +158,8 @@ void EntityTreeRenderer::update() { } bool EntityTreeRenderer::checkEnterLeaveEntities() { + bool didUpdate = false; + if (_tree && !_shuttingDown) { glm::vec3 avatarPosition = _viewState->getAvatarPosition(); @@ -172,6 +174,7 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { std::static_pointer_cast(_tree)->findEntities(avatarPosition, radius, foundEntities); // Whenever you're in an intersection between zones, we will always choose the smallest zone. + auto oldBestZone = _bestZone; _bestZone = nullptr; // NOTE: Is this what we want? _bestZoneVolume = std::numeric_limits::max(); @@ -204,7 +207,10 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { } } - applyZonePropertiesToScene(_bestZone); + if (_bestZone != oldBestZone) { + applyZonePropertiesToScene(_bestZone); + didUpdate = true; + } }); // Note: at this point we don't need to worry about the tree being locked, because we only deal with @@ -228,11 +234,9 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { } _currentEntitiesInside = entitiesContainingAvatar; _lastAvatarPosition = avatarPosition; - - return true; } } - return false; + return didUpdate; } void EntityTreeRenderer::leaveAllEntities() { @@ -322,15 +326,19 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getKeyLightProperties().getAmbientURL(), CUBE_TEXTURE); - if (_ambientTexture && _ambientTexture->isLoaded() && _ambientTexture->getGPUTexture()) { + _pendingAmbientTexture = true; + + if (_ambientTexture && _ambientTexture->isLoaded()) { _pendingAmbientTexture = false; - if (_ambientTexture->getGPUTexture()->getIrradiance()) { - sceneKeyLight->setAmbientSphere(_ambientTexture->getGPUTexture()->getIrradiance()); - sceneKeyLight->setAmbientMap(_ambientTexture->getGPUTexture()); + + auto texture = _ambientTexture->getGPUTexture(); + if (texture) { + sceneKeyLight->setAmbientSphere(texture->getIrradiance()); + sceneKeyLight->setAmbientMap(texture); isAmbientTextureSet = true; + } else { + qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << zone->getKeyLightProperties().getAmbientURL(); } - } else { - _pendingAmbientTexture = true; } } @@ -341,32 +349,30 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetUserData()) { userData = zone->getUserData(); - auto procedural = std::make_shared(userData); - if (procedural->_enabled) { - skybox->setProcedural(procedural); - } else { - skybox->setProcedural(ProceduralPointer()); - } + skybox->parse(userData); } if (zone->getSkyboxProperties().getURL().isEmpty()) { - skybox->setCubemap(gpu::TexturePointer()); + skybox->setCubemap(nullptr); _pendingSkyboxTexture = false; _skyboxTexture.clear(); } else { // Update the Texture of the Skybox with the one pointed by this zone _skyboxTexture = textureCache->getTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE); + _pendingSkyboxTexture = true; + + if (_skyboxTexture && _skyboxTexture->isLoaded()) { + _pendingSkyboxTexture = false; - if (_skyboxTexture && _skyboxTexture->isLoaded() && _skyboxTexture->getGPUTexture()) { auto texture = _skyboxTexture->getGPUTexture(); skybox->setCubemap(texture); - _pendingSkyboxTexture = false; - if (!isAmbientTextureSet && texture->getIrradiance()) { + if (!isAmbientTextureSet) { sceneKeyLight->setAmbientSphere(texture->getIrradiance()); sceneKeyLight->setAmbientMap(texture); isAmbientTextureSet = true; } } else { - _pendingSkyboxTexture = true; + skybox->setCubemap(nullptr); + qCDebug(entitiesrenderer) << "Failed to load skybox:" << zone->getSkyboxProperties().getURL(); } } diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 671043813e..e392450c08 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -19,8 +19,8 @@ #include #include -#include "../render-utils/simple_vert.h" -#include "../render-utils/simple_frag.h" +#include +#include EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderableBoxEntityItem(entityID) }; @@ -31,7 +31,9 @@ EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, void RenderableBoxEntityItem::setUserData(const QString& value) { if (value != getUserData()) { BoxEntityItem::setUserData(value); - _procedural.reset(); + if (_procedural) { + _procedural->parse(value); + } } } @@ -40,7 +42,6 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { Q_ASSERT(getType() == EntityTypes::Box); Q_ASSERT(args->_batch); - if (!_procedural) { _procedural.reset(new Procedural(this->getUserData())); _procedural->_vertexSource = simple_vert; @@ -62,7 +63,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { } batch.setModelTransform(transToCenter); // we want to include the scale as well - if (_procedural && _procedural->ready()) { + if (_procedural->ready()) { _procedural->prepare(batch, getPosition(), getDimensions()); auto color = _procedural->getColor(cubeColor); batch._glColor4f(color.r, color.g, color.b, color.a); diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index b5867fb1ee..c3437b0e4a 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -19,8 +19,8 @@ #include #include -#include "../render-utils/simple_vert.h" -#include "../render-utils/simple_frag.h" +#include +#include // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. @@ -36,7 +36,9 @@ EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entity void RenderableSphereEntityItem::setUserData(const QString& value) { if (value != getUserData()) { SphereEntityItem::setUserData(value); - _procedural.reset(); + if (_procedural) { + _procedural->parse(value); + } } } diff --git a/libraries/gl/src/gl/OglplusHelpers.cpp b/libraries/gl/src/gl/OglplusHelpers.cpp index a579f061b7..1dd7068448 100644 --- a/libraries/gl/src/gl/OglplusHelpers.cpp +++ b/libraries/gl/src/gl/OglplusHelpers.cpp @@ -33,6 +33,7 @@ static const char * SIMPLE_TEXTURED_FS = R"FS(#version 410 core #pragma line __LINE__ uniform sampler2D sampler; +uniform float alpha = 1.0; in vec2 vTexCoord; out vec4 FragColor; @@ -40,6 +41,7 @@ out vec4 FragColor; void main() { FragColor = texture(sampler, vTexCoord); + FragColor.a *= alpha; } )FS"; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index a2cd3284ef..58a82d5f11 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -146,7 +146,7 @@ public: NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type, const QByteArray& content) { TextureExtra extra = { type, content }; - return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast(); + return ResourceCache::getResource(url, QUrl(), content.isEmpty(), &extra).staticCast(); } /// Returns a texture version of an image file diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 72a09a8b3f..6fb0cc3177 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -101,8 +101,6 @@ private: /// A simple object wrapper for an OpenGL texture. class Texture { public: - friend class TextureCache; - gpu::TexturePointer getGPUTexture() const { return _textureSource->getGPUTexture(); } gpu::TextureSourcePointer _textureSource; }; diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 476ac2fa08..cb3fb43630 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -15,71 +15,68 @@ #include #include -#include "Skybox_vert.h" -#include "Skybox_frag.h" +#include "skybox_vert.h" +#include "skybox_frag.h" using namespace model; Skybox::Skybox() { - Data data; - _dataBuffer = gpu::BufferView(std::make_shared(sizeof(Data), (const gpu::Byte*) &data)); - -/* // PLease create a default engineer skybox - _cubemap.reset( gpu::Texture::createCube(gpu::Element::COLOR_RGBA_32, 1)); - unsigned char texels[] = { - 255, 0, 0, 255, - 0, 255, 255, 255, - 0, 0, 255, 255, - 255, 255, 0, 255, - 0, 255, 0, 255, - 255, 0, 255, 255, - }; - _cubemap->assignStoredMip(0, gpu::Element::COLOR_RGBA_32, sizeof(texels), texels);*/ + Schema schema; + _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema)); } void Skybox::setColor(const Color& color) { - _dataBuffer.edit()._color = color; + _schemaBuffer.edit().color = color; } void Skybox::setCubemap(const gpu::TexturePointer& cubemap) { _cubemap = cubemap; } - -void Skybox::updateDataBuffer() const { +void Skybox::updateSchemaBuffer() const { auto blend = 0.0f; if (getCubemap() && getCubemap()->isDefined()) { - blend = 1.0f; + blend = 0.5f; + // If pitch black neutralize the color if (glm::all(glm::equal(getColor(), glm::vec3(0.0f)))) { - blend = 2.0f; + blend = 1.0f; } } - if (blend != _dataBuffer.get()._blend) { - _dataBuffer.edit()._blend = blend; + if (blend != _schemaBuffer.get().blend) { + _schemaBuffer.edit().blend = blend; } } +void Skybox::prepare(gpu::Batch& batch, int textureSlot, int bufferSlot) const { + if (bufferSlot > -1) { + batch.setUniformBuffer(bufferSlot, _schemaBuffer); + } - -void Skybox::render(gpu::Batch& batch, const ViewFrustum& frustum) const { - updateDataBuffer(); - Skybox::render(batch, frustum, (*this)); + if (textureSlot > -1) { + gpu::TexturePointer skymap = getCubemap(); + // FIXME: skymap->isDefined may not be threadsafe + if (skymap && skymap->isDefined()) { + batch.setResourceTexture(textureSlot, skymap); + } + } } +void Skybox::render(gpu::Batch& batch, const ViewFrustum& frustum) const { + updateSchemaBuffer(); + Skybox::render(batch, frustum, (*this)); +} void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) { // Create the static shared elements used to render the skybox static gpu::BufferPointer theConstants; static gpu::PipelinePointer thePipeline; - const int SKYBOX_SKYMAP_SLOT = 0; - const int SKYBOX_CONSTANTS_SLOT = 0; static std::once_flag once; std::call_once(once, [&] { { - auto skyVS = gpu::Shader::createVertex(std::string(Skybox_vert)); - auto skyFS = gpu::Shader::createPixel(std::string(Skybox_frag)); + auto skyVS = gpu::Shader::createVertex(std::string(skybox_vert)); + auto skyFS = gpu::Shader::createPixel(std::string(skybox_frag)); auto skyShader = gpu::Shader::createProgram(skyVS, skyFS); gpu::Shader::BindingSet bindings; @@ -98,10 +95,6 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky // Render - gpu::TexturePointer skymap = skybox.getCubemap(); - // FIXME: skymap->isDefined may not be threadsafe - assert(skymap && skymap->isDefined()); - glm::mat4 projMat; viewFrustum.evalProjectionMatrix(projMat); @@ -112,11 +105,8 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky batch.setModelTransform(Transform()); // only for Mac batch.setPipeline(thePipeline); - batch.setUniformBuffer(SKYBOX_CONSTANTS_SLOT, skybox._dataBuffer); - batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, skymap); - + skybox.prepare(batch); batch.draw(gpu::TRIANGLE_STRIP, 4); batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, nullptr); } - diff --git a/libraries/model/src/model/Skybox.h b/libraries/model/src/model/Skybox.h index 14ba9fa005..e9e7ee8b26 100755 --- a/libraries/model/src/model/Skybox.h +++ b/libraries/model/src/model/Skybox.h @@ -30,30 +30,33 @@ public: virtual ~Skybox() {}; void setColor(const Color& color); - const Color getColor() const { return _dataBuffer.get()._color; } + const Color getColor() const { return _schemaBuffer.get().color; } void setCubemap(const gpu::TexturePointer& cubemap); const gpu::TexturePointer& getCubemap() const { return _cubemap; } + void prepare(gpu::Batch& batch, int textureSlot = SKYBOX_SKYMAP_SLOT, int bufferSlot = SKYBOX_CONSTANTS_SLOT) const; virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const; - static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); protected: + static const int SKYBOX_SKYMAP_SLOT { 0 }; + static const int SKYBOX_CONSTANTS_SLOT { 0 }; + gpu::TexturePointer _cubemap; - class Data { + class Schema { public: - glm::vec3 _color{ 1.0f, 1.0f, 1.0f }; - float _blend = 1.0f; + glm::vec3 color { 1.0f, 1.0f, 1.0f }; + float blend { 0.0f }; }; - mutable gpu::BufferView _dataBuffer; + mutable gpu::BufferView _schemaBuffer; - void updateDataBuffer() const; + void updateSchemaBuffer() const; }; -typedef std::shared_ptr< Skybox > SkyboxPointer; +typedef std::shared_ptr SkyboxPointer; }; diff --git a/libraries/model/src/model/Skybox.slf b/libraries/model/src/model/Skybox.slf deleted file mode 100755 index f8a568bcf9..0000000000 --- a/libraries/model/src/model/Skybox.slf +++ /dev/null @@ -1,59 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// skybox.frag -// fragment shader -// -// Created by Sam Gateau on 5/5/2015. -// 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 -// - -uniform samplerCube cubeMap; - -struct Skybox { - vec4 _color; -}; - -uniform skyboxBuffer { - Skybox _skybox; -}; - -in vec3 _normal; -out vec4 _fragColor; - -//PROCEDURAL_COMMON_BLOCK - -#line 1001 -//PROCEDURAL_BLOCK - -#line 2033 -void main(void) { - -#ifdef PROCEDURAL - - vec3 color = getSkyboxColor(); - _fragColor = vec4(color, 0.0); - -#else - - vec3 coord = normalize(_normal); - - // Skybox color or blend with skymap - vec3 color = _skybox._color.rgb; - if (_skybox._color.a > 0.0) { - vec3 texel = texture(cubeMap, coord).rgb; - if (_skybox._color.a < 2.0) { - color *= texel; - } else { - color = texel; - } - } - - _fragColor = vec4(color, 0.0); - -#endif - -} diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.slf b/libraries/model/src/model/skybox.slf old mode 100644 new mode 100755 similarity index 71% rename from libraries/procedural/src/procedural/ProceduralSkybox.slf rename to libraries/model/src/model/skybox.slf index 7ad6f6b5a1..7b25e36af7 --- a/libraries/procedural/src/procedural/ProceduralSkybox.slf +++ b/libraries/model/src/model/skybox.slf @@ -14,11 +14,11 @@ uniform samplerCube cubeMap; struct Skybox { - vec4 _color; + vec4 color; }; uniform skyboxBuffer { - Skybox _skybox; + Skybox skybox; }; in vec3 _normal; @@ -39,11 +39,20 @@ void main(void) { color = pow(color, vec3(2.2)); _fragColor = vec4(color, 0.0); -#else + // FIXME: scribe does not yet scrub out else statements + return; +#else vec3 coord = normalize(_normal); - vec3 texel = texture(cubeMap, coord).rgb; - vec3 color = texel * _skybox._color.rgb; + vec3 color = skybox.color.rgb; + + // blend is only set if there is a cubemap + if (skybox.color.a > 0.0) { + color = texture(cubeMap, coord).rgb; + if (skybox.color.a < 1.0) { + color *= skybox.color.rgb; + } + } _fragColor = vec4(color, 0.0); #endif diff --git a/libraries/model/src/model/Skybox.slv b/libraries/model/src/model/skybox.slv similarity index 100% rename from libraries/model/src/model/Skybox.slv rename to libraries/model/src/model/skybox.slv diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index b1e4bd53ed..922d466d42 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -62,6 +62,9 @@ QJsonValue Procedural::getProceduralData(const QString& proceduralJson) { return doc.object()[PROCEDURAL_USER_DATA_KEY]; } +Procedural::Procedural() { + _state = std::make_shared(); +} Procedural::Procedural(const QString& userDataJson) { parse(userDataJson); @@ -69,74 +72,111 @@ Procedural::Procedural(const QString& userDataJson) { } void Procedural::parse(const QString& userDataJson) { - _enabled = false; auto proceduralData = getProceduralData(userDataJson); - if (proceduralData.isObject()) { - parse(proceduralData.toObject()); + // Instead of parsing, prep for a parse on the rendering thread + // This will be called by Procedural::ready + std::lock_guard lock(_proceduralDataMutex); + _proceduralData = proceduralData.toObject(); + _proceduralDataDirty = true; +} + +bool Procedural::parseVersion(const QJsonValue& version) { + if (version.isDouble()) { + _version = (uint8_t)(floor(version.toDouble())); + } else { + // All unversioned shaders default to V1 + _version = 1; } + return (_version == 1 || _version == 2); +} + +bool Procedural::parseUrl(const QUrl& shaderUrl) { + if (!shaderUrl.isValid()) { + qWarning() << "Invalid shader URL: " << shaderUrl; + return false; + } + + if (_shaderUrl == shaderUrl) { + return true; + } + + _shaderUrl = shaderUrl; + _shaderDirty = true; + + if (_shaderUrl.isLocalFile()) { + _shaderPath = _shaderUrl.toLocalFile(); + qDebug() << "Shader path: " << _shaderPath; + if (!QFile(_shaderPath).exists()) { + return false;; + } + } else { + qDebug() << "Shader url: " << _shaderUrl; + _networkShader = ShaderCache::instance().getShader(_shaderUrl); + } + + return true; +} + +bool Procedural::parseUniforms(const QJsonObject& uniforms) { + if (_parsedUniforms != uniforms) { + _parsedUniforms = uniforms; + _uniformsDirty = true; + } + + return true; +} + +bool Procedural::parseTextures(const QJsonArray& channels) { + if (_parsedChannels != channels) { + _parsedChannels = channels; + + auto textureCache = DependencyManager::get(); + size_t channelCount = std::min(MAX_PROCEDURAL_TEXTURE_CHANNELS, (size_t)_parsedChannels.size()); + for (size_t i = 0; i < channelCount; ++i) { + QString url = _parsedChannels.at((int)i).toString(); + _channels[i] = textureCache->getTexture(QUrl(url)); + } + + _channelsDirty = true; + } + + return true; } void Procedural::parse(const QJsonObject& proceduralData) { - // grab the version number - { - auto version = proceduralData[VERSION_KEY]; - if (version.isDouble()) { - _version = (uint8_t)(floor(version.toDouble())); - } + _enabled = false; + + if (proceduralData.isEmpty()) { + return; } - // Get the path to the shader - { - QString shaderUrl = proceduralData[URL_KEY].toString(); - shaderUrl = ResourceManager::normalizeURL(shaderUrl); - _shaderUrl = QUrl(shaderUrl); - if (!_shaderUrl.isValid()) { - qWarning() << "Invalid shader URL: " << shaderUrl; - return; - } + auto version = proceduralData[VERSION_KEY]; + auto shaderUrl = proceduralData[URL_KEY].toString(); + shaderUrl = ResourceManager::normalizeURL(shaderUrl); + auto uniforms = proceduralData[UNIFORMS_KEY].toObject(); + auto channels = proceduralData[CHANNELS_KEY].toArray(); - if (_shaderUrl.isLocalFile()) { - _shaderPath = _shaderUrl.toLocalFile(); - qDebug() << "Shader path: " << _shaderPath; - if (!QFile(_shaderPath).exists()) { - return; - } - } else { - qDebug() << "Shader url: " << _shaderUrl; - _networkShader = ShaderCache::instance().getShader(_shaderUrl); - } + if (parseVersion(version) && + parseUrl(shaderUrl) && + parseUniforms(uniforms) && + parseTextures(channels)) { + _enabled = true; } - - // Grab any custom uniforms - { - auto uniforms = proceduralData[UNIFORMS_KEY]; - if (uniforms.isObject()) { - _parsedUniforms = uniforms.toObject(); - } - } - - // Grab any textures - { - auto channels = proceduralData[CHANNELS_KEY]; - if (channels.isArray()) { - auto textureCache = DependencyManager::get(); - _parsedChannels = channels.toArray(); - size_t channelCount = std::min(MAX_PROCEDURAL_TEXTURE_CHANNELS, (size_t)_parsedChannels.size()); - for (size_t i = 0; i < channelCount; ++i) { - QString url = _parsedChannels.at((int)i).toString(); - _channels[i] = textureCache->getTexture(QUrl(url)); - } - } - } - _enabled = true; } bool Procedural::ready() { + // Load any changes to the procedural + if (_proceduralDataDirty) { + std::lock_guard lock(_proceduralDataMutex); + parse(_proceduralData); + _proceduralDataDirty = false; + } + if (!_enabled) { return false; } - // Do we have a network or local shader + // Do we have a network or local shader, and if so, is it loaded? if (_shaderPath.isEmpty() && (!_networkShader || !_networkShader->isLoaded())) { return false; } @@ -160,15 +200,14 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm QFile file(_shaderPath); file.open(QIODevice::ReadOnly); _shaderSource = QTextStream(&file).readAll(); - _pipelineDirty = true; + _shaderDirty = true; _shaderModified = lastModified; } } else if (_networkShader && _networkShader->isLoaded()) { _shaderSource = _networkShader->_source; } - if (!_pipeline || _pipelineDirty) { - _pipelineDirty = true; + if (!_pipeline || _shaderDirty) { if (!_vertexShader) { _vertexShader = gpu::Shader::createVertex(_vertexSource); } @@ -192,7 +231,10 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm if (replaceIndex != std::string::npos) { fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); } - //qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); + + // Leave this here for debugging + // qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); + _fragmentShader = gpu::Shader::createPixel(fragmentShaderSource); _shader = gpu::Shader::createProgram(_vertexShader, _fragmentShader); @@ -214,11 +256,15 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm batch.setPipeline(_pipeline); - if (_pipelineDirty) { - _pipelineDirty = false; + if (_shaderDirty || _uniformsDirty) { setupUniforms(); } + if (_shaderDirty || _uniformsDirty || _channelsDirty) { + setupChannels(_shaderDirty || _uniformsDirty); + } + + _shaderDirty = _uniformsDirty = _channelsDirty = false; for (auto lambda : _uniforms) { lambda(batch); @@ -359,8 +405,14 @@ void Procedural::setupUniforms() { batch._glUniform(_standardUniformSlots[POSITION], _entityPosition); }); } +} +void Procedural::setupChannels(bool shouldCreate) { if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[CHANNEL_RESOLUTION]) { + if (!shouldCreate) { + // Instead of modifying the last element, just remove and recreate it. + _uniforms.pop_back(); + } _uniforms.push_back([=](gpu::Batch& batch) { vec3 channelSizes[MAX_PROCEDURAL_TEXTURE_CHANNELS]; for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 1b02fbd435..0e55f52f5a 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -28,27 +28,25 @@ const size_t MAX_PROCEDURAL_TEXTURE_CHANNELS{ 4 }; // FIXME better encapsulation // FIXME better mechanism for extending to things rendered using shaders other than simple.slv struct Procedural { +public: static QJsonValue getProceduralData(const QString& proceduralJson); + Procedural(); Procedural(const QString& userDataJson); void parse(const QString& userDataJson); - void parse(const QJsonObject&); + bool ready(); void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size); - void setupUniforms(); + const gpu::ShaderPointer& getShader() const { return _shader; } + glm::vec4 getColor(const glm::vec4& entityColor); - bool _enabled{ false }; - uint8_t _version{ 1 }; + uint8_t _version { 1 }; std::string _vertexSource; std::string _fragmentSource; - QString _shaderSource; - QString _shaderPath; - QUrl _shaderUrl; - quint64 _shaderModified{ 0 }; - bool _pipelineDirty{ true }; + gpu::StatePointer _state; enum StandardUniforms { DATE, @@ -60,23 +58,50 @@ struct Procedural { NUM_STANDARD_UNIFORMS }; - int32_t _standardUniformSlots[NUM_STANDARD_UNIFORMS]; +protected: + // Procedural metadata + bool _enabled { false }; + uint64_t _start { 0 }; + int32_t _frameCount { 0 }; - uint64_t _start{ 0 }; - int32_t _frameCount{ 0 }; + // Rendering object descriptions, from userData + QJsonObject _proceduralData; + std::mutex _proceduralDataMutex; + QString _shaderSource; + QString _shaderPath; + QUrl _shaderUrl; + quint64 _shaderModified { 0 }; NetworkShaderPointer _networkShader; QJsonObject _parsedUniforms; QJsonArray _parsedChannels; + bool _proceduralDataDirty { true }; + bool _shaderDirty { true }; + bool _uniformsDirty { true }; + bool _channelsDirty { true }; + // Rendering objects UniformLambdas _uniforms; + int32_t _standardUniformSlots[NUM_STANDARD_UNIFORMS]; NetworkTexturePointer _channels[MAX_PROCEDURAL_TEXTURE_CHANNELS]; gpu::PipelinePointer _pipeline; gpu::ShaderPointer _vertexShader; gpu::ShaderPointer _fragmentShader; gpu::ShaderPointer _shader; - gpu::StatePointer _state; + + // Entity metadata glm::vec3 _entityDimensions; glm::vec3 _entityPosition; + +private: + // This should only be called from the render thread, as it shares data with Procedural::prepare + void parse(const QJsonObject&); + bool parseVersion(const QJsonValue& version); + bool parseUrl(const QUrl& url); + bool parseUniforms(const QJsonObject& uniforms); + bool parseTextures(const QJsonArray& channels); + + void setupUniforms(); + void setupChannels(bool shouldCreate); }; #endif diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 167d49cbaf..4ff38eac74 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -15,55 +15,39 @@ #include #include -#include "ProceduralSkybox_vert.h" -#include "ProceduralSkybox_frag.h" +#include +#include ProceduralSkybox::ProceduralSkybox() : model::Skybox() { -} - -ProceduralSkybox::ProceduralSkybox(const ProceduralSkybox& skybox) : - model::Skybox(skybox), - _procedural(skybox._procedural) { - -} - -void ProceduralSkybox::setProcedural(const ProceduralPointer& procedural) { - _procedural = procedural; - if (_procedural) { - _procedural->_vertexSource = ProceduralSkybox_vert; - _procedural->_fragmentSource = ProceduralSkybox_frag; - // Adjust the pipeline state for background using the stencil test - _procedural->_state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - } + _procedural._vertexSource = skybox_vert; + _procedural._fragmentSource = skybox_frag; + // Adjust the pipeline state for background using the stencil test + _procedural._state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); } void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& frustum) const { - ProceduralSkybox::render(batch, frustum, (*this)); + if (_procedural.ready()) { + ProceduralSkybox::render(batch, frustum, (*this)); + } else { + Skybox::render(batch, frustum); + } } void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const ProceduralSkybox& skybox) { - if (!(skybox._procedural)) { - skybox.updateDataBuffer(); - Skybox::render(batch, viewFrustum, skybox); - } + glm::mat4 projMat; + viewFrustum.evalProjectionMatrix(projMat); - if (skybox._procedural && skybox._procedural->_enabled && skybox._procedural->ready()) { - gpu::TexturePointer skymap = skybox.getCubemap(); - // FIXME: skymap->isDefined may not be threadsafe - assert(skymap && skymap->isDefined()); + Transform viewTransform; + viewFrustum.evalViewTransform(viewTransform); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewTransform); + batch.setModelTransform(Transform()); // only for Mac - glm::mat4 projMat; - viewFrustum.evalProjectionMatrix(projMat); - - Transform viewTransform; - viewFrustum.evalViewTransform(viewTransform); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewTransform); - batch.setModelTransform(Transform()); // only for Mac - batch.setResourceTexture(0, skybox.getCubemap()); - - skybox._procedural->prepare(batch, glm::vec3(0), glm::vec3(1)); - batch.draw(gpu::TRIANGLE_STRIP, 4); - } + auto& procedural = skybox._procedural; + procedural.prepare(batch, glm::vec3(0), glm::vec3(1)); + auto textureSlot = procedural.getShader()->getTextures().findLocation("cubeMap"); + auto bufferSlot = procedural.getShader()->getBuffers().findLocation("skyboxBuffer"); + skybox.prepare(batch, textureSlot, bufferSlot); + batch.draw(gpu::TRIANGLE_STRIP, 4); } diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.h b/libraries/procedural/src/procedural/ProceduralSkybox.h index 057a1ccc74..e817ce271d 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.h +++ b/libraries/procedural/src/procedural/ProceduralSkybox.h @@ -17,22 +17,18 @@ #include "Procedural.h" -typedef std::shared_ptr ProceduralPointer; - class ProceduralSkybox: public model::Skybox { public: ProceduralSkybox(); - ProceduralSkybox(const ProceduralSkybox& skybox); - ProceduralSkybox& operator= (const ProceduralSkybox& skybox); virtual ~ProceduralSkybox() {}; - void setProcedural(const ProceduralPointer& procedural); + void parse(const QString& userData) { _procedural.parse(userData); } virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const; static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox); protected: - ProceduralPointer _procedural; + mutable Procedural _procedural; }; typedef std::shared_ptr< ProceduralSkybox > ProceduralSkyboxPointer; diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.slv b/libraries/procedural/src/procedural/ProceduralSkybox.slv deleted file mode 100644 index 810afb1033..0000000000 --- a/libraries/procedural/src/procedural/ProceduralSkybox.slv +++ /dev/null @@ -1,39 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// skybox.vert -// vertex shader -// -// Created by Sam Gateau on 5/5/2015. -// 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 gpu/Transform.slh@> - -<$declareStandardTransform()$> - -out vec3 _normal; - -void main(void) { - const float depth = 0.0; - const vec4 UNIT_QUAD[4] = vec4[4]( - vec4(-1.0, -1.0, depth, 1.0), - vec4(1.0, -1.0, depth, 1.0), - vec4(-1.0, 1.0, depth, 1.0), - vec4(1.0, 1.0, depth, 1.0) - ); - vec4 inPosition = UNIT_QUAD[gl_VertexID]; - - // standard transform - TransformCamera cam = getTransformCamera(); - vec3 clipDir = vec3(inPosition.xy, 0.0); - vec3 eyeDir; - <$transformClipToEyeDir(cam, clipDir, eyeDir)$> - <$transformEyeToWorldDir(cam, eyeDir, _normal)$> - - // Position is supposed to come in clip space - gl_Position = vec4(inPosition.xy, 0.0, 1.0); -} \ No newline at end of file diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 0cdd736fee..79f4b2a269 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -48,6 +48,10 @@ ItemID Scene::allocateID() { return _IDAllocator.fetch_add(1); } +bool Scene::isAllocatedID(const ItemID& id) { + return Item::isValidID(id) && (id < _numAllocatedItems.load()); +} + /// Enqueue change batch to the scene void Scene::enqueuePendingChanges(const PendingChanges& pendingChanges) { _changeQueueMutex.lock(); @@ -79,10 +83,22 @@ void Scene::processPendingChangesQueue() { } // Now we know for sure that we have enough items in the array to // capture anything coming from the pendingChanges + + // resets and potential NEW items resetItems(consolidatedPendingChanges._resetItems, consolidatedPendingChanges._resetPayloads); + + // Update the numItemsAtomic counter AFTER the reset changes went through + _numAllocatedItems.exchange(maxID); + + // updates updateItems(consolidatedPendingChanges._updatedItems, consolidatedPendingChanges._updateFunctors); + + // removes removeItems(consolidatedPendingChanges._removedItems); + // Update the numItemsAtomic counter AFTER the pending changes went through + _numAllocatedItems.exchange(maxID); + // ready to go back to rendering activities _itemsMutex.unlock(); } diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index 40c0baca60..b7cfc367b8 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -60,18 +60,24 @@ public: // This call is thread safe, can be called from anywhere to allocate a new ID ItemID allocateID(); + // Check that the ID is valid and allocated for this scene, this a threadsafe call + bool isAllocatedID(const ItemID& id); + + // THis is the total number of allocated items, this a threadsafe call + size_t getNumItems() const { return _numAllocatedItems.load(); } + // Enqueue change batch to the scene void enqueuePendingChanges(const PendingChanges& pendingChanges); // Process the penging changes equeued void processPendingChangesQueue(); + // This next call are NOT threadsafe, you have to call them from the correct thread to avoid any potential issues + // Access a particular item form its ID // WARNING, There is No check on the validity of the ID, so this could return a bad Item const Item& getItem(const ItemID& id) const { return _items[id]; } - size_t getNumItems() const { return _items.size(); } - // Access the spatialized items const ItemSpatialTree& getSpatialTree() const { return _masterSpatialTree; } @@ -81,6 +87,7 @@ public: protected: // Thread safe elements that can be accessed from anywhere std::atomic _IDAllocator{ 1 }; // first valid itemID will be One + std::atomic _numAllocatedItems{ 1 }; // num of allocated items, matching the _items.size() std::mutex _changeQueueMutex; PendingChangesQueue _changeQueue; diff --git a/libraries/render/src/render/SpatialTree.cpp b/libraries/render/src/render/SpatialTree.cpp index b09b6f4778..1bb3538521 100644 --- a/libraries/render/src/render/SpatialTree.cpp +++ b/libraries/render/src/render/SpatialTree.cpp @@ -264,12 +264,33 @@ Octree::Index Octree::accessCellBrick(Index cellID, const CellBrickAccessor& acc return brickID; } +Octree::Location ItemSpatialTree::evalLocation(const AABox& bound, Coord3f& minCoordf, Coord3f& maxCoordf) const { + minCoordf = evalCoordf(bound.getMinimumPoint()); + maxCoordf = evalCoordf(bound.getMaximumPoint()); + + // If the bound crosses any of the octree volume limit, then return root cell + if ( (minCoordf.x < 0.0f) + || (minCoordf.y < 0.0f) + || (minCoordf.z < 0.0f) + || (maxCoordf.x >= _size) + || (maxCoordf.y >= _size) + || (maxCoordf.z >= _size)) { + return Location(); + } + + Coord3 minCoord(minCoordf); + Coord3 maxCoord(maxCoordf); + return Location::evalFromRange(minCoord, maxCoord); +} + Octree::Locations ItemSpatialTree::evalLocations(const ItemBounds& bounds) const { Locations locations; + Coord3f minCoordf, maxCoordf; + locations.reserve(bounds.size()); for (auto& bound : bounds) { if (!bound.bound.isNull()) { - locations.emplace_back(evalLocation(bound.bound)); + locations.emplace_back(evalLocation(bound.bound, minCoordf, maxCoordf)); } else { locations.emplace_back(Location()); } @@ -344,11 +365,8 @@ bool ItemSpatialTree::removeItem(Index cellIdx, const ItemKey& key, const ItemID ItemSpatialTree::Index ItemSpatialTree::resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey) { auto newCell = INVALID_CELL; if (!newKey.isViewSpace()) { - auto minCoordf = evalCoordf(bound.getMinimumPoint()); - auto maxCoordf = evalCoordf(bound.getMaximumPoint()); - Coord3 minCoord(minCoordf); - Coord3 maxCoord(maxCoordf); - auto location = Location::evalFromRange(minCoord, maxCoord); + Coord3f minCoordf, maxCoordf; + auto location = evalLocation(bound, minCoordf, maxCoordf); // Compare range size vs cell location size and tag itemKey accordingly // If Item bound fits in sub cell then tag as small @@ -403,7 +421,21 @@ ItemSpatialTree::Index ItemSpatialTree::resetItem(Index oldCell, const ItemKey& int Octree::select(CellSelection& selection, const FrustumSelector& selector) const { Index cellID = ROOT_CELL; - return selectTraverse(cellID, selection, selector); + auto cell = getConcreteCell(cellID); + int numSelectedsIn = (int)selection.size(); + + // Always include the root cell partially containing potentially outer objects + selectCellBrick(cellID, selection, false); + + // then traverse deeper + for (int i = 0; i < NUM_OCTANTS; i++) { + Index subCellID = cell.child((Link)i); + if (subCellID != INVALID_CELL) { + selectTraverse(subCellID, selection, selector); + } + } + + return (int)selection.size() - numSelectedsIn; } diff --git a/libraries/render/src/render/SpatialTree.h b/libraries/render/src/render/SpatialTree.h index a5dbb29544..a89b9847e6 100644 --- a/libraries/render/src/render/SpatialTree.h +++ b/libraries/render/src/render/SpatialTree.h @@ -117,7 +117,6 @@ namespace render { return depth; } - class Location { void assertValid() { assert((pos.x >= 0) && (pos.y >= 0) && (pos.z >= 0)); @@ -157,6 +156,7 @@ namespace render { // Eval the location best fitting the specified range static Location evalFromRange(const Coord3& minCoord, const Coord3& maxCoord, Depth rangeDepth = MAX_DEPTH); + // Eval the intersection test against a frustum enum Intersection { Outside = 0, @@ -367,7 +367,7 @@ namespace render { // An octree of Items organizing them efficiently for culling // The octree only cares about the bound & the key of an item to store it a the right cell location class ItemSpatialTree : public Octree { - float _size { 32768.0f }; + float _size{ 32768.0f }; float _invSize { 1.0f / _size }; glm::vec3 _origin { -16384.0f }; @@ -398,10 +398,26 @@ namespace render { return getOrigin() + glm::vec3(coord) * cellWidth; } + + // Clamp a 3D relative position to make sure it is in the valid range space of the octree + glm::vec3 clampRelPosToTreeRange(const glm::vec3& pos) const { + const float EPSILON = 0.0001f; + return glm::vec3( + std::min(std::max(pos.x, 0.0f), _size - EPSILON), + std::min(std::max(pos.y, 0.0f), _size - EPSILON), + std::min(std::max(pos.z, 0.0f), _size - EPSILON)); + } + + // Eval an integer cell coordinate (at the specified deepth) from a given 3d position + // If the 3D position is out of the octree volume, then the position is clamped + // so the integer coordinate is meaningfull Coord3 evalCoord(const glm::vec3& pos, Depth depth = Octree::METRIC_COORD_DEPTH) const { - auto npos = (pos - getOrigin()); + auto npos = clampRelPosToTreeRange((pos - getOrigin())); return Coord3(npos * getInvCellWidth(depth)); // Truncate fractional part } + + // Eval a real cell coordinate (at the specified deepth) from a given 3d position + // Position is NOT clamped to the boundaries of the octree so beware of conversion to a Coord3! Coord3f evalCoordf(const glm::vec3& pos, Depth depth = Octree::METRIC_COORD_DEPTH) const { auto npos = (pos - getOrigin()); return Coord3f(npos * getInvCellWidth(depth)); @@ -412,9 +428,10 @@ namespace render { float cellWidth = getCellWidth(loc.depth); return AABox(evalPos(loc.pos, cellWidth), cellWidth); } - Location evalLocation(const AABox& bound) const { - return Location::evalFromRange(evalCoord(bound.getMinimumPoint()), evalCoord(bound.getMaximumPoint())); - } + + // Eval the cell location for a given arbitrary Bound, + // if the Bound crosses any of the Octree planes then the root cell is returned + Location evalLocation(const AABox& bound, Coord3f& minCoordf, Coord3f& maxCoordf) const; Locations evalLocations(const ItemBounds& bounds) const; // Managing itemsInserting items in cells diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index e5708572f6..b86a7f9ebc 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -68,6 +68,21 @@ void OpenVrDisplayPlugin::activate() { _compositor = vr::VRCompositor(); Q_ASSERT(_compositor); HmdDisplayPlugin::activate(); + + // set up default sensor space such that the UI overlay will align with the front of the room. + auto chaperone = vr::VRChaperone(); + if (chaperone) { + float const UI_RADIUS = 1.0f; + float const UI_HEIGHT = 1.6f; + float const UI_Z_OFFSET = 0.5; + + float xSize, zSize; + chaperone->GetPlayAreaSize(&xSize, &zSize); + glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET); + _sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos)); + } else { + qDebug() << "OpenVR: error could not get chaperone pointer"; + } } void OpenVrDisplayPlugin::deactivate() { @@ -115,7 +130,7 @@ glm::mat4 OpenVrDisplayPlugin::getHeadPose(uint32_t frameIndex) const { #endif vr::TrackedDevicePose_t predictedTrackedDevicePose[vr::k_unMaxTrackedDeviceCount]; - _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseSeated, predictedSecondsFromNow, predictedTrackedDevicePose, vr::k_unMaxTrackedDeviceCount); + _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, predictedSecondsFromNow, predictedTrackedDevicePose, vr::k_unMaxTrackedDeviceCount); // copy and process predictedTrackedDevicePoses for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index ddc7ad6540..2bc957b2d1 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -23,83 +23,77 @@ #include #include -#include "../model/Skybox_vert.h" -#include "../model/Skybox_frag.h" +#include +#include +#include +#include -#include "simple_vert.h" -#include "simple_frag.h" -#include "simple_textured_frag.h" -#include "simple_textured_emisive_frag.h" +#include +#include -#include "deferred_light_vert.h" -#include "deferred_light_limited_vert.h" +#include +#include +#include -#include "directional_light_frag.h" +#include +#include -#include "directional_ambient_light_frag.h" +#include +#include -#include "directional_skybox_light_frag.h" +#include +#include +#include +#include +#include +#include +#include +#include -#include "point_light_frag.h" -#include "spot_light_frag.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "standardTransformPNTC_vert.h" -#include "standardDrawTexture_frag.h" +#include +#include +#include +#include -#include "model_vert.h" -#include "model_shadow_vert.h" -#include "model_normal_map_vert.h" -#include "model_lightmap_vert.h" -#include "model_lightmap_normal_map_vert.h" -#include "skin_model_vert.h" -#include "skin_model_shadow_vert.h" -#include "skin_model_normal_map_vert.h" +#include +#include -#include "model_frag.h" -#include "model_shadow_frag.h" -#include "model_normal_map_frag.h" -#include "model_normal_specular_map_frag.h" -#include "model_specular_map_frag.h" -#include "model_lightmap_frag.h" -#include "model_lightmap_normal_map_frag.h" -#include "model_lightmap_normal_specular_map_frag.h" -#include "model_lightmap_specular_map_frag.h" -#include "model_translucent_frag.h" +#include +#include -#include "untextured_particle_frag.h" -#include "untextured_particle_vert.h" -#include "textured_particle_frag.h" -#include "textured_particle_vert.h" +#include +#include +#include +#include +#include -#include "hit_effect_vert.h" -#include "hit_effect_frag.h" +#include +#include +#include +#include +#include +#include -#include "overlay3D_vert.h" -#include "overlay3D_frag.h" +#include +#include -#include "Skybox_vert.h" -#include "Skybox_frag.h" +#include +#include -#include "stars_vert.h" -#include "stars_frag.h" -#include "starsGrid_frag.h" - -#include "DrawTransformUnitQuad_vert.h" -#include "DrawTexcoordRectTransformUnitQuad_vert.h" -#include "DrawViewportQuadTransformTexcoord_vert.h" -#include "DrawTexture_frag.h" -#include "DrawTextureOpaque_frag.h" -#include "DrawColoredTexture_frag.h" - -#include "sdf_text3D_vert.h" -#include "sdf_text3D_frag.h" - -#include "paintStroke_vert.h" -#include "paintStroke_frag.h" - -#include "polyvox_vert.h" -#include "polyvox_frag.h" +#include +#include // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { @@ -163,7 +157,7 @@ void QTestWindow::draw() { testShaderBuild(DrawTransformUnitQuad_vert, DrawTextureOpaque_frag); testShaderBuild(DrawTransformUnitQuad_vert, DrawColoredTexture_frag); - testShaderBuild(Skybox_vert, Skybox_frag); + testShaderBuild(skybox_vert, skybox_frag); testShaderBuild(simple_vert, simple_frag); testShaderBuild(simple_vert, simple_textured_frag); testShaderBuild(simple_vert, simple_textured_emisive_frag); @@ -209,8 +203,6 @@ void QTestWindow::draw() { testShaderBuild(overlay3D_vert, overlay3D_frag); - testShaderBuild(Skybox_vert, Skybox_frag); - testShaderBuild(paintStroke_vert,paintStroke_frag); testShaderBuild(polyvox_vert, polyvox_frag);