Merge pull request from samcake/red

Render Scene Octree : A Minute of Arc
This commit is contained in:
Brad Hefta-Gaub 2016-02-15 12:49:38 -08:00
commit 4faeea7c3f
42 changed files with 3176 additions and 826 deletions

View file

@ -14,7 +14,7 @@ var Entities, Script, print, Vec3, MyAvatar, Camera, Quat;
// NOTE: to test the full rendering of the specified number of objects (as opposed to
// testing LOD filtering), you may want to set LOD to manual maximum visibility.
var LIFETIME = 20;
var LIFETIME = 120;
var ROWS_X = 17;
var ROWS_Y = 10;
var ROWS_Z = 10;

View file

@ -0,0 +1,99 @@
//
// debugRenderOctree.js
// examples/utilities/tools
//
// Sam Gateau
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("cookies.js");
var panel = new Panel(10, 300);
var drawOctree = Render.RenderDeferredTask.DrawSceneOctree;
Render.RenderDeferredTask.DrawSceneOctree.enabled = true;
Render.RenderDeferredTask.DrawItemSelection.enabled = true;
panel.newCheckbox("Show Octree Cells",
function(value) { Render.RenderDeferredTask.DrawSceneOctree.showVisibleCells = value; },
function() { return (Render.RenderDeferredTask.DrawSceneOctree.showVisibleCells); },
function(value) { return (value); }
);
panel.newCheckbox("Show Empty Cells",
function(value) { Render.RenderDeferredTask.DrawSceneOctree.showEmptyCells = value; },
function() { return (Render.RenderDeferredTask.DrawSceneOctree.showEmptyCells); },
function(value) { return (value); }
);
panel.newCheckbox("Freeze Frustum",
function(value) { Render.RenderDeferredTask.FetchSceneSelection.freezeFrustum = value; Render.RenderDeferredTask.CullSceneSelection.freezeFrustum = value; },
function() { return (Render.RenderDeferredTask.FetchSceneSelection.freezeFrustum); },
function(value) { return (value); }
);
panel.newCheckbox("Show Inside Items",
function(value) { Render.RenderDeferredTask.DrawItemSelection.showInsideItems = value; },
function() { return (Render.RenderDeferredTask.DrawItemSelection.showInsideItems); },
function(value) { return (value); }
);
panel.newCheckbox("Show Inside Subcell Items",
function(value) { Render.RenderDeferredTask.DrawItemSelection.showInsideSubcellItems = value; },
function() { return (Render.RenderDeferredTask.DrawItemSelection.showInsideSubcellItems); },
function(value) { return (value); }
);
panel.newCheckbox("Show Partial Items",
function(value) { Render.RenderDeferredTask.DrawItemSelection.showPartialItems = value; },
function() { return (Render.RenderDeferredTask.DrawItemSelection.showPartialItems); },
function(value) { return (value); }
);
panel.newCheckbox("Show Partial Subcell Items",
function(value) { Render.RenderDeferredTask.DrawItemSelection.showPartialSubcellItems = value; },
function() { return (Render.RenderDeferredTask.DrawItemSelection.showPartialSubcellItems); },
function(value) { return (value); }
);
/*
panel.newSlider('Cells Free / Allocated', -1, 1,
function(value) { value; }, // setter
function() { return Render.RenderDeferredTask.DrawSceneOctree.numFreeCells; }, // getter
function(value) { return value; });
this.update = function () {
var numFree = Render.RenderDeferredTask.DrawSceneOctree.numFreeCells;
var numAlloc = Render.RenderDeferredTask.DrawSceneOctree.numAllocatedCells;
var title = [
' ' + name,
numFree + ' / ' + numAlloc
].join('\t');
widget.editTitle({ text: title });
slider.setMaxValue(numAlloc);
};
*/
function mouseMoveEvent(event) {
panel.mouseMoveEvent(event);
}
function mousePressEvent(event) {
panel.mousePressEvent(event);
}
function mouseReleaseEvent(event) {
panel.mouseReleaseEvent(event);
}
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
function scriptEnding() {
panel.destroy();
Render.RenderDeferredTask.DrawSceneOctree.enabled = false;
Render.RenderDeferredTask.DrawItemSelection.enabled = false;
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -3661,7 +3661,7 @@ public:
static render::ItemID _item; // unique WorldBoxRenderData
};
render::ItemID WorldBoxRenderData::_item = 0;
render::ItemID WorldBoxRenderData::_item { render::Item::INVALID_ITEM_ID };
namespace render {
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); }

View file

@ -479,7 +479,7 @@ private:
quint64 _lastFaceTrackerUpdate;
render::ScenePointer _main3DScene{ new render::Scene() };
render::ScenePointer _main3DScene{ new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) };
render::EnginePointer _renderEngine{ new render::Engine() };
gpu::ContextPointer _gpuContext; // initialized during window creation

View file

@ -239,7 +239,7 @@ protected:
virtual void updatePalms();
render::ItemID _renderItemID;
render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID };
ThreadSafeValueCache<glm::vec3> _leftPalmPositionCache { glm::vec3() };
ThreadSafeValueCache<glm::quat> _leftPalmRotationCache { glm::quat() };

View file

@ -92,7 +92,7 @@ public:
protected:
float updatePulse();
render::ItemID _renderItemID;
render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID };
bool _isLoaded;
float _alpha;

View file

@ -14,6 +14,8 @@
#include <render/Scene.h>
#include <EntityItem.h>
#include "AbstractViewStateInterface.h"
// These or the icon "name" used by the render item status value, they correspond to the atlas texture used by the DrawItemStatus
// job in the current rendering pipeline defined as of now (11/2015) in render-utils/RenderDeferredTask.cpp.
@ -66,8 +68,22 @@ public:
pendingChanges.removeItem(_myItem);
}
void notifyChanged() {
if (_myItem == render::Item::INVALID_ITEM_ID) {
return;
}
render::PendingChanges pendingChanges;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
pendingChanges.updateItem<RenderableEntityItemProxy>(_myItem, [](RenderableEntityItemProxy& data) {
});
scene->enqueuePendingChanges(pendingChanges);
}
private:
render::ItemID _myItem;
render::ItemID _myItem { render::Item::INVALID_ITEM_ID };
};
@ -75,6 +91,8 @@ private:
public: \
virtual bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override { return _renderHelper.addToScene(self, scene, pendingChanges); } \
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override { _renderHelper.removeFromScene(self, scene, pendingChanges); } \
virtual void locationChanged() override { EntityItem::locationChanged(); _renderHelper.notifyChanged(); } \
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); _renderHelper.notifyChanged(); } \
private: \
SimpleRenderableEntityItem _renderHelper;

View file

@ -96,7 +96,7 @@ private:
QVector<QVector<glm::vec3>> _points;
bool _dimensionsInitialized = true;
render::ItemID _myMetaItem;
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
bool _showCollisionHull = false;

View file

@ -310,3 +310,14 @@ void RenderableParticleEffectEntityItem::createPipelines() {
_texturedPipeline = gpu::Pipeline::create(program, state);
}
}
void RenderableParticleEffectEntityItem::notifyBoundChanged() {
if (_renderItemId == render::Item::INVALID_ITEM_ID) {
return;
}
render::PendingChanges pendingChanges;
pendingChanges.updateItem<ParticlePayloadData>(_renderItemId, [](ParticlePayloadData& payload) {
});
_scene->enqueuePendingChanges(pendingChanges);
}

View file

@ -29,10 +29,15 @@ public:
virtual void removeFromScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) override;
protected:
virtual void locationChanged() override { EntityItem::locationChanged(); notifyBoundChanged(); }
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
void notifyBoundChanged();
void createPipelines();
render::ScenePointer _scene;
render::ItemID _renderItemId;
render::ItemID _renderItemId{ render::Item::INVALID_ITEM_ID };
NetworkTexturePointer _texture;
gpu::PipelinePointer _untexturedPipeline;

View file

@ -129,7 +129,7 @@ private:
NetworkTexturePointer _zTexture;
const int MATERIAL_GPU_SLOT = 3;
render::ItemID _myItem;
render::ItemID _myItem{ render::Item::INVALID_ITEM_ID };
static gpu::PipelinePointer _pipeline;
ShapeInfo _shapeInfo;

View file

@ -234,3 +234,17 @@ void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, std::shar
_model->removeFromScene(scene, pendingChanges);
}
}
void RenderableZoneEntityItem::notifyBoundChanged() {
if (_myMetaItem == render::Item::INVALID_ITEM_ID) {
return;
}
render::PendingChanges pendingChanges;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
pendingChanges.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [](RenderableZoneEntityItemMeta& data) {
});
scene->enqueuePendingChanges(pendingChanges);
}

View file

@ -40,6 +40,10 @@ public:
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
private:
virtual void locationChanged() override { EntityItem::locationChanged(); notifyBoundChanged(); }
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
void notifyBoundChanged();
Model* getModel();
void initialSimulation();
void updateGeometry();
@ -50,7 +54,7 @@ private:
Model* _model;
bool _needsInitialSimulation;
render::ItemID _myMetaItem;
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
};
#endif // hifi_RenderableZoneEntityItem_h

View file

@ -1206,7 +1206,6 @@ void EntityItem::setDimensions(const glm::vec3& value) {
return;
}
setScale(value);
requiresRecalcBoxes();
}
/// The maximum bounding cube for the entity, independent of it's rotation.
@ -1946,3 +1945,8 @@ void EntityItem::locationChanged() {
requiresRecalcBoxes();
SpatiallyNestable::locationChanged(); // tell all the children, also
}
void EntityItem::dimensionsChanged() {
requiresRecalcBoxes();
SpatiallyNestable::dimensionsChanged(); // Do what you have to do
}

View file

@ -251,8 +251,9 @@ public:
const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity
/// registration point as ratio of entity
void setRegistrationPoint(const glm::vec3& value)
{ _registrationPoint = glm::clamp(value, 0.0f, 1.0f); requiresRecalcBoxes(); }
void setRegistrationPoint(const glm::vec3& value) {
_registrationPoint = glm::clamp(value, 0.0f, 1.0f); dimensionsChanged(); // Registration Point affects the bounding box
}
bool hasAngularVelocity() const { return getAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; }
bool hasLocalAngularVelocity() const { return getLocalAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; }
@ -424,6 +425,8 @@ protected:
void setActionDataInternal(QByteArray actionData);
virtual void locationChanged() override;
virtual void dimensionsChanged() override;
EntityTypes::EntityType _type;
quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity,
// and physics changes

View file

@ -10,35 +10,6 @@
!>
<@if not GPU_COLOR_SLH@>
<@def GPU_COLOR_SLH@>
<!
float colorComponentToLinear(float cs) {
// sRGB to linear conversion
// { cs / 12.92, cs <= 0.04045
// cl = {
// { ((cs + 0.055)/1.055)^2.4, cs > 0.04045
// constants:
// T = 0.04045
// A = 1 / 1.055 = 0.94786729857
// B = 0.055 * A = 0.05213270142
// C = 1 / 12.92 = 0.0773993808
// G = 2.4
const float T = 0.04045;
const float A = 0.947867;
const float B = 0.052132;
const float C = 0.077399;
const float G = 2.4;
if (cs > T) {
return pow((cs * A + B), G);
} else {
return cs * C;
}
}
vec3 colorToLinear(vec3 srgb) {
return vec3(colorComponentToLinear(srgb.x), colorComponentToLinear(srgb.y), colorComponentToLinear(srgb.z));
}
!>
vec3 colorToLinearRGB(vec3 srgb) {
const float GAMMA_22 = 2.2;
@ -49,4 +20,27 @@ vec4 colorToLinearRGBA(vec4 srgba) {
return vec4(colorToLinearRGB(srgba.xyz), srgba.w);
}
<@func declareColorWheel()@>
vec3 colorWheel(float normalizedHue) {
float v = normalizedHue * 6.f;
if (v < 0.f) {
return vec3(1.f, 0.f, 0.f);
} else if (v < 1.f) {
return vec3(1.f, v, 0.f);
} else if (v < 2.f) {
return vec3(1.f - (v-1.f), 1.f, 0.f);
} else if (v < 3.f) {
return vec3(0.f, 1.f, (v-2.f));
} else if (v < 4.f) {
return vec3(0.f, 1.f - (v-3.f), 1.f );
} else if (v < 5.f) {
return vec3((v-4.f), 0.f, 1.f );
} else if (v < 6.f) {
return vec3(1.f, 0.f, 1.f - (v-5.f));
} else {
return vec3(1.f, 0.f, 0.f);
}
}
<@endfunc@>
<@endif@>

View file

@ -810,3 +810,9 @@ float ViewFrustum::calculateRenderAccuracy(const AABox& bounds, float octreeSize
float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) {
return voxelSizeScale / powf(2, renderLevel);
}
float ViewFrustum::getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) const {
const float maxScale = (float)TREE_SCALE;
float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO;
return atan(maxScale / visibleDistanceAtMaxScale);
}

View file

@ -125,6 +125,11 @@ public:
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
int boundaryLevelAdjust = 0) const;
float getAccuracyAngle(float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const;
enum PlaneIndex { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE, NUM_PLANES };
const ::Plane* getPlanes() const { return _planes; }
private:
// Used for keyhole calculations
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
@ -160,7 +165,6 @@ private:
float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES;
glm::vec4 _corners[8];
glm::vec3 _cornersWorld[8];
enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE };
::Plane _planes[6]; // How will this be used?
const char* debugPlaneName (int plane) const;

View file

@ -37,7 +37,7 @@ public:
protected:
std::shared_ptr<AnimDebugDrawData> _animDebugDrawData;
std::shared_ptr<AnimDebugDrawPayload> _animDebugDrawPayload;
render::ItemID _itemID;
render::ItemID _itemID{ render::Item::INVALID_ITEM_ID };
static gpu::PipelinePointer _pipeline;

View file

@ -24,6 +24,7 @@
#include "render/DrawTask.h"
#include "render/DrawStatus.h"
#include "render/DrawSceneOctree.h"
#include "AmbientOcclusionEffect.h"
#include "AntialiasingEffect.h"
#include "ToneMappingEffect.h"
@ -50,18 +51,35 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
// Prepare the ShapePipelines
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
initDeferredPipelines(*shapePlumber);
// CPU: Fetch the renderOpaques
const auto fetchedOpaques = addJob<FetchItems>("FetchOpaque");
const auto culledOpaques = addJob<CullItems<RenderDetails::OPAQUE_ITEM>>("CullOpaque", fetchedOpaques, cullFunctor);
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", culledOpaques);
// CPU only, create the list of renderedTransparents items
const auto fetchedTransparents = addJob<FetchItems>("FetchTransparent", FetchItems(
ItemFilter::Builder::transparentShape().withoutLayered()));
const auto culledTransparents =
addJob<CullItems<RenderDetails::TRANSLUCENT_ITEM>>("CullTransparent", fetchedTransparents, cullFunctor);
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", culledTransparents, DepthSortItems(false));
// CPU jobs:
// Fetch and cull the items from the scene
auto sceneFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
const auto sceneSelection = addJob<FetchSpatialTree>("FetchSceneSelection", sceneFilter);
const auto culledSceneSelection = addJob<CullSpatialSelection>("CullSceneSelection", sceneSelection, cullFunctor, RenderDetails::OPAQUE_ITEM, sceneFilter);
// Multi filter visible items into different buckets
const int NUM_FILTERS = 3;
const int OPAQUE_SHAPE_BUCKET = 0;
const int TRANSPARENT_SHAPE_BUCKET = 1;
const int LIGHT_BUCKET = 2;
MultiFilterItem<NUM_FILTERS>::ItemFilterArray triageFilters = { {
ItemFilter::Builder::opaqueShape(),
ItemFilter::Builder::transparentShape(),
ItemFilter::Builder::light()
} };
const auto filteredItemsBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSceneSelection, triageFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
// Extract / Sort opaques / Transparents / Lights / Overlays
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredItemsBuckets[OPAQUE_SHAPE_BUCKET]);
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredItemsBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
const auto lights = filteredItemsBuckets[LIGHT_BUCKET];
// Overlays are not culled because we want to make sure they are seen
// Could be considered a bug in the current cullfunctor
const auto overlayOpaques = addJob<FetchItems>("FetchOverlayOpaque", ItemFilter::Builder::opaqueShapeLayered());
const auto fetchedOverlayOpaques = addJob<FetchItems>("FetchOverlayTransparents", ItemFilter::Builder::transparentShapeLayered());
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortTransparentOverlay", fetchedOverlayOpaques, DepthSortItems(false));
// GPU Jobs: Start preparing the deferred and lighting buffer
addJob<PrepareDeferred>("PrepareDeferred");
@ -79,7 +97,7 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
addJob<AmbientOcclusionEffect>("AmbientOcclusion");
// Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now.
addJob<DrawLight>("DrawLight", cullFunctor);
addJob<DrawLight>("DrawLight", lights);
// DeferredBuffer is complete, now let's shade it into the LightingBuffer
addJob<RenderDeferred>("RenderDeferred");
@ -93,22 +111,35 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
// Lighting Buffer ready for tone mapping
addJob<ToneMappingDeferred>("ToneMapping");
// Debugging Deferred buffer job
addJob<DebugDeferredBuffer>("DebugDeferredBuffer");
// Overlays
addJob<DrawOverlay3D>("DrawOverlay3DOpaque", overlayOpaques, true);
addJob<DrawOverlay3D>("DrawOverlay3DTransparent", overlayTransparents, false);
// Status icon rendering job
// Debugging stages
{
// Grab a texture map representing the different status icons and assign that to the drawStatsuJob
auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg";
auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath);
addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap));
// Debugging Deferred buffer job
addJob<DebugDeferredBuffer>("DebugDeferredBuffer");
// Scene Octree Debuging job
{
addJob<DrawSceneOctree>("DrawSceneOctree", sceneSelection);
addJob<DrawItemSelection>("DrawItemSelection", sceneSelection);
}
// Status icon rendering job
{
// Grab a texture map representing the different status icons and assign that to the drawStatsuJob
auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg";
auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath);
addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap));
}
}
addJob<DrawOverlay3D>("DrawOverlay3DOpaque", ItemFilter::Builder::opaqueShape().withLayered());
addJob<DrawOverlay3D>("DrawOverlay3DTransparent", ItemFilter::Builder::transparentShape().withLayered());
addJob<HitEffect>("HitEffect");
// FIXME: Hit effect is never used, let's hide it for now, probably a more generic way to add custom post process effects
// addJob<HitEffect>("HitEffect");
// Blit!
addJob<Blit>("Blit");
}
@ -130,13 +161,14 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend
}
};
void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems) {
void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.setViewportTransform(args->_viewport);
batch.setStateScissorRect(args->_viewport);
@ -158,28 +190,19 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont
});
}
DrawOverlay3D::DrawOverlay3D(ItemFilter filter) : _filter{ filter }, _shapePlumber{ std::make_shared<ShapePlumber>() } {
DrawOverlay3D::DrawOverlay3D(bool opaque) :
_shapePlumber(std::make_shared<ShapePlumber>()),
_opaquePass(opaque) {
initOverlay3DPipelines(*_shapePlumber);
}
void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const render::ItemBounds& inItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
// render backgrounds
auto& scene = sceneContext->_scene;
auto& items = scene->getMasterBucket().at(_filter);
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
ItemIDsBounds inItems;
inItems.reserve(items.size());
for (auto id : items) {
auto& item = scene->getItem(id);
if (item.getKey().isVisible() && (item.getLayer() == 1)) {
inItems.emplace_back(id);
}
}
config->setNumDrawn((int)inItems.size());
emit config->numDrawnChanged();
@ -189,7 +212,7 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon
// Clear the framebuffer without stereo
// Needs to be distinct from the other batch because using the clear call
// while stereo is enabled triggers a warning
{
if (_opaquePass) {
gpu::Batch batch;
batch.enableStereo(false);
batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, true);
@ -262,7 +285,7 @@ void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const
auto& items = scene->getMasterBucket().at(ItemFilter::Builder::background());
ItemIDsBounds inItems;
ItemBounds inItems;
inItems.reserve(items.size());
for (auto id : items) {
inItems.emplace_back(id);

View file

@ -43,6 +43,7 @@ class DrawConfig : public render::Job::Config {
Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
public:
int getNumDrawn() { return numDrawn; }
void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
@ -59,12 +60,12 @@ protected:
class DrawDeferred {
public:
using Config = DrawConfig;
using JobModel = render::Job::ModelI<DrawDeferred, render::ItemIDsBounds, Config>;
using JobModel = render::Job::ModelI<DrawDeferred, render::ItemBounds, Config>;
DrawDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
void configure(const Config& config) { _maxDrawn = config.maxDrawn; }
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemIDsBounds& inItems);
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems);
protected:
render::ShapePlumberPointer _shapePlumber;
@ -110,17 +111,17 @@ protected:
class DrawOverlay3D {
public:
using Config = DrawOverlay3DConfig;
using JobModel = render::Job::Model<DrawOverlay3D, Config>;
using JobModel = render::Job::ModelI<DrawOverlay3D, render::ItemBounds, Config>;
DrawOverlay3D(render::ItemFilter filter);
DrawOverlay3D(bool opaque);
void configure(const Config& config) { _maxDrawn = config.maxDrawn; }
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems);
protected:
render::ItemFilter _filter;
render::ShapePlumberPointer _shapePlumber;
int _maxDrawn; // initialized by Config
bool _opaquePass{ true };
};
class Blit {

View file

@ -0,0 +1,337 @@
//
// CullTask.cpp
// render/src/render
//
// Created by Sam Gateau on 2/2/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CullTask.h"
#include <algorithm>
#include <assert.h>
#include <PerfStat.h>
#include <ViewFrustum.h>
#include <gpu/Context.h>
using namespace render;
void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
const ItemBounds& inItems, ItemBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
RenderArgs* args = renderContext->args;
ViewFrustum* frustum = args->_viewFrustum;
details._considered += inItems.size();
// Culling / LOD
for (auto item : inItems) {
if (item.bound.isNull()) {
outItems.emplace_back(item); // One more Item to render
continue;
}
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...
bool outOfView;
{
PerformanceTimer perfTimer("boxInFrustum");
outOfView = frustum->boxInFrustum(item.bound) == ViewFrustum::OUTSIDE;
}
if (!outOfView) {
bool bigEnoughToRender;
{
PerformanceTimer perfTimer("shouldRender");
bigEnoughToRender = cullFunctor(args, item.bound);
}
if (bigEnoughToRender) {
outItems.emplace_back(item); // One more Item to render
} else {
details._tooSmall++;
}
} else {
details._outOfView++;
}
}
details._rendered += outItems.size();
}
struct ItemBoundSort {
float _centerDepth = 0.0f;
float _nearDepth = 0.0f;
float _farDepth = 0.0f;
ItemID _id = 0;
AABox _bounds;
ItemBoundSort() {}
ItemBoundSort(float centerDepth, float nearDepth, float farDepth, ItemID id, const AABox& bounds) : _centerDepth(centerDepth), _nearDepth(nearDepth), _farDepth(farDepth), _id(id), _bounds(bounds) {}
};
struct FrontToBackSort {
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
return (left._centerDepth < right._centerDepth);
}
};
struct BackToFrontSort {
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
return (left._centerDepth > right._centerDepth);
}
};
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
auto& scene = sceneContext->_scene;
RenderArgs* args = renderContext->args;
// Allocate and simply copy
outItems.clear();
outItems.reserve(inItems.size());
// Make a local dataset of the center distance and closest point distance
std::vector<ItemBoundSort> itemBoundSorts;
itemBoundSorts.reserve(outItems.size());
for (auto itemDetails : inItems) {
auto item = scene->getItem(itemDetails.id);
auto bound = itemDetails.bound; // item.getBound();
float distance = args->_viewFrustum->distanceToCamera(bound.calcCenter());
itemBoundSorts.emplace_back(ItemBoundSort(distance, distance, distance, itemDetails.id, bound));
}
// sort against Z
if (frontToBack) {
FrontToBackSort frontToBackSort;
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), frontToBackSort);
} else {
BackToFrontSort backToFrontSort;
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), backToFrontSort);
}
// FInally once sorted result to a list of itemID
for (auto& item : itemBoundSorts) {
outItems.emplace_back(ItemBound(item._id, item._bounds));
}
}
void DepthSortItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) {
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems);
}
void FetchItems::configure(const Config& config) {
}
void FetchItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
auto& scene = sceneContext->_scene;
outItems.clear();
const auto& bucket = scene->getMasterBucket();
const auto& items = bucket.find(_filter);
if (items != bucket.end()) {
outItems.reserve(items->second.size());
for (auto& id : items->second) {
auto& item = scene->getItem(id);
outItems.emplace_back(ItemBound(id, item.getBound()));
}
}
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
}
void FetchSpatialTree::configure(const Config& config) {
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
_freezeFrustum = config.freezeFrustum;
_lodAngle = config.lodAngle;
}
void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
RenderArgs* args = renderContext->args;
auto& scene = sceneContext->_scene;
// start fresh
outSelection.clear();
// Eventually use a frozen frustum
auto queryFrustum = *args->_viewFrustum;
if (_freezeFrustum) {
if (_justFrozeFrustum) {
_justFrozeFrustum = false;
_frozenFrutstum = *args->_viewFrustum;
}
queryFrustum = _frozenFrutstum;
}
// Octree selection!
float angle = glm::degrees(queryFrustum.getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
scene->getSpatialTree().selectCellItems(outSelection, _filter, queryFrustum, angle);
}
void CullSpatialSelection::configure(const Config& config) {
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
_freezeFrustum = config.freezeFrustum;
}
void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
RenderArgs* args = renderContext->args;
auto& scene = sceneContext->_scene;
auto& details = args->_details.edit(_detailType);
details._considered += inSelection.numItems();
// Eventually use a frozen frustum
auto argFrustum = args->_viewFrustum;
if (_freezeFrustum) {
if (_justFrozeFrustum) {
_justFrozeFrustum = false;
_frozenFrutstum = *args->_viewFrustum;
}
args->_viewFrustum = &_frozenFrutstum; // replace the true view frustum by the frozen one
}
// Culling Frustum / solidAngle test helper class
struct Test {
CullFunctor _functor;
RenderArgs* _args;
RenderDetails::Item& _renderDetails;
glm::vec3 _eyePos;
float _squareTanAlpha;
Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) :
_functor(functor),
_args(pargs),
_renderDetails(renderDetails)
{
// FIXME: Keep this code here even though we don't use it yet
/*_eyePos = _args->_viewFrustum->getPosition();
float a = glm::degrees(_args->_viewFrustum->getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust));
auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees
angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree
auto tanAlpha = tan(angle);
_squareTanAlpha = (float)(tanAlpha * tanAlpha);
*/
}
bool frustumTest(const AABox& bound) {
if (_args->_viewFrustum->boxInFrustum(bound) == ViewFrustum::OUTSIDE) {
_renderDetails._outOfView++;
return false;
}
return true;
}
bool solidAngleTest(const AABox& bound) {
// FIXME: Keep this code here even though we don't use it yet
//auto eyeToPoint = bound.calcCenter() - _eyePos;
//auto boundSize = bound.getDimensions();
//float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
//if (test < 0.0f) {
if (!_functor(_args, bound)) {
_renderDetails._tooSmall++;
return false;
}
return true;
}
};
Test test(_cullFunctor, args, details);
// Now we have a selection of items to render
outItems.clear();
outItems.reserve(inSelection.numItems());
// Now get the bound, and
// filter individually against the _filter
// visibility cull if partially selected ( octree cell contianing it was partial)
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
// inside & fit items: easy, just filter
{
PerformanceTimer perfTimer("insideFitItems");
for (auto id : inSelection.insideItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// inside & subcell items: filter & distance cull
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
// partial & fit items: filter & frustum cull
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
// partial & subcell items:: filter & frutum cull & solidangle cull
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
}
details._rendered += outItems.size();
// Restore frustum if using the frozen one:
if (_freezeFrustum) {
args->_viewFrustum = argFrustum;
}
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
}

View file

@ -0,0 +1,236 @@
//
// CullTask.h
// render/src/render
//
// Created by Sam Gateau on 2/2/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_render_CullTask_h
#define hifi_render_CullTask_h
#include "Engine.h"
#include "ViewFrustum.h"
namespace render {
using CullFunctor = std::function<bool(const RenderArgs*, const AABox&)>;
void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
const ItemBounds& inItems, ItemBounds& outItems);
void depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems);
class FetchItemsConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems)
public:
int getNumItems() { return numItems; }
int numItems{ 0 };
};
class FetchItems {
public:
using Config = FetchItemsConfig;
using JobModel = Job::ModelO<FetchItems, ItemBounds, Config>;
FetchItems() {}
FetchItems(const ItemFilter& filter) : _filter(filter) {}
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
void configure(const Config& config);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems);
};
template<RenderDetails::Type T>
class CullItems {
public:
using JobModel = Job::ModelIO<CullItems<T>, ItemBounds, ItemBounds>;
CullItems(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) {
const auto& args = renderContext->args;
auto& details = args->_details.edit(T);
outItems.clear();
outItems.reserve(inItems.size());
render::cullItems(renderContext, _cullFunctor, details, inItems, outItems);
}
protected:
CullFunctor _cullFunctor;
};
class FetchSpatialTreeConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems)
Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
Q_PROPERTY(float LODAngle MEMBER lodAngle NOTIFY dirty)
public:
int numItems{ 0 };
int getNumItems() { return numItems; }
bool freezeFrustum{ false };
float lodAngle{ 2.0 };
public slots:
void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
signals:
void dirty();
};
class FetchSpatialTree {
bool _freezeFrustum{ false }; // initialized by Config
bool _justFrozeFrustum{ false };
ViewFrustum _frozenFrutstum;
float _lodAngle;
public:
using Config = FetchSpatialTreeConfig;
using JobModel = Job::ModelO<FetchSpatialTree, ItemSpatialTree::ItemSelection, Config>;
FetchSpatialTree() {}
FetchSpatialTree(const ItemFilter& filter) : _filter(filter) {}
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
void configure(const Config& config);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection);
};
class CullSpatialSelectionConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems)
Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
public:
int numItems{ 0 };
int getNumItems() { return numItems; }
bool freezeFrustum{ false };
public slots:
void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
signals:
void dirty();
};
class CullSpatialSelection {
bool _freezeFrustum{ false }; // initialized by Config
bool _justFrozeFrustum{ false };
ViewFrustum _frozenFrutstum;
public:
using Config = CullSpatialSelectionConfig;
using JobModel = Job::ModelIO<CullSpatialSelection, ItemSpatialTree::ItemSelection, ItemBounds, Config>;
CullSpatialSelection(CullFunctor cullFunctor, RenderDetails::Type type, const ItemFilter& filter) :
_cullFunctor{ cullFunctor },
_detailType(type),
_filter(filter) {}
CullSpatialSelection(CullFunctor cullFunctor) :
_cullFunctor{ cullFunctor } {}
CullFunctor _cullFunctor;
RenderDetails::Type _detailType{ RenderDetails::OPAQUE_ITEM };
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
void configure(const Config& config);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems);
};
class FilterItemSelectionConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems)
public:
int numItems{ 0 };
int getNumItems() { return numItems; }
};
class FilterItemSelection {
public:
using Config = FilterItemSelectionConfig;
using JobModel = Job::ModelIO<FilterItemSelection, ItemBounds, ItemBounds, Config>;
FilterItemSelection() {}
FilterItemSelection(const ItemFilter& filter) :
_filter(filter) {}
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
void configure(const Config& config);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems);
};
class MultiFilterItemConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems)
public:
int numItems{ 0 };
int getNumItems() { return numItems; }
};
template < class T, int NUM >
class VaryingArray : public std::array<Varying, NUM> {
public:
VaryingArray() {
for (size_t i = 0; i < NUM; i++) {
(*this)[i] = Varying(T());
}
}
};
template <int NUM_FILTERS>
class MultiFilterItem {
public:
using ItemFilterArray = std::array<ItemFilter, NUM_FILTERS>;
using ItemBoundsArray = VaryingArray<ItemBounds, NUM_FILTERS>;
using Config = MultiFilterItemConfig;
using JobModel = Job::ModelIO<MultiFilterItem, ItemBounds, ItemBoundsArray, Config>;
MultiFilterItem() {}
MultiFilterItem(const ItemFilterArray& filters) :
_filters(filters) {}
ItemFilterArray _filters;
void configure(const Config& config) {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBoundsArray& outItems) {
auto& scene = sceneContext->_scene;
// Clear previous values
for (size_t i = 0; i < NUM_FILTERS; i++) {
outItems[i].template edit<ItemBounds>().clear();
}
// For each item, filter it into the buckets
for (auto itemBound : inItems) {
auto& item = scene->getItem(itemBound.id);
auto itemKey = item.getKey();
for (size_t i = 0; i < NUM_FILTERS; i++) {
if (_filters[i].test(itemKey)) {
outItems[i].template edit<ItemBounds>().emplace_back(itemBound);
}
}
}
}
};
class DepthSortItems {
public:
using JobModel = Job::ModelIO<DepthSortItems, ItemBounds, ItemBounds>;
bool _frontToBack;
DepthSortItems(bool frontToBack = true) : _frontToBack(frontToBack) {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems);
};
}
#endif // hifi_render_CullTask_h;

View file

@ -0,0 +1,278 @@
//
// DrawSceneOctree.cpp
// render/src/render
//
// Created by Sam Gateau on 1/25/16.
// 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 "DrawSceneOctree.h"
#include <algorithm>
#include <assert.h>
#include <PerfStat.h>
#include <RenderArgs.h>
#include <gpu/Context.h>
#include <gpu/StandardShaderLib.h>
#include "drawCellBounds_vert.h"
#include "drawCellBounds_frag.h"
#include "drawLODReticle_frag.h"
#include "drawItemBounds_vert.h"
#include "drawItemBounds_frag.h"
using namespace render;
const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() {
if (!_drawCellBoundsPipeline) {
auto vs = gpu::Shader::createVertex(std::string(drawCellBounds_vert));
auto ps = gpu::Shader::createPixel(std::string(drawCellBounds_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings);
_drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation");
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
// Blend on transparent
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
// Good to go add the brand new pipeline
_drawCellBoundsPipeline = gpu::Pipeline::create(program, state);
}
return _drawCellBoundsPipeline;
}
const gpu::PipelinePointer DrawSceneOctree::getDrawLODReticlePipeline() {
if (!_drawLODReticlePipeline) {
auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS();
auto ps = gpu::Shader::createPixel(std::string(drawLODReticle_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings);
// _drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation");
auto state = std::make_shared<gpu::State>();
// Blend on transparent
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
// Good to go add the brand new pipeline
_drawLODReticlePipeline = gpu::Pipeline::create(program, state);
}
return _drawLODReticlePipeline;
}
void DrawSceneOctree::configure(const Config& config) {
_showVisibleCells = config.showVisibleCells;
_showEmptyCells = config.showEmptyCells;
}
void DrawSceneOctree::run(const SceneContextPointer& sceneContext,
const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
RenderArgs* args = renderContext->args;
auto& scene = sceneContext->_scene;
std::static_pointer_cast<Config>(renderContext->jobConfig)->numAllocatedCells = (int)scene->getSpatialTree().getNumAllocatedCells();
std::static_pointer_cast<Config>(renderContext->jobConfig)->numFreeCells = (int)scene->getSpatialTree().getNumFreeCells();
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
glm::mat4 projMat;
Transform viewMat;
args->_viewFrustum->evalProjectionMatrix(projMat);
args->_viewFrustum->evalViewTransform(viewMat);
batch.setViewportTransform(args->_viewport);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
batch.setModelTransform(Transform());
// bind the one gpu::Pipeline we need
batch.setPipeline(getDrawCellBoundsPipeline());
if (_showVisibleCells) {
for (const auto& cellID : inSelection.cellSelection.insideCells) {
auto cell = scene->getSpatialTree().getConcreteCell(cellID);
auto cellLoc = cell.getlocation();
glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth);
bool doDraw = true;
if (cell.isBrickEmpty() || !cell.hasBrick()) {
if (!_showEmptyCells) {
doDraw = false;
}
cellLocation.w *= -1;
}
if (doDraw) {
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
batch.draw(gpu::LINES, 24, 0);
}
}
for (const auto& cellID : inSelection.cellSelection.partialCells) {
auto cell = scene->getSpatialTree().getConcreteCell(cellID);
auto cellLoc = cell.getlocation();
glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth);
bool doDraw = true;
if (cell.isBrickEmpty() || !cell.hasBrick()) {
if (!_showEmptyCells) {
doDraw = false;
}
cellLocation.w *= -1;
}
if (doDraw) {
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
batch.draw(gpu::LINES, 24, 0);
}
}
}
// Draw the LOD Reticle
{
float angle = glm::degrees(args->_viewFrustum->getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
Transform crosshairModel;
crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0));
crosshairModel.setScale(1000.0 * tan(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO
batch.setViewTransform(Transform());
batch.setModelTransform(crosshairModel);
batch.setPipeline(getDrawLODReticlePipeline());
batch.draw(gpu::TRIANGLE_STRIP, 4, 0);
}
});
}
const gpu::PipelinePointer DrawItemSelection::getDrawItemBoundPipeline() {
if (!_drawItemBoundPipeline) {
auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert));
auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings);
_drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos");
_drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim");
_drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation");
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
// Blend on transparent
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
// Good to go add the brand new pipeline
_drawItemBoundPipeline = gpu::Pipeline::create(program, state);
}
return _drawItemBoundPipeline;
}
void DrawItemSelection::configure(const Config& config) {
_showInsideItems = config.showInsideItems;
_showInsideSubcellItems = config.showInsideSubcellItems;
_showPartialItems = config.showPartialItems;
_showPartialSubcellItems = config.showPartialSubcellItems;
}
void DrawItemSelection::run(const SceneContextPointer& sceneContext,
const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
RenderArgs* args = renderContext->args;
auto& scene = sceneContext->_scene;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
glm::mat4 projMat;
Transform viewMat;
args->_viewFrustum->evalProjectionMatrix(projMat);
args->_viewFrustum->evalViewTransform(viewMat);
batch.setViewportTransform(args->_viewport);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
batch.setModelTransform(Transform());
// bind the one gpu::Pipeline we need
batch.setPipeline(getDrawItemBoundPipeline());
if (_showInsideItems) {
for (const auto& itemID : inSelection.insideItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 0, itemCell.depth);
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
}
if (_showInsideSubcellItems) {
for (const auto& itemID : inSelection.insideSubcellItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 1, itemCell.depth);
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
}
if (_showPartialItems) {
for (const auto& itemID : inSelection.partialItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 0, itemCell.depth);
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
}
if (_showPartialSubcellItems) {
for (const auto& itemID : inSelection.partialSubcellItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 1, itemCell.depth);
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
}
});
}

View file

@ -0,0 +1,130 @@
//
// DrawSceneOctree.h
// render/src/render
//
// Created by Sam Gateau on 1/25/16.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_render_DrawSceneOctree_h
#define hifi_render_DrawSceneOctree_h
#include "DrawTask.h"
#include "gpu/Batch.h"
#include <ViewFrustum.h>
namespace render {
class DrawSceneOctreeConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty())
Q_PROPERTY(bool showVisibleCells MEMBER showVisibleCells WRITE setShowVisibleCells)
Q_PROPERTY(bool showEmptyCells MEMBER showEmptyCells WRITE setShowEmptyCells)
Q_PROPERTY(int numAllocatedCells READ getNumAllocatedCells)
Q_PROPERTY(int numFreeCells READ getNumFreeCells)
public:
DrawSceneOctreeConfig() : Job::Config(false) {}
bool showVisibleCells{ true };
bool showEmptyCells{ false };
int numAllocatedCells{ 0 };
int numFreeCells{ 0 };
int getNumAllocatedCells() const { return numAllocatedCells; }
int getNumFreeCells() const { return numFreeCells; }
public slots:
void setShowVisibleCells(bool show) { showVisibleCells = show; emit dirty(); }
void setShowEmptyCells(bool show) { showEmptyCells = show; emit dirty(); }
signals:
void dirty();
};
class DrawSceneOctree {
int _drawCellLocationLoc;
gpu::PipelinePointer _drawCellBoundsPipeline;
gpu::BufferPointer _cells;
gpu::PipelinePointer _drawLODReticlePipeline;
int _drawItemBoundPosLoc = -1;
int _drawItemBoundDimLoc = -1;
gpu::PipelinePointer _drawItemBoundPipeline;
bool _showVisibleCells; // initialized by Config
bool _showEmptyCells; // initialized by Config
public:
using Config = DrawSceneOctreeConfig;
using JobModel = Job::ModelI<DrawSceneOctree, ItemSpatialTree::ItemSelection, Config>;
DrawSceneOctree() {}
void configure(const Config& config);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& selection);
const gpu::PipelinePointer getDrawCellBoundsPipeline();
const gpu::PipelinePointer getDrawLODReticlePipeline();
const gpu::PipelinePointer getDrawItemBoundPipeline();
};
class DrawItemSelectionConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty())
Q_PROPERTY(bool showInsideItems MEMBER showInsideItems WRITE setShowInsideItems)
Q_PROPERTY(bool showInsideSubcellItems MEMBER showInsideSubcellItems WRITE setShowInsideSubcellItems)
Q_PROPERTY(bool showPartialItems MEMBER showPartialItems WRITE setShowPartialItems)
Q_PROPERTY(bool showPartialSubcellItems MEMBER showPartialSubcellItems WRITE setShowPartialSubcellItems)
public:
DrawItemSelectionConfig() : Job::Config(false) {}
bool showInsideItems{ true };
bool showInsideSubcellItems{ true };
bool showPartialItems{ true };
bool showPartialSubcellItems{ true };
public slots:
void setShowInsideItems(bool show) { showInsideItems = show; emit dirty(); }
void setShowInsideSubcellItems(bool show) { showInsideSubcellItems = show; emit dirty(); }
void setShowPartialItems(bool show) { showPartialItems = show; emit dirty(); }
void setShowPartialSubcellItems(bool show) { showPartialSubcellItems = show; emit dirty(); }
signals:
void dirty();
};
class DrawItemSelection {
int _drawItemBoundPosLoc = -1;
int _drawItemBoundDimLoc = -1;
int _drawCellLocationLoc = -1;
gpu::PipelinePointer _drawItemBoundPipeline;
bool _showInsideItems; // initialized by Config
bool _showInsideSubcellItems; // initialized by Config
bool _showPartialItems; // initialized by Config
bool _showPartialSubcellItems; // initialized by Config
public:
using Config = DrawItemSelectionConfig;
using JobModel = Job::ModelI<DrawItemSelection, ItemSpatialTree::ItemSelection, Config>;
DrawItemSelection() {}
void configure(const Config& config);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& selection);
const gpu::PipelinePointer getDrawItemBoundPipeline();
};
}
#endif // hifi_render_DrawStatus_h

View file

@ -43,6 +43,7 @@ const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() {
_drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos");
_drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim");
_drawItemCellLocLoc = program->getUniforms().findLocation("inCellLocation");
auto state = std::make_shared<gpu::State>();
@ -104,7 +105,7 @@ void DrawStatus::configure(const Config& config) {
void DrawStatus::run(const SceneContextPointer& sceneContext,
const RenderContextPointer& renderContext,
const ItemIDsBounds& inItems) {
const ItemBounds& inItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
RenderArgs* args = renderContext->args;
@ -121,20 +122,30 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
if (!_itemStatus) {
_itemStatus = std::make_shared<gpu::Buffer>();;
}
if (!_itemCells) {
_itemCells = std::make_shared<gpu::Buffer>();;
}
_itemBounds->resize((inItems.size() * sizeof(AABox)));
_itemStatus->resize((inItems.size() * NUM_STATUS_VEC4_PER_ITEM * sizeof(glm::vec4)));
_itemCells->resize((inItems.size() * sizeof(Octree::Location)));
AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
for (auto& item : inItems) {
if (!item.bounds.isInvalid()) {
if (!item.bounds.isNull()) {
(*itemAABox) = item.bounds;
if (!item.bound.isInvalid()) {
if (!item.bound.isNull()) {
(*itemAABox) = item.bound;
} else {
(*itemAABox).setBox(item.bounds.getCorner(), 0.1f);
(*itemAABox).setBox(item.bound.getCorner(), 0.1f);
}
auto& itemScene = scene->getItem(item.id);
(*itemCell) = scene->getSpatialTree().getCellLocation(itemScene.getCell());
auto itemStatusPointer = itemScene.getStatus();
if (itemStatusPointer) {
// Query the current status values, this is where the statusGetter lambda get called
@ -159,6 +170,7 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
nbItems++;
itemAABox++;
itemCell++;
}
}
}
@ -184,6 +196,7 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
const unsigned int VEC3_ADRESS_OFFSET = 3;
@ -191,8 +204,15 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
for (int i = 0; i < nbItems; i++) {
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*) (itemAABox + i));
batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET);
glm::ivec4 cellLocation(itemCell->pos.x, itemCell->pos.y, itemCell->pos.z, itemCell->depth);
batch._glUniform4iv(_drawItemCellLocLoc, 1, ((const int*)(&cellLocation)));
batch.draw(gpu::LINES, 24, 0);
itemCell++;
}
}

View file

@ -21,11 +21,11 @@ namespace render {
Q_PROPERTY(bool showDisplay MEMBER showDisplay WRITE setShowDisplay)
Q_PROPERTY(bool showNetwork MEMBER showNetwork WRITE setShowNetwork)
public:
DrawStatusConfig() : Job::Config(false) {}
DrawStatusConfig() : Job::Config(false) {} // FIXME FOR debug
void dirtyHelper();
bool showDisplay{ false };
bool showDisplay{ true }; // FIXME FOR debug
bool showNetwork{ false };
public slots:
@ -39,13 +39,13 @@ namespace render {
class DrawStatus {
public:
using Config = DrawStatusConfig;
using JobModel = Job::ModelI<DrawStatus, ItemIDsBounds, Config>;
using JobModel = Job::ModelI<DrawStatus, ItemBounds, Config>;
DrawStatus() {}
DrawStatus(const gpu::TexturePointer statusIconMap) { setStatusIconMap(statusIconMap); }
void configure(const Config& config);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems);
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems);
const gpu::PipelinePointer getDrawItemBoundsPipeline();
const gpu::PipelinePointer getDrawItemStatusPipeline();
@ -59,6 +59,7 @@ namespace render {
int _drawItemBoundPosLoc = -1;
int _drawItemBoundDimLoc = -1;
int _drawItemCellLocLoc = -1;
int _drawItemStatusPosLoc = -1;
int _drawItemStatusDimLoc = -1;
int _drawItemStatusValue0Loc = -1;
@ -68,6 +69,7 @@ namespace render {
gpu::PipelinePointer _drawItemBoundsPipeline;
gpu::PipelinePointer _drawItemStatusPipeline;
gpu::BufferPointer _itemBounds;
gpu::BufferPointer _itemCells;
gpu::BufferPointer _itemStatus;
gpu::TexturePointer _statusIconMap;
};

View file

@ -20,112 +20,7 @@
using namespace render;
void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
RenderArgs* args = renderContext->args;
ViewFrustum* frustum = args->_viewFrustum;
details._considered += inItems.size();
// Culling / LOD
for (auto item : inItems) {
if (item.bounds.isNull()) {
outItems.emplace_back(item); // One more Item to render
continue;
}
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...
bool outOfView;
{
PerformanceTimer perfTimer("boxInFrustum");
outOfView = frustum->boxInFrustum(item.bounds) == ViewFrustum::OUTSIDE;
}
if (!outOfView) {
bool bigEnoughToRender;
{
PerformanceTimer perfTimer("shouldRender");
bigEnoughToRender = cullFunctor(args, item.bounds);
}
if (bigEnoughToRender) {
outItems.emplace_back(item); // One more Item to render
} else {
details._tooSmall++;
}
} else {
details._outOfView++;
}
}
details._rendered += outItems.size();
}
struct ItemBound {
float _centerDepth = 0.0f;
float _nearDepth = 0.0f;
float _farDepth = 0.0f;
ItemID _id = 0;
AABox _bounds;
ItemBound() {}
ItemBound(float centerDepth, float nearDepth, float farDepth, ItemID id, const AABox& bounds) : _centerDepth(centerDepth), _nearDepth(nearDepth), _farDepth(farDepth), _id(id), _bounds(bounds) {}
};
struct FrontToBackSort {
bool operator() (const ItemBound& left, const ItemBound& right) {
return (left._centerDepth < right._centerDepth);
}
};
struct BackToFrontSort {
bool operator() (const ItemBound& left, const ItemBound& right) {
return (left._centerDepth > right._centerDepth);
}
};
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
auto& scene = sceneContext->_scene;
RenderArgs* args = renderContext->args;
// Allocate and simply copy
outItems.clear();
outItems.reserve(inItems.size());
// Make a local dataset of the center distance and closest point distance
std::vector<ItemBound> itemBounds;
itemBounds.reserve(outItems.size());
for (auto itemDetails : inItems) {
auto item = scene->getItem(itemDetails.id);
auto bound = itemDetails.bounds; // item.getBound();
float distance = args->_viewFrustum->distanceToCamera(bound.calcCenter());
itemBounds.emplace_back(ItemBound(distance, distance, distance, itemDetails.id, bound));
}
// sort against Z
if (frontToBack) {
FrontToBackSort frontToBackSort;
std::sort (itemBounds.begin(), itemBounds.end(), frontToBackSort);
} else {
BackToFrontSort backToFrontSort;
std::sort (itemBounds.begin(), itemBounds.end(), backToFrontSort);
}
// FInally once sorted result to a list of itemID
for (auto& itemBound : itemBounds) {
outItems.emplace_back(ItemIDAndBounds(itemBound._id, itemBound._bounds));
}
}
void render::renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems) {
void render::renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) {
auto& scene = sceneContext->_scene;
RenderArgs* args = renderContext->args;
@ -152,7 +47,7 @@ void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, cons
}
void render::renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
const ShapePlumberPointer& shapeContext, const ItemIDsBounds& inItems, int maxDrawnItems) {
const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems) {
auto& scene = sceneContext->_scene;
RenderArgs* args = renderContext->args;
@ -166,58 +61,20 @@ void render::renderShapes(const SceneContextPointer& sceneContext, const RenderC
}
}
void FetchItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemIDsBounds& outItems) {
auto& scene = sceneContext->_scene;
outItems.clear();
const auto& bucket = scene->getMasterBucket();
const auto& items = bucket.find(_filter);
if (items != bucket.end()) {
outItems.reserve(items->second.size());
for (auto& id : items->second) {
auto& item = scene->getItem(id);
outItems.emplace_back(ItemIDAndBounds(id, item.getBound()));
}
}
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
}
void DepthSortItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems);
}
void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
// render lights
auto& scene = sceneContext->_scene;
auto& items = scene->getMasterBucket().at(ItemFilter::Builder::light());
ItemIDsBounds inItems;
inItems.reserve(items.size());
for (auto id : items) {
auto item = scene->getItem(id);
inItems.emplace_back(ItemIDAndBounds(id, item.getBound()));
}
RenderArgs* args = renderContext->args;
auto& details = args->_details.edit(RenderDetails::OTHER_ITEM);
ItemIDsBounds culledItems;
culledItems.reserve(inItems.size());
cullItems(renderContext, _cullFunctor, details, inItems, culledItems);
// render lights
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
renderItems(sceneContext, renderContext, culledItems);
renderItems(sceneContext, renderContext, inLights);
args->_batch = nullptr;
});
}
void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ShapesIDsBounds& outShapes) {
void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapesIDsBounds& outShapes) {
auto& scene = sceneContext->_scene;
outShapes.clear();
@ -225,7 +82,7 @@ void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const Rend
auto key = scene->getItem(item.id).getShapeKey();
auto outItems = outShapes.find(key);
if (outItems == outShapes.end()) {
outItems = outShapes.insert(std::make_pair(key, ItemIDsBounds{})).first;
outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first;
outItems->second.reserve(inItems.size());
}
@ -245,7 +102,7 @@ void DepthSortShapes::run(const SceneContextPointer& sceneContext, const RenderC
auto& inItems = pipeline.second;
auto outItems = outShapes.find(pipeline.first);
if (outItems == outShapes.end()) {
outItems = outShapes.insert(std::make_pair(pipeline.first, ItemIDsBounds{})).first;
outItems = outShapes.insert(std::make_pair(pipeline.first, ItemBounds{})).first;
}
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems->second);

View file

@ -13,94 +13,38 @@
#define hifi_render_DrawTask_h
#include "Engine.h"
#include "CullTask.h"
#include "ShapePipeline.h"
#include "gpu/Batch.h"
namespace render {
using CullFunctor = std::function<bool(const RenderArgs*, const AABox&)>;
void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
const ItemIDsBounds& inItems, ItemIDsBounds& outItems);
void depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemIDsBounds& inItems, ItemIDsBounds& outItems);
void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems);
void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemIDsBounds& inItems, int maxDrawnItems = -1);
class FetchItemsConfig : public Job::Config {
Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems)
public:
int getNumItems() { return numItems; }
int numItems{ 0 };
};
class FetchItems {
public:
using Config = FetchItemsConfig;
using JobModel = Job::ModelO<FetchItems, ItemIDsBounds, Config>;
FetchItems() {}
FetchItems(const ItemFilter& filter) : _filter(filter) {}
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
void configure(const Config& config) {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemIDsBounds& outItems);
};
template<RenderDetails::Type T>
class CullItems {
public:
CullItems(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
const auto& args = renderContext->args;
auto& details = args->_details.edit(T);
outItems.clear();
outItems.reserve(inItems.size());
render::cullItems(renderContext, _cullFunctor, details, inItems, outItems);
}
using JobModel = Job::ModelIO<CullItems<T>, ItemIDsBounds, ItemIDsBounds>;
protected:
CullFunctor _cullFunctor;
};
class DepthSortItems {
public:
bool _frontToBack;
DepthSortItems(bool frontToBack = true) : _frontToBack(frontToBack) {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ItemIDsBounds& outItems);
using JobModel = Job::ModelIO<DepthSortItems, ItemIDsBounds, ItemIDsBounds>;
};
void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems);
void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
class DrawLight {
public:
DrawLight(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext);
using JobModel = Job::Model<DrawLight>;
using JobModel = Job::ModelI<DrawLight, ItemBounds>;
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights);
protected:
CullFunctor _cullFunctor;
};
class PipelineSortShapes {
public:
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ShapesIDsBounds& outShapes);
using JobModel = Job::ModelIO<PipelineSortShapes, ItemIDsBounds, ShapesIDsBounds>;
using JobModel = Job::ModelIO<PipelineSortShapes, ItemBounds, ShapesIDsBounds>;
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapesIDsBounds& outShapes);
};
class DepthSortShapes {
public:
using JobModel = Job::ModelIO<DepthSortShapes, ShapesIDsBounds, ShapesIDsBounds>;
bool _frontToBack;
DepthSortShapes(bool frontToBack = true) : _frontToBack(frontToBack) {}
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapesIDsBounds& inShapes, ShapesIDsBounds& outShapes);
using JobModel = Job::ModelIO<DepthSortShapes, ShapesIDsBounds, ShapesIDsBounds>;
};
}

View file

@ -0,0 +1,73 @@
//
// Item.cpp
// render/src/render
//
// Created by Sam Gateau on 1/26/16.
// Copyright 2014 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 "Item.h"
#include <numeric>
#include "gpu/Batch.h"
using namespace render;
const Item::Status::Value Item::Status::Value::INVALID = Item::Status::Value();
const float Item::Status::Value::RED = 0.0f;
const float Item::Status::Value::YELLOW = 60.0f;
const float Item::Status::Value::GREEN = 120.0f;
const float Item::Status::Value::CYAN = 180.0f;
const float Item::Status::Value::BLUE = 240.0f;
const float Item::Status::Value::MAGENTA = 300.0f;
void Item::Status::Value::setScale(float scale) {
_scale = (std::numeric_limits<unsigned short>::max() -1) * 0.5f * (1.0f + std::max(std::min(scale, 1.0f), 0.0f));
}
void Item::Status::Value::setColor(float hue) {
// Convert the HUe from range [0, 360] to signed normalized value
const float HUE_MAX = 360.0f;
_color = (std::numeric_limits<unsigned char>::max()) * (std::max(std::min(hue, HUE_MAX), 0.0f) / HUE_MAX);
}
void Item::Status::Value::setIcon(unsigned char icon) {
_icon = icon;
}
Item::Status::Values Item::Status::getCurrentValues() const {
Values currentValues(_values.size());
auto currentValue = currentValues.begin();
for (auto& getter : _values) {
(*currentValue) = getter();
currentValue++;
}
return currentValues;
}
void Item::PayloadInterface::addStatusGetter(const Status::Getter& getter) {
if (!_status) {
_status = std::make_shared<Status>();
}
_status->addGetter(getter);
}
void Item::PayloadInterface::addStatusGetters(const Status::Getters& getters) {
if (!_status) {
_status = std::make_shared<Status>();
}
for (auto& g : getters) {
_status->addGetter(g);
}
}
void Item::resetPayload(const PayloadPointer& payload) {
if (!payload) {
kill();
} else {
_payload = payload;
_key = _payload->getKey();
}
}

View file

@ -0,0 +1,464 @@
//
// Item.h
// render/src/render
//
// Created by Sam Gateau on 1/26/16.
// Copyright 2014 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_render_Item_h
#define hifi_render_Item_h
#include <atomic>
#include <bitset>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <vector>
#include <AABox.h>
#include <RenderArgs.h>
#include "model/Material.h"
#include "ShapePipeline.h"
namespace render {
class Context;
// Key is the KEY to filter Items and create specialized lists
class ItemKey {
public:
enum FlagBit {
TYPE_SHAPE = 0, // Item is a Shape
TYPE_LIGHT, // Item is a Light
TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work...
VIEW_SPACE, // Transformed in view space, and not in world space
DYNAMIC, // Dynamic and bound will change unlike static item
DEFORMED, // Deformed within bound, not solid
INVISIBLE, // Visible or not? could be just here to cast shadow
SHADOW_CASTER, // Item cast shadows
PICKABLE, // Item can be picked/selected
LAYERED, // Item belongs to one of the layers different from the default layer
SMALLER,
NUM_FLAGS, // Not a valid flag
};
typedef std::bitset<NUM_FLAGS> Flags;
// The key is the Flags
Flags _flags;
ItemKey() : _flags(0) {}
ItemKey(const Flags& flags) : _flags(flags) {}
bool operator== (const ItemKey& rhs) const { return _flags == rhs._flags; }
bool operator!= (const ItemKey& rhs) const { return _flags != rhs._flags; }
class Builder {
friend class ItemKey;
Flags _flags{ 0 };
public:
Builder() {}
ItemKey build() const { return ItemKey(_flags); }
Builder& withTypeShape() { _flags.set(TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _flags.set(TYPE_LIGHT); return (*this); }
Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); }
Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); }
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
Builder& withDeformed() { _flags.set(DEFORMED); return (*this); }
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }
Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); }
Builder& withPickable() { _flags.set(PICKABLE); return (*this); }
Builder& withLayered() { _flags.set(LAYERED); return (*this); }
// Convenient standard keys that we will keep on using all over the place
static Builder opaqueShape() { return Builder().withTypeShape(); }
static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); }
static Builder light() { return Builder().withTypeLight(); }
static Builder background() { return Builder().withViewSpace().withLayered(); }
};
ItemKey(const Builder& builder) : ItemKey(builder._flags) {}
bool isShape() const { return _flags[TYPE_SHAPE]; }
bool isLight() const { return _flags[TYPE_LIGHT]; }
bool isOpaque() const { return !_flags[TRANSLUCENT]; }
bool isTransparent() const { return _flags[TRANSLUCENT]; }
bool isWorldSpace() const { return !_flags[VIEW_SPACE]; }
bool isViewSpace() const { return _flags[VIEW_SPACE]; }
bool isStatic() const { return !_flags[DYNAMIC]; }
bool isDynamic() const { return _flags[DYNAMIC]; }
bool isRigid() const { return !_flags[DEFORMED]; }
bool isDeformed() const { return _flags[DEFORMED]; }
bool isVisible() const { return !_flags[INVISIBLE]; }
bool isInvisible() const { return _flags[INVISIBLE]; }
bool isShadowCaster() const { return _flags[SHADOW_CASTER]; }
bool isPickable() const { return _flags[PICKABLE]; }
bool isLayered() const { return _flags[LAYERED]; }
// Probably not public, flags used by the scene
bool isSmall() const { return _flags[SMALLER]; }
void setSmaller(bool smaller) { (smaller ? _flags.set(SMALLER) : _flags.reset(SMALLER)); }
};
inline QDebug operator<<(QDebug debug, const ItemKey& itemKey) {
debug << "[ItemKey: isOpaque:" << itemKey.isOpaque()
<< ", isStatic:" << itemKey.isStatic()
<< ", isWorldSpace:" << itemKey.isWorldSpace()
<< "]";
return debug;
}
class ItemFilter {
public:
ItemKey::Flags _value{ 0 };
ItemKey::Flags _mask{ 0 };
ItemFilter(const ItemKey::Flags& value = ItemKey::Flags(0), const ItemKey::Flags& mask = ItemKey::Flags(0)) : _value(value), _mask(mask) {}
class Builder {
friend class ItemFilter;
ItemKey::Flags _value{ 0 };
ItemKey::Flags _mask{ 0 };
public:
Builder() {}
ItemFilter build() const { return ItemFilter(_value, _mask); }
Builder& withTypeShape() { _value.set(ItemKey::TYPE_SHAPE); _mask.set(ItemKey::TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _value.set(ItemKey::TYPE_LIGHT); _mask.set(ItemKey::TYPE_LIGHT); return (*this); }
Builder& withOpaque() { _value.reset(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
Builder& withTransparent() { _value.set(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
Builder& withWorldSpace() { _value.reset(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
Builder& withViewSpace() { _value.set(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
Builder& withStatic() { _value.reset(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
Builder& withDynamic() { _value.set(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
Builder& withRigid() { _value.reset(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
Builder& withDeformed() { _value.set(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
Builder& withVisible() { _value.reset(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
Builder& withInvisible() { _value.set(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
Builder& withNoShadowCaster() { _value.reset(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
Builder& withShadowCaster() { _value.set(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
Builder& withPickable() { _value.set(ItemKey::PICKABLE); _mask.set(ItemKey::PICKABLE); return (*this); }
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
// Convenient standard keys that we will keep on using all over the place
static Builder visibleWorldItems() { return Builder().withVisible().withWorldSpace(); }
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); }
static Builder light() { return Builder().withTypeLight(); }
static Builder background() { return Builder().withViewSpace().withLayered(); }
static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); }
static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().withWorldSpace().withLayered(); }
};
ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {}
// Item Filter operator testing if a key pass the filter
bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); }
class Less {
public:
bool operator() (const ItemFilter& left, const ItemFilter& right) const {
if (left._value.to_ulong() == right._value.to_ulong()) {
return left._mask.to_ulong() < right._mask.to_ulong();
} else {
return left._value.to_ulong() < right._value.to_ulong();
}
}
};
};
inline QDebug operator<<(QDebug debug, const ItemFilter& me) {
debug << "[ItemFilter: opaqueShape:" << me.test(ItemKey::Builder::opaqueShape().build())
<< "]";
return debug;
}
using ItemID = uint32_t;
using ItemCell = int32_t;
class Item {
public:
typedef std::vector<Item> Vector;
typedef ItemID ID;
static const ID INVALID_ITEM_ID = 0;
static const ItemCell INVALID_CELL = -1;
// Bound is the AABBox fully containing this item
typedef AABox Bound;
// Status records the life history and performances of this item while performing at rendering and updating.
// This is Used for monitoring and dynamically adjust the quality
class Status {
public:
// Status::Value class is the data used to represent the transient information of a status as a square icon
// The "icon" is a square displayed in the 3D scene over the render::Item AABB center.
// It can be scaled in the range [0, 1] and the color hue in the range [0, 360] representing the color wheel hue
class Value {
unsigned short _scale = 0xFFFF;
unsigned char _color = 0xFF;
unsigned char _icon = 0xFF;
public:
const static Value INVALID; // Invalid value meanss the status won't show
Value() {}
Value(float scale, float hue, unsigned char icon = 0xFF) { setScale(scale); setColor(hue); setIcon(icon); }
// It can be scaled in the range [0, 1]
void setScale(float scale);
// the color hue in the range [0, 360] representing the color wheel hue
void setColor(float hue);
// the icon to display in the range [0, 255], where 0 means no icon, just filled quad and anything else would
// hopefully have an icon available to display (see DrawStatusJob)
void setIcon(unsigned char icon);
// Standard color Hue
static const float RED; // 0.0f;
static const float YELLOW; // 60.0f;
static const float GREEN; // 120.0f;
static const float CYAN; // 180.0f;
static const float BLUE; // 240.0f;
static const float MAGENTA; // 300.0f;
// Retreive the Value data tightely packed as an int
int getPackedData() const { return *((const int*) this); }
};
typedef std::function<Value()> Getter;
typedef std::vector<Getter> Getters;
Getters _values;
void addGetter(const Getter& getter) { _values.push_back(getter); }
size_t getNumValues() const { return _values.size(); }
using Values = std::vector <Value>;
Values getCurrentValues() const;
};
typedef std::shared_ptr<Status> StatusPointer;
// Update Functor
class UpdateFunctorInterface {
public:
virtual ~UpdateFunctorInterface() {}
};
typedef std::shared_ptr<UpdateFunctorInterface> UpdateFunctorPointer;
// Payload is whatever is in this Item and implement the Payload Interface
class PayloadInterface {
public:
virtual const ItemKey getKey() const = 0;
virtual const Bound getBound() const = 0;
virtual int getLayer() const = 0;
virtual void render(RenderArgs* args) = 0;
virtual const ShapeKey getShapeKey() const = 0;
~PayloadInterface() {}
// Status interface is local to the base class
const StatusPointer& getStatus() const { return _status; }
void addStatusGetter(const Status::Getter& getter);
void addStatusGetters(const Status::Getters& getters);
protected:
StatusPointer _status;
friend class Item;
virtual void update(const UpdateFunctorPointer& functor) = 0;
};
typedef std::shared_ptr<PayloadInterface> PayloadPointer;
Item() {}
~Item() {}
// Main scene / item managment interface Reset/Update/Kill
void resetPayload(const PayloadPointer& payload);
void resetCell(ItemCell cell, bool _small) { _cell = cell; _key.setSmaller(_small); }
void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Communicate update to the payload
void kill() { _payload.reset(); _key._flags.reset(); _cell = INVALID_CELL; } // Kill means forget the payload and key and cell
// Check heuristic key
const ItemKey& getKey() const { return _key; }
// Check spatial cell
const ItemCell& getCell() const { return _cell; }
// Payload Interface
// Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace())
const Bound getBound() const { return _payload->getBound(); }
// Get the layer where the item belongs. 0 by default meaning NOT LAYERED
int getLayer() const { return _payload->getLayer(); }
// Render call for the item
void render(RenderArgs* args) const { _payload->render(args); }
// Shape Type Interface
const ShapeKey getShapeKey() const { return _payload->getShapeKey(); }
// Access the status
const StatusPointer& getStatus() const { return _payload->getStatus(); }
protected:
PayloadPointer _payload;
ItemKey _key;
ItemCell _cell{ INVALID_CELL };
friend class Scene;
};
typedef Item::UpdateFunctorInterface UpdateFunctorInterface;
typedef Item::UpdateFunctorPointer UpdateFunctorPointer;
typedef std::vector<UpdateFunctorPointer> UpdateFunctors;
template <class T> class UpdateFunctor : public Item::UpdateFunctorInterface {
public:
typedef std::function<void(T&)> Func;
Func _func;
UpdateFunctor(Func func): _func(func) {}
~UpdateFunctor() {}
};
inline QDebug operator<<(QDebug debug, const Item& item) {
debug << "[Item: _key:" << item.getKey() << ", bounds:" << item.getBound() << "]";
return debug;
}
// THe Payload class is the real Payload to be used
// THis allow anything to be turned into a Payload as long as the required interface functions are available
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
// of the Payload interface
template <class T> const ItemKey payloadGetKey(const std::shared_ptr<T>& payloadData) { return ItemKey(); }
template <class T> const Item::Bound payloadGetBound(const std::shared_ptr<T>& payloadData) { return Item::Bound(); }
template <class T> int payloadGetLayer(const std::shared_ptr<T>& payloadData) { return 0; }
template <class T> void payloadRender(const std::shared_ptr<T>& payloadData, RenderArgs* args) { }
// Shape type interface
// This allows shapes to characterize their pipeline via a ShapeKey, to be picked with a subclass of Shape.
// When creating a new shape payload you need to create a specialized version, or the ShapeKey will be ownPipeline,
// implying that the shape will setup its own pipeline without the use of the ShapeKey.
template <class T> const ShapeKey shapeGetShapeKey(const std::shared_ptr<T>& payloadData) { return ShapeKey::Builder::ownPipeline(); }
template <class T> class Payload : public Item::PayloadInterface {
public:
typedef std::shared_ptr<T> DataPointer;
typedef UpdateFunctor<T> Updater;
Payload(const DataPointer& data) : _data(data) {}
// Payload general interface
virtual const ItemKey getKey() const { return payloadGetKey<T>(_data); }
virtual const Item::Bound getBound() const { return payloadGetBound<T>(_data); }
virtual int getLayer() const { return payloadGetLayer<T>(_data); }
virtual void render(RenderArgs* args) { payloadRender<T>(_data, args); }
// Shape Type interface
virtual const ShapeKey getShapeKey() const { return shapeGetShapeKey<T>(_data); }
protected:
DataPointer _data;
// Update mechanics
virtual void update(const UpdateFunctorPointer& functor) { std::static_pointer_cast<Updater>(functor)->_func((*_data)); }
friend class Item;
};
// Let's show how to make a simple FooPayload example:
/*
class Foo {
public:
mutable ItemKey _myownKey;
void makeMywnKey() const {
_myownKey = ItemKey::Builder().withTypeShape().build();
}
const Item::Bound evaluateMyBound() {
// Do stuff here to get your final Bound
return Item::Bound();
}
};
typedef Payload<Foo> FooPayload;
typedef std::shared_ptr<Foo> FooPointer;
// In a Source file, not a header, implement the Payload interface function specialized for Foo:
template <> const ItemKey payloadGetKey(const FooPointer& foo) {
// Foo's way of provinding its Key
foo->makeMyKey();
return foo->_myownKey;
}
template <> const Item::Bound payloadGetBound(const FooPointer& foo) {
// evaluate Foo's own bound
return foo->evaluateMyBound();
}
// In this example, do not specialize the payloadRender call which means the compiler will use the default version which does nothing
*/
// End of the example
typedef Item::PayloadPointer PayloadPointer;
typedef std::vector< PayloadPointer > Payloads;
// A few typedefs for standard containers of ItemIDs
using ItemIDs = std::vector<ItemID>;
using ItemIDSet = std::set<ItemID>;
// Handy type to just pass the ID and the bound of an item
class ItemBound {
public:
ItemBound(ItemID id) : id(id) { }
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }
ItemID id;
AABox bound;
};
// many Item Bounds in a vector
using ItemBounds = std::vector<ItemBound>;
// A map of items by ShapeKey to optimize rendering pipeline assignments
using ShapesIDsBounds = std::unordered_map<ShapeKey, ItemBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;
}
#endif // hifi_render_Item_h

View file

@ -56,62 +56,6 @@ void ItemBucketMap::allocateStandardOpaqueTranparentBuckets() {
(*this)[ItemFilter::Builder::transparentShape().withLayered()];
}
const Item::Status::Value Item::Status::Value::INVALID = Item::Status::Value();
const float Item::Status::Value::RED = 0.0f;
const float Item::Status::Value::YELLOW = 60.0f;
const float Item::Status::Value::GREEN = 120.0f;
const float Item::Status::Value::CYAN = 180.0f;
const float Item::Status::Value::BLUE = 240.0f;
const float Item::Status::Value::MAGENTA = 300.0f;
void Item::Status::Value::setScale(float scale) {
_scale = (std::numeric_limits<unsigned short>::max() -1) * 0.5f * (1.0f + std::max(std::min(scale, 1.0f), 0.0f));
}
void Item::Status::Value::setColor(float hue) {
// Convert the HUe from range [0, 360] to signed normalized value
const float HUE_MAX = 360.0f;
_color = (std::numeric_limits<unsigned char>::max()) * (std::max(std::min(hue, HUE_MAX), 0.0f) / HUE_MAX);
}
void Item::Status::Value::setIcon(unsigned char icon) {
_icon = icon;
}
Item::Status::Values Item::Status::getCurrentValues() const {
Values currentValues(_values.size());
auto currentValue = currentValues.begin();
for (auto& getter : _values) {
(*currentValue) = getter();
currentValue++;
}
return currentValues;
}
void Item::PayloadInterface::addStatusGetter(const Status::Getter& getter) {
if (!_status) {
_status = std::make_shared<Status>();
}
_status->addGetter(getter);
}
void Item::PayloadInterface::addStatusGetters(const Status::Getters& getters) {
if (!_status) {
_status = std::make_shared<Status>();
}
for (auto& g : getters) {
_status->addGetter(g);
}
}
void Item::resetPayload(const PayloadPointer& payload) {
if (!payload) {
kill();
} else {
_payload = payload;
_key = _payload->getKey();
}
}
void PendingChanges::resetItem(ItemID id, const PayloadPointer& payload) {
_resetItems.push_back(id);
@ -127,7 +71,6 @@ void PendingChanges::updateItem(ItemID id, const UpdateFunctorPointer& functor)
_updateFunctors.push_back(functor);
}
void PendingChanges::merge(PendingChanges& changes) {
_resetItems.insert(_resetItems.end(), changes._resetItems.begin(), changes._resetItems.end());
_resetPayloads.insert(_resetPayloads.end(), changes._resetPayloads.begin(), changes._resetPayloads.end());
@ -136,7 +79,9 @@ void PendingChanges::merge(PendingChanges& changes) {
_updateFunctors.insert(_updateFunctors.end(), changes._updateFunctors.begin(), changes._updateFunctors.end());
}
Scene::Scene() {
Scene::Scene(glm::vec3 origin, float size) :
_masterSpatialTree(origin, size)
{
_items.push_back(Item()); // add the itemID #0 to nothing
_masterBucketMap.allocateStandardOpaqueTranparentBuckets();
}
@ -186,29 +131,68 @@ void Scene::processPendingChangesQueue() {
}
void Scene::resetItems(const ItemIDs& ids, Payloads& payloads) {
auto resetID = ids.begin();
auto resetPayload = payloads.begin();
for (;resetID != ids.end(); resetID++, resetPayload++) {
auto& item = _items[(*resetID)];
for (auto resetID : ids) {
// Access the true item
auto& item = _items[resetID];
auto oldKey = item.getKey();
auto oldCell = item.getCell();
// Reset the item with a new payload
item.resetPayload(*resetPayload);
auto newKey = item.getKey();
_masterBucketMap.reset((*resetID), oldKey, item.getKey());
// Reset the item in the Bucket map
_masterBucketMap.reset(resetID, oldKey, newKey);
// Reset the item in the spatial tree
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), resetID, newKey);
item.resetCell(newCell, newKey.isSmall());
// next loop
resetPayload++;
}
}
void Scene::removeItems(const ItemIDs& ids) {
for (auto removedID :ids) {
_masterBucketMap.erase(removedID, _items[removedID].getKey());
_items[removedID].kill();
// Access the true item
auto& item = _items[removedID];
auto oldCell = item.getCell();
auto oldKey = item.getKey();
// Remove from Bucket map
_masterBucketMap.erase(removedID, item.getKey());
// Remove from spatial tree
_masterSpatialTree.removeItem(oldCell, oldKey, removedID);
// Kill it
item.kill();
}
}
void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) {
auto updateID = ids.begin();
auto updateFunctor = functors.begin();
for (;updateID != ids.end(); updateID++, updateFunctor++) {
_items[(*updateID)].update((*updateFunctor));
for (auto updateID : ids) {
// Access the true item
auto& item = _items[updateID];
auto oldCell = item.getCell();
auto oldKey = item.getKey();
// Update it
_items[updateID].update((*updateFunctor));
auto newKey = item.getKey();
// Update the citem in the spatial tree if needed
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), updateID, newKey);
item.resetCell(newCell, newKey.isSmall());
// next loop
updateFunctor++;
}
}

View file

@ -12,434 +12,12 @@
#ifndef hifi_render_Scene_h
#define hifi_render_Scene_h
#include <atomic>
#include <bitset>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <vector>
#include <AABox.h>
#include <RenderArgs.h>
#include "model/Material.h"
#include "ShapePipeline.h"
#include "Item.h"
#include "SpatialTree.h"
namespace render {
class Context;
// Key is the KEY to filter Items and create specialized lists
class ItemKey {
public:
enum FlagBit {
TYPE_SHAPE = 0, // Item is a Shape
TYPE_LIGHT, // Item is a Light
TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work...
VIEW_SPACE, // Transformed in view space, and not in world space
DYNAMIC, // Dynamic and bound will change unlike static item
DEFORMED, // Deformed within bound, not solid
INVISIBLE, // Visible or not? could be just here to cast shadow
SHADOW_CASTER, // Item cast shadows
PICKABLE, // Item can be picked/selected
LAYERED, // Item belongs to one of the layers different from the default layer
NUM_FLAGS, // Not a valid flag
};
typedef std::bitset<NUM_FLAGS> Flags;
// The key is the Flags
Flags _flags;
ItemKey() : _flags(0) {}
ItemKey(const Flags& flags) : _flags(flags) {}
class Builder {
friend class ItemKey;
Flags _flags{ 0 };
public:
Builder() {}
ItemKey build() const { return ItemKey(_flags); }
Builder& withTypeShape() { _flags.set(TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _flags.set(TYPE_LIGHT); return (*this); }
Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); }
Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); }
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
Builder& withDeformed() { _flags.set(DEFORMED); return (*this); }
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }
Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); }
Builder& withPickable() { _flags.set(PICKABLE); return (*this); }
Builder& withLayered() { _flags.set(LAYERED); return (*this); }
// Convenient standard keys that we will keep on using all over the place
static Builder opaqueShape() { return Builder().withTypeShape(); }
static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); }
static Builder light() { return Builder().withTypeLight(); }
static Builder background() { return Builder().withViewSpace().withLayered(); }
};
ItemKey(const Builder& builder) : ItemKey(builder._flags) {}
bool isShape() const { return _flags[TYPE_SHAPE]; }
bool isLight() const { return _flags[TYPE_LIGHT]; }
bool isOpaque() const { return !_flags[TRANSLUCENT]; }
bool isTransparent() const { return _flags[TRANSLUCENT]; }
bool isWorldSpace() const { return !_flags[VIEW_SPACE]; }
bool isViewSpace() const { return _flags[VIEW_SPACE]; }
bool isStatic() const { return !_flags[DYNAMIC]; }
bool isDynamic() const { return _flags[DYNAMIC]; }
bool isRigid() const { return !_flags[DEFORMED]; }
bool isDeformed() const { return _flags[DEFORMED]; }
bool isVisible() const { return !_flags[INVISIBLE]; }
bool isInvisible() const { return _flags[INVISIBLE]; }
bool isShadowCaster() const { return _flags[SHADOW_CASTER]; }
bool isPickable() const { return _flags[PICKABLE]; }
bool isLayered() const { return _flags[LAYERED]; }
};
inline QDebug operator<<(QDebug debug, const ItemKey& itemKey) {
debug << "[ItemKey: isOpaque:" << itemKey.isOpaque()
<< ", isStatic:" << itemKey.isStatic()
<< ", isWorldSpace:" << itemKey.isWorldSpace()
<< "]";
return debug;
}
class ItemFilter {
public:
ItemKey::Flags _value{ 0 };
ItemKey::Flags _mask{ 0 };
ItemFilter(const ItemKey::Flags& value = ItemKey::Flags(0), const ItemKey::Flags& mask = ItemKey::Flags(0)) : _value(value), _mask(mask) {}
class Builder {
friend class ItemFilter;
ItemKey::Flags _value{ 0 };
ItemKey::Flags _mask{ 0 };
public:
Builder() {}
ItemFilter build() const { return ItemFilter(_value, _mask); }
Builder& withTypeShape() { _value.set(ItemKey::TYPE_SHAPE); _mask.set(ItemKey::TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _value.set(ItemKey::TYPE_LIGHT); _mask.set(ItemKey::TYPE_LIGHT); return (*this); }
Builder& withOpaque() { _value.reset(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
Builder& withTransparent() { _value.set(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
Builder& withWorldSpace() { _value.reset(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
Builder& withViewSpace() { _value.set(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
Builder& withStatic() { _value.reset(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
Builder& withDynamic() { _value.set(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
Builder& withRigid() { _value.reset(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
Builder& withDeformed() { _value.set(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
Builder& withVisible() { _value.reset(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
Builder& withInvisible() { _value.set(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
Builder& withNoShadowCaster() { _value.reset(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
Builder& withShadowCaster() { _value.set(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
Builder& withPickable() { _value.set(ItemKey::PICKABLE); _mask.set(ItemKey::PICKABLE); return (*this); }
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
// Convenient standard keys that we will keep on using all over the place
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); }
static Builder light() { return Builder().withTypeLight(); }
static Builder background() { return Builder().withViewSpace().withLayered(); }
};
ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {}
// Item Filter operator testing if a key pass the filter
bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); }
class Less {
public:
bool operator() (const ItemFilter& left, const ItemFilter& right) const {
if (left._value.to_ulong() == right._value.to_ulong()) {
return left._mask.to_ulong() < right._mask.to_ulong();
} else {
return left._value.to_ulong() < right._value.to_ulong();
}
}
};
};
inline QDebug operator<<(QDebug debug, const ItemFilter& me) {
debug << "[ItemFilter: opaqueShape:" << me.test(ItemKey::Builder::opaqueShape().build())
<< "]";
return debug;
}
class Item {
public:
typedef std::vector<Item> Vector;
typedef unsigned int ID;
static const ID INVALID_ITEM_ID = 0;
// Bound is the AABBox fully containing this item
typedef AABox Bound;
// Status records the life history and performances of this item while performing at rendering and updating.
// This is Used for monitoring and dynamically adjust the quality
class Status {
public:
// Status::Value class is the data used to represent the transient information of a status as a square icon
// The "icon" is a square displayed in the 3D scene over the render::Item AABB center.
// It can be scaled in the range [0, 1] and the color hue in the range [0, 360] representing the color wheel hue
class Value {
unsigned short _scale = 0xFFFF;
unsigned char _color = 0xFF;
unsigned char _icon = 0xFF;
public:
const static Value INVALID; // Invalid value meanss the status won't show
Value() {}
Value(float scale, float hue, unsigned char icon = 0xFF) { setScale(scale); setColor(hue); setIcon(icon); }
// It can be scaled in the range [0, 1]
void setScale(float scale);
// the color hue in the range [0, 360] representing the color wheel hue
void setColor(float hue);
// the icon to display in the range [0, 255], where 0 means no icon, just filled quad and anything else would
// hopefully have an icon available to display (see DrawStatusJob)
void setIcon(unsigned char icon);
// Standard color Hue
static const float RED; // 0.0f;
static const float YELLOW; // 60.0f;
static const float GREEN; // 120.0f;
static const float CYAN; // 180.0f;
static const float BLUE; // 240.0f;
static const float MAGENTA; // 300.0f;
// Retreive the Value data tightely packed as an int
int getPackedData() const { return *((const int*) this); }
};
typedef std::function<Value()> Getter;
typedef std::vector<Getter> Getters;
Getters _values;
void addGetter(const Getter& getter) { _values.push_back(getter); }
size_t getNumValues() const { return _values.size(); }
using Values = std::vector <Value>;
Values getCurrentValues() const;
};
typedef std::shared_ptr<Status> StatusPointer;
// Update Functor
class UpdateFunctorInterface {
public:
virtual ~UpdateFunctorInterface() {}
};
typedef std::shared_ptr<UpdateFunctorInterface> UpdateFunctorPointer;
// Payload is whatever is in this Item and implement the Payload Interface
class PayloadInterface {
public:
virtual const ItemKey getKey() const = 0;
virtual const Bound getBound() const = 0;
virtual int getLayer() const = 0;
virtual void render(RenderArgs* args) = 0;
virtual const ShapeKey getShapeKey() const = 0;
~PayloadInterface() {}
// Status interface is local to the base class
const StatusPointer& getStatus() const { return _status; }
void addStatusGetter(const Status::Getter& getter);
void addStatusGetters(const Status::Getters& getters);
protected:
StatusPointer _status;
friend class Item;
virtual void update(const UpdateFunctorPointer& functor) = 0;
};
typedef std::shared_ptr<PayloadInterface> PayloadPointer;
Item() {}
~Item() {}
// Main scene / item managment interface Reset/Update/Kill
void resetPayload(const PayloadPointer& payload);
void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Communicate update to the payload
void kill() { _payload.reset(); _key._flags.reset(); } // Kill means forget the payload and key
// Check heuristic key
const ItemKey& getKey() const { return _key; }
// Payload Interface
// Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace())
const Bound getBound() const { return _payload->getBound(); }
// Get the layer where the item belongs. 0 by default meaning NOT LAYERED
int getLayer() const { return _payload->getLayer(); }
// Render call for the item
void render(RenderArgs* args) const { _payload->render(args); }
// Shape Type Interface
const ShapeKey getShapeKey() const { return _payload->getShapeKey(); }
// Access the status
const StatusPointer& getStatus() const { return _payload->getStatus(); }
protected:
PayloadPointer _payload;
ItemKey _key;
friend class Scene;
};
typedef Item::UpdateFunctorInterface UpdateFunctorInterface;
typedef Item::UpdateFunctorPointer UpdateFunctorPointer;
typedef std::vector<UpdateFunctorPointer> UpdateFunctors;
template <class T> class UpdateFunctor : public Item::UpdateFunctorInterface {
public:
typedef std::function<void(T&)> Func;
Func _func;
UpdateFunctor(Func func): _func(func) {}
~UpdateFunctor() {}
};
inline QDebug operator<<(QDebug debug, const Item& item) {
debug << "[Item: _key:" << item.getKey() << ", bounds:" << item.getBound() << "]";
return debug;
}
// THe Payload class is the real Payload to be used
// THis allow anything to be turned into a Payload as long as the required interface functions are available
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
// of the Payload interface
template <class T> const ItemKey payloadGetKey(const std::shared_ptr<T>& payloadData) { return ItemKey(); }
template <class T> const Item::Bound payloadGetBound(const std::shared_ptr<T>& payloadData) { return Item::Bound(); }
template <class T> int payloadGetLayer(const std::shared_ptr<T>& payloadData) { return 0; }
template <class T> void payloadRender(const std::shared_ptr<T>& payloadData, RenderArgs* args) { }
// Shape type interface
// This allows shapes to characterize their pipeline via a ShapeKey, to be picked with a subclass of Shape.
// When creating a new shape payload you need to create a specialized version, or the ShapeKey will be ownPipeline,
// implying that the shape will setup its own pipeline without the use of the ShapeKey.
template <class T> const ShapeKey shapeGetShapeKey(const std::shared_ptr<T>& payloadData) { return ShapeKey::Builder::ownPipeline(); }
template <class T> class Payload : public Item::PayloadInterface {
public:
typedef std::shared_ptr<T> DataPointer;
typedef UpdateFunctor<T> Updater;
Payload(const DataPointer& data) : _data(data) {}
// Payload general interface
virtual const ItemKey getKey() const { return payloadGetKey<T>(_data); }
virtual const Item::Bound getBound() const { return payloadGetBound<T>(_data); }
virtual int getLayer() const { return payloadGetLayer<T>(_data); }
virtual void render(RenderArgs* args) { payloadRender<T>(_data, args); }
// Shape Type interface
virtual const ShapeKey getShapeKey() const { return shapeGetShapeKey<T>(_data); }
protected:
DataPointer _data;
// Update mechanics
virtual void update(const UpdateFunctorPointer& functor) { std::static_pointer_cast<Updater>(functor)->_func((*_data)); }
friend class Item;
};
// Let's show how to make a simple FooPayload example:
/*
class Foo {
public:
mutable ItemKey _myownKey;
void makeMywnKey() const {
_myownKey = ItemKey::Builder().withTypeShape().build();
}
const Item::Bound evaluateMyBound() {
// Do stuff here to get your final Bound
return Item::Bound();
}
};
typedef Payload<Foo> FooPayload;
typedef std::shared_ptr<Foo> FooPointer;
// In a Source file, not a header, implement the Payload interface function specialized for Foo:
template <> const ItemKey payloadGetKey(const FooPointer& foo) {
// Foo's way of provinding its Key
foo->makeMyKey();
return foo->_myownKey;
}
template <> const Item::Bound payloadGetBound(const FooPointer& foo) {
// evaluate Foo's own bound
return foo->evaluateMyBound();
}
// In this example, do not specialize the payloadRender call which means the compiler will use the default version which does nothing
*/
// End of the example
typedef Item::PayloadPointer PayloadPointer;
typedef std::vector< PayloadPointer > Payloads;
// A few typedefs for standard containers of ItemIDs
typedef Item::ID ItemID;
typedef std::vector<ItemID> ItemIDs;
typedef std::set<ItemID> ItemIDSet;
class ItemIDAndBounds {
public:
ItemIDAndBounds(ItemID id) : id(id) { }
ItemIDAndBounds(ItemID id, const AABox& bounds) : id(id), bounds(bounds) { }
ItemID id;
AABox bounds;
};
// A list of items to be passed between rendering jobs
using ItemIDsBounds = std::vector<ItemIDAndBounds>;
// A map of items by ShapeKey to optimize rendering pipeline assignments
using ShapesIDsBounds = std::unordered_map<ShapeKey, ItemIDsBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;
// A map of ItemIDSets allowing to create bucket lists of items which are filtering correctly
// A map of ItemIDSets allowing to create bucket lists of items which are filtered according to their key
class ItemBucketMap : public std::map<ItemFilter, ItemIDSet, ItemFilter::Less> {
public:
@ -490,7 +68,7 @@ typedef std::queue<PendingChanges> PendingChangesQueue;
// Items are notified accordingly on any update message happening
class Scene {
public:
Scene();
Scene(glm::vec3 origin, float size);
~Scene() {}
/// This call is thread safe, can be called from anywhere to allocate a new ID
@ -499,8 +77,8 @@ public:
/// Enqueue change batch to the scene
void enqueuePendingChanges(const PendingChanges& pendingChanges);
/// Access the main bucketmap of items
const ItemBucketMap& getMasterBucket() const { return _masterBucketMap; }
// Process the penging changes equeued
void processPendingChangesQueue();
/// Access a particular item form its ID
/// WARNING, There is No check on the validity of the ID, so this could return a bad Item
@ -508,9 +86,12 @@ public:
size_t getNumItems() const { return _items.size(); }
/// Access the main bucketmap of items
const ItemBucketMap& getMasterBucket() const { return _masterBucketMap; }
void processPendingChangesQueue();
/// Access the item spatial tree
const ItemSpatialTree& getSpatialTree() const { return _masterSpatialTree; }
protected:
// Thread safe elements that can be accessed from anywhere
std::atomic<unsigned int> _IDAllocator{ 1 }; // first valid itemID will be One
@ -522,6 +103,8 @@ protected:
std::mutex _itemsMutex;
Item::Vector _items;
ItemBucketMap _masterBucketMap;
ItemSpatialTree _masterSpatialTree;
void resetItems(const ItemIDs& ids, Payloads& payloads);
void removeItems(const ItemIDs& ids);
@ -530,8 +113,6 @@ protected:
friend class Engine;
};
typedef std::shared_ptr<Scene> ScenePointer;
typedef std::vector<ScenePointer> Scenes;

View file

@ -0,0 +1,76 @@
<!
// render/SceneOctree.slh
//
// Created by Sam Gateau on 1/29/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
!>
<@if not RENDER_OCTREE_SLH@>
<@def RENDER_OCTREE_SLH@>
const float _size = 32768.0;
const float _invSize = 1.0 / _size;
const vec3 _origin = vec3(-16384.0);
float getSize() { return _size; }
vec3 getOrigin() { return _origin; }
const int MAX_DEPTH = 15;
const float DEPTH_DIM[16] = float[16](
1.0,
2.0,
4.0,
8.0,
16.0,
32.0,
64.0,
128.0,
256.0,
512.0,
1024.0,
2048.0,
4096.0,
8192.0,
16384.0,
32768.0 );
const float INV_DEPTH_DIM[16] = float[16](
1.0,
1.0 / 2.0,
1.0 / 4.0,
1.0 / 8.0,
1.0 / 16.0,
1.0 / 32.0,
1.0 / 64.0,
1.0 / 128.0,
1.0 / 256.0,
1.0 / 512.0,
1.0 / 1024.0,
1.0 / 2048.0,
1.0 / 4096.0,
1.0 / 8192.0,
1.0 / 16384.0,
1.0 / 32768.0 );
int getDepthDimension(int depth) { return 1 << depth; }
float getDepthDimensionf(int depth) { return DEPTH_DIM[depth]; }
float getInvDepthDimension(int depth) { return INV_DEPTH_DIM[depth]; }
float getCellWidth(int depth) { return _size * getInvDepthDimension(depth); }
float getInvCellWidth(int depth) { return getDepthDimensionf(depth) * _invSize; }
vec3 evalPos(ivec3 coord, int depth) {
return getOrigin() + vec3(coord) * getCellWidth(depth);
}
vec3 evalPosDepthWidth(ivec3 coord, float cellWidth) {
return getOrigin() + vec3(coord) * cellWidth;
}
vec4 evalBound(ivec4 loc) {
float cellWidth = getCellWidth(loc.w);
return vec4(evalPosDepthWidth(loc.xyz, cellWidth), cellWidth);
}
<@endif@>

View file

@ -0,0 +1,591 @@
//
// SpatialTree.h
// render/src/render
//
// Created by Sam Gateau on 1/25/16.
// Copyright 2014 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 "SpatialTree.h"
#include <ViewFrustum.h>
using namespace render;
const float Octree::INV_DEPTH_DIM[] = {
1.0f,
1.0f / 2.0f,
1.0f / 4.0f,
1.0f / 8.0f,
1.0f / 16.0f,
1.0f / 32.0f,
1.0f / 64.0f,
1.0f / 128.0f,
1.0f / 256.0f,
1.0f / 512.0f,
1.0f / 1024.0f,
1.0f / 2048.0f,
1.0f / 4096.0f,
1.0f / 8192.0f,
1.0f / 16384.0f,
1.0f / 32768.0f };
/*
const float Octree::COORD_SUBCELL_WIDTH[] = { // 2 ^ MAX_DEPTH / 2 ^ (depth + 1)
16384.0f,
8192.0f,
4096.0f,
2048.0f,
1024.0f,
512.0f,
256.0f,
128.0f,
64.0f,
32.0f,
16.0f,
8.0f,
4.0f,
2.0f,
1.0f,
0.5f };
*/
Octree::Location::vector Octree::Location::pathTo(const Location& dest) {
Location current{ dest };
vector path(dest.depth + 1);
path[dest.depth] = dest;
while (current.depth > 0) {
current = current.parent();
path[current.depth] = current;
}
return path;
}
Octree::Location Octree::Location::evalFromRange(const Coord3& minCoord, const Coord3& maxCoord, Depth rangeDepth) {
Depth depthOffset = MAX_DEPTH - rangeDepth;
Depth depth = depthOffset;
Coord3 mask(depthBitmask(depth));
while (depth < rangeDepth) {
Coord3 nextMask = mask | depthBitmask(depth + 1);
if ((minCoord & nextMask) != (maxCoord & nextMask)) {
break;
}
mask = nextMask;
depth++;
}
if (depth == 0) {
return Location();
} else {
// Location depth and coordinate are found, need to bring the coordinate from sourceDepth to destinationDepth
auto sourceCoord = (minCoord & mask);
return Location(sourceCoord >> Coord3(rangeDepth - depth), depth);
}
}
Octree::Indices Octree::indexConcreteCellPath(const Locations& path) const {
Index currentIndex = ROOT_CELL;
Indices cellPath(1, currentIndex);
// Start the path after the root cell so at #1
for (size_t p = 1; p < path.size(); p++) {
auto& location = path[p];
auto nextIndex = getConcreteCell(currentIndex).child(location.octant());
if (nextIndex == INVALID_CELL) {
break;
}
// One more cell index on the path, moving on
currentIndex = nextIndex;
cellPath.push_back(currentIndex);
}
return cellPath;
}
Octree::Index Octree::allocateCell(Index parent, const Location& location) {
if (_cells[parent].hasChild(location.octant())) {
return _cells[parent].child(location.octant());
}
assert(_cells[parent].getlocation().child(location.octant()) == location);
Index newIndex;
if (_freeCells.empty()) {
newIndex = (Index)_cells.size();
if (newIndex >= MAXIMUM_INDEX) {
// abort! we are trying to go overboard with the total number of allocated bricks
return INVALID_CELL;
}
_cells.push_back(Cell(parent, location));
} else {
newIndex = _freeCells.back();
_freeCells.pop_back();
_cells[newIndex] = Cell(parent, location);
}
_cells[parent].setChild(location.octant(), newIndex);
return newIndex;
}
void Octree::freeCell(Index index) {
if (checkCellIndex(index)) {
auto & cell = _cells[index];
cell.free();
_freeCells.push_back(index);
}
}
void Octree::cleanCellBranch(Index index) {
auto& cell = editCell(index);
// Free the brick
if (cell.isBrickEmpty()) {
if (cell.hasBrick()) {
freeBrick(cell.brick());
cell.setBrick(INVALID_CELL);
}
} else {
// If the brick is still filled, stop clearing
return;
}
// Free the cell ?
Index parentIdx = cell.parent();
if (!cell.hasParent()) {
if (index == ROOT_CELL) {
// Stop here, this is the root cell!
return;
} else {
// This is not expected
assert(false);
return;
}
}
bool traverseParent = false;
if (!cell.hasChildren()) {
editCell(parentIdx).setChild(cell.getlocation().octant(), INVALID_CELL);
freeCell(index);
traverseParent = true;
}
if (traverseParent) {
cleanCellBranch(parentIdx);
}
}
Octree::Indices Octree::indexCellPath(const Locations& path) {
// First through the allocated cells
Indices cellPath = indexConcreteCellPath(path);
// Catch up from the last allocated cell on the path
auto currentIndex = cellPath.back();
for (int l = (Index) cellPath.size(); l < (Index) path.size(); l++) {
auto& location = path[l];
// Allocate the new index & connect it to the parent
auto newIndex = allocateCell(currentIndex, location);
// One more cell index on the path, moving on
currentIndex = newIndex;
cellPath.push_back(currentIndex);
// Except !!! if we actually couldn't allocate anymore
if (newIndex == INVALID_CELL) {
// no more cellID available, stop allocating
// THe last index added is INVALID_CELL so the caller will know we failed allocating everything
break;
}
}
return cellPath;
}
Octree::Index Octree::allocateBrick() {
if (_freeBricks.empty()) {
Index brickIdx = (int)_bricks.size();
if (brickIdx >= MAXIMUM_INDEX) {
// abort! we are trying to go overboard with the total number of allocated bricks
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_CELL;
}
_bricks.push_back(Brick());
return brickIdx;
} else {
Index brickIdx = _freeBricks.back();
_freeBricks.pop_back();
return brickIdx;
}
}
void Octree::freeBrick(Index index) {
if (checkBrickIndex(index)) {
auto & brick = _bricks[index];
brick.free();
_freeBricks.push_back(index);
}
}
Octree::Index Octree::accessCellBrick(Index cellID, const CellBrickAccessor& accessor, bool createBrick) {
assert(checkCellIndex(cellID));
auto& cell = editCell(cellID);
// Allocate a brick if needed
if (!cell.hasBrick()) {
if (!createBrick) {
return INVALID_CELL;
}
auto newBrick = allocateBrick();
if (newBrick == INVALID_CELL) {
// This should never happen but just in case...
return INVALID_CELL;
}
cell.setBrick(newBrick);
}
// Access the brick
auto brickID = cell.brick();
auto& brick = _bricks[brickID];
// Execute the accessor
accessor(cell, brick, brickID);
return brickID;
}
Octree::Locations ItemSpatialTree::evalLocations(const ItemBounds& bounds) const {
Locations locations;
locations.reserve(bounds.size());
for (auto& bound : bounds) {
if (!bound.bound.isNull()) {
locations.emplace_back(evalLocation(bound.bound));
} else {
locations.emplace_back(Location());
}
}
return locations;
}
ItemSpatialTree::Index ItemSpatialTree::insertItem(Index cellIdx, const ItemKey& key, const ItemID& item) {
// Add the item to the brick (and a brick if needed)
accessCellBrick(cellIdx, [&](Cell& cell, Brick& brick, Octree::Index cellID) {
auto& itemIn = (key.isSmall() ? brick.subcellItems : brick.items);
itemIn.push_back(item);
cell.setBrickFilled();
}, true);
return cellIdx;
}
bool ItemSpatialTree::updateItem(Index cellIdx, const ItemKey& oldKey, const ItemKey& key, const ItemID& item) {
// In case we missed that one, nothing to do
if (cellIdx == INVALID_CELL) {
return true;
}
auto success = false;
// only if key changed
assert(oldKey != key);
// Get to the brick where the item is and update where it s stored
accessCellBrick(cellIdx, [&](Cell& cell, Brick& brick, Octree::Index cellID) {
auto& itemIn = (key.isSmall() ? brick.subcellItems : brick.items);
auto& itemOut = (oldKey.isSmall() ? brick.subcellItems : brick.items);
itemIn.push_back(item);
itemOut.erase(std::find(itemOut.begin(), itemOut.end(), item));
}, false); // do not create brick!
return success;
}
bool ItemSpatialTree::removeItem(Index cellIdx, const ItemKey& key, const ItemID& item) {
// In case we missed that one, nothing to do
if (cellIdx == INVALID_CELL) {
return true;
}
auto success = false;
// Remove the item from the brick
bool emptyCell = false;
accessCellBrick(cellIdx, [&](Cell& cell, Brick& brick, Octree::Index brickID) {
auto& itemList = (key.isSmall() ? brick.subcellItems : brick.items);
itemList.erase(std::find(itemList.begin(), itemList.end(), item));
if (brick.items.empty() && brick.subcellItems.empty()) {
cell.setBrickEmpty();
emptyCell = true;
}
success = true;
}, false); // do not create brick!
// Because we know the cell is now empty, lets try to clean the octree here
if (emptyCell) {
cleanCellBranch(cellIdx);
}
return success;
}
ItemSpatialTree::Index ItemSpatialTree::resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey) {
auto newCell = INVALID_CELL;
if (!newKey.isViewSpace()) {
auto minCoordf = evalCoordf(bound.getMinimumPoint());
auto maxCoordf = evalCoordf(bound.getMaximumPoint());
Coord3 minCoord(minCoordf);
Coord3 maxCoord(maxCoordf);
auto location = Location::evalFromRange(minCoord, maxCoord);
// Compare range size vs cell location size and tag itemKey accordingly
// If Item bound fits in sub cell then tag as small
auto rangeSizef = maxCoordf - minCoordf;
float cellHalfSize = 0.5f * getCellWidth(location.depth);
bool subcellItem = std::max(std::max(rangeSizef.x, rangeSizef.y), rangeSizef.z) < cellHalfSize;
if (subcellItem) {
newKey.setSmaller(subcellItem);
} else {
newKey.setSmaller(false);
}
newCell = indexCell(location);
} else {
// A very rare case, if we were adding items with boundary semantic expressed in view space
}
// Did we fail finding a cell for the item?
if (newCell == INVALID_CELL) {
// Remove the item where it was
if (oldCell != INVALID_CELL) {
removeItem(oldCell, oldKey, item);
}
return newCell;
}
// Staying in the same cell
else if (newCell == oldCell) {
// Did the key changed, if yes update
if (newKey._flags != oldKey._flags) {
updateItem(newCell, oldKey, newKey, item);
return newCell;
}
return newCell;
}
// do we know about this item ?
else if (oldCell == INVALID_CELL) {
insertItem(newCell, newKey, item);
return newCell;
}
// A true update of cell is required
else {
// Add the item to the brick (and a brick if needed)
insertItem(newCell, newKey, item);
// And remove it from the previous one
removeItem(oldCell, oldKey, item);
return newCell;
}
}
int Octree::select(CellSelection& selection, const FrustumSelector& selector) const {
Index cellID = ROOT_CELL;
return selectTraverse(cellID, selection, selector);
}
Octree::Location::Intersection Octree::Location::intersectCell(const Location& cell, const Coord4f frustum[6]) {
const Coord3f CornerOffsets[8] = {
{ 0.0, 0.0, 0.0 },
{ 1.0, 0.0, 0.0 },
{ 0.0, 1.0, 0.0 },
{ 1.0, 1.0, 0.0 },
{ 0.0, 0.0, 1.0 },
{ 1.0, 0.0, 1.0 },
{ 0.0, 1.0, 1.0 },
{ 1.0, 1.0, 1.0 },
};
struct Tool {
static int normalToIndex(const Coord3f& n) {
int index = 0;
if (n.x >= 0.0f) index |= 1;
if (n.y >= 0.0f) index |= 2;
if (n.z >= 0.0f) index |= 4;
return index;
}
static bool halfPlaneTest(const Coord4f& plane, const Coord3f& pos) {
return (glm::dot(plane, Coord4f(pos, 1.0f)) >= 0.0f);
}
};
Coord3f cellSize = Coord3f(Octree::getInvDepthDimension(cell.depth));
Coord3f cellPos = Coord3f(cell.pos) * cellSize;
bool partialFlag = false;
for (int p = 0; p < ViewFrustum::NUM_PLANES; p++) {
Coord4f plane = frustum[p];
Coord3f planeNormal(plane);
int index = Tool::normalToIndex(planeNormal);
auto negTestPoint = cellPos + cellSize * CornerOffsets[index];
if (!Tool::halfPlaneTest(plane, negTestPoint)) {
return Outside;
}
index = Tool::normalToIndex(-planeNormal);
auto posTestPoint = cellPos + cellSize * CornerOffsets[index];
if (!Tool::halfPlaneTest(plane, posTestPoint)) {
partialFlag = true;
}
}
if (partialFlag) {
return Intersect;
}
return Inside;
}
int Octree::selectTraverse(Index cellID, CellSelection& selection, const FrustumSelector& selector) const {
int numSelectedsIn = (int) selection.size();
auto cell = getConcreteCell(cellID);
auto cellLocation = cell.getlocation();
auto intersection = Octree::Location::intersectCell(cellLocation, selector.frustum);
switch (intersection) {
case Octree::Location::Outside:
// cell is outside, stop traversing this branch
return 0;
break;
case Octree::Location::Inside: {
// traverse all the Cell Branch and collect items in the selection
selectBranch(cellID, selection, selector);
break;
}
case Octree::Location::Intersect:
default: {
// Cell is partially in
// Test for lod
auto cellLocation = cell.getlocation();
float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
if (lod < 0.0f) {
return 0;
break;
}
// Select this cell partially in frustum
selectCellBrick(cellID, selection, false);
// then traverse deeper
for (int i = 0; i < NUM_OCTANTS; i++) {
Index subCellID = cell.child((Link)i);
if (subCellID != INVALID_CELL) {
selectTraverse(subCellID, selection, selector);
}
}
}
}
return (int) selection.size() - numSelectedsIn;
}
int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const {
int numSelectedsIn = (int) selection.size();
auto cell = getConcreteCell(cellID);
auto cellLocation = cell.getlocation();
float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
if (lod < 0.0f) {
return 0;
}
// Select this cell fully inside the frustum
selectCellBrick(cellID, selection, true);
// then traverse deeper
for (int i = 0; i < NUM_OCTANTS; i++) {
Index subCellID = cell.child((Link)i);
if (subCellID != INVALID_CELL) {
selectBranch(subCellID, selection, selector);
}
}
return (int) selection.size() - numSelectedsIn;
}
int Octree::selectCellBrick(Index cellID, CellSelection& selection, bool inside) const {
int numSelectedsIn = (int) selection.size();
auto cell = getConcreteCell(cellID);
selection.cells(inside).push_back(cellID);
if (!cell.isBrickEmpty()) {
// Collect the items of this cell
selection.bricks(inside).push_back(cell.brick());
}
return (int) selection.size() - numSelectedsIn;
}
int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const {
auto worldPlanes = frustum.getPlanes();
FrustumSelector selector;
for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) {
::Plane octPlane;
octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH));
selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient());
}
selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH);
selector.setAngle(glm::radians(lodAngle));
return Octree::select(selection, selector);
}
int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const {
selectCells(selection.cellSelection, frustum, lodAngle);
// Just grab the items in every selected bricks
for (auto brickId : selection.cellSelection.insideBricks) {
auto& brickItems = getConcreteBrick(brickId).items;
selection.insideItems.insert(selection.insideItems.end(), brickItems.begin(), brickItems.end());
auto& brickSubcellItems = getConcreteBrick(brickId).subcellItems;
selection.insideSubcellItems.insert(selection.insideSubcellItems.end(), brickSubcellItems.begin(), brickSubcellItems.end());
}
for (auto brickId : selection.cellSelection.partialBricks) {
auto& brickItems = getConcreteBrick(brickId).items;
selection.partialItems.insert(selection.partialItems.end(), brickItems.begin(), brickItems.end());
auto& brickSubcellItems = getConcreteBrick(brickId).subcellItems;
selection.partialSubcellItems.insert(selection.partialSubcellItems.end(), brickSubcellItems.begin(), brickSubcellItems.end());
}
return (int) selection.numItems();
}

View file

@ -0,0 +1,459 @@
//
// SpatialTree.h
// render/src/render
//
// Created by Sam Gateau on 1/25/16.
// Copyright 2014 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_render_Octree_h
#define hifi_render_Octree_h
#include <vector>
#include <memory>
#include <cassert>
#include <array>
#include <functional>
#include <limits>
#include <glm/glm.hpp>
#include <glm/gtx/bit.hpp>
#include <AABox.h>
// maybe we could avoid the Item inclusion here for the OCtree class?
#include "Item.h"
namespace render {
class Brick {
public:
std::vector<ItemID> items;
std::vector<ItemID> subcellItems;
void free() {};
};
class Octree {
public:
using LinkStorage = int8_t;
enum Link : LinkStorage {
Octant_L_B_N = 0,
Octant_R_B_N = 1,
Octant_L_T_N = 2,
Octant_R_T_N = 3,
Octant_L_B_F = 4,
Octant_R_B_F = 5,
Octant_L_T_F = 6,
Octant_R_T_F = 7,
NUM_OCTANTS,
Parent = NUM_OCTANTS,
BrickLink = Parent + 1,
NUM_LINKS = BrickLink + 1,
XAxis = 0x01,
YAxis = 0x02,
ZAxis = 0x04,
NoLink = 0x0F,
};
using Octant = Link;
// Depth, Dim, Volume
// {0, 1, 1}
// {1, 2, 8}
// {2, 4, 64}
// {3, 8, 512}
// {4, 16, 4096}
// {5, 32, 32768}
// {6, 64, 262144}
// {7, 128, 2097152}
// {8, 256, 16777216}
// {9, 512, 134217728}
// {10, 1024, 1073741824}
// {11, 2048, 8589934592}
// {12, 4096, 68719476736}
// {13, 8192, 549755813888}
// {14, 16384, 4398046511104}
// {15, 32768, 35184372088832}
// {16, 65536, 281474976710656}
// Max depth is 15 => 32Km root down to 1m cells
using Depth = int8_t;
static const Depth ROOT_DEPTH { 0 };
static const Depth MAX_DEPTH { 15 };
static const Depth METRIC_COORD_DEPTH { 15 };
static const float INV_DEPTH_DIM[Octree::MAX_DEPTH + 1];
static int getDepthDimension(Depth depth) { return 1 << depth; }
static float getDepthDimensionf(Depth depth) { return (float) getDepthDimension(depth); }
static float getInvDepthDimension(Depth depth) { return INV_DEPTH_DIM[depth]; }
static float getCoordSubcellWidth(Depth depth) { return (1.7320f * getInvDepthDimension(depth) * 0.5f); }
// Need 16bits integer coordinates on each axes: 32768 cell positions
using Coord = int16_t;
using Coord3 = glm::i16vec3;
using Coord4 = glm::i16vec4;
using Coord3f = glm::vec3;
using Coord4f = glm::vec4;
static Coord depthBitmask(Depth depth) { return Coord(1 << (MAX_DEPTH - depth)); }
static Depth coordToDepth(Coord length) {
Depth depth = MAX_DEPTH;
while (length) {
length >>= 1;
depth--;
}
return depth;
}
class Location {
void assertValid() {
assert((pos.x >= 0) && (pos.y >= 0) && (pos.z >= 0));
assert((pos.x < (1 << depth)) && (pos.y < (1 << depth)) && (pos.z < (1 << depth)));
}
public:
using vector = std::vector< Location >;
Location() {}
Location(const Coord3& xyz, Depth d) : pos(xyz), depth(d) { assertValid(); }
Location(Depth d) : pos(0), depth(d) { assertValid(); }
Coord3 pos { 0 };
uint8_t spare { 0 };
Depth depth { ROOT_DEPTH };
Coord3f getCenter() const {
Coord3f center = (Coord3f(pos) + Coord3f(0.5f)) * Coord3f(Octree::getInvDepthDimension(depth));
return center;
}
bool operator== (const Location& other) const { return pos == other.pos && depth == other.depth; }
// Eval the octant of this cell relative to its parent
Octant octant() const { return Octant((pos.x & 1) | ((pos.y & 1) << 1) | ((pos.z & 1) << 2)); }
Coord3 octantAxes(Link octant) const { return Coord3((Coord)bool(octant & XAxis), (Coord)bool(octant & YAxis), (Coord)bool(octant & ZAxis)); }
// Get the Parent cell Location of this cell
Location parent() const { return Location{ (pos >> Coord3(1)), Depth(depth <= 0 ? 0 : depth - 1) }; }
Location child(Link octant) const { return Location{ (pos << Coord3(1)) | octantAxes(octant), Depth(depth + 1) }; }
// Eval the list of locations (the path) from root to the destination.
// the root cell is included
static vector pathTo(const Location& destination);
// Eval the location best fitting the specified range
static Location evalFromRange(const Coord3& minCoord, const Coord3& maxCoord, Depth rangeDepth = MAX_DEPTH);
// Eval the intersection test against a frustum
enum Intersection {
Outside = 0,
Intersect,
Inside,
};
static Intersection intersectCell(const Location& cell, const Coord4f frustum[6]);
};
using Locations = Location::vector;
// Cell or Brick Indexing
using Index = ItemCell; // int32_t
static const Index INVALID_CELL = -1;
static const Index ROOT_CELL = 0;
// With a maximum of INT_MAX(2 ^ 31) cells in our octree
static const Index MAXIMUM_INDEX = INT_MAX; // For fun, test setting this to 100 and this should still works with only the allocated maximum number of cells
using Indices = std::vector<Index>;
// the cell description
class Cell {
public:
enum BitFlags : uint8_t {
HasChildren = 0x01,
BrickFilled = 0x02,
};
void free() { _location = Location(); for (auto& link : _links) { link = INVALID_CELL; } }
const Location& getlocation() const { return _location; }
Index parent() const { return _links[Parent]; }
bool hasParent() const { return parent() != INVALID_CELL; }
Index child(Link octant) const { return _links[octant]; }
bool hasChild(Link octant) const { return child(octant) != INVALID_CELL; }
bool hasChildren() const { return (_location.spare & HasChildren); }
void setChild(Link octant, Index child) {
_links[octant] = child;
if (child != INVALID_CELL) {
_location.spare |= HasChildren;
} else {
if (!checkHasChildren()) {
_location.spare &= ~HasChildren;
}
}
}
Index brick() const { return _links[BrickLink]; }
bool hasBrick() const { return _links[BrickLink] != INVALID_CELL; }
void setBrick(Index brick) { _links[BrickLink] = brick; }
void setBrickFilled() { _location.spare |= BrickFilled; }
void setBrickEmpty() { _location.spare &= ~BrickFilled; }
bool isBrickEmpty() const { return !(_location.spare & BrickFilled); }
Cell() :
_links({ { INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL } })
{}
Cell(Index parent, Location loc) :
_links({ { INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, parent, INVALID_CELL } }),
_location(loc)
{}
private:
std::array<Index, NUM_LINKS> _links;
Location _location;
bool checkHasChildren() const {
for (LinkStorage octant = Octant_L_B_N; octant < NUM_OCTANTS; octant++) {
if (hasChild((Link)octant)) {
return true;
}
}
return false;
}
};
using Cells = std::vector< Cell >;
using Bricks = std::vector< Brick >;
bool checkCellIndex(Index index) const { return (index >= 0) && (index < (Index) _cells.size()); }
bool checkBrickIndex(Index index) const { return ((index >= 0) && (index < (Index) _bricks.size())); }
Octree() {};
// Clean a cell branch starting from the leave:
// Check that the cell brick is empty, if so free it else stop
// Check that the cell has no children, if so free itself else stop
// Apply the same logic to the parent cell
void cleanCellBranch(Index index);
// Indexing/Allocating the cells as the tree gets populated
// Return the cell Index/Indices at the specified location/path, allocate all the cells on the path from the root if needed
Indices indexCellPath(const Locations& path);
Index indexCell(const Location& loc) { return indexCellPath(Location::pathTo(loc)).back(); }
// Same as indexCellPath except that NO cells are allocated, only the COncrete cells previously allocated
// the returned indices stops at the last existing cell on the requested path.
Indices indexConcreteCellPath(const Locations& path) const;
// Get the cell location from the CellID
Location getCellLocation(Index cellID) const {
if (checkCellIndex(cellID)) {
return getConcreteCell(cellID).getlocation();
}
return Location();
}
// Reach a concrete cell
const Cell& getConcreteCell(Index index) const {
assert(checkCellIndex(index));
return _cells[index];
}
// Let s talk about the Cell Bricks now
using CellBrickAccessor = std::function<void(Cell& cell, Brick& brick, Index brickIdx)>;
// acces a cell (must be concrete), then call the brick accessor if the brick exists ( or is just created if authorized to)
// This returns the Brick index
Index accessCellBrick(Index cellID, const CellBrickAccessor& accessor, bool createBrick = true);
const Brick& getConcreteBrick(Index index) const {
assert(checkBrickIndex(index));
return _bricks[index];
}
// Cell Selection and traversal
class CellSelection {
public:
Indices insideCells;
Indices insideBricks;
Indices partialCells;
Indices partialBricks;
Indices& cells(bool inside) { return (inside ? insideCells : partialCells); }
Indices& bricks(bool inside) { return (inside ? insideBricks : partialBricks); }
size_t size() const { return insideBricks.size() + partialBricks.size(); }
void clear() {
insideCells.clear();
insideBricks.clear();
partialCells.clear();
partialBricks.clear();
}
};
class FrustumSelector {
public:
Coord4f frustum[6];
Coord3f eyePos;
float angle;
float squareTanAlpha;
const float MAX_LOD_ANGLE = glm::radians(45.0f);
const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f);
void setAngle(float a) {
angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a));
auto tanAlpha = tan(angle);
squareTanAlpha = (float)(tanAlpha * tanAlpha);
}
float testSolidAngle(const Coord3f& point, float size) const {
auto eyeToPoint = point - eyePos;
return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
}
};
int select(CellSelection& selection, const FrustumSelector& selector) const;
int selectTraverse(Index cellID, CellSelection& selection, const FrustumSelector& selector) const;
int selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const;
int selectCellBrick(Index cellID, CellSelection& selection, bool inside) const;
int getNumAllocatedCells() const { return (int)_cells.size(); }
int getNumFreeCells() const { return (int)_freeCells.size(); }
protected:
Index allocateCell(Index parent, const Location& location);
void freeCell(Index index);
Index allocateBrick();
void freeBrick(Index index);
Cell& editCell(Index index) {
assert(checkCellIndex(index));
return _cells[index];
}
// Octree members
Cells _cells = Cells(1, Cell()); // start with only the Cell root
Bricks _bricks;
Indices _freeCells; // stack of free cells to be reused for allocation
Indices _freeBricks; // stack of free bricks to be reused for allocation
};
}
// CLose the namespace here before including the Item in the picture, maybe Octre could stay pure of it
namespace render {
// An octree of Items organizing them efficiently for culling
// The octree only cares about the bound & the key of an item to store it a the right cell location
class ItemSpatialTree : public Octree {
float _size { 32768.0f };
float _invSize { 1.0f / _size };
glm::vec3 _origin { -16384.0f };
void init(glm::vec3 origin, float size) {
_size = size;
_invSize = 1.0f / _size;
_origin = origin;
}
public:
// THe overall size and origin of the tree are defined at creation
ItemSpatialTree(glm::vec3 origin, float size) { init(origin, size); }
float getSize() const { return _size; }
const glm::vec3& getOrigin() const { return _origin; }
float getCellWidth(Depth depth) const { return _size * getInvDepthDimension(depth); }
float getInvCellWidth(Depth depth) const { return getDepthDimensionf(depth) * _invSize; }
float getCellHalfDiagonalSquare(Depth depth) const {
float cellHalfWidth = 0.5f * getCellWidth(depth);
return 3.0f * cellHalfWidth * cellHalfWidth;
}
glm::vec3 evalPos(const Coord3& coord, Depth depth = Octree::METRIC_COORD_DEPTH) const {
return getOrigin() + glm::vec3(coord) * getCellWidth(depth);
}
glm::vec3 evalPos(const Coord3& coord, float cellWidth) const {
return getOrigin() + glm::vec3(coord) * cellWidth;
}
Coord3 evalCoord(const glm::vec3& pos, Depth depth = Octree::METRIC_COORD_DEPTH) const {
auto npos = (pos - getOrigin());
return Coord3(npos * getInvCellWidth(depth)); // Truncate fractional part
}
Coord3f evalCoordf(const glm::vec3& pos, Depth depth = Octree::METRIC_COORD_DEPTH) const {
auto npos = (pos - getOrigin());
return Coord3f(npos * getInvCellWidth(depth));
}
// Bound to Location
AABox evalBound(const Location& loc) const {
float cellWidth = getCellWidth(loc.depth);
return AABox(evalPos(loc.pos, cellWidth), cellWidth);
}
Location evalLocation(const AABox& bound) const {
return Location::evalFromRange(evalCoord(bound.getMinimumPoint()), evalCoord(bound.getMaximumPoint()));
}
Locations evalLocations(const ItemBounds& bounds) const;
// Managing itemsInserting items in cells
// Cells need to have been allocated first calling indexCell
Index insertItem(Index cellIdx, const ItemKey& key, const ItemID& item);
bool updateItem(Index cellIdx, const ItemKey& oldKey, const ItemKey& key, const ItemID& item);
bool removeItem(Index cellIdx, const ItemKey& key, const ItemID& item);
Index resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey);
// Selection and traverse
int selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const;
class ItemSelection {
public:
CellSelection cellSelection;
ItemIDs insideItems;
ItemIDs insideSubcellItems;
ItemIDs partialItems;
ItemIDs partialSubcellItems;
ItemIDs& items(bool inside) { return (inside ? insideItems : partialItems); }
ItemIDs& subcellItems(bool inside) { return (inside ? insideSubcellItems : partialSubcellItems); }
size_t insideNumItems() const { return insideItems.size() + insideSubcellItems.size(); }
size_t partialNumItems() const { return partialItems.size() + partialSubcellItems.size(); }
size_t numItems() const { return insideNumItems() + partialNumItems(); }
void clear() {
cellSelection.clear();
insideItems.clear();
insideSubcellItems.clear();
partialItems.clear();
partialSubcellItems.clear();
}
};
int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const;
};
}
#endif // hifi_render_Octree_h

View file

@ -37,7 +37,7 @@ public:
template <class T> Varying(const T& data) : _concept(std::make_shared<Model<T>>(data)) {}
template <class T> T& edit() { return std::static_pointer_cast<Model<T>>(_concept)->_data; }
template <class T> const T& get() { return std::static_pointer_cast<const Model<T>>(_concept)->_data; }
template <class T> const T& get() const { return std::static_pointer_cast<const Model<T>>(_concept)->_data; }
protected:
class Concept {

View file

@ -0,0 +1,20 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// drawCellBounds.slf
// fragment shader
//
// Created by Sam Gateau on 1/25/2016.
// 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
//
in vec4 varColor;
out vec4 outFragColor;
void main(void) {
outFragColor = varColor;
}

View file

@ -0,0 +1,67 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// drawCellBounds.slv
// Vertex shader
//
// Created by Sam Gateau on 1/25/2016
// 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()$>
<@include SceneOctree.slh@>
uniform ivec4 inCellLocation;
out vec4 varColor;
void main(void) {
const vec4 UNIT_BOX[8] = vec4[8](
vec4(0.0, 0.0, 0.0, 1.0),
vec4(1.0, 0.0, 0.0, 1.0),
vec4(0.0, 1.0, 0.0, 1.0),
vec4(1.0, 1.0, 0.0, 1.0),
vec4(0.0, 0.0, 1.0, 1.0),
vec4(1.0, 0.0, 1.0, 1.0),
vec4(0.0, 1.0, 1.0, 1.0),
vec4(1.0, 1.0, 1.0, 1.0)
);
const int UNIT_BOX_LINE_INDICES[24] = int[24](
0, 1,
1, 3,
3, 2,
2, 0,
4, 5,
5, 7,
7, 6,
6, 4,
2, 6,
3, 7,
0, 4,
1, 5
);
vec4 pos = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]];
int cellIsEmpty = sign(inCellLocation.w);
ivec4 cellLocation = ivec4(inCellLocation.xyz, (inCellLocation.w < 0 ? -inCellLocation.w : inCellLocation.w));
vec4 cellBound = evalBound(cellLocation);
pos.xyz = cellBound.xyz + vec3(cellBound.w) * pos.xyz;
// standard transform
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, pos, gl_Position)$>
varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * cellIsEmpty);
}

View file

@ -12,9 +12,18 @@
//
in vec4 varColor;
in vec2 varTexcoord;
out vec4 outFragColor;
void main(void) {
outFragColor = vec4(1.0, 1.0, 1.0, 1.0);
float var = step(fract(varTexcoord.x * varTexcoord.y * 1.0), 0.5);
if (varColor.a == 0) {
outFragColor = vec4(mix(vec3(0.0), varColor.xyz, var), mix(0.0, 1.0, var));
} else {
outFragColor = vec4(mix(vec3(1.0), varColor.xyz, var), varColor.a);
}
}

View file

@ -13,22 +13,28 @@
//
<@include gpu/Transform.slh@>
<$declareStandardTransform()$>
<@include gpu/Color.slh@>
<$declareColorWheel()$>
uniform vec3 inBoundPos;
uniform vec3 inBoundDim;
uniform ivec4 inCellLocation;
out vec4 varColor;
out vec2 varTexcoord;
void main(void) {
const vec4 UNIT_BOX[8] = vec4[8](
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 0.0),
vec4(1.0, 0.0, 0.0, 1.0),
vec4(0.0, 1.0, 0.0, 1.0),
vec4(1.0, 1.0, 0.0, 1.0),
vec4(1.0, 1.0, 0.0, 2.0),
vec4(0.0, 0.0, 1.0, 1.0),
vec4(1.0, 0.0, 1.0, 1.0),
vec4(0.0, 1.0, 1.0, 1.0),
vec4(1.0, 1.0, 1.0, 1.0)
vec4(1.0, 0.0, 1.0, 2.0),
vec4(0.0, 1.0, 1.0, 2.0),
vec4(1.0, 1.0, 1.0, 3.0)
);
const int UNIT_BOX_LINE_INDICES[24] = int[24](
0, 1,
@ -44,14 +50,18 @@ void main(void) {
0, 4,
1, 5
);
vec4 pos = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]];
vec4 cubeVec = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]];
pos.xyz = inBoundPos + inBoundDim * pos.xyz;
vec4 pos = vec4(inBoundPos + inBoundDim * cubeVec.xyz, 1.0);
// standard transform
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, pos, gl_Position)$>
// varTexcoord = (pos.xy + 1) * 0.5;
bool subcell = bool((inCellLocation.z));
float cellDepth = float(inCellLocation.w);
varColor = vec4(colorWheel(fract(cellDepth / 5.0)), 1.0 - float(subcell));
varTexcoord = vec2(cubeVec.w, length(inBoundDim));
}

View file

@ -0,0 +1,26 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// Draw the LOD reticle used to visualize the current LOD angle
//
// Created by Sam Gateau on 2/4/16
// 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
//
in vec2 varTexCoord0;
out vec4 outFragColor;
void main(void) {
vec2 circlePos = 2.0 * ( varTexCoord0.xy * 2.0 - vec2(1.0) );
float radius = length(circlePos);
float lodEdge = step(abs(1.0 - radius), 0.05);
float cellEdge = step(abs(2.0 - radius), 0.05);
outFragColor = vec4(lodEdge * vec3(1.0, 1.0, 0.0) + cellEdge * vec3(0.0, 1.0, 1.0), lodEdge + 0.5 * cellEdge);
}