void Octree::Raycast(RayOctreeQuery& query) const { PROFILE(Raycast); query.result_.Clear(); WorkQueue* queue = GetSubsystem<WorkQueue>(); // If no worker threads or no triangle-level testing, do not create work items if (!queue->GetNumThreads() || query.level_ < RAY_TRIANGLE) GetDrawablesInternal(query); else { // Threaded ray query: first get the drawables rayQuery_ = &query; rayQueryDrawables_.Clear(); GetDrawablesOnlyInternal(query, rayQueryDrawables_); // Check that amount of drawables is large enough to justify threading if (rayQueryDrawables_.Size() >= RAYCASTS_PER_WORK_ITEM * 2) { for (unsigned i = 0; i < rayQueryResults_.Size(); ++i) rayQueryResults_[i].Clear(); PODVector<Drawable*>::Iterator start = rayQueryDrawables_.Begin(); while (start != rayQueryDrawables_.End()) { SharedPtr<WorkItem> item = queue->GetFreeItem(); item->priority_ = M_MAX_UNSIGNED; item->workFunction_ = RaycastDrawablesWork; item->aux_ = const_cast<Octree*>(this); PODVector<Drawable*>::Iterator end = rayQueryDrawables_.End(); if (end - start > RAYCASTS_PER_WORK_ITEM) end = start + RAYCASTS_PER_WORK_ITEM; item->start_ = &(*start); item->end_ = &(*end); queue->AddWorkItem(item); start = end; } // Merge per-thread results queue->Complete(M_MAX_UNSIGNED); for (unsigned i = 0; i < rayQueryResults_.Size(); ++i) query.result_.Insert(query.result_.End(), rayQueryResults_[i].Begin(), rayQueryResults_[i].End()); } else { for (PODVector<Drawable*>::Iterator i = rayQueryDrawables_.Begin(); i != rayQueryDrawables_.End(); ++i) (*i)->ProcessRayQuery(query, query.result_); } } Sort(query.result_.Begin(), query.result_.End(), CompareRayQueryResults); }
Octree::Octree(Context* context) : Component(context), Octant(BoundingBox(-DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE), 0, 0, this), numLevels_(DEFAULT_OCTREE_LEVELS) { // Resize threaded ray query intermediate result vector according to number of worker threads WorkQueue* workQueue = GetSubsystem<WorkQueue>(); rayQueryResults_.Resize(workQueue ? workQueue->GetNumThreads() + 1 : 1); // If the engine is running headless, subscribe to RenderUpdate events for manually updating the octree // to allow raycasts and animation update if (!GetSubsystem<Graphics>()) SubscribeToEvent(E_RENDERUPDATE, HANDLER(Octree, HandleRenderUpdate)); }
void Octree::Update(const FrameInfo& frame) { // Let drawables update themselves before reinsertion. This can be used for animation if (!drawableUpdates_.Empty()) { PROFILE(UpdateDrawables); // Perform updates in worker threads. Notify the scene that a threaded update is going on and components // (for example physics objects) should not perform non-threadsafe work when marked dirty Scene* scene = GetScene(); WorkQueue* queue = GetSubsystem<WorkQueue>(); scene->BeginThreadedUpdate(); int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread int drawablesPerItem = Max((int)(drawableUpdates_.Size() / numWorkItems), 1); PODVector<Drawable*>::Iterator start = drawableUpdates_.Begin(); // Create a work item for each thread for (int i = 0; i < numWorkItems; ++i) { SharedPtr<WorkItem> item = queue->GetFreeItem(); item->priority_ = M_MAX_UNSIGNED; item->workFunction_ = UpdateDrawablesWork; item->aux_ = const_cast<FrameInfo*>(&frame); PODVector<Drawable*>::Iterator end = drawableUpdates_.End(); if (i < numWorkItems - 1 && end - start > drawablesPerItem) end = start + drawablesPerItem; item->start_ = &(*start); item->end_ = &(*end); queue->AddWorkItem(item); start = end; } queue->Complete(M_MAX_UNSIGNED); scene->EndThreadedUpdate(); } // Notify drawable update being finished. Custom animation (eg. IK) can be done at this point Scene* scene = GetScene(); if (scene) { using namespace SceneDrawableUpdateFinished; VariantMap& eventData = GetEventDataMap(); eventData[P_SCENE] = scene; eventData[P_TIMESTEP] = frame.timeStep_; scene->SendEvent(E_SCENEDRAWABLEUPDATEFINISHED, eventData); } // Reinsert drawables that have been moved or resized, or that have been newly added to the octree and do not sit inside // the proper octant yet if (!drawableUpdates_.Empty()) { PROFILE(ReinsertToOctree); for (PODVector<Drawable*>::Iterator i = drawableUpdates_.Begin(); i != drawableUpdates_.End(); ++i) { Drawable* drawable = *i; drawable->updateQueued_ = false; Octant* octant = drawable->GetOctant(); const BoundingBox& box = drawable->GetWorldBoundingBox(); // Skip if no octant or does not belong to this octree anymore if (!octant || octant->GetRoot() != this) continue; // Skip if still fits the current octant if (drawable->IsOccludee() && octant->GetCullingBox().IsInside(box) == INSIDE && octant->CheckDrawableFit(box)) continue; InsertDrawable(drawable); #ifdef _DEBUG // Verify that the drawable will be culled correctly octant = drawable->GetOctant(); if (octant != this && octant->GetCullingBox().IsInside(box) != INSIDE) { LOGERROR("Drawable is not fully inside its octant's culling bounds: drawable box " + box.ToString() + " octant box " + octant->GetCullingBox().ToString()); } #endif } } drawableUpdates_.Clear(); }
void Renderer2D::HandleBeginViewUpdate(StringHash eventType, VariantMap& eventData) { using namespace BeginViewUpdate; // Check that we are updating the correct scene if (GetScene() != eventData[P_SCENE].GetPtr()) return; frame_ = static_cast<View*>(eventData[P_VIEW].GetPtr())->GetFrameInfo(); URHO3D_PROFILE(UpdateRenderer2D); Camera* camera = static_cast<Camera*>(eventData[P_CAMERA].GetPtr()); frustum_ = &camera->GetFrustum(); if (camera->IsOrthographic() && camera->GetNode()->GetWorldDirection() == Vector3::FORWARD) { // Define bounding box with min and max points frustumBoundingBox_.Define(frustum_->vertices_[2], frustum_->vertices_[4]); frustum_ = 0; } viewMask_ = camera->GetViewMask(); // Check visibility { URHO3D_PROFILE(CheckDrawableVisibility); WorkQueue* queue = GetSubsystem<WorkQueue>(); int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread int drawablesPerItem = drawables_.Size() / numWorkItems; PODVector<Drawable2D*>::Iterator start = drawables_.Begin(); for (int i = 0; i < numWorkItems; ++i) { SharedPtr<WorkItem> item = queue->GetFreeItem(); item->priority_ = M_MAX_UNSIGNED; item->workFunction_ = CheckDrawableVisibility; item->aux_ = this; PODVector<Drawable2D*>::Iterator end = drawables_.End(); if (i < numWorkItems - 1 && end - start > drawablesPerItem) end = start + drawablesPerItem; item->start_ = &(*start); item->end_ = &(*end); queue->AddWorkItem(item); start = end; } queue->Complete(M_MAX_UNSIGNED); } ViewBatchInfo2D& viewBatchInfo = viewBatchInfos_[camera]; // Create vertex buffer if (!viewBatchInfo.vertexBuffer_) viewBatchInfo.vertexBuffer_ = new VertexBuffer(context_); UpdateViewBatchInfo(viewBatchInfo, camera); // Go through the drawables to form geometries & batches and calculate the total vertex / index count, // but upload the actual vertex data later. The idea is that the View class copies our batch vector to // its internal data structures, so we can reuse the batches for each view, provided that unique Geometry // objects are used for each view to specify the draw ranges batches_.Resize(viewBatchInfo.batchCount_); for (unsigned i = 0; i < viewBatchInfo.batchCount_; ++i) { batches_[i].material_ = viewBatchInfo.materials_[i]; batches_[i].geometry_ = viewBatchInfo.geometries_[i]; } }
void DrawableProxy2D::HandleBeginViewUpdate(StringHash eventType, VariantMap& eventData) { using namespace BeginViewUpdate; // Check that we are updating the correct scene if (GetScene() != eventData[P_SCENE].GetPtr()) return; PROFILE(UpdateDrawableProxy2D); if (orderDirty_) { Sort(drawables_.Begin(), drawables_.End(), CompareDrawable2Ds); orderDirty_ = false; } Camera* camera = static_cast<Camera*>(eventData[P_CAMERA].GetPtr()); frustum_ = &camera->GetFrustum(); if (camera->IsOrthographic() && camera->GetNode()->GetWorldDirection() == Vector3::FORWARD) { // Define bounding box with min and max points frustumBoundingBox_.Define(frustum_->vertices_[2], frustum_->vertices_[4]); frustum_ = 0; } { PROFILE(CheckDrawableVisibility); WorkQueue* queue = GetSubsystem<WorkQueue>(); int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread int drawablesPerItem = drawables_.Size() / numWorkItems; PODVector<Drawable2D*>::Iterator start = drawables_.Begin(); for (int i = 0; i < numWorkItems; ++i) { SharedPtr<WorkItem> item = queue->GetFreeItem(); item->priority_ = M_MAX_UNSIGNED; item->workFunction_ = CheckDrawableVisibility; item->aux_ = this; PODVector<Drawable2D*>::Iterator end = drawables_.End(); if (i < numWorkItems - 1 && end - start > drawablesPerItem) end = start + drawablesPerItem; item->start_ = &(*start); item->end_ = &(*end); queue->AddWorkItem(item); start = end; } queue->Complete(M_MAX_UNSIGNED); } vertexCount_ = 0; for (unsigned i = 0; i < drawables_.Size(); ++i) { if (drawables_[i]->GetVisibility()) vertexCount_ += drawables_[i]->GetVertices().Size(); } indexCount_ = vertexCount_ / 4 * 6; // Go through the drawables to form geometries & batches, but upload the actual vertex data later materials_.Clear(); Material* material = 0; unsigned iStart = 0; unsigned iCount = 0; unsigned vStart = 0; unsigned vCount = 0; for (unsigned d = 0; d < drawables_.Size(); ++d) { if (!drawables_[d]->GetVisibility()) continue; Material* usedMaterial = drawables_[d]->GetUsedMaterial(); const Vector<Vertex2D>& vertices = drawables_[d]->GetVertices(); if (material != usedMaterial) { if (material) { AddBatch(material, iStart, iCount, vStart, vCount); iStart += iCount; iCount = 0; vStart += vCount; vCount = 0; } material = usedMaterial; } iCount += vertices.Size() / 4 * 6; vCount += vertices.Size(); } if (material) AddBatch(material, iStart, iCount, vStart, vCount); // Now the amount of batches is known. Build the part of source batches that are sensitive to threading issues // (material & geometry pointers) unsigned count = materials_.Size(); batches_.Resize(count); for (unsigned i = 0; i < count; ++i) { batches_[i].material_ = materials_[i]; batches_[i].geometry_ = geometries_[i]; } }