diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d137d1b824..ea30909a08 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -206,14 +206,13 @@ endif() # link required hifi libraries link_hifi_libraries( - shared task octree ktx gpu gl procedural graphics graphics-scripting render + shared workload task octree ktx gpu gl procedural graphics graphics-scripting render pointers recording fbx networking model-networking entities avatars trackers audio audio-client animation script-engine physics render-utils entities-renderer avatars-renderer ui qml auto-updater midi controllers plugins image trackers ui-plugins display-plugins input-plugins - workload ${PLATFORM_GL_BACKEND} ) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 10fcf9c7a0..2033e8ee8e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2539,6 +2539,8 @@ Application::~Application() { _main3DScene = nullptr; _renderEngine = nullptr; + _gameWorkload.shutdown(); + DependencyManager::destroy(); _entityClipboard->eraseAllOctreeElements(); @@ -2995,6 +2997,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("HMD", DependencyManager::get().data()); surfaceContext->setContextProperty("Scene", DependencyManager::get().data()); surfaceContext->setContextProperty("Render", _renderEngine->getConfiguration().get()); + surfaceContext->setContextProperty("Workload", _gameWorkload._engine->getConfiguration().get()); surfaceContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface()); surfaceContext->setContextProperty("Snapshot", DependencyManager::get().data()); @@ -4534,6 +4537,10 @@ void Application::idle() { qFatal("Unable to make main thread context current"); } + { + _gameWorkload.updateViews(_viewFrustum, getMyAvatar()->getHeadPosition()); + _gameWorkload._engine->run(); + } { PerformanceTimer perfTimer("update"); PerformanceWarning warn(showWarnings, "Application::idle()... update()"); @@ -4929,6 +4936,9 @@ void Application::init() { avatar->setCollisionSound(sound); } }, Qt::QueuedConnection); + + _gameWorkload.startup(getEntities()->getWorkloadSpace(), _main3DScene, _entitySimulation); + _entitySimulation->setWorkloadSpace(getEntities()->getWorkloadSpace()); } void Application::loadAvatarScripts(const QVector& urls) { @@ -5280,14 +5290,16 @@ void Application::setKeyboardFocusEntity(const EntityItemID& entityItemID) { auto entityId = _keyboardFocusedEntity.get(); if (entities->wantsKeyboardFocus(entityId)) { entities->setProxyWindow(entityId, _window->windowHandle()); - auto entity = getEntities()->getEntity(entityId); if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); } _lastAcceptedKeyPress = usecTimestampNow(); - setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), - entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); + auto entity = getEntities()->getEntity(entityId); + if (entity) { + setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), + entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); + } } } } @@ -5615,7 +5627,10 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(simulation_physics, "Simulation"); PerformanceTimer perfTimer("simulation"); + if (_physicsEnabled) { + auto t0 = std::chrono::high_resolution_clock::now(); + auto t1 = t0; { PROFILE_RANGE(simulation_physics, "PrePhysics"); PerformanceTimer perfTimer("prePhysics)"); @@ -5639,6 +5654,8 @@ void Application::update(float deltaTime) { _entitySimulation->applyDynamicChanges(); + t1 = std::chrono::high_resolution_clock::now(); + avatarManager->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); avatarManager->getObjectsToAddToPhysics(motionStates); @@ -5651,6 +5668,7 @@ void Application::update(float deltaTime) { dynamic->prepareForPhysicsSimulation(); }); } + auto t2 = std::chrono::high_resolution_clock::now(); { PROFILE_RANGE(simulation_physics, "StepPhysics"); PerformanceTimer perfTimer("stepPhysics"); @@ -5658,6 +5676,7 @@ void Application::update(float deltaTime) { _physicsEngine->stepSimulation(); }); } + auto t3 = std::chrono::high_resolution_clock::now(); { if (_physicsEngine->hasOutgoingChanges()) { { @@ -5703,12 +5722,26 @@ void Application::update(float deltaTime) { // NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework _physicsEngine->dumpStatsIfNecessary(); } + auto t4 = std::chrono::high_resolution_clock::now(); if (!_aboutToQuit) { // NOTE: the getEntities()->update() call below will wait for lock // and will provide non-physical entity motion getEntities()->update(true); // update the models... } + + auto t5 = std::chrono::high_resolution_clock::now(); + + workload::Timings timings(6); + timings[0] = (t4 - t0); + timings[1] = (t5 - t4); + timings[2] = (t4 - t3); + timings[3] = (t3 - t2); + timings[4] = (t2 - t1); + timings[5] = (t1 - t0); + + _gameWorkload.updateSimulationTimings(timings); + } } } else { @@ -6562,6 +6595,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); + scriptEngine->registerGlobalObject("Workload", _gameWorkload._engine->getConfiguration().get()); GraphicsScriptingInterface::registerMetaTypes(scriptEngine.data()); scriptEngine->registerGlobalObject("Graphics", DependencyManager::get().data()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 236edf8bb0..346ea258da 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -72,6 +72,8 @@ #include "ui/overlays/Overlays.h" #include "UndoStackScriptingInterface.h" +#include "workload/GameWorkload.h" + #include #include #include @@ -274,6 +276,8 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } + const GameWorkload& getGameWorkload() const { return _gameWorkload; } + virtual void pushPostUpdateLambda(void* key, const std::function& func) override; void updateMyAvatarLookAtPosition(); @@ -657,6 +661,8 @@ private: render::EnginePointer _renderEngine{ new render::RenderEngine() }; gpu::ContextPointer _gpuContext; // initialized during window creation + GameWorkload _gameWorkload; + mutable QMutex _renderArgsMutex{ QMutex::Recursive }; struct AppRenderArgs { render::Args _renderArgs; diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 8e15de673f..f97c02bca3 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -77,7 +77,7 @@ void addAvatarEntities(const QVariantList& avatarEntities) { entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation - entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); entityProperties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree"; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 9d86745341..c7ee868855 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -490,9 +490,9 @@ void Stats::updateStats(bool force) { }; for (int32_t j = 0; j < categories.size(); ++j) { QString recordKey = "/idle/update/" + categories[j]; - itr = allRecords.find(recordKey); - if (itr != allRecords.end()) { - float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC; + auto record = PerformanceTimer::getTimerRecord(recordKey); + if (record.getCount()) { + float dt = (float) record.getMovingAverage() / (float)USECS_PER_MSEC; QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt); idleUpdateStats.push(SortableStat(message, dt)); } diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index ade478347c..ee267beb78 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -254,6 +254,7 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); + _webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get()); _webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this); diff --git a/interface/src/workload/GameWorkload.cpp b/interface/src/workload/GameWorkload.cpp new file mode 100644 index 0000000000..afbd166c89 --- /dev/null +++ b/interface/src/workload/GameWorkload.cpp @@ -0,0 +1,82 @@ +// +// GameWorkload.cpp +// +// Created by Sam Gateau on 2/16/2018. +// Copyright 2018 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 "GameWorkload.h" +#include "GameWorkloadRenderer.h" +#include +#include +#include + +#include "PhysicsBoundary.h" + +class WorkloadEngineBuilder { +public: + using Inputs = workload::VaryingSet2; + using Outputs = workload::RegionTracker::Outputs; + using JobModel = workload::Task::ModelIO; + void build(JobModel& model, const workload::Varying& in, workload::Varying& out) { + + const auto& inViews = in.getN(0); + const auto& inTimings = in.getN(1); + + const auto usedViews = model.addJob("setupViews", inViews); + + const auto controlViewsIn = workload::ControlViews::Input(usedViews, inTimings).asVarying(); + const auto fixedViews = model.addJob("controlViews", controlViewsIn); + + const auto regionTrackerOut = model.addJob("spaceClassifier", fixedViews); + + model.addJob("PhysicsBoundary", regionTrackerOut); + + model.addJob("SpaceToRender"); + + out = regionTrackerOut; + } +}; + +GameWorkloadContext::GameWorkloadContext(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation): WorkloadContext(space), + _scene(scene), _simulation(simulation) +{ +} + +GameWorkloadContext::~GameWorkloadContext() { +} + + +GameWorkload::GameWorkload() : + _engine(std::make_shared(WorkloadEngineBuilder::JobModel::create("Workload"), std::shared_ptr())) +{ +} + +GameWorkload::~GameWorkload() { + shutdown(); +} + +void GameWorkload::startup(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation) { + _engine->reset(std::make_shared(space, scene, simulation)); +} + +void GameWorkload::shutdown() { + _engine.reset(); +} + +void GameWorkload::updateViews(const ViewFrustum& frustum, const glm::vec3& headPosition) { + workload::Views views; + views.emplace_back(workload::View::evalFromFrustum(frustum, headPosition - frustum.getPosition())); + views.emplace_back(workload::View::evalFromFrustum(frustum)); + _engine->feedInput(0, views); +} + +void GameWorkload::updateSimulationTimings(const workload::Timings& timings) { + _engine->feedInput(1, timings); +} diff --git a/interface/src/workload/GameWorkload.h b/interface/src/workload/GameWorkload.h new file mode 100644 index 0000000000..4b08be8a56 --- /dev/null +++ b/interface/src/workload/GameWorkload.h @@ -0,0 +1,45 @@ +// +// GameWorkload.h +// +// Created by Sam Gateau on 2/16/2018. +// Copyright 2018 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_GameWorkload_h +#define hifi_GameWorkload_h + +#include +#include + +#include +#include "PhysicalEntitySimulation.h" + +class GameWorkloadContext : public workload::WorkloadContext { +public: + GameWorkloadContext(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation); + virtual ~GameWorkloadContext(); + + render::ScenePointer _scene; + PhysicalEntitySimulationPointer _simulation; +}; + +class GameWorkload { +public: + GameWorkload(); + ~GameWorkload(); + + void startup(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation); + void shutdown(); + + void updateViews(const ViewFrustum& frustum, const glm::vec3& headPosition); + void updateSimulationTimings(const workload::Timings& timings); + + workload::EnginePointer _engine; +}; +#endif // hifi_GameWorkload_h diff --git a/interface/src/workload/GameWorkloadRenderer.cpp b/interface/src/workload/GameWorkloadRenderer.cpp new file mode 100644 index 0000000000..8cf19b56dd --- /dev/null +++ b/interface/src/workload/GameWorkloadRenderer.cpp @@ -0,0 +1,257 @@ +// +// GameWorkloadRender.cpp +// +// Created by Sam Gateau on 2/20/2018. +// Copyright 2018 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 "GameWorkloadRenderer.h" + +#include +#include + +#include +#include + +#include "render-utils/drawWorkloadProxy_vert.h" +#include "render-utils/drawWorkloadView_vert.h" +#include "render-utils/drawWorkloadProxy_frag.h" +#include "render-utils/drawWorkloadView_frag.h" + + +void GameSpaceToRender::configure(const Config& config) { + _freezeViews = config.freezeViews; + _showAllProxies = config.showProxies; + _showAllViews = config.showViews; +} + +void GameSpaceToRender::run(const workload::WorkloadContextPointer& runContext, Outputs& outputs) { + auto gameWorkloadContext = std::dynamic_pointer_cast(runContext); + if (!gameWorkloadContext) { + return; + } + auto space = gameWorkloadContext->_space; + if (!space) { + return; + } + + auto visible = _showAllProxies || _showAllViews; + auto showProxies = _showAllProxies; + auto showViews = _showAllViews; + auto freezeViews = _freezeViews; + + render::Transaction transaction; + auto scene = gameWorkloadContext->_scene; + + // Nothing really needed, early exit + if (!visible) { + if (render::Item::isValidID(_spaceRenderItemID)) { + transaction.updateItem(_spaceRenderItemID, [](GameWorkloadRenderItem& item) { + item.setVisible(false); + }); + scene->enqueueTransaction(transaction); + } + return; + } + + workload::Proxy::Vector proxies(space->getNumAllocatedProxies()); + space->copyProxyValues(proxies.data(), (uint32_t)proxies.size()); + + workload::Views views(space->getNumViews()); + space->copyViews(views); + + // Valid space, let's display its content + if (!render::Item::isValidID(_spaceRenderItemID)) { + _spaceRenderItemID = scene->allocateID(); + auto renderItem = std::make_shared(); + renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f); + renderItem->setAllProxies(proxies); + transaction.resetItem(_spaceRenderItemID, std::make_shared(renderItem)); + } + + transaction.updateItem(_spaceRenderItemID, [visible, showProxies, proxies, freezeViews, showViews, views](GameWorkloadRenderItem& item) { + item.setVisible(visible); + item.showProxies(showProxies); + item.setAllProxies(proxies); + item.showViews(showViews); + item.setAllViews(views); + }); + + scene->enqueueTransaction(transaction); +} + +namespace render { + template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload) { + return payload->getKey(); + } + template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload) { + if (payload) { + return payload->getBound(); + } + return Item::Bound(); + } + template <> void payloadRender(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args) { + if (payload) { + payload->render(args); + } + } + template <> const ShapeKey shapeGetShapeKey(const GameWorkloadRenderItem::Pointer& payload) { + return ShapeKey::Builder::ownPipeline(); + } +} + +GameWorkloadRenderItem::GameWorkloadRenderItem() : _key(render::ItemKey::Builder::opaqueShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1)) { +} + +render::ItemKey GameWorkloadRenderItem::getKey() const { + return _key; +} + +void GameWorkloadRenderItem::setVisible(bool isVisible) { + if (isVisible) { + _key = render::ItemKey::Builder(_key).withVisible(); + } else { + _key = render::ItemKey::Builder(_key).withInvisible(); + } +} + +void GameWorkloadRenderItem::showProxies(bool show) { + _showProxies = show; +} + +void GameWorkloadRenderItem::showViews(bool show) { + _showViews = show; +} + + +void GameWorkloadRenderItem::setAllProxies(const workload::Proxy::Vector& proxies) { + _myOwnProxies = proxies; + static const uint32_t sizeOfProxy = sizeof(workload::Proxy); + if (!_allProxiesBuffer) { + _allProxiesBuffer = std::make_shared(sizeOfProxy); + } + + _allProxiesBuffer->setData(proxies.size() * sizeOfProxy, (const gpu::Byte*) proxies.data()); + _numAllProxies = (uint32_t) proxies.size(); +} + +void GameWorkloadRenderItem::setAllViews(const workload::Views& views) { + _myOwnViews = views; + static const uint32_t sizeOfView = sizeof(workload::View); + if (!_allViewsBuffer) { + _allViewsBuffer = std::make_shared(sizeOfView); + } + + _allViewsBuffer->setData(views.size() * sizeOfView, (const gpu::Byte*) views.data()); + _numAllViews = (uint32_t)views.size(); +} + +const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() { + if (!_drawAllProxiesPipeline) { + auto vs = drawWorkloadProxy_vert::getShader(); + auto ps = drawWorkloadProxy_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("workloadProxiesBuffer", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + /* state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);*/ + + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + _drawAllProxiesPipeline = gpu::Pipeline::create(program, state); + } + return _drawAllProxiesPipeline; +} + + +const gpu::PipelinePointer GameWorkloadRenderItem::getViewsPipeline() { + if (!_drawAllViewsPipeline) { + auto vs = drawWorkloadView_vert::getShader(); + auto ps = drawWorkloadView_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("workloadViewsBuffer", 1)); + slotBindings.insert(gpu::Shader::Binding("drawMeshBuffer", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + /* state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);*/ + + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + _drawAllViewsPipeline = gpu::Pipeline::create(program, state); + } + return _drawAllViewsPipeline; +} + +const gpu::BufferPointer GameWorkloadRenderItem::getDrawViewBuffer() { + if (!_drawViewBuffer) { + int numSegments = 64; + float angleStep = (float)M_PI * 2.0f / (float)numSegments; + + struct Vert { + glm::vec4 p; + }; + std::vector verts(numSegments + 1); + for (int i = 0; i < numSegments; i++) { + float angle = (float)i * angleStep; + verts[i].p.x = cos(angle); + verts[i].p.y = sin(angle); + verts[i].p.z = angle; + verts[i].p.w = 1.0f; + } + verts[numSegments] = verts[0]; + verts[numSegments].p.w = 0.0f; + + _drawViewBuffer = std::make_shared(verts.size() * sizeof(Vert), (const gpu::Byte*) verts.data()); + _numDrawViewVerts = numSegments + 1; + } + return _drawViewBuffer; +} + +void GameWorkloadRenderItem::render(RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + + batch.setModelTransform(Transform()); + + batch.setResourceBuffer(0, _allProxiesBuffer); + batch.setResourceBuffer(1, _allViewsBuffer); + + // Show Proxies + if (_showProxies) { + batch.setPipeline(getProxiesPipeline()); + + static const int NUM_VERTICES_PER_PROXY = 3; + batch.draw(gpu::TRIANGLES, NUM_VERTICES_PER_PROXY * _numAllProxies, 0); + } + + // Show Views + if (_showViews) { + batch.setPipeline(getViewsPipeline()); + + batch.setUniformBuffer(0, getDrawViewBuffer()); + static const int NUM_VERTICES_PER_DRAWVIEWVERT = 2; + static const int NUM_REGIONS = 3; + batch.draw(gpu::TRIANGLE_STRIP, NUM_REGIONS * NUM_VERTICES_PER_DRAWVIEWVERT * _numDrawViewVerts * _numAllViews, 0); + + } + + batch.setResourceBuffer(0, nullptr); + batch.setResourceBuffer(1, nullptr); + +} + + + diff --git a/interface/src/workload/GameWorkloadRenderer.h b/interface/src/workload/GameWorkloadRenderer.h new file mode 100644 index 0000000000..a25598821e --- /dev/null +++ b/interface/src/workload/GameWorkloadRenderer.h @@ -0,0 +1,104 @@ +// +// GameWorkloadRender.h +// +// Created by Sam Gateau on 2/20/2018. +// Copyright 2018 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_GameWorkloadRenderer_h +#define hifi_GameWorkloadRenderer_h + +#include "GameWorkload.h" + +class GameSpaceToRenderConfig : public workload::Job::Config { + Q_OBJECT + Q_PROPERTY(bool freezeViews MEMBER freezeViews NOTIFY dirty) + Q_PROPERTY(bool showProxies MEMBER showProxies NOTIFY dirty) + Q_PROPERTY(bool showViews MEMBER showViews NOTIFY dirty) +public: + + bool freezeViews{ false }; + bool showProxies{ false }; + bool showViews{ false }; +signals: + void dirty(); + +protected: +}; + +class GameSpaceToRender { +public: + using Config = GameSpaceToRenderConfig; + using Outputs = render::Transaction; + using JobModel = workload::Job::ModelO; + + GameSpaceToRender() {} + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, Outputs& outputs); + +protected: + render::ItemID _spaceRenderItemID{ render::Item::INVALID_ITEM_ID }; + bool _freezeViews{ false }; + bool _showAllProxies{ false }; + bool _showAllViews{ false }; +}; + + +class GameWorkloadRenderItem { +public: + using Payload = render::Payload; + using Pointer = Payload::DataPointer; + + GameWorkloadRenderItem(); + ~GameWorkloadRenderItem() {} + void render(RenderArgs* args); + + render::Item::Bound& editBound() { return _bound; } + const render::Item::Bound& getBound() { return _bound; } + + void setVisible(bool visible); + void showProxies(bool show); + void showViews(bool show); + + void setAllProxies(const workload::Proxy::Vector& proxies); + void setAllViews(const workload::Views& views); + + render::ItemKey getKey() const; + +protected: + render::Item::Bound _bound; + + workload::Proxy::Vector _myOwnProxies; + gpu::BufferPointer _allProxiesBuffer; + uint32_t _numAllProxies{ 0 }; + + workload::Views _myOwnViews; + gpu::BufferPointer _allViewsBuffer; + uint32_t _numAllViews{ 0 }; + + gpu::PipelinePointer _drawAllProxiesPipeline; + const gpu::PipelinePointer getProxiesPipeline(); + + gpu::PipelinePointer _drawAllViewsPipeline; + const gpu::PipelinePointer getViewsPipeline(); + + uint32_t _numDrawViewVerts{ 0 }; + gpu::BufferPointer _drawViewBuffer; + const gpu::BufferPointer getDrawViewBuffer(); + + render::ItemKey _key; + bool _showProxies{ true }; + bool _showViews{ true }; +}; + +namespace render { + template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload); + template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload); + template <> void payloadRender(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args); + template <> const ShapeKey shapeGetShapeKey(const GameWorkloadRenderItem::Pointer& payload); +} + +#endif diff --git a/interface/src/workload/PhysicsBoundary.cpp b/interface/src/workload/PhysicsBoundary.cpp new file mode 100644 index 0000000000..927121ac04 --- /dev/null +++ b/interface/src/workload/PhysicsBoundary.cpp @@ -0,0 +1,33 @@ +// +// PhysicsBoundary.h +// +// Created by Andrew Meadows 2018.04.05 +// Copyright 2018 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 "PhysicsBoundary.h" + +#include +#include + +#include "workload/GameWorkload.h" + +void PhysicsBoundary::run(const workload::WorkloadContextPointer& context, const Inputs& inputs) { + auto space = context->_space; + if (!space) { + return; + } + GameWorkloadContext* gameContext = static_cast(context.get()); + PhysicalEntitySimulationPointer simulation = gameContext->_simulation; + const auto& regionChanges = inputs.get0(); + for (uint32_t i = 0; i < (uint32_t)regionChanges.size(); ++i) { + const workload::Space::Change& change = regionChanges[i]; + auto entity = space->getOwner(change.proxyId).get(); + if (entity) { + simulation->changeEntity(entity); + } + } +} diff --git a/interface/src/workload/PhysicsBoundary.h b/interface/src/workload/PhysicsBoundary.h new file mode 100644 index 0000000000..c316fa5686 --- /dev/null +++ b/interface/src/workload/PhysicsBoundary.h @@ -0,0 +1,31 @@ +// +// PhysicsBoundary.h +// +// Created by Andrew Meadows 2018.04.05 +// Copyright 2018 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_PhysicsGatekeeper_h +#define hifi_PhysicsGatekeeper_h + +#include +#include +#include + +#include "PhysicalEntitySimulation.h" + +class PhysicsBoundary { +public: + using Config = workload::Job::Config; + using Inputs = workload::RegionTracker::Outputs; + using Outputs = bool; + using JobModel = workload::Job::ModelI; // this doesn't work + + PhysicsBoundary() {} + void configure(const Config& config) { } + void run(const workload::WorkloadContextPointer& context, const Inputs& inputs); +}; + +#endif // hifi_PhysicsGatekeeper_h diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 6f4f819793..a70c8294d5 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -14,6 +14,7 @@ include_hifi_library_headers(audio) include_hifi_library_headers(entities) include_hifi_library_headers(octree) include_hifi_library_headers(task) +include_hifi_library_headers(workload) include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 3ee063ff7a..12b9b3dea5 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME entities-renderer) AUTOSCRIBE_SHADER_LIB(gpu graphics procedural render render-utils) setup_hifi_library(Network Script) -link_hifi_libraries(shared gpu procedural graphics model-networking script-engine render render-utils image qml ui pointers) +link_hifi_libraries(shared workload gpu procedural graphics model-networking script-engine render render-utils image qml ui pointers) include_hifi_library_headers(networking) include_hifi_library_headers(gl) include_hifi_library_headers(ktx) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index afd6149d92..a42fd6b263 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -206,6 +206,7 @@ void EntityTreeRenderer::clear() { } // remove all entities from the scene + _space->clear(); auto scene = _viewState->getMain3DScene(); if (scene) { render::Transaction transaction; @@ -290,6 +291,16 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r if (!entity->isParentPathComplete()) { continue; } + if (entity->getSpaceIndex() == -1) { + std::unique_lock lock(_spaceLock); + auto spaceIndex = _space->allocateID(); + workload::Sphere sphere(entity->getWorldPosition(), entity->getBoundingRadius()); + workload::Transaction transaction; + transaction.reset(spaceIndex, sphere, workload::Owner(entity)); + _space->enqueueTransaction(transaction); + entity->setSpaceIndex(spaceIndex); + connect(entity.get(), &EntityItem::spaceUpdate, this, &EntityTreeRenderer::handleSpaceUpdate, Qt::QueuedConnection); + } auto entityID = entity->getEntityItemID(); processedIds.insert(entityID); @@ -299,7 +310,6 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } } - if (!processedIds.empty()) { for (const auto& processedId : processedIds) { _entitiesToAdd.erase(processedId); @@ -419,10 +429,12 @@ void EntityTreeRenderer::update(bool simulate) { EntityTreePointer tree = std::static_pointer_cast(_tree); // here we update _currentFrame and _lastAnimated and sync with the server properties. - tree->update(simulate); - - // Update the rendereable entities as needed { + PerformanceTimer perfTimer("tree::update"); + tree->update(simulate); + } + + { // Update the rendereable entities as needed PROFILE_RANGE(simulation_physics, "Scene"); PerformanceTimer sceneTimer("scene"); auto scene = _viewState->getMain3DScene(); @@ -434,6 +446,24 @@ void EntityTreeRenderer::update(bool simulate) { scene->enqueueTransaction(transaction); } } + { + PerformanceTimer perfTimer("workload::transaction"); + workload::Transaction spaceTransaction; + { // update proxies in the workload::Space + std::unique_lock lock(_spaceLock); + spaceTransaction.update(_spaceUpdates); + _spaceUpdates.clear(); + } + { + std::vector staleProxies; + tree->swapStaleProxies(staleProxies); + spaceTransaction.remove(staleProxies); + { + std::unique_lock lock(_spaceLock); + _space->enqueueTransaction(spaceTransaction); + } + } + } if (simulate) { // Handle enter/leave entity logic @@ -450,6 +480,11 @@ void EntityTreeRenderer::update(bool simulate) { } } +void EntityTreeRenderer::handleSpaceUpdate(std::pair proxyUpdate) { + std::unique_lock lock(_spaceLock); + _spaceUpdates.emplace_back(proxyUpdate.first, proxyUpdate.second); +} + bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar) { bool didUpdate = false; float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index de3033936a..f810aa64b6 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -24,6 +24,7 @@ #include #include #include +#include class AbstractScriptingServicesInterface; class AbstractViewStateInterface; @@ -116,6 +117,9 @@ public: EntityItemPointer getEntity(const EntityItemID& id); void onEntityChanged(const EntityItemID& id); + // Access the workload Space + workload::SpacePointer getWorkloadSpace() const { return _space; } + signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); @@ -135,6 +139,8 @@ public slots: EntityRendererPointer renderableForEntityId(const EntityItemID& id) const; render::ItemID renderableIdForEntityId(const EntityItemID& id) const; + void handleSpaceUpdate(std::pair proxyUpdate); + protected: virtual OctreePointer createTree() override { EntityTreePointer newTree = EntityTreePointer(new EntityTree(true)); @@ -255,6 +261,10 @@ private: static int _entitiesScriptEngineCount; static CalculateEntityLoadingPriority _calculateEntityLoadingPriorityFunc; static std::function _entitiesShouldFadeFunction; + + mutable std::mutex _spaceLock; + workload::SpacePointer _space{ new workload::Space() }; + workload::Transaction::Updates _spaceUpdates; }; diff --git a/libraries/entities/src/DeleteEntityOperator.cpp b/libraries/entities/src/DeleteEntityOperator.cpp index d369e08ecf..1dca171ae3 100644 --- a/libraries/entities/src/DeleteEntityOperator.cpp +++ b/libraries/entities/src/DeleteEntityOperator.cpp @@ -95,7 +95,7 @@ bool DeleteEntityOperator::preRecursion(const OctreeElementPointer& element) { EntityItemPointer theEntity = details.entity; bool entityDeleted = entityTreeElement->removeEntityItem(theEntity, true); // remove it from the element assert(entityDeleted); - (void)entityDeleted; // quite warning + (void)entityDeleted; // quiet warning about unused variable _tree->clearEntityMapEntry(details.entity->getEntityItemID()); _foundCount++; } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 70881fbc40..31ec189cf9 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -367,6 +367,10 @@ int EntityItem::expectedBytes() { return MINIMUM_HEADER_BYTES; } +const uint8_t PENDING_STATE_NOTHING = 0; +const uint8_t PENDING_STATE_TAKE = 1; +const uint8_t PENDING_STATE_RELEASE = 2; + // clients use this method to unpack FULL updates from entity-server int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { setSourceUUID(args.sourceUUID); @@ -678,7 +682,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // setters to ignore what the server says. filterRejection = newSimOwner.getID().isNull(); if (weOwnSimulation) { - if (newSimOwner.getID().isNull() && !_simulationOwner.pendingRelease(lastEditedFromBufferAdjusted)) { + if (newSimOwner.getID().isNull() && !pendingRelease(lastEditedFromBufferAdjusted)) { // entity-server is trying to clear our ownership (probably at our own request) // but we actually want to own it, therefore we ignore this clear event // and pretend that we own it (e.g. we assume we'll receive ownership soon) @@ -693,32 +697,53 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // recompute weOwnSimulation for later weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); } - } else if (_simulationOwner.pendingTake(now - maxPingRoundTrip)) { - // we sent a bid already but maybe before this packet was sent from the server - weOwnSimulation = true; + } else if (_pendingOwnershipState == PENDING_STATE_TAKE) { + // we're waiting to receive acceptance of a bid + // this ownership data either satisifies our bid or does not + bool bidIsSatisfied = newSimOwner.getID() == myNodeID && + (newSimOwner.getPriority() == _pendingOwnershipPriority || + (_pendingOwnershipPriority == VOLUNTEER_SIMULATION_PRIORITY && + newSimOwner.getPriority() == RECRUIT_SIMULATION_PRIORITY)); + if (newSimOwner.getID().isNull()) { - // the entity-server is trying to clear someone else's ownership + // the entity-server is clearing someone else's ownership if (!_simulationOwner.isNull()) { markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); somethingChanged = true; _simulationOwner.clearCurrentOwner(); } - } else if (newSimOwner.getID() == myNodeID) { - // the entity-server is awarding us ownership which is what we want - _simulationOwner.set(newSimOwner); + } else { + if (newSimOwner.getID() != _simulationOwner.getID()) { + markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); + } + if (_simulationOwner.set(newSimOwner)) { + // the entity-server changed ownership + somethingChanged = true; + } + } + if (bidIsSatisfied || (somethingChanged && _pendingOwnershipTimestamp < now - maxPingRoundTrip)) { + // the bid has been satisfied, or it has been invalidated by data sent AFTER the bid should have been received + // in either case: accept our fate and clear pending state + _pendingOwnershipState = PENDING_STATE_NOTHING; + _pendingOwnershipPriority = 0; + } + weOwnSimulation = bidIsSatisfied || (_simulationOwner.getID() == myNodeID); + } else { + // we are not waiting to take ownership + if (newSimOwner.getID() != _simulationOwner.getID()) { + markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); + } + if (_simulationOwner.set(newSimOwner)) { + // the entity-server changed ownership... + somethingChanged = true; + if (newSimOwner.getID() == myNodeID) { + // we have recieved ownership + weOwnSimulation = true; + // accept our fate and clear pendingState (just in case) + _pendingOwnershipState = PENDING_STATE_NOTHING; + _pendingOwnershipPriority = 0; + } } - } else if (newSimOwner.matchesValidID(myNodeID) && !_simulationOwner.pendingTake(now)) { - // entity-server tells us that we have simulation ownership while we never requested this for this EntityItem, - // this could happen when the user reloads the cache and entity tree. - markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); - somethingChanged = true; - _simulationOwner.clearCurrentOwner(); - weOwnSimulation = false; - } else if (_simulationOwner.set(newSimOwner)) { - markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); - somethingChanged = true; - // recompute weOwnSimulation for later - weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); } } @@ -1333,18 +1358,39 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c properties._accelerationChanged = true; } -void EntityItem::flagForOwnershipBid(uint8_t priority) { - markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); - auto nodeList = DependencyManager::get(); - if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { - // we already own it - _simulationOwner.promotePriority(priority); - } else { - // we don't own it yet - _simulationOwner.setPendingPriority(priority, usecTimestampNow()); +void EntityItem::setScriptSimulationPriority(uint8_t priority) { + uint8_t newPriority = stillHasGrabActions() ? glm::max(priority, SCRIPT_GRAB_SIMULATION_PRIORITY) : priority; + if (newPriority != _scriptSimulationPriority) { + // set the dirty flag to trigger a bid or ownership update + markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); + _scriptSimulationPriority = newPriority; } } +void EntityItem::clearScriptSimulationPriority() { + // DO NOT markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) here, because this + // is only ever called from the code that actually handles the dirty flags, and it knows best. + _scriptSimulationPriority = stillHasGrabActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0; +} + +void EntityItem::setPendingOwnershipPriority(uint8_t priority) { + _pendingOwnershipTimestamp = usecTimestampNow(); + _pendingOwnershipPriority = priority; + _pendingOwnershipState = (_pendingOwnershipPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; +} + +bool EntityItem::pendingRelease(uint64_t timestamp) const { + return _pendingOwnershipPriority == 0 && + _pendingOwnershipState == PENDING_STATE_RELEASE && + _pendingOwnershipTimestamp >= timestamp; +} + +bool EntityItem::stillWaitingToTakeOwnership(uint64_t timestamp) const { + return _pendingOwnershipPriority > 0 && + _pendingOwnershipState == PENDING_STATE_TAKE && + _pendingOwnershipTimestamp >= timestamp; +} + bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; @@ -1977,10 +2023,6 @@ void EntityItem::clearSimulationOwnership() { } -void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) { - _simulationOwner.setPendingPriority(priority, timestamp); -} - QString EntityItem::actionsToDebugString() { QString result; QVector serializedActions; @@ -2076,6 +2118,7 @@ bool EntityItem::updateAction(EntitySimulationPointer simulation, const QUuid& a } bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& actionID) { + // TODO: some action bool success = false; withWriteLock([&] { checkWaitingToRemove(simulation); @@ -2403,12 +2446,17 @@ void EntityItem::locationChanged(bool tellPhysics) { } } SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also + std::pair data(_spaceIndex, glm::vec4(getWorldPosition(), _boundingRadius)); + emit spaceUpdate(data); somethingChangedNotification(); } void EntityItem::dimensionsChanged() { requiresRecalcBoxes(); SpatiallyNestable::dimensionsChanged(); // Do what you have to do + _boundingRadius = 0.5f * glm::length(getScaledDimensions()); + std::pair data(_spaceIndex, glm::vec4(getWorldPosition(), _boundingRadius)); + emit spaceUpdate(data); somethingChangedNotification(); } @@ -3012,6 +3060,11 @@ void EntityItem::retrieveMarketplacePublicKey() { }); } +void EntityItem::setSpaceIndex(int32_t index) { + assert(_spaceIndex == -1); + _spaceIndex = index; +} + void EntityItem::preDelete() { } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0acf8dbbc1..a9dc617a6c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -311,14 +311,21 @@ public: const SimulationOwner& getSimulationOwner() const { return _simulationOwner; } void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const SimulationOwner& owner); - void promoteSimulationPriority(uint8_t priority); uint8_t getSimulationPriority() const { return _simulationOwner.getPriority(); } QUuid getSimulatorID() const { return _simulationOwner.getID(); } void clearSimulationOwnership(); - void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp); - uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); } - void rememberHasSimulationOwnershipBid() const; + + // TODO: move this "ScriptSimulationPriority" and "PendingOwnership" stuff into EntityMotionState + // but first would need to do some other cleanup. In the meantime these live here as "scratch space" + // to allow libs that don't know about each other to communicate. + void setScriptSimulationPriority(uint8_t priority); + void clearScriptSimulationPriority(); + uint8_t getScriptSimulationPriority() const { return _scriptSimulationPriority; } + void setPendingOwnershipPriority(uint8_t priority); + uint8_t getPendingOwnershipPriority() const { return _pendingOwnershipPriority; } + bool pendingRelease(uint64_t timestamp) const; + bool stillWaitingToTakeOwnership(uint64_t timestamp) const; // Certifiable Properties QString getItemName() const; @@ -411,7 +418,6 @@ public: void getAllTerseUpdateProperties(EntityItemProperties& properties) const; - void flagForOwnershipBid(uint8_t priority); void flagForMotionStateChange() { _flags |= Simulation::DIRTY_MOTION_TYPE; } QString actionsToDebugString(); @@ -500,6 +506,10 @@ public: void setCauterized(bool value) { _cauterized = value; } bool getCauterized() const { return _cauterized; } + float getBoundingRadius() const { return _boundingRadius; } + void setSpaceIndex(int32_t index); + int32_t getSpaceIndex() const { return _spaceIndex; } + virtual void preDelete(); virtual void postParentFixup() {} @@ -517,6 +527,7 @@ public: signals: void requestRenderUpdate(); + void spaceUpdate(std::pair data); protected: QHash _changeHandlers; @@ -668,6 +679,17 @@ protected: quint64 _lastUpdatedQueryAACubeTimestamp { 0 }; uint64_t _simulationOwnershipExpiry { 0 }; + float _boundingRadius { 0.0f }; + int32_t _spaceIndex { -1 }; // index to proxy in workload::Space + + // TODO: move this "scriptSimulationPriority" and "pendingOwnership" stuff into EntityMotionState + // but first would need to do some other cleanup. In the meantime these live here as "scratch space" + // to allow libs that don't know about each other to communicate. + uint64_t _pendingOwnershipTimestamp { 0 }; // timestamp of last owenership change request + uint8_t _pendingOwnershipPriority { 0 }; // priority of last ownership change request + uint8_t _pendingOwnershipState { 0 }; // TAKE or RELEASE + uint8_t _scriptSimulationPriority { 0 }; // target priority based on script operations + bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera bool _cloneable { ENTITY_ITEM_DEFAULT_CLONEABLE }; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e46eb73910..e1cdb10212 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -339,7 +339,7 @@ public: void clearSimulationOwner(); void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const QByteArray& data); - void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); } + void setSimulationPriority(uint8_t priority) { _simulationOwner.setPriority(priority); } void setActionDataDirty() { _actionDataChanged = true; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 98407a74c6..5a11d231e1 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -290,7 +290,7 @@ bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properti entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation - entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); properties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; @@ -494,7 +494,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); - entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); } } if (properties.queryAACubeRelatedPropertyChanged()) { @@ -1363,7 +1363,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, } action->setIsMine(true); success = entity->addAction(simulation, action); - entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); return false; // Physics will cause a packet to be sent, so don't send from here. }); if (success) { @@ -1379,7 +1379,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { bool success = entity->updateAction(simulation, actionID, arguments); if (success) { - entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); } return success; }); @@ -1393,7 +1393,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& success = entity->removeAction(simulation, actionID); if (success) { // reduce from grab to poke - entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); } return false; // Physics will cause a packet to be sent, so don't send from here. }); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index ba088cb7fd..05e71a8f06 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -31,6 +31,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { void EntitySimulation::updateEntities() { QMutexLocker lock(&_mutex); uint64_t now = usecTimestampNow(); + PerformanceTimer perfTimer("EntitySimulation::updateEntities"); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3d96107071..a58f01a83b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -97,6 +97,7 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { if (_simulation) { _simulation->clearEntities(); } + _staleProxies.clear(); QHash localMap; localMap.swap(_entityMap); this->withWriteLock([&] { @@ -276,10 +277,11 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { } _isDirty = true; - emit addingEntity(entity->getEntityItemID()); // find and hook up any entities with this entity as a (previously) missing parent fixupNeedsParentFixups(); + + emit addingEntity(entity->getEntityItemID()); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { @@ -359,21 +361,34 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull()) { // the sender is taking ownership - properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { + // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash + // when dynamic objects first activate and multiple participants bid simultaneously + properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + } simulationBlocked = false; } else if (entity->getSimulatorID() == senderID) { // the sender is asserting ownership, maybe changing priority simulationBlocked = false; + // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash + // when dynamic objects first activate and multiple participants bid simultaneously + if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { + properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + } } else { // the sender is trying to steal ownership from another simulator // so we apply the rules for ownership change: // (1) higher priority wins - // (2) equal priority wins if ownership filter has expired except... + // (2) equal priority wins if ownership filter has expired + // (3) VOLUNTEER priority is promoted to RECRUIT uint8_t oldPriority = entity->getSimulationPriority(); uint8_t newPriority = properties.getSimulationOwner().getPriority(); if (newPriority > oldPriority || (newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) { simulationBlocked = false; + if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { + properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + } } } if (!simulationBlocked) { @@ -391,6 +406,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } if (simulationBlocked) { // squash ownership and physics-related changes. + // TODO? replace these eight calls with just one? properties.setSimulationOwnerChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); @@ -729,6 +745,12 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) if (theEntity->isSimulated()) { _simulation->prepareEntityForDelete(theEntity); } + + // keep a record of valid stale spaceIndices so they can be removed from the Space + int32_t spaceIndex = theEntity->getSpaceIndex(); + if (spaceIndex != -1) { + _staleProxies.push_back(spaceIndex); + } } } @@ -1853,6 +1875,7 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { void EntityTree::update(bool simulate) { PROFILE_RANGE(simulation_physics, "UpdateTree"); + PerformanceTimer perfTimer("updateTree"); withWriteLock([&] { fixupNeedsParentFixups(); if (simulate && _simulation) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d5097b9a12..22b468cf4e 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -274,6 +274,8 @@ public: void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } + void swapStaleProxies(std::vector& proxies) { proxies.swap(_staleProxies); } + void setIsServerlessMode(bool value) { _serverlessDomain = value; } bool isServerlessMode() const { return _serverlessDomain; } @@ -408,6 +410,8 @@ private: static std::function _addMaterialToOverlayOperator; static std::function _removeMaterialFromOverlayOperator; + std::vector _staleProxies; + bool _serverlessDomain { false }; std::map _namedPaths; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index cf42e93821..3ed84035ac 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -53,20 +53,29 @@ void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) { } void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + if (entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); - _simpleKinematicEntities.insert(entity); - entity->setLastSimulated(usecTimestampNow()); - } - if (!entity->getSimulatorID().isNull()) { + if (entity->getDynamic()) { + // we don't allow dynamic objects to move without an owner so nothing to do here + } else if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } + } else { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); - } else if (entity->getDynamic() && entity->hasLocalVelocity()) { - QMutexLocker lock(&_mutex); - _entitiesThatNeedSimulationOwner.insert(entity); - uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); + + if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } } } @@ -77,32 +86,50 @@ void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { - { - QMutexLocker lock(&_mutex); - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - int numKinematicEntities = _simpleKinematicEntities.size(); - _simpleKinematicEntities.insert(entity); - if (numKinematicEntities != _simpleKinematicEntities.size()) { - entity->setLastSimulated(usecTimestampNow()); + uint32_t flags = entity->getDirtyFlags(); + if ((flags & Simulation::DIRTY_SIMULATOR_ID) || (flags & Simulation::DIRTY_VELOCITIES)) { + if (entity->getSimulatorID().isNull()) { + QMutexLocker lock(&_mutex); + _entitiesWithSimulationOwner.remove(entity); + + if (entity->getDynamic()) { + // we don't allow dynamic objects to move without an owner + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } + } else if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } else { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } } } else { - _simpleKinematicEntities.remove(entity); + QMutexLocker lock(&_mutex); + _entitiesWithSimulationOwner.insert(entity); + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); + _entitiesThatNeedSimulationOwner.remove(entity); + + if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } else { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } + } } } - if (entity->getSimulatorID().isNull()) { - QMutexLocker lock(&_mutex); - _entitiesWithSimulationOwner.remove(entity); - if (entity->getDynamic() && entity->hasLocalVelocity()) { - _entitiesThatNeedSimulationOwner.insert(entity); - uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); - } - } else { - QMutexLocker lock(&_mutex); - _entitiesWithSimulationOwner.insert(entity); - _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); - _entitiesThatNeedSimulationOwner.remove(entity); - } entity->clearDirtyFlags(); } @@ -131,6 +158,12 @@ void SimpleEntitySimulation::expireStaleOwnerships(uint64_t now) { uint64_t expiry = entity->getSimulationOwnershipExpiry(); if (now > expiry) { itemItr = _entitiesWithSimulationOwner.erase(itemItr); + if (entity->getDynamic()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } + } // remove ownership and dirty all the tree elements that contain the it entity->clearSimulationOwnership(); diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index b312c6deb9..69fbf0ae16 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -16,29 +16,20 @@ #include -const uint8_t PENDING_STATE_NOTHING = 0; -const uint8_t PENDING_STATE_TAKE = 1; -const uint8_t PENDING_STATE_RELEASE = 2; - // static const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; SimulationOwner::SimulationOwner() : _id(), _expiry(0), - _pendingBidTimestamp(0), - _priority(0), - _pendingBidPriority(0), - _pendingState(PENDING_STATE_NOTHING) + _priority(0) { } SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) : _id(id), _expiry(0), - _pendingBidTimestamp(0), - _priority(priority), - _pendingBidPriority(0) + _priority(priority) { } @@ -61,22 +52,13 @@ bool SimulationOwner::fromByteArray(const QByteArray& data) { void SimulationOwner::clear() { _id = QUuid(); _expiry = 0; - _pendingBidTimestamp = 0; _priority = 0; - _pendingBidPriority = 0; - _pendingState = PENDING_STATE_NOTHING; } void SimulationOwner::setPriority(uint8_t priority) { _priority = priority; } -void SimulationOwner::promotePriority(uint8_t priority) { - if (priority > _priority) { - _priority = priority; - } -} - bool SimulationOwner::setID(const QUuid& id) { if (_id != id) { _id = id; @@ -101,25 +83,11 @@ bool SimulationOwner::set(const SimulationOwner& owner) { return setID(owner._id) || oldPriority != _priority; } -void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) { - _pendingBidPriority = priority; - _pendingBidTimestamp = timestamp; - _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; -} - void SimulationOwner::updateExpiry() { const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC; _expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY; } -bool SimulationOwner::pendingRelease(uint64_t timestamp) { - return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp; -} - -bool SimulationOwner::pendingTake(uint64_t timestamp) { - return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp; -} - void SimulationOwner::clearCurrentOwner() { _id = QUuid(); _expiry = 0; diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 8e5ad42601..353255728c 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -19,75 +19,75 @@ #include // HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions -// of the same world. When portions overlap only one participant is allowed to be the authority for any -// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and -// their duty is to send transform/velocity updates for the entity to the central entity-server. -// The entity-server relays updates to other participants who apply them as "state synchronization" -// to their own simulation. +// of the same domain. Even when portions overlap only one participant is allowed to be the current +// authority for any particular object's physical simulation. The authoritative participant is called the +// "simulation owner" and its duty is to send "state synchronization" (transform/velocity) updates for an +// entity to the entity-server. The entity-server relays updates to other participants who apply them to +// their own simulation. // // Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update: -// { -// "simulationOwner": { "ownerID" : sessionID, "priority" : priority }, +// { "simulationOwner": { "ownerID" : sessionID, "priority" : priority }, // transform/velocity properties // } // -// The entity-server is the authority as to who owns what and may reject a bid. -// The rules for handling a bid are as follows: +// The entity-server is the authority as to who owns what and may reject a bid. The rules for handling a +// bid are as follows: // // (1) A bid may be refused for special ownership restrictions, but otherwise... // // (2) A bid at higher priority is accepted // -// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec) -// of the last ownership transition, otherwise it is accepted +// (3) A bid at equal priority is accepted unless it was received shortly after (within 200msec) of the +// last ownership change. This to avoid rapid ownership transitions should multiple participants +// bid simultaneously. // -// (4) The current owner is the only participant allowed to clear ownership (entity-server can override). +// (4) The current owner is the only participant allowed to clear their ownership or adjust priority. // -// (5) The current owner is the only participant allowed to adjust priority (entity-server can override). -// -// (6) If an owner does not update the transform or velocities of an owned entity within some period +// (5) If an owner does not update the transform or velocities of an owned entity within some period // (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle // the case when an owner drops off the network. // // The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules // for bidding are as follows: // -// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the -// simulation owner and priority as if they really did change but doesn't actually modify them -// locally. Thus, if the bid packet is lost the participant will re-send after some period. -// The participant only updates its knowledge of who owns what when it recieves an update from the -// entity-server. An exception is when the participant creates a moving entity: it assumes it starts -// off owning any moving entities it creates. +// (6) A participant (almost) never assumes its bid is accepted by the entity-server. It packs the +// simulation owner properties as if they really did change but doesn't actually modify them +// locally. Instead it waits to hear back from the entity-server for bid acceptance. If the entity +// remains unowned the participant will resend the bid (assuming the bid pakcet was lost). The +// exception is when the participant creates a moving entity: it assumes it starts off owning any +// moving entities it creates. // -// (8) When an unowned entity becomes active in the physics simulation the participant will -// start a timer and if the entity is still unowned after some period (0.5 seconds) -// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER +// (7) When an entity becomes active in the physics simulation but is not owned the participant will +// start a timer and if it is still unowned after expiry (0.5 seconds) the participant will +// bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER // priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to -// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership +// RECRUIT (=VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership // when multiple participants (with variable ping-times to the server) bid simultaneously for a // recently activated entity. // -// (9) When a participant changes an entity's transform/velocity (via script) it will bid at -// priority = POKE (=127). +// (8) When a participant's script changes an entity's transform/velocity the participant will bid at +// priority = POKE (=127) // -// (10) When an entity collides with MyAvatar the participant it will bid at priority = POKE. +// (9) When an entity collides against MyAvatar the participant will bid at priority = POKE. // -// (11) When a participant grabs (via script) an entity it will bid at priority = GRAB (=128). +// (10) When a participant grabs an entity it will bid at priority = GRAB (=128). This to allow UserA +// to whack UserB with a "sword" without losing ownership, since UserB will bid at POKE. If UserB +// wants to contest for ownership they must also GRAB it. // -// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will -// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger). +// (11) When EntityA, locally owned at priority = N, collides with an unowned EntityB the owner will +// also bid for EntityB at priority = N-1 (or VOLUNTEER, whichever is larger). // -// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will -// send an update to: clear their ownerhsip, set priority to zero, and set the object's -// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership -// has been cleared until it hears from the entity-server. Thus, if the packet is lost the -// owner will re-send after some period. +// (12) When an entity comes to rest and is deactivated in the physics simulation the owner will send +// an update to: clear their ownerhsip, set priority to zero, and set the entity's velocities to +// zero. As per a normal bid, the owner does NOT assume its ownership has been cleared until +// it hears back from the entity-server. Thus, if the packet is lost the owner will re-send after +// expiry. // -// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it -// immediately at priority = VOLUNTEER. +// (13) When an entity is still active but the owner no longer wants to own it, the owner will drop its +// priority to YIELD (=1, below VOLUNTEER) thereby signalling to other participants to bid for it. // -// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority -// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it. +// (14) When an entity's ownership priority drops to YIELD (=1, below VOLUNTEER) other participants may +// bid for it immediately at VOLUNTEER. // const uint8_t YIELD_SIMULATION_PRIORITY = 1; const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1; @@ -120,24 +120,18 @@ public: void clear(); void setPriority(uint8_t priority); - void promotePriority(uint8_t priority); // return true if id is changed bool setID(const QUuid& id); bool set(const QUuid& id, uint8_t priority); bool set(const SimulationOwner& owner); - void setPendingPriority(uint8_t priority, uint64_t timestamp); bool isNull() const { return _id.isNull(); } bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } void updateExpiry(); - bool hasExpired() const { return usecTimestampNow() > _expiry; } - uint8_t getPendingPriority() const { return _pendingBidPriority; } - bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE - bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE void clearCurrentOwner(); bool operator>=(uint8_t priority) const { return _priority >= priority; } @@ -154,10 +148,7 @@ public: private: QUuid _id; // owner uint64_t _expiry; // time when ownership can transition at equal priority - uint64_t _pendingBidTimestamp; // time when pending bid was set uint8_t _priority; // priority of current owner - uint8_t _pendingBidPriority; // priority at which we'd like to own it - uint8_t _pendingState; // NOTHING, TAKE, or RELEASE }; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp index a95426a06d..f1f388d501 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp @@ -72,7 +72,7 @@ int GL45Backend::makeResourceBufferSlots(const ShaderObject& shaderProgram, cons auto requestedBinding = slotBindings.find(info.name); if (requestedBinding != slotBindings.end()) { info.binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, info.index, info.binding); + glShaderStorageBlockBinding(glprogram, info.index, info.binding); resourceBufferSlotMap[info.binding] = info.index; } } @@ -88,7 +88,7 @@ int GL45Backend::makeResourceBufferSlots(const ShaderObject& shaderProgram, cons auto slotIt = std::find_if(resourceBufferSlotMap.begin(), resourceBufferSlotMap.end(), GLBackend::isUnusedSlot); if (slotIt != resourceBufferSlotMap.end()) { info.binding = slotIt - resourceBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, info.index, info.binding); + glShaderStorageBlockBinding(glprogram, info.index, info.binding); } else { // This should never happen, an active ssbo cannot find an available slot among the max available?! info.binding = -1; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index c342ecffc5..e90c3ff24c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -33,7 +33,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::CollisionMask16Bytes); + return static_cast(EntityVersion::YieldSimulationOwnership); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 64054fd080..9569f21da7 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -236,7 +236,8 @@ enum class EntityVersion : PacketVersion { ShadowControl, MaterialData, CloneableData, - CollisionMask16Bytes + CollisionMask16Bytes, + YieldSimulationOwnership }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index ad082c1a6e..96a55c8477 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME physics) setup_hifi_library() -link_hifi_libraries(shared fbx entities graphics) +link_hifi_libraries(shared task workload fbx entities graphics) include_hifi_library_headers(networking) include_hifi_library_headers(gpu) include_hifi_library_headers(avatars) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 594ea476f6..b78e496aad 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -80,7 +80,6 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); - _bidPriority = _entity->getPendingOwnershipPriority(); if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { // client-only entities are always thus, so we cache this fact in _ownershipState _ownershipState = EntityMotionState::OwnershipState::Unownable; @@ -140,28 +139,22 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { if (_entity->getSimulatorID().isNull()) { // simulation ownership has been removed if (glm::length2(_entity->getWorldVelocity()) == 0.0f) { - // this object is coming to rest --> clear the ACTIVATION flag and _bidPriority + // TODO: also check angularVelocity + // this object is coming to rest flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; _body->setActivationState(WANTS_DEACTIVATION); - _bidPriority = 0; const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet _body->setDeactivationTime(ACTIVATION_EXPIRY); } else { // disowned object is still moving --> start timer for ownership bid // TODO? put a delay in here proportional to distance from object? - upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _bumpedPriority = glm::max(_bumpedPriority, VOLUNTEER_SIMULATION_PRIORITY); _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } _loopsWithoutOwner = 0; _numInactiveUpdates = 0; - } else if (isLocallyOwned()) { - // we just inherited ownership, make sure our desired priority matches what we have - upgradeBidPriority(_entity->getSimulationPriority()); - } else { - // the entity is owned by someone else, so we clear _bidPriority here - // but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation - // in which case we may try to bid again - _bidPriority = 0; + } else if (!isLocallyOwned()) { + // the entity is owned by someone else _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _numInactiveUpdates = 0; } @@ -170,9 +163,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bit means one of the following: // (1) we own it but may need to change the priority OR... // (2) we don't own it but should bid (because a local script has been changing physics properties) - uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); - upgradeBidPriority(newPriority); - // reset bid expiry so that we bid ASAP _nextBidExpiry = 0; } @@ -298,7 +288,7 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) { - upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _bumpedPriority = glm::max(_bumpedPriority, VOLUNTEER_SIMULATION_PRIORITY); } } } @@ -325,21 +315,23 @@ void EntityMotionState::setShape(const btCollisionShape* shape) { } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { - // NOTE: we only get here if we think we own the simulation + // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // Since we own the simulation: make sure _bidPriority is not less than current owned priority - // because: an _bidPriority of zero indicates that we should drop ownership when we have it. - upgradeBidPriority(_entity->getSimulationPriority()); + // because: a _bidPriority of zero indicates that we should drop ownership in the send. + // TODO: need to be able to detect when logic dictates we *decrease* priority + // WIP: print info whenever _bidPriority mismatches what is known to the entity + + if (_entity->dynamicDataNeedsTransmit()) { + return true; + } bool parentTransformSuccess; Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); Transform worldToLocal; - Transform worldVelocityToLocal; if (parentTransformSuccess) { localToWorld.evalInverse(worldToLocal); - worldVelocityToLocal = worldToLocal; - worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); } int numSteps = simulationStep - _lastStep; @@ -388,12 +380,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } } - if (_entity->dynamicDataNeedsTransmit()) { - uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; - upgradeBidPriority(priority); - return true; - } - // Else we measure the error between current and extrapolated transform (according to expected behavior // of remote EntitySimulation) and return true if the error is significant. @@ -438,6 +424,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { + // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. @@ -462,6 +449,7 @@ void EntityMotionState::updateSendVelocities() { if (!_body->isActive()) { // make sure all derivatives are zero clearObjectVelocities(); + // we pretend we sent the inactive update for this object _numInactiveUpdates = 1; } else { glm::vec3 gravity = _entity->getGravity(); @@ -526,10 +514,10 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s properties.setLastEdited(now); // we don't own the simulation for this entity yet, but we're sending a bid for it - uint8_t bidPriority = glm::max(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY); - properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); - // copy _bidPriority into pendingPriority... - _entity->setPendingOwnershipPriority(_bidPriority, now); + uint8_t finalBidPriority = computeFinalBidPriority(); + _entity->clearScriptSimulationPriority(); + properties.setSimulationOwner(Physics::getSessionUUID(), finalBidPriority); + _entity->setPendingOwnershipPriority(finalBidPriority); EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -548,10 +536,10 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s _lastStep = step; _nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS; - // finally: clear _bidPriority - // which will may get promoted before next bid - // or maybe we'll win simulation ownership - _bidPriority = 0; + // after sending a bid/update we clear _bumpedPriority + // which might get promoted again next frame (after local script or simulation interaction) + // or we might win the bid + _bumpedPriority = 0; } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { @@ -590,22 +578,30 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setLastEdited(now); _entity->setSimulationOwnershipExpiry(now + MAX_OUTGOING_SIMULATION_UPDATE_PERIOD); - if (_numInactiveUpdates > 0) { + if (_numInactiveUpdates > 0 && _entity->getScriptSimulationPriority() == 0) { // the entity is stopped and inactive so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); - _bidPriority = 0; - _entity->setPendingOwnershipPriority(_bidPriority, now); - } else if (_bidPriority != _entity->getSimulationPriority()) { - // our desired priority has changed - if (_bidPriority == 0) { - // we should release ownership - properties.clearSimulationOwner(); - } else { - // we just need to change the priority - properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority); + _entity->setPendingOwnershipPriority(0); + } else { + uint8_t newPriority = computeFinalBidPriority(); + _entity->clearScriptSimulationPriority(); + // if we get here then we own the simulation and the object is NOT going inactive + // if newPriority is zero, then it must be outside of R1, which means we should really set it to YIELD + // which we achive by just setting it to the max of the two + newPriority = glm::max(newPriority, YIELD_SIMULATION_PRIORITY); + if (newPriority != _entity->getSimulationPriority() && + !(newPriority == VOLUNTEER_SIMULATION_PRIORITY && _entity->getSimulationPriority() == RECRUIT_SIMULATION_PRIORITY)) { + // our desired priority has changed + if (newPriority == 0) { + // we should release ownership + properties.clearSimulationOwner(); + } else { + // we just need to inform the entity-server + properties.setSimulationOwner(Physics::getSessionUUID(), newPriority); + } + _entity->setPendingOwnershipPriority(newPriority); } - _entity->setPendingOwnershipPriority(_bidPriority, now); } EntityItemID id(_entity->getID()); @@ -640,6 +636,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ }); _lastStep = step; + + // after sending a bid/update we clear _bumpedPriority + // which might get promoted again next frame (after local script or simulation interaction) + // or we might win the bid + _bumpedPriority = 0; } uint32_t EntityMotionState::getIncomingDirtyFlags() { @@ -686,7 +687,7 @@ uint8_t EntityMotionState::getSimulationPriority() const { } void EntityMotionState::slaveBidPriority() { - upgradeBidPriority(_entity->getSimulationPriority()); + _bumpedPriority = glm::max(_bumpedPriority, _entity->getSimulationPriority()); } // virtual @@ -697,7 +698,7 @@ QUuid EntityMotionState::getSimulatorID() const { void EntityMotionState::bump(uint8_t priority) { assert(priority != 0); - upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + _bumpedPriority = glm::max(_bumpedPriority, --priority); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -765,7 +766,6 @@ void EntityMotionState::setMotionType(PhysicsMotionType motionType) { resetMeasuredBodyAcceleration(); } - // virtual QString EntityMotionState::getName() const { assert(entityTreeIsLocked()); @@ -777,15 +777,15 @@ void EntityMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma _entity->computeCollisionGroupAndFinalMask(group, mask); } -bool EntityMotionState::shouldSendBid() { - if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) { - return true; - } else { - // NOTE: this 'else' case has a side-effect: it clears _bidPriority - // which may be updated next simulation step (via collision or script event) - _bidPriority = 0; - return false; - } +bool EntityMotionState::shouldSendBid() const { + // NOTE: this method is only ever called when the entity's simulation is NOT locally owned + return _body->isActive() && (_region == workload::Region::R1) && + glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority(); +} + +uint8_t EntityMotionState::computeFinalBidPriority() const { + return (_region == workload::Region::R1) ? + glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) : 0; } bool EntityMotionState::isLocallyOwned() const { @@ -793,8 +793,17 @@ bool EntityMotionState::isLocallyOwned() const { } bool EntityMotionState::isLocallyOwnedOrShouldBe() const { - return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) || - _entity->getSimulatorID() == Physics::getSessionUUID(); + // this method could also be called "shouldGenerateCollisionEventForLocalScripts()" + // because that is the only reason it's used + if (_entity->getSimulatorID() == Physics::getSessionUUID()) { + return true; + } else { + return computeFinalBidPriority() > glm::max(VOLUNTEER_SIMULATION_PRIORITY, _entity->getSimulationPriority()); + } +} + +void EntityMotionState::setRegion(uint8_t region) { + _region = region; } void EntityMotionState::initForBid() { @@ -807,10 +816,6 @@ void EntityMotionState::initForOwned() { _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; } -void EntityMotionState::upgradeBidPriority(uint8_t priority) { - _bidPriority = glm::max(_bidPriority, priority); -} - void EntityMotionState::clearObjectVelocities() const { // If transform or velocities are flagged as dirty it means a network or scripted change // occured between the beginning and end of the stepSimulation() and we DON'T want to apply diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 603130b5ff..c2d4f8d87e 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -14,6 +14,7 @@ #include #include +#include #include "ObjectMotionState.h" @@ -86,13 +87,17 @@ public: virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; - bool shouldSendBid(); + bool shouldSendBid() const; + uint8_t computeFinalBidPriority() const; + bool isLocallyOwned() const override; bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() friend class PhysicalEntitySimulation; OwnershipState getOwnershipState() const { return _ownershipState; } + void setRegion(uint8_t region); + protected: void updateSendVelocities(); uint64_t getNextBidExpiry() const { return _nextBidExpiry; } @@ -102,11 +107,7 @@ protected: void updateServerPhysicsVariables(); bool remoteSimulationOutOfSync(uint32_t simulationStep); - // changes _bidPriority only if priority is larger - void upgradeBidPriority(uint8_t priority); - - // upgradeBidPriority to value stored in _entity - void slaveBidPriority(); + void slaveBidPriority(); // computeNewBidPriority() with value stored in _entity void clearObjectVelocities() const; @@ -154,8 +155,8 @@ protected: uint8_t _loopsWithoutOwner; mutable uint8_t _accelerationNearlyGravityCount; uint8_t _numInactiveUpdates { 1 }; - uint8_t _bidPriority { 0 }; - bool _serverVariablesSet { false }; + uint8_t _bumpedPriority { 0 }; // the target simulation priority according to collision history + uint8_t _region { workload::Region::INVALID }; bool isServerlessMode(); }; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index e2970d6a03..ab6c543b17 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -48,12 +48,17 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { QMutexLocker lock(&_mutex); assert(entity); assert(!entity->isDead()); - if (entity->shouldBePhysical()) { + uint8_t region = _space->getRegion(entity->getSpaceIndex()); + bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical(); + bool canBeKinematic = region <= workload::Region::R3; + if (shouldBePhysical) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (!motionState) { + if (motionState) { + motionState->setRegion(region); + } else { _entitiesToAddToPhysics.insert(entity); } - } else if (entity->isMovingRelativeToParent()) { + } else if (canBeKinematic && entity->isMovingRelativeToParent()) { _simpleKinematicEntities.insert(entity); } } @@ -120,25 +125,43 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { QMutexLocker lock(&_mutex); assert(entity); EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + uint8_t region = _space->getRegion(entity->getSpaceIndex()); + bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical(); + bool canBeKinematic = region <= workload::Region::R3; if (motionState) { - if (!entity->shouldBePhysical()) { - // the entity should be removed from the physical simulation + if (!shouldBePhysical) { + if (motionState->isLocallyOwned()) { + // zero velocities by first deactivating the RigidBody + btRigidBody* body = motionState->getRigidBody(); + if (body) { + body->forceActivationState(ISLAND_SLEEPING); + motionState->updateSendVelocities(); // has side-effect of zeroing entity velocities for inactive body + } + + // send packet to remove ownership + // NOTE: this packet will NOT be resent if lost, but the good news is: + // the entity-server will eventually clear velocity and ownership for timeout + motionState->sendUpdate(_entityPacketSender, _physicsEngine->getNumSubsteps()); + } + + // remove from the physical simulation _incomingChanges.remove(motionState); _physicalObjects.remove(motionState); removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); - if (entity->isMovingRelativeToParent()) { + if (canBeKinematic && entity->isMovingRelativeToParent()) { _simpleKinematicEntities.insert(entity); } } else { _incomingChanges.insert(motionState); } - } else if (entity->shouldBePhysical()) { + motionState->setRegion(region); + } else if (shouldBePhysical) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. // Perhaps it's shape has changed and it can now be added? _entitiesToAddToPhysics.insert(entity); _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic - } else if (entity->isMovingRelativeToParent()) { + } else if (canBeKinematic && entity->isMovingRelativeToParent()) { _simpleKinematicEntities.insert(entity); } else { _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic @@ -187,18 +210,21 @@ const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhys for (auto entity: _entitiesToRemoveFromPhysics) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); assert(motionState); + // TODO CLEan this, just a n extra check to avoid the crash that shouldn;t happen + if (motionState) { - _entitiesToAddToPhysics.remove(entity); - if (entity->isDead() && entity->getElement()) { - _deadEntities.insert(entity); + _entitiesToAddToPhysics.remove(entity); + if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); + } + + _incomingChanges.remove(motionState); + removeOwnershipData(motionState); + _physicalObjects.remove(motionState); + + // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) + _objectsToDelete.push_back(motionState); } - - _incomingChanges.remove(motionState); - removeOwnershipData(motionState); - _physicalObjects.remove(motionState); - - // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) - _objectsToDelete.push_back(motionState); } _entitiesToRemoveFromPhysics.clear(); return _objectsToDelete; @@ -249,6 +275,9 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re _physicalObjects.insert(motionState); result.push_back(motionState); entityItr = _entitiesToAddToPhysics.erase(entityItr); + + // make sure the motionState's region is up-to-date before it is actually added to physics + motionState->setRegion(_space->getRegion(entity->getSpaceIndex())); } else { //qWarning() << "Failed to generate new shape for entity." << entity->getName(); ++entityItr; diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 7b6fe221fb..3f04347f18 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -19,6 +19,7 @@ #include #include +#include #include "PhysicsEngine.h" #include "EntityMotionState.h" @@ -45,6 +46,7 @@ public: ~PhysicalEntitySimulation(); void init(EntityTreePointer tree, PhysicsEnginePointer engine, EntityEditPacketSender* packetSender); + void setWorkloadSpace(const workload::SpacePointer space) { _space = space; } virtual void addDynamic(EntityDynamicPointer dynamic) override; virtual void applyDynamicChanges() override; @@ -102,6 +104,7 @@ private: VectorOfEntityMotionStates _owned; VectorOfEntityMotionStates _bids; + workload::SpacePointer _space; uint64_t _nextBidExpiry; uint32_t _lastStepSendPackets { 0 }; }; diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf new file mode 100644 index 0000000000..1304e68c7f --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -0,0 +1,32 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// drawItemBounds.frag +// fragment shader +// +// Created by Sam Gateau on 6/29/15. +// 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 DeferredBufferWrite.slh@> +<@include gpu/Paint.slh@> + +in vec4 varColor; +in vec3 varTexcoord; + +void main(void) { + if (varColor.w > 0.0) { + float r = sqrt(dot(varTexcoord.xyz,varTexcoord.xyz)); + float a = paintStripe(r * varColor.w, 0.0, 1.0 / varColor.w, 0.05 / varColor.w); + if (a <= 0.1 || r > 1.1) { + discard; + } + } + + packDeferredFragmentUnlit( + vec3(0.0, 1.0, 0.0), + 1.0, + varColor.rgb); +} diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv new file mode 100644 index 0000000000..64fb335fd6 --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -0,0 +1,94 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// drawItemBounds.slv +// vertex shader +// +// Created by Sam Gateau on 6/29/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()$> + +<@include gpu/Color.slh@> +<$declareColorWheel()$> + +uniform vec4 inColor; + + +struct WorkloadProxy { + vec4 sphere; + vec4 region; +}; + +#if defined(GPU_GL410) +uniform samplerBuffer workloadProxiesBuffer; +WorkloadProxy getWorkloadProxy(int i) { + int offset = 2 * i; + WorkloadProxy proxy; + proxy.sphere = texelFetch(workloadProxiesBuffer, offset); + proxy.region = texelFetch(workloadProxiesBuffer, offset + 1); + return proxy; +} +#else +layout(std140) buffer workloadProxiesBuffer { + WorkloadProxy _proxies[]; +}; +WorkloadProxy getWorkloadProxy(int i) { + WorkloadProxy proxy = _proxies[i]; + return proxy; +} +#endif + + + +out vec4 varColor; +out vec3 varTexcoord; + +void main(void) { + const vec4 UNIT_SPRITE[3] = vec4[3]( + vec4(-1.0, -1.0, 0.0, 1.0), + vec4(3.0, -1.0, 0.0, 1.0), + vec4(-1.0, 3.0, 0.0, 1.0) + ); + const int UNIT_SPRITE_INDICES[3] = int[3]( + 0, 1, 2 + ); + int proxyID = gl_VertexID / 3; + int vertexID = gl_VertexID - proxyID * 3; + + vec4 spriteVert = UNIT_SPRITE[UNIT_SPRITE_INDICES[vertexID]]; + + WorkloadProxy proxy = getWorkloadProxy(proxyID); + vec4 proxyPosWorld = vec4(proxy.sphere.xyz, 1.0); + + // standard transform, bring proxy in view space + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + vec4 proxyPosEye; + <$transformModelToEyePos(cam, obj, proxyPosWorld, proxyPosEye)$> + + // Define the billboarded space + vec3 dirZ = -normalize(proxyPosEye.xyz); + vec3 dirX = normalize(cross(vec3(0.0, 1.0, 0.0), dirZ)); + vec3 dirY = vec3(0.0, 1.0, 0.0); + + vec4 pos = vec4(proxyPosEye.xyz + proxy.sphere.w * ( dirX * spriteVert.x + dirY * spriteVert.y /* + dirZ * spriteVert.z*/), 1.0); + varTexcoord = spriteVert.xyz; + <$transformEyeToClipPos(cam, pos, gl_Position)$> + + // Convert region to color + int region = floatBitsToInt(proxy.region.x); + region = (0x000000FF & region); + + varColor = vec4(colorWheel(float(region) / 4.0), proxy.sphere.w); + + if (region == 4) { + gl_Position = vec4(0.0); + } +} diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf new file mode 100644 index 0000000000..1304e68c7f --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -0,0 +1,32 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// drawItemBounds.frag +// fragment shader +// +// Created by Sam Gateau on 6/29/15. +// 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 DeferredBufferWrite.slh@> +<@include gpu/Paint.slh@> + +in vec4 varColor; +in vec3 varTexcoord; + +void main(void) { + if (varColor.w > 0.0) { + float r = sqrt(dot(varTexcoord.xyz,varTexcoord.xyz)); + float a = paintStripe(r * varColor.w, 0.0, 1.0 / varColor.w, 0.05 / varColor.w); + if (a <= 0.1 || r > 1.1) { + discard; + } + } + + packDeferredFragmentUnlit( + vec3(0.0, 1.0, 0.0), + 1.0, + varColor.rgb); +} diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv new file mode 100644 index 0000000000..f5497d250c --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -0,0 +1,120 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// drawItemBounds.slv +// vertex shader +// +// Created by Sam Gateau on 6/29/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()$> + +<@include gpu/Color.slh@> +<$declareColorWheel()$> + +uniform vec4 inColor; + + +struct WorkloadView { + vec4 direction_far; + vec4 fov; + vec4 origin; + vec4 backFront[2]; + vec4 regions[3]; +}; + +#if defined(GPU_GL410) +uniform samplerBuffer workloadViewsBuffer; +WorkloadView getWorkloadView(int i) { + int offset = 2 * i; + WorkloadView view; + view.origin = texelFetch(workloadViewsBuffer, offset); + view.radiuses = texelFetch(workloadViewsBuffer, offset + 1); + return view; +} +#else +layout(std140) buffer workloadViewsBuffer { + WorkloadView _views[]; +}; +WorkloadView getWorkloadView(int i) { + WorkloadView view = _views[i]; + return view; +} +#endif + + + + + +out vec4 varColor; +out vec3 varTexcoord; + +const int NUM_VERTICES_PER_SEGMENT = 2; +const int NUM_SEGMENT_PER_VIEW_REGION = 65; +const int NUM_VERTICES_PER_VIEW_REGION = NUM_SEGMENT_PER_VIEW_REGION * NUM_VERTICES_PER_SEGMENT; +const int NUM_REGIONS_PER_VIEW = 3; +const int NUM_VERTICES_PER_VIEW = NUM_VERTICES_PER_VIEW_REGION * NUM_REGIONS_PER_VIEW; + + +layout(std140) uniform drawMeshBuffer { + vec4 verts[NUM_SEGMENT_PER_VIEW_REGION]; +}; + +void main(void) { + int viewID = gl_VertexID / NUM_VERTICES_PER_VIEW; + int viewVertexID = gl_VertexID - viewID * NUM_VERTICES_PER_VIEW; + + int regionID = viewVertexID / NUM_VERTICES_PER_VIEW_REGION; + int regionVertexID = viewVertexID - regionID * NUM_VERTICES_PER_VIEW_REGION; + + int segmentID = regionVertexID / NUM_VERTICES_PER_SEGMENT; + int segmentVertexID = regionVertexID - segmentID * NUM_VERTICES_PER_SEGMENT; + + vec4 segment = verts[segmentID]; + + vec4 spriteVert = vec4(segment.y, 0.0, segment.x, 1.0); + vec3 spriteTan = vec3(segment.x, 0.0, -segment.y); + + vec3 lateralDir = vec3(0.0, -1.0 + 2.0 * float(segmentVertexID), 0.0); + + WorkloadView view = getWorkloadView(viewID); + vec4 region = view.regions[regionID]; + vec4 proxyPosWorld = vec4(region.xyz, 1.0); + float regionRadius = region.w; + + // Define the sprite space + vec3 dirZ = -normalize(view.direction_far.xyz); + vec3 dirY = vec3(0.0, 1.0, 0.0); + vec3 dirX = normalize(cross(dirY, dirZ)); + dirY = normalize(cross(dirZ, dirX)); + + spriteVert.xyz *= regionRadius; + vec3 originSpaceVert = (dirX * spriteVert.x + dirY * spriteVert.y + dirZ * spriteVert.z); + vec4 pos = vec4(proxyPosWorld.xyz + originSpaceVert, 1.0); + + vec3 originSpaceTan = normalize(dirX * spriteTan.x + dirY * spriteTan.y + dirZ * spriteTan.z); + + // standard transform, bring pos in view space + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + vec4 posEye; + <$transformModelToEyePos(cam, obj, pos, posEye)$> + vec3 tanEye; + <$transformModelToEyeDir(cam, obj, originSpaceTan, tanEye)$> + + lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); + posEye.xyz += (0.05 * (regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + + <$transformEyeToClipPos(cam, posEye, gl_Position)$> + + varTexcoord = spriteVert.xyz; + + // Convert region to color + varColor = vec4(colorWheel(float(regionID) / 4.0), -1.0); +} diff --git a/libraries/task/src/task/Config.h b/libraries/task/src/task/Config.h index 00fa01808b..4379dbbaa6 100644 --- a/libraries/task/src/task/Config.h +++ b/libraries/task/src/task/Config.h @@ -201,8 +201,6 @@ public: class TaskConfig : public JobConfig { Q_OBJECT public: - using QConfigPointer = std::shared_ptr; - using Persistent = PersistentConfig; TaskConfig() = default; @@ -231,7 +229,8 @@ public: if (tokens.empty()) { tokens.push_back(QString()); - } else { + } + else { while (tokens.size() > 1) { auto name = tokens.front(); tokens.pop_front(); diff --git a/libraries/task/src/task/Task.h b/libraries/task/src/task/Task.h index c06ec05121..60ccd26b70 100644 --- a/libraries/task/src/task/Task.h +++ b/libraries/task/src/task/Task.h @@ -179,6 +179,7 @@ public: const std::string& getName() const { return _concept->getName(); } const Varying getInput() const { return _concept->getInput(); } const Varying getOutput() const { return _concept->getOutput(); } + QConfigPointer& getConfiguration() const { return _concept->getConfiguration(); } void applyConfiguration() { return _concept->applyConfiguration(); } @@ -376,6 +377,7 @@ public: using Context = JC; using ContextPointer = std::shared_ptr; using Config = TaskConfig; + using TaskType = Task; using ConceptPointer = typename TaskType::ConceptPointer; diff --git a/libraries/workload/CMakeLists.txt b/libraries/workload/CMakeLists.txt index 3fcf00e0e9..beaed55734 100644 --- a/libraries/workload/CMakeLists.txt +++ b/libraries/workload/CMakeLists.txt @@ -1,5 +1,3 @@ set(TARGET_NAME workload) setup_hifi_library() - -link_hifi_libraries(shared) - +link_hifi_libraries(shared task) diff --git a/libraries/workload/src/workload/Engine.cpp b/libraries/workload/src/workload/Engine.cpp new file mode 100644 index 0000000000..fcce6025df --- /dev/null +++ b/libraries/workload/src/workload/Engine.cpp @@ -0,0 +1,19 @@ +// +// Engine.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.08 +// Copyright 2018 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 "Engine.h" + +#include + +namespace workload { + WorkloadContext::WorkloadContext(const SpacePointer& space) : task::JobContext(), _space(space) {} +} // namespace workload + diff --git a/libraries/workload/src/workload/Engine.h b/libraries/workload/src/workload/Engine.h new file mode 100644 index 0000000000..7df8d8dd1a --- /dev/null +++ b/libraries/workload/src/workload/Engine.h @@ -0,0 +1,43 @@ +// +// Engine.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.08 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// 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_workload_Engine_h +#define hifi_workload_Engine_h + +#include +#include + +#include + +#include "Space.h" + +namespace workload { + class WorkloadContext : public task::JobContext { + public: + WorkloadContext(const SpacePointer& space); + virtual ~WorkloadContext() {} + + SpacePointer _space; + }; + + using WorkloadContextPointer = std::shared_ptr; + + Task_DeclareCategoryTimeProfilerClass(WorkloadTimeProfiler, trace_workload); + + // Instanciate the specialized types of Job Engine and Task for the Workload context + Task_DeclareTypeAliases(WorkloadContext, WorkloadTimeProfiler) + using EnginePointer = std::shared_ptr; +} // namespace workload + +#endif // hifi_workload_Space_h diff --git a/libraries/workload/src/workload/Proxy.h b/libraries/workload/src/workload/Proxy.h new file mode 100644 index 0000000000..8e0be6f6f7 --- /dev/null +++ b/libraries/workload/src/workload/Proxy.h @@ -0,0 +1,59 @@ +// +// Proxy.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.01.30 +// Copyright 2018 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_workload_Proxy_h +#define hifi_workload_Proxy_h + +#include "View.h" + +namespace workload { + +class Owner { +public: + Owner() = default; + Owner(const Owner& other) = default; + Owner& operator=(const Owner& other) = default; + template Owner(const T& data) : _concept(std::make_shared>(data)) {} + ~Owner() {} + template const T get() const { return std::static_pointer_cast>(_concept)->_data; } +protected: + class Concept { + public: + virtual ~Concept() = default; + }; + template class Model : public Concept { + public: + using Data = T; + Data _data; + Model(const Data& data) : _data(data) {} + virtual ~Model() = default; + }; +private: + std::shared_ptr _concept; +}; + +class Proxy { +public: + Proxy() : sphere(0.0f) {} + Proxy(const Sphere& s) : sphere(s) {} + + Sphere sphere; + uint8_t region{ Region::INVALID }; + uint8_t prevRegion{ Region::INVALID }; + uint16_t _padding; + uint32_t _paddings[3]; + + using Vector = std::vector; +}; + + +} // namespace workload + +#endif // hifi_workload_Proxy_h diff --git a/libraries/workload/src/workload/Region.h b/libraries/workload/src/workload/Region.h new file mode 100644 index 0000000000..43aed9aef4 --- /dev/null +++ b/libraries/workload/src/workload/Region.h @@ -0,0 +1,74 @@ +// +// Region.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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_workload_Region_h +#define hifi_workload_Region_h + +namespace workload { + +class Region { +public: + using Type = uint8_t; + + enum Name : uint8_t { + R1 = 0, + R2, + R3, + UNKNOWN, + INVALID, + }; + + static const uint8_t NUM_CLASSIFICATIONS = 4; + static const uint8_t NUM_TRANSITIONS = NUM_CLASSIFICATIONS * (NUM_CLASSIFICATIONS - 1); + + static const uint8_t NUM_VIEW_REGIONS = (NUM_CLASSIFICATIONS - 1); + + static uint8_t computeTransitionIndex(uint8_t prevIndex, uint8_t newIndex); + +}; + +inline uint8_t Region::computeTransitionIndex(uint8_t prevIndex, uint8_t newIndex) { + // given prevIndex and newIndex compute an index into the transition list, + // where the lists between unchanged indices don't exist (signaled by index = -1). + // + // Given an NxN array + // let p = i + N * j + // + // then k = -1 when i == j + // = p - (1 + p/(N+1)) when i != j + // + // i 0 1 2 3 + // j +-------+-------+-------+-------+ + // |p = 0 | 1 | 2 | 3 | + // 0 | | | | | + // |k = -1 | 0 | 1 | 2 | + // +-------+-------+-------+-------+ + // | 4 | 5 | 6 | 7 | + // 1 | | | | | + // | 3 | -1 | 4 | 5 | + // +-------+-------+-------+-------+ + // | 8 | 9 | 10 | 11 | + // 2 | | | | | + // | 6 | 7 | -1 | 8 | + // +-------+-------+-------+-------+ + // | 12 | 13 | 14 | 15 | + // 3 | | | | | + // | 9 | 10 | 11 | -1 | + // +-------+-------+-------+-------+ + uint8_t p = prevIndex + Region::NUM_CLASSIFICATIONS * newIndex; + if (0 == (p % (Region::NUM_CLASSIFICATIONS + 1))) { + return -1; + } + return p - (1 + p / (Region::NUM_CLASSIFICATIONS + 1)); +} + +} // namespace workload + +#endif // hifi_workload_Region_h \ No newline at end of file diff --git a/libraries/workload/src/workload/RegionState.cpp b/libraries/workload/src/workload/RegionState.cpp new file mode 100644 index 0000000000..47179ad6f7 --- /dev/null +++ b/libraries/workload/src/workload/RegionState.cpp @@ -0,0 +1,76 @@ +// +// RegionState.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.03.07 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "RegionState.h" + +using namespace workload; + +void RegionState::configure(const Config& config) { +} + + +void RegionState::run(const workload::WorkloadContextPointer& renderContext, const Inputs& inputs) { + // inputs is a vector of vectors of proxyId's: + // + // inputs[0] = vector of ids exiting region 0 + // inputs[1] = vector of ids entering region 0 + // ... + // inputs[2N] = vector of ids exiting region N + // inputs[2N + 1] = vector of ids entering region N + assert(inputs.size() == 2 * Region::UNKNOWN); + + // The id's in each vector are sorted in ascending order + // because the source vectors are scanned in ascending order. + + for (uint32_t i = 0; i < _state.size(); ++i) { + const IndexVector& going = inputs[2 * i]; + const IndexVector& coming = inputs[2 * i + 1]; + if (coming.size() == 0 && going.size() == 0) { + continue; + } + if (_state[i].empty()) { + assert(going.empty()); + _state[i] = coming; + } else { + // NOTE: all vectors are sorted by proxyId! + // which means we can build the new vector by walking three vectors (going, current, coming) in one pass + IndexVector& oldState = _state[i]; + IndexVector newState; + newState.reserve(oldState.size() - going.size() + coming.size()); + uint32_t goingIndex = 0; + uint32_t comingIndex = 0; + for (uint32_t j = 0; j < oldState.size(); ++j) { + int32_t proxyId = oldState[j]; + while (comingIndex < coming.size() && coming[comingIndex] < proxyId) { + newState.push_back(coming[comingIndex]); + ++comingIndex; + } + if (goingIndex < going.size() && going[goingIndex] == proxyId) { + ++goingIndex; + } else { + newState.push_back(proxyId); + } + } + assert(goingIndex == going.size()); + while (comingIndex < coming.size()) { + newState.push_back(coming[comingIndex]); + ++comingIndex; + } + oldState.swap(newState); + } + } + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->setNum(0, (uint32_t) _state[0].size(), (uint32_t) _state[1].size(), (uint32_t) _state[2].size()); +} diff --git a/libraries/workload/src/workload/RegionState.h b/libraries/workload/src/workload/RegionState.h new file mode 100644 index 0000000000..40db9d4982 --- /dev/null +++ b/libraries/workload/src/workload/RegionState.h @@ -0,0 +1,67 @@ +// +// RegionState.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.03.07 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// 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_workload_RegionState_h +#define hifi_workload_RegionState_h + +#include "Space.h" +#include "Engine.h" + +namespace workload { + class RegionStateConfig : public Job::Config{ + Q_OBJECT + Q_PROPERTY(float numR0 READ getNumR0 NOTIFY dirty) + Q_PROPERTY(float numR1 READ getNumR1 NOTIFY dirty) + Q_PROPERTY(float numR2 READ getNumR2 NOTIFY dirty) + Q_PROPERTY(float numR3 READ getNumR3 NOTIFY dirty) + public: + + uint32_t getNumR0() const { return data.numR0; } + uint32_t getNumR1() const { return data.numR1; } + uint32_t getNumR2() const { return data.numR2; } + uint32_t getNumR3() const { return data.numR3; } + + void setNum(const uint32_t r0, const uint32_t r1, const uint32_t r2, const uint32_t r3) { + data.numR0 = r0; data.numR1 = r1; data.numR2 = r2; data.numR3 = r3; emit dirty(); + } + + struct Data { + uint32_t numR0{ 0 }; + uint32_t numR1{ 0 }; + uint32_t numR2{ 0 }; + uint32_t numR3{ 0 }; + } data; + + signals: + void dirty(); + }; + + class RegionState { + public: + using Config = RegionStateConfig; + using Inputs = IndexVectors; + using JobModel = workload::Job::ModelI; + + RegionState() { + _state.resize(Region::UNKNOWN); + } + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, const Inputs& inputs); + + protected: + IndexVectors _state; + }; +} +#endif // hifi_workload_RegionState_h diff --git a/libraries/workload/src/workload/RegionTracker.cpp b/libraries/workload/src/workload/RegionTracker.cpp new file mode 100644 index 0000000000..381b92c414 --- /dev/null +++ b/libraries/workload/src/workload/RegionTracker.cpp @@ -0,0 +1,50 @@ +// +// RegionTracker.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "RegionTracker.h" + +#include "Region.h" + +using namespace workload; + +void RegionTracker::configure(const Config& config) { +} + +void RegionTracker::run(const WorkloadContextPointer& context, Outputs& outputs) { + auto& outChanges = outputs.edit0(); + auto& outRegionChanges = outputs.edit1(); + + + outChanges.clear(); + outRegionChanges.clear(); + + auto space = context->_space; + if (space) { + //Changes changes; + space->categorizeAndGetChanges(outChanges); + + // use exit/enter lists for each region less than Region::UNKNOWN + outRegionChanges.resize(2 * (workload::Region::NUM_CLASSIFICATIONS - 1)); + for (uint32_t i = 0; i < outChanges.size(); ++i) { + Space::Change& change = outChanges[i]; + if (change.prevRegion < Region::UNKNOWN) { + // EXIT list index = 2 * regionIndex + outRegionChanges[2 * change.prevRegion].push_back(change.proxyId); + } + if (change.region < Region::UNKNOWN) { + // ENTER list index = 2 * regionIndex + 1 + outRegionChanges[2 * change.region + 1].push_back(change.proxyId); + } + } + } +} diff --git a/libraries/workload/src/workload/RegionTracker.h b/libraries/workload/src/workload/RegionTracker.h new file mode 100644 index 0000000000..29a9af1f84 --- /dev/null +++ b/libraries/workload/src/workload/RegionTracker.h @@ -0,0 +1,44 @@ +// +// RegionTracker.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// 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_workload_RegionTracker_h +#define hifi_workload_RegionTracker_h + +#include "Space.h" +#include "Engine.h" + +namespace workload { + + class RegionTrackerConfig : public Job::Config { + Q_OBJECT + public: + RegionTrackerConfig() : Job::Config(true) {} + }; + + class RegionTracker { + public: + using Config = RegionTrackerConfig; + using Outputs = VaryingSet2; + using JobModel = workload::Job::ModelO; + + RegionTracker() {} + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, Outputs& outputs); + + protected: + }; +} // namespace workload + +#endif // hifi_workload_RegionTracker_h diff --git a/libraries/workload/src/workload/Space.cpp b/libraries/workload/src/workload/Space.cpp index 1e2bfb228a..27a8639f3a 100644 --- a/libraries/workload/src/workload/Space.cpp +++ b/libraries/workload/src/workload/Space.cpp @@ -13,72 +13,92 @@ // #include "Space.h" - +#include #include #include using namespace workload; -int32_t Space::createProxy(const Space::Sphere& newSphere) { - if (_freeIndices.empty()) { - _proxies.emplace_back(Space::Proxy(newSphere)); - return (int32_t)_proxies.size() - 1; - } else { - int32_t index = _freeIndices.back(); - _freeIndices.pop_back(); - _proxies[index].sphere = newSphere; - _proxies[index].region = Space::REGION_UNKNOWN; - _proxies[index].prevRegion = Space::REGION_UNKNOWN; - return index; +Space::Space() : Collection() { +} + +void Space::processTransactionFrame(const Transaction& transaction) { + std::unique_lock lock(_proxiesMutex); + // Here we should be able to check the value of last ProxyID allocated + // and allocate new proxies accordingly + ProxyID maxID = _IDAllocator.getNumAllocatedIndices(); + if (maxID > (Index) _proxies.size()) { + _proxies.resize(maxID + 100); // allocate the maxId and more + _owners.resize(maxID + 100); + } + // Now we know for sure that we have enough items in the array to + // capture anything coming from the transaction + + processResets(transaction._resetItems); + processUpdates(transaction._updatedItems); + processRemoves(transaction._removedItems); +} + +void Space::processResets(const Transaction::Resets& transactions) { + for (auto& reset : transactions) { + // Access the true item + auto proxyID = std::get<0>(reset); + auto& item = _proxies[proxyID]; + + // Reset the item with a new payload + item.sphere = (std::get<1>(reset)); + item.prevRegion = item.region = Region::UNKNOWN; + + _owners[proxyID] = (std::get<2>(reset)); } } -void Space::deleteProxy(int32_t proxyId) { - if (proxyId >= (int32_t)_proxies.size() || _proxies.empty()) { - return; +void Space::processRemoves(const Transaction::Removes& transactions) { + for (auto removedID : transactions) { + _IDAllocator.freeIndex(removedID); + + // Access the true item + auto& item = _proxies[removedID]; + + // Kill it + item.prevRegion = item.region = Region::INVALID; + _owners[removedID] = Owner(); } - if (proxyId == (int32_t)_proxies.size() - 1) { - // remove proxy on back - _proxies.pop_back(); - if (!_freeIndices.empty()) { - // remove any freeIndices on back - std::sort(_freeIndices.begin(), _freeIndices.end()); - while(!_freeIndices.empty() && _freeIndices.back() == (int32_t)_proxies.size() - 1) { - _freeIndices.pop_back(); - _proxies.pop_back(); - } +} + +void Space::processUpdates(const Transaction::Updates& transactions) { + for (auto& update : transactions) { + auto updateID = std::get<0>(update); + if (updateID == INVALID_PROXY_ID) { + continue; } - } else { - _proxies[proxyId].region = Space::REGION_INVALID; - _freeIndices.push_back(proxyId); - } -} -void Space::updateProxy(int32_t proxyId, const Space::Sphere& newSphere) { - if (proxyId >= (int32_t)_proxies.size()) { - return; - } - _proxies[proxyId].sphere = newSphere; -} + // Access the true item + auto& item = _proxies[updateID]; -void Space::setViews(const std::vector& views) { - _views = views; + // Update the item + item.sphere = (std::get<1>(update)); + } } void Space::categorizeAndGetChanges(std::vector& changes) { + std::unique_lock lock(_proxiesMutex); uint32_t numProxies = (uint32_t)_proxies.size(); uint32_t numViews = (uint32_t)_views.size(); for (uint32_t i = 0; i < numProxies; ++i) { Proxy& proxy = _proxies[i]; - if (proxy.region < Space::REGION_INVALID) { - uint8_t region = Space::REGION_UNKNOWN; + if (proxy.region < Region::INVALID) { + glm::vec3 proxyCenter = glm::vec3(proxy.sphere); + float proxyRadius = proxy.sphere.w; + uint8_t region = Region::UNKNOWN; for (uint32_t j = 0; j < numViews; ++j) { - float distance2 = glm::distance2(_views[j].center, glm::vec3(_proxies[i].sphere)); - for (uint8_t c = 0; c < region; ++c) { - float touchDistance = _views[j].radiuses[c] + _proxies[i].sphere.w; - if (distance2 < touchDistance * touchDistance) { - region = c; + auto& view = _views[j]; + // for each 'view' we need only increment 'k' below the current value of 'region' + for (uint8_t k = 0; k < region; ++k) { + float touchDistance = proxyRadius + view.regions[k].w; + if (distance2(proxyCenter, glm::vec3(view.regions[k])) < touchDistance * touchDistance) { + region = k; break; } } @@ -92,3 +112,41 @@ void Space::categorizeAndGetChanges(std::vector& changes) { } } +uint32_t Space::copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const { + std::unique_lock lock(_proxiesMutex); + auto numCopied = std::min(numDestProxies, (uint32_t)_proxies.size()); + memcpy(proxies, _proxies.data(), numCopied * sizeof(Proxy)); + return numCopied; +} + +const Owner Space::getOwner(int32_t proxyID) const { + std::unique_lock lock(_proxiesMutex); + if (isAllocatedID(proxyID) && (proxyID < (Index)_proxies.size())) { + return _owners[proxyID]; + } + return Owner(); +} + +uint8_t Space::getRegion(int32_t proxyID) const { + std::unique_lock lock(_proxiesMutex); + if (isAllocatedID(proxyID) && (proxyID < (Index)_proxies.size())) { + return _proxies[proxyID].region; + } + return (uint8_t)Region::INVALID; +} + +void Space::clear() { + std::unique_lock lock(_proxiesMutex); + _IDAllocator.clear(); + _proxies.clear(); + _owners.clear(); + _views.clear(); +} + +void Space::setViews(const Views& views) { + _views = views; +} + +void Space::copyViews(std::vector& copy) const { + copy = _views; +} diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index be47cddc4e..960c905f7c 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -15,39 +15,17 @@ #ifndef hifi_workload_Space_h #define hifi_workload_Space_h +#include #include #include +#include "Transaction.h" + namespace workload { -class Space { +class Space : public Collection { public: - static const uint8_t REGION_NEAR = 0; - static const uint8_t REGION_MIDDLE = 1; - static const uint8_t REGION_FAR = 2; - static const uint8_t REGION_UNKNOWN = 3; - static const uint8_t REGION_INVALID = 4; - - using Sphere = glm::vec4; // = center, w = radius - - class Proxy { - public: - Proxy(const Sphere& s) : sphere(s) {} - Sphere sphere; - uint8_t region { REGION_UNKNOWN }; - uint8_t prevRegion { REGION_UNKNOWN }; - }; - - class View { - public: - View(const glm::vec3& pos, float nearRadius, float midRadius, float farRadius) : center(pos) { - radiuses[0] = nearRadius; - radiuses[1] = midRadius; - radiuses[2] = farRadius; - } - glm::vec3 center { 0.0f, 0.0f, 0.0f }; - float radiuses[3] { 0.0f, 0.0f, 0.0f }; - }; + using ProxyUpdate = std::pair; class Change { public: @@ -57,23 +35,43 @@ public: uint8_t prevRegion { 0 }; }; - Space() {} + Space(); - int32_t createProxy(const Sphere& sphere); - void deleteProxy(int32_t proxyId); - void updateProxy(int32_t proxyId, const Sphere& sphere); - void setViews(const std::vector& views); + void setViews(const Views& views); - uint32_t getNumObjects() const { return (uint32_t)(_proxies.size() - _freeIndices.size()); } + uint32_t getNumViews() const { return (uint32_t)(_views.size()); } + void copyViews(std::vector& copy) const; + + uint32_t getNumObjects() const { return _IDAllocator.getNumLiveIndices(); } // (uint32_t)(_proxies.size() - _freeIndices.size()); } + uint32_t getNumAllocatedProxies() const { return (uint32_t)(_IDAllocator.getNumAllocatedIndices()); } void categorizeAndGetChanges(std::vector& changes); + uint32_t copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const; + const Owner getOwner(int32_t proxyID) const; + uint8_t getRegion(int32_t proxyID) const; + + void clear(); private: - std::vector _proxies; - std::vector _views; - std::vector _freeIndices; + + void processTransactionFrame(const Transaction& transaction) override; + void processResets(const Transaction::Resets& transactions); + void processRemoves(const Transaction::Removes& transactions); + void processUpdates(const Transaction::Updates& transactions); + + // The database of proxies is protected for editing by a mutex + mutable std::mutex _proxiesMutex; + Proxy::Vector _proxies; + std::vector _owners; + + Views _views; }; +using SpacePointer = std::shared_ptr; +using Changes = std::vector; +using IndexVectors = std::vector; +using Timings = std::vector; + } // namespace workload #endif // hifi_workload_Space_h diff --git a/libraries/workload/src/workload/SpaceClassifier.cpp b/libraries/workload/src/workload/SpaceClassifier.cpp new file mode 100644 index 0000000000..418a510589 --- /dev/null +++ b/libraries/workload/src/workload/SpaceClassifier.cpp @@ -0,0 +1,34 @@ +// +// SpaceClassifier.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 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 "SpaceClassifier.h" + +#include "ViewTask.h" +#include "RegionState.h" + +using namespace workload; + +void PerformSpaceTransaction::configure(const Config& config) { + +} +void PerformSpaceTransaction::run(const WorkloadContextPointer& context) { + context->_space->enqueueFrame(); + context->_space->processTransactionQueue(); +} + +void SpaceClassifierTask::build(JobModel& model, const Varying& in, Varying& out) { + model.addJob("assignSpaceViews", in); + model.addJob("updateSpace"); + const auto regionTrackerOut = model.addJob("regionTracker"); + const auto regionChanges = regionTrackerOut.getN(1); + model.addJob("regionState", regionChanges); + out = regionTrackerOut; +} + diff --git a/libraries/workload/src/workload/SpaceClassifier.h b/libraries/workload/src/workload/SpaceClassifier.h new file mode 100644 index 0000000000..b6231a91ff --- /dev/null +++ b/libraries/workload/src/workload/SpaceClassifier.h @@ -0,0 +1,47 @@ +// +// SpaceClassifier.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 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_workload_SpaceClassifier_h +#define hifi_workload_SpaceClassifier_h + +#include "ViewTask.h" +#include "RegionTracker.h" + +namespace workload { + class SpaceClassifierTask { + public: + using Inputs = Views; + using Outputs = RegionTracker::Outputs; + using JobModel = Task::ModelIO; + void build(JobModel& model, const Varying& in, Varying& out); + }; + + + class PerformSpaceTransactionConfig : public Job::Config { + Q_OBJECT + public: + signals : + void dirty(); + + protected: + }; + + class PerformSpaceTransaction { + public: + using Config = PerformSpaceTransactionConfig; + using JobModel = Job::Model; + + void configure(const Config& config); + void run(const WorkloadContextPointer& context); + protected: + }; +} // namespace workload + +#endif // hifi_workload_SpaceClassifier_h diff --git a/libraries/workload/src/workload/Transaction.cpp b/libraries/workload/src/workload/Transaction.cpp new file mode 100644 index 0000000000..31c28cdf22 --- /dev/null +++ b/libraries/workload/src/workload/Transaction.cpp @@ -0,0 +1,166 @@ +// +// Transaction.cpp +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.12 +// Copyright 2018 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 "Transaction.h" + +using namespace workload; + + +void Transaction::reset(ProxyID id, const ProxyPayload& payload, const Owner& owner) { + _resetItems.emplace_back(Reset{ id, payload, owner }); +} + +void Transaction::remove(ProxyID id) { + _removedItems.emplace_back(id); +} + +void Transaction::update(ProxyID id, const ProxyPayload& payload) { + _updatedItems.emplace_back(id, payload); +} + +void Transaction::reserve(const std::vector& transactionContainer) { + size_t resetItemsCount = 0; + size_t removedItemsCount = 0; + size_t updatedItemsCount = 0; + + for (const auto& transaction : transactionContainer) { + resetItemsCount += transaction._resetItems.size(); + removedItemsCount += transaction._removedItems.size(); + updatedItemsCount += transaction._updatedItems.size(); + } + + _resetItems.reserve(resetItemsCount); + _removedItems.reserve(removedItemsCount); + _updatedItems.reserve(updatedItemsCount); +} + +void Transaction::merge(const std::vector& transactionContainer) { + reserve(transactionContainer); + for (const auto& transaction : transactionContainer) { + merge(transaction); + } +} + + +void Transaction::merge(std::vector&& transactionContainer) { + reserve(transactionContainer); + auto begin = std::make_move_iterator(transactionContainer.begin()); + auto end = std::make_move_iterator(transactionContainer.end()); + for (auto itr = begin; itr != end; ++itr) { + merge(*itr); + } + transactionContainer.clear(); +} + + +template +void moveElements(T& target, T& source) { + target.insert(target.end(), std::make_move_iterator(source.begin()), std::make_move_iterator(source.end())); + source.clear(); +} + +template +void copyElements(T& target, const T& source) { + target.insert(target.end(), source.begin(), source.end()); +} + + +void Transaction::reset(const Resets& resets) { + copyElements(_resetItems, resets); +} + +void Transaction::remove(const Removes& removes) { + copyElements(_removedItems, removes); +} + +void Transaction::update(const Updates& updates) { + copyElements(_updatedItems, updates); +} + +void Transaction::merge(Transaction&& transaction) { + moveElements(_resetItems, transaction._resetItems); + moveElements(_removedItems, transaction._removedItems); + moveElements(_updatedItems, transaction._updatedItems); +} + +void Transaction::merge(const Transaction& transaction) { + copyElements(_resetItems, transaction._resetItems); + copyElements(_removedItems, transaction._removedItems); + copyElements(_updatedItems, transaction._updatedItems); +} + +void Transaction::clear() { + _resetItems.clear(); + _removedItems.clear(); + _updatedItems.clear(); +} + + + + +Collection::Collection() { +} + +Collection::~Collection() { +} + +ProxyID Collection::allocateID() { + // Just increment and return the previous value initialized at 0 + return _IDAllocator.allocateIndex(); +} + +bool Collection::isAllocatedID(const ProxyID& id) const { + return _IDAllocator.checkIndex(id); +} + +/// Enqueue change batch to the Collection +void Collection::enqueueTransaction(const Transaction& transaction) { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(transaction); +} + +void Collection::enqueueTransaction(Transaction&& transaction) { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(std::move(transaction)); +} + +uint32_t Collection::enqueueFrame() { + TransactionQueue localTransactionQueue; + { + std::unique_lock lock(_transactionQueueMutex); + localTransactionQueue.swap(_transactionQueue); + } + + Transaction consolidatedTransaction; + consolidatedTransaction.merge(std::move(localTransactionQueue)); + { + std::unique_lock lock(_transactionFramesMutex); + _transactionFrames.push_back(consolidatedTransaction); + } + + return ++_transactionFrameNumber; +} + + +void Collection::processTransactionQueue() { + static TransactionFrames queuedFrames; + { + // capture the queued frames and clear the queue + std::unique_lock lock(_transactionFramesMutex); + queuedFrames.swap(_transactionFrames); + } + + // go through the queue of frames and process them + for (auto& frame : queuedFrames) { + processTransactionFrame(frame); + } + + queuedFrames.clear(); +} diff --git a/libraries/workload/src/workload/Transaction.h b/libraries/workload/src/workload/Transaction.h new file mode 100644 index 0000000000..aa2a427127 --- /dev/null +++ b/libraries/workload/src/workload/Transaction.h @@ -0,0 +1,181 @@ +// +// Transaction.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.12 +// Copyright 2018 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_workload_Transaction_h +#define hifi_workload_Transaction_h + +#include +#include +#include +#include +#include + +#include "Proxy.h" + + +namespace workload { + + namespace indexed_container { + + using Index = int32_t; + const Index MAXIMUM_INDEX{ 1 << 30 }; + const Index INVALID_INDEX{ -1 }; + using Indices = std::vector< Index >; + + template + class Allocator { + public: + Allocator() {} + Indices _freeIndices; + Index _nextNewIndex{ 0 }; + + bool checkIndex(Index index) const { return ((index >= 0) && (index < _nextNewIndex)); } + Index getNumLiveIndices() const { return _nextNewIndex - (Index)_freeIndices.size(); } + Index getNumFreeIndices() const { return (Index)_freeIndices.size(); } + Index getNumAllocatedIndices() const { return _nextNewIndex; } + + Index allocateIndex() { + if (_freeIndices.empty()) { + Index index = _nextNewIndex; + if (index >= MaxNumElements) { + // abort! we are trying to go overboard with the total number of allocated elements + assert(false); + // This should never happen because Bricks are allocated along with the cells and there + // is already a cap on the cells allocation + return INVALID_INDEX; + } + _nextNewIndex++; + return index; + } else { + // TODO: sort _freeIndices when neccessary to help keep used allocated indices more tightly packed + Index index = _freeIndices.back(); + _freeIndices.pop_back(); + return index; + } + } + + void freeIndex(Index index) { + if (checkIndex(index)) { + _freeIndices.push_back(index); + } + } + + void clear() { + _freeIndices.clear(); + _nextNewIndex = 0; + } + }; + } + + + using Index = indexed_container::Index; + using IndexVector = indexed_container::Indices; + + using ProxyID = Index; + const ProxyID INVALID_PROXY_ID{ indexed_container ::INVALID_INDEX }; + +// Transaction is the mechanism to make any change to the Space. +// Whenever a new proxy need to be reset, +// or when an proxy changes its position or its size +// or when an proxy is destroyed +// These changes must be expressed through the corresponding command from the Transaction +// The Transaction is then queued on the Space so all the pending transactions can be consolidated and processed at the time +// of updating the space at the Frame boundary. +// +class Transaction { + friend class Space; +public: + using ProxyPayload = Sphere; + + using Reset = std::tuple; + using Remove = ProxyID; + using Update = std::tuple; + + using Resets = std::vector; + using Removes = std::vector; + using Updates = std::vector; + + Transaction() {} + ~Transaction() {} + + // Proxy transactions + void reset(ProxyID id, const ProxyPayload& sphere, const Owner& owner); + void reset(const Resets& resets); + void remove(ProxyID id); + void remove(const Removes& removes); + bool hasRemovals() const { return !_removedItems.empty(); } + + void update(ProxyID id, const ProxyPayload& sphere); + void update(const Updates& updates); + + void reserve(const std::vector& transactionContainer); + void merge(const std::vector& transactionContainer); + void merge(std::vector&& transactionContainer); + void merge(const Transaction& transaction); + void merge(Transaction&& transaction); + void clear(); + +protected: + + + Resets _resetItems; + Removes _removedItems; + Updates _updatedItems; +}; +typedef std::vector TransactionQueue; + +class Collection { +public: + Collection(); + ~Collection(); + + // This call is thread safe, can be called from anywhere to allocate a new ID + ProxyID allocateID(); + + // Check that the ID is valid and allocated for this collection, this a threadsafe call + bool isAllocatedID(const ProxyID& id) const; + + // THis is the total number of allocated proxies, this a threadsafe call + Index getNumAllocatedProxies() const { return _IDAllocator.getNumAllocatedIndices(); } + + // Enqueue transaction to the collection + void enqueueTransaction(const Transaction& transaction); + + // Enqueue transaction to the collection + void enqueueTransaction(Transaction&& transaction); + + // Enqueue end of frame transactions boundary + uint32_t enqueueFrame(); + + // Process the pending transactions queued + virtual void processTransactionQueue(); + +protected: + + // Thread safe elements that can be accessed from anywhere + indexed_container::Allocator<> _IDAllocator; + + std::mutex _transactionQueueMutex; + TransactionQueue _transactionQueue; + + + std::mutex _transactionFramesMutex; + using TransactionFrames = std::vector; + TransactionFrames _transactionFrames; + uint32_t _transactionFrameNumber{ 0 }; + + // Process one transaction frame + virtual void processTransactionFrame(const Transaction& transaction) = 0; +}; + +} // namespace workload + +#endif // hifi_workload_Transaction_h diff --git a/libraries/workload/src/workload/View.cpp b/libraries/workload/src/workload/View.cpp new file mode 100644 index 0000000000..8cb593d2a7 --- /dev/null +++ b/libraries/workload/src/workload/View.cpp @@ -0,0 +1,69 @@ +// +// View.cpp +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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 "View.h" +#include + +using namespace workload; + +void View::setFov(float angleRad) { + float halfAngle = angleRad * 0.5f; + + fov_halfAngle_tan_cos_sin.x = halfAngle; + fov_halfAngle_tan_cos_sin.y = tanf(halfAngle); + fov_halfAngle_tan_cos_sin.z = cosf(halfAngle); + fov_halfAngle_tan_cos_sin.w = sinf(halfAngle); +} + +void View::makeHorizontal() { + direction = glm::normalize(glm::vec3(direction.x, 0.0f, direction.z)); +} + +View View::evalFromFrustum(const ViewFrustum& frustum, const glm::vec3& offset) { + View view; + view.origin = frustum.getPosition() + offset; + view.direction = frustum.getDirection(); + view.setFov(frustum.getFieldOfView()); + + return view; +} + +Sphere View::evalRegionSphere(const View& view, float originRadius, float maxDistance) { + float radius = (maxDistance + originRadius) / 2.0f; + float center = radius - originRadius; + return Sphere(view.origin + view.direction * center, radius); +} + +void View::updateRegionsDefault(View& view) { + std::vector config(Region::NUM_VIEW_REGIONS * 2, 0.0f); + + float refFar = 10.0f; + float refClose = 2.0f; + for (int i = 0; i < Region::NUM_VIEW_REGIONS; i++) { + float weight = i + 1.0f; + config[i * 2] = refClose; + config[i * 2 + 1] = refFar * weight; + refFar *= 2.0f; + } + updateRegionsFromBackFrontDistances(view, config.data()); +} + +void View::updateRegionsFromBackFronts(View& view) { + for (int i = 0; i < Region::NUM_VIEW_REGIONS; i++) { + view.regions[i] = evalRegionSphere(view, view.regionBackFronts[i].x, view.regionBackFronts[i].y); + } +} + +void View::updateRegionsFromBackFrontDistances(View& view, const float* configDistances) { + for (int i = 0; i < Region::NUM_VIEW_REGIONS; i++) { + view.regionBackFronts[i] = glm::vec2(configDistances[i * 2], configDistances[i * 2 + 1]); + } + updateRegionsFromBackFronts(view); +} diff --git a/libraries/workload/src/workload/View.h b/libraries/workload/src/workload/View.h new file mode 100644 index 0000000000..972caf5101 --- /dev/null +++ b/libraries/workload/src/workload/View.h @@ -0,0 +1,74 @@ +// +// View.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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_workload_View_h +#define hifi_workload_View_h + +#include +#include +#include +#include + +#include "Region.h" + +class ViewFrustum; + +namespace workload { + +using Sphere = glm::vec4; + +class View { +public: + View() = default; + View(const View& view) = default; + + // View attributes: + + // direction + glm::vec3 direction{ 0.0f, 0.0f, -1.0f }; + + // Max radius + float maxRadius{ FLT_MAX }; + + // Fov stores the half field of view angle, and tan/cos/sin ready to go, default is fov of 90deg + glm::vec4 fov_halfAngle_tan_cos_sin { glm::quarter_pi(), 1.0f, glm::root_two() * 0.5f, glm::root_two() * 0.5f}; + + // Origin position + glm::vec3 origin{ 0.0f }; + + // Origin radius + float originRadius{ 0.5f }; + + // N regions distances + glm::vec2 regionBackFronts[Region::NUM_VIEW_REGIONS + 1]; + + // N regions spheres + Sphere regions[Region::NUM_VIEW_REGIONS]; + + // Set fov properties from angle + void setFov(float angleRad); + + // Helper function to force the direction in the XZ plane + void makeHorizontal(); + + static View evalFromFrustum(const ViewFrustum& frustum, const glm::vec3& offset = glm::vec3()); + static Sphere evalRegionSphere(const View& view, float originRadius, float maxDistance); + + static void updateRegionsDefault(View& view); + static void updateRegionsFromBackFronts(View& view); + static void updateRegionsFromBackFrontDistances(View& view, const float* configDistances); +}; + +using Views = std::vector; + +} // namespace workload + +#endif // hifi_workload_View_h diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp new file mode 100644 index 0000000000..4b77733c52 --- /dev/null +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -0,0 +1,160 @@ +// +// ViewTask.cpp +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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 "ViewTask.h" + +using namespace workload; + + +void AssignSpaceViews::run(const WorkloadContextPointer& renderContext, const Input& inputs) { + // Just do what it says + renderContext->_space->setViews(inputs); +} + +void SetupViews::configure(const Config& config) { + data = config.data; +} + +void SetupViews::run(const WorkloadContextPointer& renderContext, const Input& inputs, Output& outputs) { + // If views are frozen don't use the input + if (!data.freezeViews) { + _views = inputs; + } + + auto& outViews = outputs; + outViews.clear(); + + // Filter the first view centerer on the avatar head if needed + if (_views.size() >= 2) { + if (data.useAvatarView) { + outViews.push_back(_views[0]); + outViews.insert(outViews.end(), _views.begin() + 2, _views.end()); + } else { + outViews.insert(outViews.end(), _views.begin() + 1, _views.end()); + } + } else { + outViews = _views; + } + + // Force frutum orientation horizontal if needed + if (outViews.size() > 0 && data.forceViewHorizontal) { + outViews[0].makeHorizontal(); + } + + // Force frutum orientation horizontal if needed + if (outViews.size() > 0 && data.simulateSecondaryCamera) { + auto view = outViews[0]; + auto secondaryDirectionFlat = glm::normalize(glm::vec3(view.direction.x, 0.0f, view.direction.z)); + auto secondaryDirection = glm::normalize(glm::vec3(secondaryDirectionFlat.z, 0.0f, -secondaryDirectionFlat.x)); + + view.origin += -20.0f * secondaryDirection; + view.direction = -secondaryDirection; + + outViews.insert(outViews.begin() + 1, view); + } + + // Update regions based on the current config + for (auto& v : outViews) { + View::updateRegionsFromBackFrontDistances(v, (float*) &data); + } + + // outViews is ready to be used +} + + +ControlViews::ControlViews() { + for (int32_t i = 0; i < workload::Region::NUM_VIEW_REGIONS; i++) { + regionBackFronts[i] = MIN_VIEW_BACK_FRONTS[i]; + regionRegulators[i] = Regulator(std::chrono::milliseconds(2), MIN_VIEW_BACK_FRONTS[i], MAX_VIEW_BACK_FRONTS[i], glm::vec2(RELATIVE_STEP_DOWN), glm::vec2(RELATIVE_STEP_UP)); + } +} + +void ControlViews::configure(const Config& config) { + _data = config.data; +} + +void ControlViews::run(const workload::WorkloadContextPointer& runContext, const Input& inputs, Output& outputs) { + const auto& inViews = inputs.get0(); + const auto& inTimings = inputs.get1(); + auto& outViews = outputs; + outViews.clear(); + outViews = inViews; + + if (_data.regulateViewRanges && inTimings.size()) { + regulateViews(outViews, inTimings); + } + + // Export the timings for debuging + if (inTimings.size()) { + _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[0]).count(); + _dataExport.timings[workload::Region::R2] = _dataExport.timings[workload::Region::R1]; + _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[1]).count(); + + auto config = std::static_pointer_cast(runContext->jobConfig); + config->dataExport = _dataExport; + config->emitDirty(); + } +} + +glm::vec2 Regulator::run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current) { + // Regulate next value based on current moving toward the goal budget + float error_ms = std::chrono::duration(_budget - measured).count(); + float coef = glm::clamp(error_ms / std::chrono::duration(regulationDuration).count(), -1.0f, 1.0f); + return current * (1.0f + coef * (error_ms < 0.0f ? _relativeStepDown : _relativeStepUp)); +} + +glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { + // Clamp to min max + return glm::clamp(backFront, _minRange, _maxRange); +} + +void ControlViews::regulateViews(workload::Views& outViews, const workload::Timings& timings) { + + for (auto& outView : outViews) { + for (int32_t r = 0; r < workload::Region::NUM_VIEW_REGIONS; r++) { + outView.regionBackFronts[r] = regionBackFronts[r]; + } + } + + auto loopDuration = std::chrono::nanoseconds{ std::chrono::milliseconds(16) }; + regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[0], regionBackFronts[workload::Region::R1]); + regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[0], regionBackFronts[workload::Region::R2]); + regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[1], regionBackFronts[workload::Region::R3]); + + enforceRegionContainment(); + + _dataExport.ranges[workload::Region::R1] = regionBackFronts[workload::Region::R1]; + _dataExport.ranges[workload::Region::R2] = regionBackFronts[workload::Region::R2]; + _dataExport.ranges[workload::Region::R3] = regionBackFronts[workload::Region::R3]; + + for (auto& outView : outViews) { + outView.regionBackFronts[workload::Region::R1] = regionBackFronts[workload::Region::R1]; + outView.regionBackFronts[workload::Region::R2] = regionBackFronts[workload::Region::R2]; + outView.regionBackFronts[workload::Region::R3] = regionBackFronts[workload::Region::R3]; + + workload::View::updateRegionsFromBackFronts(outView); + } +} + +void ControlViews::enforceRegionContainment() { + // inner regions should never overreach outer + // and each region should never exceed its min/max limits + const glm::vec2 MIN_REGION_GAP = { 1.0f, 2.0f }; + // enforce outside --> in + for (int32_t i = workload::Region::NUM_VIEW_REGIONS - 2; i >= 0; --i) { + int32_t j = i + 1; + regionBackFronts[i] = regionRegulators[i].clamp(glm::min(regionBackFronts[i], regionBackFronts[j] - MIN_REGION_GAP)); + } + // enforce inside --> out + for (int32_t i = 1; i < workload::Region::NUM_VIEW_REGIONS; ++i) { + int32_t j = i - 1; + regionBackFronts[i] = regionRegulators[i].clamp(glm::max(regionBackFronts[i], regionBackFronts[j] + MIN_REGION_GAP)); + } +} diff --git a/libraries/workload/src/workload/ViewTask.h b/libraries/workload/src/workload/ViewTask.h new file mode 100644 index 0000000000..867f22d534 --- /dev/null +++ b/libraries/workload/src/workload/ViewTask.h @@ -0,0 +1,257 @@ +// +// ViewTask.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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_workload_ViewTask_h +#define hifi_workload_ViewTask_h + +#include "Engine.h" + +template +QVariantList toVariantList(const QList &list) +{ + QVariantList newList; + foreach(const T &item, list) + newList << item; + + return newList; +} + +namespace workload { + + const std::vector MIN_VIEW_BACK_FRONTS = { + { 3.0f, 4.0f }, + { 6.0f, 8.0f }, + { 9.0f, 12.0f } + }; + + const std::vector MAX_VIEW_BACK_FRONTS = { + { 100.0f, 200.0f }, + { 150.0f, 300.0f }, + { 250.0f, 500.0f } + }; + + const float RELATIVE_STEP_DOWN = 0.05f; + const float RELATIVE_STEP_UP = 0.04f; + + class SetupViewsConfig : public Job::Config{ + Q_OBJECT + Q_PROPERTY(float r1Front READ getR1Front WRITE setR1Front NOTIFY dirty) + Q_PROPERTY(float r1Back READ getR1Back WRITE setR1Back NOTIFY dirty) + Q_PROPERTY(float r2Front READ getR2Front WRITE setR2Front NOTIFY dirty) + Q_PROPERTY(float r2Back READ getR2Back WRITE setR2Back NOTIFY dirty) + Q_PROPERTY(float r3Front READ getR3Front WRITE setR3Front NOTIFY dirty) + Q_PROPERTY(float r3Back READ getR3Back WRITE setR3Back NOTIFY dirty) + Q_PROPERTY(bool freezeViews READ getFreezeView WRITE setFreezeView NOTIFY dirty) + Q_PROPERTY(bool useAvatarView READ useAvatarView WRITE setUseAvatarView NOTIFY dirty) + Q_PROPERTY(bool forceViewHorizontal READ forceViewHorizontal WRITE setForceViewHorizontal NOTIFY dirty) + + Q_PROPERTY(bool simulateSecondaryCamera READ simulateSecondaryCamera WRITE setSimulateSecondaryCamera NOTIFY dirty) + + public: + + float getR1Front() const { return data.r1Front; } + float getR1Back() const { return data.r1Back; } + float getR2Front() const { return data.r2Front; } + float getR2Back() const { return data.r2Back; } + float getR3Front() const { return data.r3Front; } + float getR3Back() const { return data.r3Back; } + + void setR1Front(float d) { data.r1Front = d; emit dirty(); } + void setR1Back(float d) { data.r1Back = d; emit dirty(); } + void setR2Front(float d) { data.r2Front = d; emit dirty(); } + void setR2Back(float d) { data.r2Back = d; emit dirty(); } + void setR3Front(float d) { data.r3Front = d; emit dirty(); } + void setR3Back(float d) { data.r3Back = d; emit dirty(); } + + bool getFreezeView() const { return data.freezeViews; } + void setFreezeView(bool freeze) { data.freezeViews = freeze; emit dirty(); } + bool useAvatarView() const { return data.useAvatarView; } + void setUseAvatarView(bool use) { data.useAvatarView = use; emit dirty(); } + bool forceViewHorizontal() const { return data.forceViewHorizontal; } + void setForceViewHorizontal(bool use) { data.forceViewHorizontal = use; emit dirty(); } + + bool simulateSecondaryCamera() const { return data.simulateSecondaryCamera; } + void setSimulateSecondaryCamera(bool use) { data.simulateSecondaryCamera = use; emit dirty(); } + + struct Data { + float r1Back { MAX_VIEW_BACK_FRONTS[0].x }; + float r1Front { MAX_VIEW_BACK_FRONTS[0].y }; + + float r2Back{ MAX_VIEW_BACK_FRONTS[1].x }; + float r2Front{ MAX_VIEW_BACK_FRONTS[1].y }; + + float r3Back{ MAX_VIEW_BACK_FRONTS[2].x }; + float r3Front{ MAX_VIEW_BACK_FRONTS[2].y }; + + bool freezeViews{ false }; + bool useAvatarView{ false }; + bool forceViewHorizontal{ false }; + bool simulateSecondaryCamera{ false }; + } data; + + signals: + void dirty(); + }; + + class SetupViews { + public: + using Config = SetupViewsConfig; + using Input = Views; + using Output = Views; + using JobModel = Job::ModelIO; + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, const Input& inputs, Output& outputs); + + protected: + Config::Data data; + Views _views; + }; + + class AssignSpaceViews { + public: + using Input = Views; + using JobModel = Job::ModelI; + + void run(const workload::WorkloadContextPointer& renderContext, const Input& inputs); + }; + + + class ControlViewsConfig : public workload::Job::Config { + Q_OBJECT + Q_PROPERTY(bool regulateViewRanges READ regulateViewRanges WRITE setRegulateViewRanges NOTIFY dirty) + + + Q_PROPERTY(float r1Timing READ r1Timing NOTIFY dirty) + Q_PROPERTY(float r2Timing READ r2Timing NOTIFY dirty) + Q_PROPERTY(float r3Timing READ r3Timing NOTIFY dirty) + + Q_PROPERTY(float r1RangeBack READ r1RangeBack NOTIFY dirty) + Q_PROPERTY(float r2RangeBack READ r2RangeBack NOTIFY dirty) + Q_PROPERTY(float r3RangeBack READ r3RangeBack NOTIFY dirty) + + Q_PROPERTY(float r1RangeFront READ r1RangeFront NOTIFY dirty) + Q_PROPERTY(float r2RangeFront READ r2RangeFront NOTIFY dirty) + Q_PROPERTY(float r3RangeFront READ r3RangeFront NOTIFY dirty) + /* + Q_PROPERTY(float r1MinRangeBack READ r1MinRangeBack WRITE setR1MinRangeBack NOTIFY dirty) + Q_PROPERTY(float r2MinRangeBack READ r2MinRangeBack WRITE setR2MinRangeBack NOTIFY dirty) + Q_PROPERTY(float r3MinRangeBack READ r3MinRangeBack WRITE setR3MinRangeBack NOTIFY dirty) + + Q_PROPERTY(float r1MinRangeFront READ r1MinRangeFront WRITE setR1MinRangeFront NOTIFY dirty) + Q_PROPERTY(float r2MinRangeFront READ r2MinRangeFront WRITE setR2MinRangeFront NOTIFY dirty) + Q_PROPERTY(float r3MinRangeFront READ r3MinRangeFront WRITE setR3MinRangeFront NOTIFY dirty) + + Q_PROPERTY(float r1MaxRangeBack READ r1MaxRangeBack WRITE setR1MaxRangeBack NOTIFY dirty) + Q_PROPERTY(float r2MaxRangeBack READ r2MaxRangeBack WRITE setR2MaxRangeBack NOTIFY dirty) + Q_PROPERTY(float r3MaxRangeBack READ r3MaxRangeBack WRITE setR3MaxRangeBack NOTIFY dirty) + + Q_PROPERTY(float r1MaxRangeFront READ r1MaxRangeFront WRITE setR1MaxRangeFront NOTIFY dirty) + Q_PROPERTY(float r2MaxRangeFront READ r2MaxRangeFront WRITE setR2MaxRangeFront NOTIFY dirty) + Q_PROPERTY(float r3MaxRangeFront READ r3MaxRangeFront WRITE setR3MaxRangeFront NOTIFY dirty) + + Q_PROPERTY(float r1SpeedDownBack READ r1SpeedDownBack WRITE setR1SpeedDownBack NOTIFY dirty) + Q_PROPERTY(float r2SpeedDownBack READ r2SpeedDownBack WRITE setR2SpeedDownBack NOTIFY dirty) + Q_PROPERTY(float r3SpeedDownBack READ r3SpeedDownBack WRITE setR3SpeedDownBack NOTIFY dirty) + + Q_PROPERTY(float r1SpeedDownFront READ r1SpeedDownFront WRITE setR1SpeedDownFront NOTIFY dirty) + Q_PROPERTY(float r2SpeedDownFront READ r2SpeedDownFront WRITE setR2SpeedDownFront NOTIFY dirty) + Q_PROPERTY(float r3SpeedDownFront READ r3SpeedDownFront WRITE setR3SpeedDownFront NOTIFY dirty) + + Q_PROPERTY(float r1SpeedUpBack READ r1SpeedUpBack WRITE setR1SpeedUpBack NOTIFY dirty) + Q_PROPERTY(float r2SpeedUpBack READ r2SpeedUpBack WRITE setR2SpeedUpBack NOTIFY dirty) + Q_PROPERTY(float r3SpeedUpBack READ r3SpeedUpBack WRITE setR3SpeedUpBack NOTIFY dirty) + + Q_PROPERTY(float r1SpeedUpFront READ r1SpeedUpFront WRITE setR1SpeedUpFront NOTIFY dirty) + Q_PROPERTY(float r2SpeedUpFront READ r2SpeedUpFront WRITE setR2SpeedUpFront NOTIFY dirty) + Q_PROPERTY(float r3SpeedUpFront READ r3SpeedUpFront WRITE setR3SpeedUpFront NOTIFY dirty)*/ + + public: + + bool regulateViewRanges() const { return data.regulateViewRanges; } + void setRegulateViewRanges(bool use) { data.regulateViewRanges = use; emit dirty(); } + + float r1Timing() const { return dataExport.timings[workload::Region::R1]; } + float r2Timing() const { return dataExport.timings[workload::Region::R2]; } + float r3Timing() const { return dataExport.timings[workload::Region::R3]; } + + float r1RangeBack() const { return dataExport.ranges[workload::Region::R1].x; } + float r2RangeBack() const { return dataExport.ranges[workload::Region::R2].x; } + float r3RangeBack() const { return dataExport.ranges[workload::Region::R3].x; } + + float r1RangeFront() const { return dataExport.ranges[workload::Region::R1].y; } + float r2RangeFront() const { return dataExport.ranges[workload::Region::R2].y; } + float r3RangeFront() const { return dataExport.ranges[workload::Region::R3].y; } + + + struct Data { + bool regulateViewRanges{ false }; + } data; + + struct DataExport { + static const int SIZE{ workload::Region::NUM_VIEW_REGIONS }; + float timings[SIZE]; + glm::vec2 ranges[SIZE]; + QList _timings { 6, 2.0 }; + + } dataExport; + + void emitDirty() { emit dirty(); } + + public slots: + Q_INVOKABLE QVariantList getTimings() const { return toVariantList(dataExport._timings); } + signals: + void dirty(); + }; + + struct Regulator { + using Timing_ns = std::chrono::nanoseconds; + Timing_ns _budget{ std::chrono::milliseconds(2) }; + glm::vec2 _minRange{ MIN_VIEW_BACK_FRONTS[0] }; + glm::vec2 _maxRange{ MAX_VIEW_BACK_FRONTS[0] }; + + glm::vec2 _relativeStepDown{ RELATIVE_STEP_DOWN }; + glm::vec2 _relativeStepUp{ RELATIVE_STEP_UP }; + + + Regulator() {} + Regulator(const Timing_ns& budget_ns, const glm::vec2& minRange, const glm::vec2& maxRange, const glm::vec2& relativeStepDown, const glm::vec2& relativeStepUp) : + _budget(budget_ns), _minRange(minRange), _maxRange(maxRange), _relativeStepDown(relativeStepDown), _relativeStepUp(relativeStepUp) {} + + glm::vec2 run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current); + glm::vec2 clamp(const glm::vec2& backFront) const; + }; + + class ControlViews { + public: + using Config = ControlViewsConfig; + using Input = workload::VaryingSet2; + using Output = workload::Views; + using JobModel = workload::Job::ModelIO; + + ControlViews(); + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& runContext, const Input& inputs, Output& outputs); + + std::array regionBackFronts; + std::array regionRegulators; + + void regulateViews(workload::Views& views, const workload::Timings& timings); + void enforceRegionContainment(); + + protected: + Config::Data _data; + Config::DataExport _dataExport; + }; + +} // namespace workload + +#endif // hifi_workload_ViewTask_h diff --git a/scripts/developer/utilities/lib/jet/jet.js b/scripts/developer/utilities/lib/jet/jet.js index 16840e59bd..85842b8861 100644 --- a/scripts/developer/utilities/lib/jet/jet.js +++ b/scripts/developer/utilities/lib/jet/jet.js @@ -13,7 +13,7 @@ // traverse task tree function task_traverse(root, functor, depth) { if (root.isTask()) { - depth++; + depth++; for (var i = 0; i -GRID_HALFSIZE; i--) { + for (j = -GRID_HALFSIZE; j < GRID_HALFSIZE; j++) { + backdrop.push(addObject(i, j, GRID_DROP_C, lifetime)); + } + } + +} + +function addFloor(lifetime) { + var floorDim = { x:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN, y: TILE_DIM.y, z:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN}; + var center = getStagePosOriAt(0, 0, -0.5).pos; + + return (Entities.addEntity({ + type: "Shape", + shape: "Cube", + name: "Floor", + color: { red: 20, green: 20, blue: 40 }, + position: center, + rotation: stageOrientation, + dimensions: floorDim, + lifetime: (lifetime === undefined) ? DEFAULT_LIFETIME : lifetime, + + shapeType:shapeTypes[1], + // dynamic: true, + // gravity:{"x":0,"y":-9.8,"z":0}, + // velocity:{"x":0,"y":0.01,"z":0}, + restitution:0.999, + friction:0.001, + damping:0.3, + + })); +} + +function addZone(hasKeyLight, hasAmbient, lifetime) { + var zoneDim = { x:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN, y:GRID_WORLD_SIZE, z:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN}; + var center = getStagePosOriAt(0, 0, -1).pos; + + var lightDir = Vec3.normalize(Vec3.sum(Vec3.multiply(-1, Quat.getUp(stageOrientation)), Vec3.multiply(-1, Quat.getRight(stageOrientation)))) + + return (Entities.addEntity({ + type: "Zone", + name: "Backdrop zone", + + position: center, + rotation: stageOrientation, + dimensions: zoneDim, + lifetime: (lifetime === undefined) ? DEFAULT_LIFETIME : lifetime, + + keyLightMode: "enabled", + skyboxMode: "enabled", + ambientLightMode: "enabled", + + keyLight:{ + intensity: 0.8 * hasKeyLight, + direction: { + "x": 0.037007175385951996, + "y": -0.7071067690849304, + "z": -0.7061376571655273 + }, + castShadows: true, + }, + ambientLight: { + ambientIntensity: 1.0 * hasAmbient, + ambientURL: "https://github.com/highfidelity/hifi_tests/blob/master/assets/skymaps/Sky_Day-Sun-Mid-photo.ktx?raw=true", + }, + + hazeMode:"disabled", + backgroundMode:"skybox", + skybox:{ + color: {"red":2,"green":2,"blue":2}, // Dark grey background + skyboxURL: "https://github.com/highfidelity/hifi_tests/blob/master/assets/skymaps/Sky_Day-Sun-Mid-photo.ktx?raw=true", + } + })); +} + +function addTestScene(name, lifetime) { + var scene = []; + // scene.push(addFloor(lifetime)); + // scene.push(addZone(true, true, lifetime)); + + addObjectGrid(scene, lifetime); + + return scene; +} + + + +// Stage position and orientation initialised at setup +stageOrientation = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0); +stageRoot = {"x":0.0,"y":0.0,"z":0.0}; +stageAxisA = Vec3.multiply(TILE_UNIT, Quat.getForward(stageOrientation)); +stageAxisB = Vec3.multiply(TILE_UNIT, Quat.getRight(stageOrientation)); +stageAxisC = Vec3.multiply(TILE_UNIT, Quat.getUp(stageOrientation)); + +setupScene = function (lifetime) { + MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0); + var orientation = MyAvatar.orientation; + orientation = Quat.safeEulerAngles(orientation); + orientation.x = 0; + orientation = Quat.fromVec3Degrees(orientation); + + stageOrientation = orientation; + stageAxisA = Vec3.multiply(TILE_UNIT, Quat.getForward(stageOrientation)); + stageAxisB = Vec3.multiply(TILE_UNIT, Quat.getRight(stageOrientation)); + stageAxisC = Vec3.multiply(TILE_UNIT, Quat.getUp(stageOrientation)); + + stageRoot = Vec3.sum(MyAvatar.position, Vec3.multiply(-ROOT_Z_OFFSET, Quat.getForward(orientation))); + stageRoot = Vec3.sum(stageRoot, Vec3.multiply(ROOT_Y_OFFSET, Quat.getUp(orientation))); + + return addTestScene("Physics_stage_backdrop", lifetime); +} + +getStagePosOriAt = function (a, b, c) { + var center = Vec3.sum(stageRoot, Vec3.multiply(a, stageAxisA)); + center = Vec3.sum(center, Vec3.multiply(b, stageAxisB)); + center = Vec3.sum(center, Vec3.multiply(c, stageAxisC)); + + return { "pos": center, "ori": stageOrientation}; +} + + +var scene = [] + +createScene = function() { + clearScene(); + scene = setupScene(); +} + +clearScene = function() { + for (var i = 0; i < scene.length; i++) { + Entities.deleteEntity(scene[i]); + } +} + +changeResolution = function(res) { + updateWorldSizeAndResolution(GRID_WORLD_SIZE, res); +} + +getResolution = function() { + return GRID_WORLD_RESOLUTION; +} + +changeSize = function(size) { + updateWorldSizeAndResolution(size, GRID_WORLD_RESOLUTION); +} + +getSize = function() { + return GRID_WORLD_SIZE; +} + + +getNumObjects = function() { + return GRID_SIZE * GRID_SIZE; +} + +bumpUpFloor = function() { + print("bumpUpFloor") + if (scene.length > 0) { + Entities.editEntity(scene[0],{ velocity: {x: 0, y:-2.0,z: 0}}) + } +} \ No newline at end of file diff --git a/scripts/developer/utilities/workload/workload.js b/scripts/developer/utilities/workload/workload.js new file mode 100644 index 0000000000..d74eb4e6d5 --- /dev/null +++ b/scripts/developer/utilities/workload/workload.js @@ -0,0 +1,120 @@ +"use strict"; + +// +// Workload.js +// tablet-workload-engine app +// +// Copyright 2018 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() { + var TABLET_BUTTON_NAME = "Workload"; + var QMLAPP_URL = Script.resolvePath("./workloadInspector.qml"); + var ICON_URL = Script.resolvePath("../../../system/assets/images/luci-i.svg"); + var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/luci-a.svg"); + + + var onAppScreen = false; + + function onClicked() { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: ICON_URL, + activeIcon: ACTIVE_ICON_URL + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onAppScreen = true; + } else { + onAppScreen = false; + } + + button.editProperties({isActive: onAppScreen}); + wireEventBridge(onAppScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + + Script.include("./test_physics_scene.js") + + function fromQml(message) { + switch (message.method) { + case "createScene": + createScene(); + updateGridInQML() + break; + case "clearScene": + clearScene(); + updateGridInQML() + break; + case "changeSize": + changeSize(message.params.count); + updateGridInQML() + break; + case "changeResolution": + changeResolution(message.params.count); + updateGridInQML() + break; + case "bumpUpFloor": + bumpUpFloor(); + break; + } + + } + function updateGridInQML() { + sendToQml({method: "gridSize", params: { v: getSize() }}) + sendToQml({method: "objectCount", params: { v: getNumObjects() }}) + } + + function sendToQml(message) { + tablet.sendToQml(message); + } + + updateGridInQML() +}()); \ No newline at end of file diff --git a/scripts/developer/utilities/workload/workloadInspector.qml b/scripts/developer/utilities/workload/workloadInspector.qml new file mode 100644 index 0000000000..8076f5c1c2 --- /dev/null +++ b/scripts/developer/utilities/workload/workloadInspector.qml @@ -0,0 +1,372 @@ +// +// _workload.qml +// +// Created by Sam Gateau on 3/1/2018 +// Copyright 2018 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.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls +import "../render/configSlider" +import "../lib/jet/qml" as Jet +import "../lib/plotperf" + + +Rectangle { + HifiConstants { id: hifi;} + id: _workload; + + width: parent ? parent.width : 400 + height: parent ? parent.height : 600 + anchors.margins: hifi.dimensions.contentMargin.x + + color: hifi.colors.baseGray; + + function broadcastCreateScene() { + sendToScript({method: "createScene", params: { count:2 }}); + } + + function broadcastClearScene() { + sendToScript({method: "clearScene", params: { count:2 }}); + } + + function broadcastChangeSize(value) { + sendToScript({method: "changeSize", params: { count:value }}); + } + + function broadcastChangeResolution(value) { + sendToScript({method: "changeResolution", params: { count:value }}); + } + + function broadcastBumpUpFloor(value) { + sendToScript({method: "bumpUpFloor", params: { count:0 }}); + } + + function fromScript(message) { + switch (message.method) { + case "gridSize": + print("assigned value! " + message.params.v) + gridSizeLabel.text = ("Grid size [m] = " + message.params.v) + gridSize.setValue(message.params.v) + break; + case "resolution": + print("assigned value! " + message.params.v) + resolution.setValue(message.params.v) + break; + case "objectCount": + print("assigned objectCount! " + message.params.v) + objectCount.text = ("Num objects = " + message.params.v) + break; + } + } + + Column { + id: stats + spacing: 5 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + //padding: hifi.dimensions.contentMargin.x + + HifiControls.Label { + text: "Workload" + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + HifiControls.CheckBox { + boxSize: 20 + text: "Freeze Views" + checked: Workload.getConfig("setupViews")["freezeViews"] + onCheckedChanged: { Workload.getConfig("SpaceToRender")["freezeViews"] = checked, Workload.getConfig("setupViews")["freezeViews"] = checked; } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Use Avatar View" + checked: Workload.getConfig("setupViews")["useAvatarView"] + onCheckedChanged: { Workload.getConfig("setupViews")["useAvatarView"] = checked; } + } + HifiControls.CheckBox { + boxSize: 20 + text: "force View Horizontal" + checked: Workload.getConfig("setupViews")["forceViewHorizontal"] + onCheckedChanged: { Workload.getConfig("setupViews")["forceViewHorizontal"] = checked; } + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + HifiControls.CheckBox { + boxSize: 20 + text: "Simulate Secondary" + checked: Workload.getConfig("setupViews")["simulateSecondaryCamera"] + onCheckedChanged: { Workload.getConfig("setupViews")["simulateSecondaryCamera"] = checked; } + } + } + + Separator {} + HifiControls.CheckBox { + boxSize: 20 + text: "Regulate View Ranges" + checked: Workload.getConfig("controlViews")["regulateViewRanges"] + onCheckedChanged: { Workload.getConfig("controlViews")["regulateViewRanges"] = checked; } + } + + RowLayout { + visible: !Workload.getConfig("controlViews")["regulateViewRanges"] + anchors.left: parent.left + anchors.right: parent.right + Column { + anchors.left: parent.left + anchors.right: parent.horizontalCenter + HifiControls.Label { + text: "Back [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "R1:r1Back:250.0:0.0", + "R2:r2Back:250.0:0.0", + "R3:r3Back:250.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + config: Workload.getConfig("setupViews") + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + integral: true + + labelAreaWidthScale: 0.4 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + Column { + anchors.left: parent.horizontalCenter + anchors.right: parent.right + HifiControls.Label { + text: "Front [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "r1Front:500:1.0", + "r2Front:500:1.0", + "r3Front:500:1.0" + ] + ConfigSlider { + showLabel: false + config: Workload.getConfig("setupViews") + property: modelData.split(":")[0] + max: modelData.split(":")[1] + min: modelData.split(":")[2] + integral: true + + labelAreaWidthScale: 0.3 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + } + /*RowLayout { + visible: Workload.getConfig("controlViews")["regulateViewRanges"] + anchors.left: parent.left + anchors.right: parent.right + Column { + anchors.left: parent.left + anchors.right: parent.horizontalCenter + HifiControls.Label { + text: "Back [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "R1:r1RangeBack:50.0:0.0", + "R2:r2RangeBack:50.0:0.0", + "R3:r3RangeBack:50.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + config: Workload.getConfig("controlViews") + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + integral: true + + labelAreaWidthScale: 0.4 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + Column { + anchors.left: parent.horizontalCenter + anchors.right: parent.right + HifiControls.Label { + text: "Front [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "r1RangeFront:300:1.0", + "r2RangeFront:300:1.0", + "r3RangeFront:300:1.0" + ] + ConfigSlider { + showLabel: false + config: Workload.getConfig("controlViews") + property: modelData.split(":")[0] + max: modelData.split(":")[1] + min: modelData.split(":")[2] + integral: true + + labelAreaWidthScale: 0.3 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + }*/ + property var controlViews: Workload.getConfig("controlViews") + + PlotPerf { + title: "Timings" + height: 100 + object: stats.controlViews + valueScale: 1.0 + valueUnit: "ms" + plots: [ + { + prop: "r2Timing", + label: "Physics + Collisions", + color: "#1AC567" + }, + { + prop: "r3Timing", + label: "Kinematic + Update", + color: "#1A67C5" + } + ] + } + Separator {} + HifiControls.Label { + text: "Numbers:"; + } + HifiControls.Label { + text: "R1= " + Workload.getConfig("regionState")["numR1"]; + } + HifiControls.Label { + text: "R2= " + Workload.getConfig("regionState")["numR2"]; + } + HifiControls.Label { + text: "R3= " + Workload.getConfig("regionState")["numR3"]; + } + + Separator {} + HifiControls.Label { + text: "Display" + } + HifiControls.CheckBox { + boxSize: 20 + text: "Show Proxies" + checked: Workload.getConfig("SpaceToRender")["showProxies"] + onCheckedChanged: { Workload.getConfig("SpaceToRender")["showProxies"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Show Views" + checked: Workload.getConfig("SpaceToRender")["showViews"] + onCheckedChanged: { Workload.getConfig("SpaceToRender")["showViews"] = checked } + } + Separator {} + HifiControls.Label { + text: "Test" + } + Row { + spacing: 5 + anchors.left: parent.left + anchors.right: parent.right + HifiControls.Button { + text: "create scene" + onClicked: { + print("pressed") + _workload.broadcastCreateScene() + } + } + HifiControls.Button { + text: "clear scene" + onClicked: { + print("pressed") + _workload.broadcastClearScene() + } + } + /*HifiControls.Button { + text: "bump floor" + onClicked: { + print("pressed") + _workload.broadcastBumpUpFloor() + } + }*/ + } + HifiControls.Label { + id: gridSizeLabel + anchors.left: parent.left + anchors.right: parent.right + text: "Grid side size [m]" + } + HifiControls.Slider { + id: gridSize + stepSize: 1.0 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.topMargin: 0 + minimumValue: 1 + maximumValue: 200 + value: 100 + + onValueChanged: { _workload.broadcastChangeSize(value) } + } + + HifiControls.Label { + id: objectCount + anchors.left: parent.left + anchors.right: parent.right + text: "Num objects" + } + HifiControls.Slider { + id: resolution + stepSize: 1.0 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.topMargin: 0 + minimumValue: 5 + maximumValue: 75 + value: 5 + + onValueChanged: { _workload.broadcastChangeResolution(value) } + } + + Separator {} + + /*Jet.TaskList { + rootConfig: Workload + anchors.left: parent.left + anchors.right: parent.right + + height: 300 + }*/ + } +} diff --git a/tests-manual/render-perf/CMakeLists.txt b/tests-manual/render-perf/CMakeLists.txt index d688474379..93ff325a98 100644 --- a/tests-manual/render-perf/CMakeLists.txt +++ b/tests-manual/render-perf/CMakeLists.txt @@ -13,7 +13,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries( - shared task networking animation + shared task workload networking animation ktx image octree gl gpu ${PLATFORM_GL_BACKEND} render render-utils graphics fbx model-networking graphics-scripting diff --git a/tests/workload/src/SpaceTests.cpp b/tests/workload/src/SpaceTests.cpp index 1c6b0491a7..23e12074ea 100644 --- a/tests/workload/src/SpaceTests.cpp +++ b/tests/workload/src/SpaceTests.cpp @@ -55,7 +55,9 @@ void SpaceTests::testOverlaps() { float newRadius = 1.0f; glm::vec3 newPosition = viewCenter + glm::vec3(0.0f, 0.0f, far + newRadius - DELTA); workload::Space::Sphere newSphere(newPosition, newRadius); - space.updateProxy(proxyId, newSphere); + std::vector updates; + updates.push_back(workload::Space::ProxyUpdate(proxyId, newSphere)); + space.updateProxies(updates); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 1); @@ -68,7 +70,9 @@ void SpaceTests::testOverlaps() { float newRadius = 1.0f; glm::vec3 newPosition = viewCenter + glm::vec3(0.0f, 0.0f, mid + newRadius - DELTA); workload::Space::Sphere newSphere(newPosition, newRadius); - space.updateProxy(proxyId, newSphere); + std::vector updates; + updates.push_back(workload::Space::ProxyUpdate(proxyId, newSphere)); + space.updateProxies(updates); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 1); @@ -81,7 +85,9 @@ void SpaceTests::testOverlaps() { float newRadius = 1.0f; glm::vec3 newPosition = viewCenter + glm::vec3(0.0f, 0.0f, near + newRadius - DELTA); workload::Space::Sphere newSphere(newPosition, newRadius); - space.updateProxy(proxyId, newSphere); + std::vector updates; + updates.push_back(workload::Space::ProxyUpdate(proxyId, newSphere)); + space.updateProxies(updates); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 1); @@ -92,7 +98,9 @@ void SpaceTests::testOverlaps() { { // delete proxy // NOTE: atm deleting a proxy doesn't result in a "Change" - space.deleteProxy(proxyId); + std::vector deadProxies; + deadProxies.push_back(proxyId); + space.deleteProxies(deadProxies); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 0); @@ -145,9 +153,9 @@ void SpaceTests::benchmark() { uint32_t numProxies[] = { 100, 1000, 10000, 100000 }; uint32_t numTests = 4; std::vector timeToAddAll; - std::vector timeToRemoveAll; std::vector timeToMoveView; std::vector timeToMoveProxies; + std::vector timeToRemoveAll; for (uint32_t i = 0; i < numTests; ++i) { workload::Space space;