diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 972aaf8..94ec19b 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -139,6 +139,9 @@ namespace VoxelForgeCore std::uint64_t passageTileRouteSegmentCandidates = 0; std::uint64_t passageTileMouthSegmentCandidates = 0; std::uint64_t resolvedBoundaryBandQueries = 0; + std::uint64_t resolvedBoundaryBandColumnEvaluations = 0; + std::uint64_t resolvedBoundaryBandColumnCacheHits = 0; + std::uint64_t resolvedBoundaryBandColumnOffGridFallbacks = 0; std::uint64_t passageComposerQueries = 0; std::uint64_t passageIterations = 0; std::uint64_t passageTileBoundChecks = 0; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 8626d7e..2c53c35 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -339,6 +339,15 @@ public: verticalBoundSkipsByCaller{},negativeReachSkipsByCaller{}, positiveReachSkipsByCaller{}; }; + struct FResolvedBoundaryColumnData + { + std::int32_t minimumXVoxel=0,minimumYVoxel=0,stepVoxel=0,dimension=0; + std::size_t pairCount=0; + std::vector pairReady; + std::vector bands; + std::uint64_t evaluations=0; + mutable std::atomic cacheHits{0},offGridFallbacks{0}; + }; FScopedPassageTileQueries(const FStandaloneShapeWorld& shape, const std::array& minimumSubvoxels, const std::array& maximumSubvoxels, @@ -502,6 +511,8 @@ public: passageCarveReachMetres_(source.passageCarveReachMetres_), passageSweepData_(source.passageSweepData_), ownsPassageSweepDiagnostics_(false), + resolvedBoundaryColumnData_(source.resolvedBoundaryColumnData_), + ownsResolvedBoundaryColumnDiagnostics_(false), clampCounters_(source.clampCounters_),ownsClampCounters_(false), projectionCacheSize_(source.projectionCacheSize_), reuseCounters_(source.reuseCounters_),ownsReuseCounters_(false) @@ -546,6 +557,7 @@ public: std::memory_order_relaxed); } } + copyResolvedBoundaryColumnDiagnostics(); if(ownsReuseCounters_&&reuseCounters_) std::printf("[L98PassageReuseTile] lod=%d projection_columns=%llu projection_computes=%llu projection_hits=%llu floor_profile_reads=%llu carve_prepared=%llu support_prepared=%llu capsule_prepared=%llu mouth_prepared=%llu\n", profileLod_, @@ -626,6 +638,23 @@ public: bool capturesCallerDiagnostics() const noexcept{return profileStats_!=nullptr;} void prepareSweptTubeBase(const FIntVector& origin,std::int32_t step, std::int32_t cells,std::uint32_t seed); + void prepareResolvedBoundaryColumns(const FIntVector& origin, + std::int32_t step,std::int32_t cells); + const VoxelForgeCore::ResolvedLayoutTransitionBandsV1* cachedTransitionBands( + float X,float Y,const VoxelForgeCore::ResolvedLayoutStratumV2& upper, + const VoxelForgeCore::ResolvedLayoutStratumV2& lower) const noexcept; + void copyResolvedBoundaryColumnDiagnostics() const noexcept + { + if(!ownsResolvedBoundaryColumnDiagnostics_||!profileStats_ + ||!resolvedBoundaryColumnData_)return; + profileStats_->resolvedBoundaryBandColumnEvaluations= + resolvedBoundaryColumnData_->evaluations; + profileStats_->resolvedBoundaryBandColumnCacheHits= + resolvedBoundaryColumnData_->cacheHits.load(std::memory_order_relaxed); + profileStats_->resolvedBoundaryBandColumnOffGridFallbacks= + resolvedBoundaryColumnData_->offGridFallbacks.load( + std::memory_order_relaxed); + } const FPassageSweepSample* sweptSample(std::size_t passageIndex, double qx,double qy,double qz) const; bool hasSweptTubeBase() const noexcept{return passageSweepData_!=nullptr;} @@ -917,6 +946,8 @@ public: double passageCarveReachMetres_=0.0; std::shared_ptr passageSweepData_; bool ownsPassageSweepDiagnostics_=true; + std::shared_ptr resolvedBoundaryColumnData_; + bool ownsResolvedBoundaryColumnDiagnostics_=true; mutable std::shared_ptr clampCounters_; bool ownsClampCounters_=true; std::size_t projectionCacheSize_=0; @@ -1546,8 +1577,12 @@ public: static_cast(X)*Owner->CoreVoxelSizeMicrons)); const auto yMicrometres=static_cast(std::llround( static_cast(Y)*Owner->CoreVoxelSizeMicrons)); - const auto bands=ResolvedTransitionBandsAtXY(*transitionUpper, - *transitionLower,xMicrometres,yMicrometres,skipPassageFade); + const auto* cachedBands=tileQueries&&tileQueries->matches(*this) + ?tileQueries->cachedTransitionBands(X,Y,*transitionUpper, + *transitionLower):nullptr; + const auto bands=cachedBands?*cachedBands:ResolvedTransitionBandsAtXY( + *transitionUpper,*transitionLower,xMicrometres,yMicrometres, + skipPassageFade); const std::int64_t solidBottom=std::min( bands.upperBedrockBottomMicrometres,bands.lowerCeilingBottomMicrometres); const std::int64_t solidTop=std::max( @@ -1609,8 +1644,11 @@ public: #if defined(VF_L30_ENABLE_COST_COUNTERS) if(skipPassageFade)++ThreadClassificationStats().TransitionPassageTileSkips; #endif - const auto resolvedBands=ResolvedTransitionBandsAtXY(*upper,*lower, - xMicrometres,yMicrometres,skipPassageFade); + const auto* cachedBands=tileQueries&&tileQueries->matches(*this) + ?tileQueries->cachedTransitionBands(X,Y,*upper,*lower):nullptr; + const auto resolvedBands=cachedBands?*cachedBands: + ResolvedTransitionBandsAtXY(*upper,*lower,xMicrometres,yMicrometres, + skipPassageFade); // Treat the two authored bands as one continuous solid interval. Its // exterior faces remain the lower ceiling bottom and upper bedrock top; // measuring signed distance from those faces also clears native generator @@ -14424,6 +14462,115 @@ private: inline static thread_local const FPassageCandidateBrick* ActivePassageCandidateBrick=nullptr; }; +inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareResolvedBoundaryColumns( + const FIntVector& origin,std::int32_t step,std::int32_t cells) +{ + if(!shape_||!mayContainResolvedBoundaryBand_||step<=0||cells<=0 + ||!shape_->ResolvedLayout||!shape_->Owner + ||shape_->Owner->CoreVoxelSizeMicrons==0)return; + + constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; + const auto& layout=*shape_->ResolvedLayout; + auto data=std::make_shared(); + data->minimumXVoxel=origin.X-step; + data->minimumYVoxel=origin.Y-step; + data->stepVoxel=step; + data->dimension=cells+3; + for(const auto& stratum:layout.strates) + if(stratum.order>=0) + data->pairCount=std::max(data->pairCount, + static_cast(stratum.order)+1); + if(data->pairCount==0)return; + data->pairReady.assign(data->pairCount,0); + const double minimumZMicrometres=static_cast(minimumSubvoxels_[2]) + *shape_->Owner->CoreVoxelSizeMicrons/Q; + const double maximumZMicrometres=static_cast(maximumSubvoxels_[2]) + *shape_->Owner->CoreVoxelSizeMicrons/Q; + std::size_t activePairCount=0; + for(const auto& upper:layout.strates) + { + if(upper.order<0||static_cast(upper.order)>=data->pairCount + ||!shape_->IsResolvedLayoutTransitionActive(upper))continue; + const auto* lower=shape_->FindResolvedLayoutStratumByOrder(upper.order+1); + if(!lower)continue; + const auto maximumDisplacement= + shape_->ResolvedTransitionMaximumFaceDisplacementMicrometres(upper,*lower); + const double transitionMinimum=std::min(upper.bedrock.bottomMicrometres, + lower->ceiling.bottomMicrometres)-maximumDisplacement; + const double transitionMaximum=std::max(upper.bedrock.topMicrometres, + lower->ceiling.topMicrometres)+maximumDisplacement; + if(maximumZMicrometres=transitionMaximum)continue; + data->pairReady[static_cast(upper.order)]=1; + ++activePairCount; + } + if(activePairCount==0)return; + + const std::size_t columnCount=static_cast(data->dimension) + *static_cast(data->dimension); + if(columnCount>std::numeric_limits::max()/data->pairCount)return; + data->bands.resize(columnCount*data->pairCount); + data->evaluations=static_cast(columnCount)*activePairCount; + const bool skipPassageFade=!mayContainTransitionPassageFade_; + for(std::int32_t gy=0;gydimension;++gy) + for(std::int32_t gx=0;gxdimension;++gx) + { + const float x=static_cast(static_cast(data->minimumXVoxel) + +static_cast(gx)*data->stepVoxel); + const float y=static_cast(static_cast(data->minimumYVoxel) + +static_cast(gy)*data->stepVoxel); + const std::int64_t xMicrometres=static_cast(std::llround( + static_cast(x)*shape_->Owner->CoreVoxelSizeMicrons)); + const std::int64_t yMicrometres=static_cast(std::llround( + static_cast(y)*shape_->Owner->CoreVoxelSizeMicrons)); + const std::size_t column=static_cast(gy) + *static_cast(data->dimension)+static_cast(gx); + for(const auto& upper:layout.strates) + { + if(upper.order<0||static_cast(upper.order)>=data->pairCount + ||!data->pairReady[static_cast(upper.order)])continue; + const auto* lower=shape_->FindResolvedLayoutStratumByOrder(upper.order+1); + if(!lower)continue; + data->bands[column*data->pairCount+static_cast(upper.order)]= + shape_->ResolvedTransitionBandsAtXY(upper,*lower,xMicrometres, + yMicrometres,skipPassageFade); + } + } + resolvedBoundaryColumnData_=std::move(data); +} + +inline const VoxelForgeCore::ResolvedLayoutTransitionBandsV1* +FStandaloneShapeWorld::FScopedPassageTileQueries::cachedTransitionBands( + float X,float Y,const VoxelForgeCore::ResolvedLayoutStratumV2& upper, + const VoxelForgeCore::ResolvedLayoutStratumV2& lower) const noexcept +{ + const auto* data=resolvedBoundaryColumnData_.get(); + if(!data||upper.order<0||lower.order!=upper.order+1 + ||static_cast(upper.order)>=data->pairCount + ||!data->pairReady[static_cast(upper.order)])return nullptr; + const double xCoordinate=(static_cast(X)-data->minimumXVoxel)/data->stepVoxel; + const double yCoordinate=(static_cast(Y)-data->minimumYVoxel)/data->stepVoxel; + const auto gx=static_cast(std::llround(xCoordinate)); + const auto gy=static_cast(std::llround(yCoordinate)); + if(gx<0||gy<0||gx>=data->dimension||gy>=data->dimension + ||static_cast(static_cast(data->minimumXVoxel) + +gx*data->stepVoxel)!=X + ||static_cast(static_cast(data->minimumYVoxel) + +gy*data->stepVoxel)!=Y) + { + if(profileStats_) + data->offGridFallbacks.fetch_add(1,std::memory_order_relaxed); + return nullptr; + } + const std::size_t column=static_cast(gy) + *static_cast(data->dimension)+static_cast(gx); + const std::size_t index=column*data->pairCount + +static_cast(upper.order); + if(index>=data->bands.size())return nullptr; + if(profileStats_)data->cacheHits.fetch_add(1,std::memory_order_relaxed); + return &data->bands[index]; +} + inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBase( const FIntVector& origin,std::int32_t step,std::int32_t cells,std::uint32_t seed) { diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index 0a5d451..81a04b1 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -2806,6 +2806,7 @@ namespace VoxelForgeCore options.profilePassageComposer?&out.diagnostics:nullptr,key.lod, options.diagnosticPassageSourceFloorVoxels, options.diagnosticPassageCarveReachMetres); + passageFeatureScope.prepareResolvedBoundaryColumns(origin,MeshStep,MeshCells); passageFeatureScope.prepareSweptTubeBase(origin,MeshStep,MeshCells, static_cast(world.Seed)); static std::atomic l98ProjectionProbeStarted{false}; @@ -3367,6 +3368,7 @@ namespace VoxelForgeCore out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration( std::chrono::steady_clock::now()-MeshAssemblyStart).count(); CopyIslandCacheMetrics(); + passageFeatureScope.copyResolvedBoundaryColumnDiagnostics(); return out; } diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h index 3019e39..3f706f9 100644 --- a/Standalone/Source/StreamingWorld.h +++ b/Standalone/Source/StreamingWorld.h @@ -1538,7 +1538,7 @@ private: static_cast(D.islandSharedCacheConflictReplacements), static_cast(D.islandSharedCacheLockWaitNanoseconds)/1000.0); if(D.passageComposerProfiled) - std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu mouth_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu tile_bound_checks=%llu tile_bound_rejects=%llu tile_bound_skipped_iterations=%llu upper_mouth_checks=%llu column_candidates=%llu column_distance_calls=%llu negative_bounds=%llu negative_rejects=%llu positive_bounds=%llu positive_rejects=%llu carve_segment_visits=%llu smooth_carve_joins=%llu support_segment_visits=%llu smooth_support_joins=%llu support_capsule_checks=%llu later_bore_checks=%llu other_capsule_checks=%llu mouth_support_calls=%llu mouth_route_segment_checks=%llu relief_field_calls=%llu smoothstep_evaluations=%llu sweep_segments=%llu sweep_pairs=%llu sweep_outside_pairs=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n", + std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu mouth_candidates=%llu boundary_band_queries=%llu boundary_column_evaluations=%llu boundary_column_hits=%llu boundary_column_offgrid=%llu queries=%llu passage_iterations=%llu tile_bound_checks=%llu tile_bound_rejects=%llu tile_bound_skipped_iterations=%llu upper_mouth_checks=%llu column_candidates=%llu column_distance_calls=%llu negative_bounds=%llu negative_rejects=%llu positive_bounds=%llu positive_rejects=%llu carve_segment_visits=%llu smooth_carve_joins=%llu support_segment_visits=%llu smooth_support_joins=%llu support_capsule_checks=%llu later_bore_checks=%llu other_capsule_checks=%llu mouth_support_calls=%llu mouth_route_segment_checks=%llu relief_field_calls=%llu smoothstep_evaluations=%llu sweep_segments=%llu sweep_pairs=%llu sweep_outside_pairs=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n", Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level, Event.WorkerMilliseconds,static_cast(Event.DensitySamples), static_cast(D.fieldCalls), @@ -1546,6 +1546,9 @@ private: static_cast(D.passageTileRouteSegmentCandidates), static_cast(D.passageTileMouthSegmentCandidates), static_cast(D.resolvedBoundaryBandQueries), + static_cast(D.resolvedBoundaryBandColumnEvaluations), + static_cast(D.resolvedBoundaryBandColumnCacheHits), + static_cast(D.resolvedBoundaryBandColumnOffGridFallbacks), static_cast(D.passageComposerQueries), static_cast(D.passageIterations), static_cast(D.passageTileBoundChecks),