Skip to content

Commit a490363

Browse files
committed
asdlk
Signed-off-by: Felix Schlepper <felix.schlepper@cern.ch>
1 parent 74232db commit a490363

5 files changed

Lines changed: 160 additions & 106 deletions

File tree

Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ struct TrackingParameters {
6767
int ReseedIfShorter = 6; // reseed for the final fit track with the length shorter than this
6868
std::vector<float> MinPt = {0.f, 0.f, 0.f, 0.f};
6969
uint16_t StartLayerMask = 0x7F;
70+
int PNChunkSize = 0;
7071
bool RepeatRefitOut = false; // repeat outward refit using inward refit as a seed
7172
bool ShiftRefToCluster = true; // TrackFit: after update shift the linearization reference to cluster
7273
bool PerPrimaryVertexProcessing = false;

Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class TrackerTraits
5353
virtual void computeLayerCells(const int iteration);
5454
virtual void findCellsNeighbours(const int iteration);
5555
virtual void findRoads(const int iteration);
56-
virtual void processNeighbours(int iLayer, int iLevel, const bounded_vector<CellSeedN>& currentCellSeed, const bounded_vector<int>& currentCellId, bounded_vector<CellSeedN>& updatedCellSeed, bounded_vector<int>& updatedCellId);
56+
virtual void processNeighbours(int iLayer, int iLevel, const bounded_vector<CellSeedN>& currentCellSeed, const bounded_vector<int>& currentCellId, bounded_vector<CellSeedN>& updatedCellSeed, bounded_vector<int>& updatedCellId, int beginCell = 0, int endCell = -1);
5757

5858
void updateTrackingParameters(const std::vector<TrackingParameters>& trkPars) { mTrkParams = trkPars; }
5959
TimeFrame<NLayers>* getTimeFrame() { return mTimeFrame; }

Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ struct VertexerParamConfig : public o2::conf::ConfigurableParamHelper<VertexerPa
3636
float duplicateZCut = 0.7985643f;
3737
float finalSelectionZCut = 0.2932624f;
3838
float duplicateDistance2Cut = 0.0223001f;
39-
float tanLambdaCut = 0.002f; // tanLambda = deltaZ/deltaR
39+
float tanLambdaCut = 0.002f; // tanLambda = deltaZ/deltaR
4040
float nSigmaCut = 0.0479011f;
41-
float maxZPositionAllowed = 25.f; // 4x sZ of the beam
41+
float maxZPositionAllowed = 25.f; // 4x sZ of the beam
4242

4343
// Artefacts selections
4444
int clusterContributorsCut = 3; // minimum number of contributors for an accepted final vertex
@@ -71,6 +71,7 @@ struct TrackerParamConfig : public o2::conf::ConfigurableParamHelper<TrackerPara
7171
int addTimeError[7] = {0}; // configure the width of the window in BC to be considered for the tracking.
7272
int minTrackLgtIter[MaxIter] = {}; // minimum track length at each iteration, used only if >0, otherwise use code defaults
7373
uint8_t startLayerMask[MaxIter] = {}; // mask of start layer for this iteration (if >0)
74+
int pnChunkSize[MaxIter] = {}; // chunk level in input cells for processNeighbours at each iteration (if >0)
7475
float minPtIterLgt[MaxIter * (MaxTrackLength - MinTrackLength + 1)] = {}; // min.pT for given track length at this iteration, used only if >0, otherwise use code defaults
7576
float sysErrY2[7] = {0}; // systematic error^2 in Y per layer
7677
float sysErrZ2[7] = {0}; // systematic error^2 in Z per layer

Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ std::string TrackingParameters::asString() const
2626
{
2727
std::string str = std::format("NZb:{} NPhB:{} PerVtx:{} DropFail:{} ClSh:{} TtklMinPt:{:.2f} MinCl:{}",
2828
ZBins, PhiBins, PerPrimaryVertexProcessing, DropTFUponFailure, ClusterSharing, TrackletMinPt, MinTrackLength);
29+
if (PNChunkSize > 0) {
30+
str += std::format(" PNCh:{}", PNChunkSize);
31+
}
2932
bool first = true;
3033
for (int il = NLayers; il >= MinTrackLength; il--) {
3134
int slot = NLayers - il;
@@ -145,6 +148,7 @@ std::vector<TrackingParameters> TrackingMode::getTrackingParameters(TrackingMode
145148
if (tc.startLayerMask[ip] > 0) {
146149
trackParams[2].StartLayerMask = tc.startLayerMask[ip];
147150
}
151+
param.PNChunkSize = tc.pnChunkSize[ip] > 0 ? 1 << tc.pnChunkSize[ip] : 0;
148152
if (tc.minTrackLgtIter[ip] > 0) {
149153
param.MinTrackLength = tc.minTrackLgtIter[ip];
150154
}

Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx

Lines changed: 151 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -514,12 +514,22 @@ void TrackerTraits<NLayers>::findCellsNeighbours(const int iteration)
514514
}
515515

516516
template <int NLayers>
517-
void TrackerTraits<NLayers>::processNeighbours(int iLayer, int iLevel, const bounded_vector<CellSeedN>& currentCellSeed, const bounded_vector<int>& currentCellId, bounded_vector<CellSeedN>& updatedCellSeeds, bounded_vector<int>& updatedCellsIds)
517+
void TrackerTraits<NLayers>::processNeighbours(int iLayer, int iLevel, const bounded_vector<CellSeedN>& currentCellSeed, const bounded_vector<int>& currentCellId, bounded_vector<CellSeedN>& updatedCellSeeds, bounded_vector<int>& updatedCellsIds, int beginCell, int endCell)
518518
{
519-
auto propagator = o2::base::Propagator::Instance();
519+
updatedCellSeeds.clear();
520+
updatedCellsIds.clear();
521+
522+
const int totalCells = static_cast<int>(currentCellSeed.size());
523+
beginCell = std::clamp(beginCell, 0, totalCells);
524+
endCell = endCell < 0 ? totalCells : std::clamp(endCell, beginCell, totalCells);
525+
if (beginCell >= endCell) {
526+
return;
527+
}
520528

529+
auto propagator = o2::base::Propagator::Instance();
521530
mTaskArena->execute([&] {
522-
auto forCellNeighbours = [&](auto Tag, int iCell, int offset = 0) -> int {
531+
auto forCellNeighbours = [&](auto Tag, int localCell, int offset = 0) -> int {
532+
const int iCell = beginCell + localCell;
523533
const auto& currentCell{currentCellSeed[iCell]};
524534

525535
if constexpr (decltype(Tag)::value != PassMode::TwoPassInsert::value) {
@@ -604,15 +614,15 @@ void TrackerTraits<NLayers>::processNeighbours(int iLayer, int iLevel, const bou
604614
return foundSeeds;
605615
};
606616

607-
const int nCells = static_cast<int>(currentCellSeed.size());
617+
const int nCells = endCell - beginCell;
608618
if (mTaskArena->max_concurrency() <= 1) {
609-
for (int iCell{0}; iCell < nCells; ++iCell) {
610-
forCellNeighbours(PassMode::OnePass{}, iCell);
619+
for (int localCell{0}; localCell < nCells; ++localCell) {
620+
forCellNeighbours(PassMode::OnePass{}, localCell);
611621
}
612622
} else {
613623
bounded_vector<int> perCellCount(nCells + 1, 0, mMemoryPool.get());
614-
tbb::parallel_for(0, nCells, [&](const int iCell) {
615-
perCellCount[iCell] = forCellNeighbours(PassMode::TwoPassCount{}, iCell);
624+
tbb::parallel_for(0, nCells, [&](const int localCell) {
625+
perCellCount[localCell] = forCellNeighbours(PassMode::TwoPassCount{}, localCell);
616626
});
617627

618628
std::exclusive_scan(perCellCount.begin(), perCellCount.end(), perCellCount.begin(), 0);
@@ -623,12 +633,12 @@ void TrackerTraits<NLayers>::processNeighbours(int iLayer, int iLevel, const bou
623633
updatedCellSeeds.resize(totalNeighbours);
624634
updatedCellsIds.resize(totalNeighbours);
625635

626-
tbb::parallel_for(0, nCells, [&](const int iCell) {
627-
int offset = perCellCount[iCell];
628-
if (offset == perCellCount[iCell + 1]) {
636+
tbb::parallel_for(0, nCells, [&](const int localCell) {
637+
int offset = perCellCount[localCell];
638+
if (offset == perCellCount[localCell + 1]) {
629639
return;
630640
}
631-
forCellNeighbours(PassMode::TwoPassInsert{}, iCell, offset);
641+
forCellNeighbours(PassMode::TwoPassInsert{}, localCell, offset);
632642
});
633643
}
634644
});
@@ -644,119 +654,157 @@ void TrackerTraits<NLayers>::findRoads(const int iteration)
644654
for (int startLevel{mTrkParams[iteration].CellsPerRoad()}; startLevel >= mTrkParams[iteration].CellMinimumLevel(); --startLevel) {
645655

646656
auto seedFilter = [&](const auto& seed) {
647-
return seed.getQ2Pt() <= 1.e3 && seed.getChi2() <= mTrkParams[0].MaxChi2NDF * ((startLevel + 2) * 2 - 5);
657+
return seed.getQ2Pt() <= 1.e3 && seed.getChi2() <= mTrkParams[iteration].MaxChi2NDF * ((startLevel + 2) * 2 - 5);
648658
};
649659

650-
bounded_vector<CellSeedN> trackSeeds(mMemoryPool.get());
651-
for (int startLayer{mTrkParams[iteration].NeighboursPerRoad()}; startLayer >= startLevel - 1; --startLayer) {
652-
if ((mTrkParams[iteration].StartLayerMask & (1 << (startLayer + 2))) == 0) {
653-
continue;
654-
}
655-
656-
bounded_vector<int> lastCellId(mMemoryPool.get()), updatedCellId(mMemoryPool.get());
657-
bounded_vector<CellSeedN> lastCellSeed(mMemoryPool.get()), updatedCellSeed(mMemoryPool.get());
658-
659-
processNeighbours(startLayer, startLevel, mTimeFrame->getCells()[startLayer], lastCellId, updatedCellSeed, updatedCellId);
660-
661-
int level = startLevel;
662-
for (int iLayer{startLayer - 1}; iLayer > 0 && level > 2; --iLayer) {
663-
lastCellSeed.swap(updatedCellSeed);
664-
lastCellId.swap(updatedCellId);
665-
deepVectorClear(updatedCellSeed); /// tame the memory peaks
666-
deepVectorClear(updatedCellId); /// tame the memory peaks
667-
processNeighbours(iLayer, --level, lastCellSeed, lastCellId, updatedCellSeed, updatedCellId);
668-
}
669-
deepVectorClear(lastCellId); /// tame the memory peaks
670-
deepVectorClear(lastCellSeed); /// tame the memory peaks
671-
672-
if (!updatedCellSeed.empty()) {
673-
trackSeeds.reserve(trackSeeds.size() + std::count_if(updatedCellSeed.begin(), updatedCellSeed.end(), seedFilter));
674-
std::copy_if(updatedCellSeed.begin(), updatedCellSeed.end(), std::back_inserter(trackSeeds), seedFilter);
675-
}
676-
}
677-
678-
if (trackSeeds.empty()) {
679-
continue;
680-
}
681-
682660
bounded_vector<TrackITSExt> tracks(mMemoryPool.get());
683-
mTaskArena->execute([&] {
684-
auto forSeed = [&](auto Tag, int iSeed, int offset = 0) {
685-
TrackITSExt temporaryTrack = seedTrackForRefit(trackSeeds[iSeed]);
686-
o2::track::TrackPar linRef{temporaryTrack};
687-
bool fitSuccess = fitTrack(temporaryTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, o2::constants::math::VeryBig, 0, &linRef);
688-
if (!fitSuccess) {
689-
return 0;
690-
}
691-
temporaryTrack.getParamOut() = temporaryTrack.getParamIn();
692-
linRef = temporaryTrack.getParamOut(); // use refitted track as lin.reference
693-
temporaryTrack.resetCovariance();
694-
temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2);
695-
temporaryTrack.setChi2(0);
696-
fitSuccess = fitTrack(temporaryTrack, mTrkParams[0].NLayers - 1, -1, -1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, 50.f, 0, &linRef);
697-
if (!fitSuccess || temporaryTrack.getPt() < mTrkParams[iteration].MinPt[mTrkParams[iteration].NLayers - temporaryTrack.getNClusters()]) {
698-
return 0;
699-
}
700-
if (mTrkParams[0].RepeatRefitOut) { // repeat outward refit seeding and linearizing with the stable inward fit result
701-
o2::track::TrackParCov saveInw{temporaryTrack};
702-
linRef = saveInw; // use refitted track as lin.reference
703-
float saveChi2 = temporaryTrack.getChi2();
661+
auto fitSeeds = [&](const bounded_vector<CellSeedN>& finalSeeds) {
662+
bounded_vector<TrackITSExt> trackChunk(mMemoryPool.get());
663+
mTaskArena->execute([&] {
664+
auto forSeed = [&](auto Tag, int iSeed, int offset = 0) {
665+
if (!seedFilter(finalSeeds[iSeed])) {
666+
return 0;
667+
}
668+
669+
TrackITSExt temporaryTrack = seedTrackForRefit(finalSeeds[iSeed]);
670+
o2::track::TrackPar linRef{temporaryTrack};
671+
bool fitSuccess = fitTrack(temporaryTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, o2::constants::math::VeryBig, 0, &linRef);
672+
if (!fitSuccess) {
673+
return 0;
674+
}
675+
temporaryTrack.getParamOut() = temporaryTrack.getParamIn();
676+
linRef = temporaryTrack.getParamOut(); // use refitted track as lin.reference
704677
temporaryTrack.resetCovariance();
705678
temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2);
706679
temporaryTrack.setChi2(0);
707-
fitSuccess = fitTrack(temporaryTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, o2::constants::math::VeryBig, 0, &linRef);
708-
if (!fitSuccess) {
680+
fitSuccess = fitTrack(temporaryTrack, mTrkParams[0].NLayers - 1, -1, -1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, 50.f, 0, &linRef);
681+
if (!fitSuccess || temporaryTrack.getPt() < mTrkParams[iteration].MinPt[mTrkParams[iteration].NLayers - temporaryTrack.getNClusters()]) {
709682
return 0;
710683
}
711-
temporaryTrack.getParamOut() = temporaryTrack.getParamIn();
712-
temporaryTrack.getParamIn() = saveInw;
713-
temporaryTrack.setChi2(saveChi2);
714-
}
684+
if (mTrkParams[0].RepeatRefitOut) { // repeat outward refit seeding and linearizing with the stable inward fit result
685+
o2::track::TrackParCov saveInw{temporaryTrack};
686+
linRef = saveInw; // use refitted track as lin.reference
687+
float saveChi2 = temporaryTrack.getChi2();
688+
temporaryTrack.resetCovariance();
689+
temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2);
690+
temporaryTrack.setChi2(0);
691+
fitSuccess = fitTrack(temporaryTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, o2::constants::math::VeryBig, 0, &linRef);
692+
if (!fitSuccess) {
693+
return 0;
694+
}
695+
temporaryTrack.getParamOut() = temporaryTrack.getParamIn();
696+
temporaryTrack.getParamIn() = saveInw;
697+
temporaryTrack.setChi2(saveChi2);
698+
}
715699

716-
if constexpr (decltype(Tag)::value == PassMode::OnePass::value) {
717-
tracks.push_back(temporaryTrack);
718-
} else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) {
719-
// nothing to do
720-
} else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) {
721-
tracks[offset] = temporaryTrack;
700+
if constexpr (decltype(Tag)::value == PassMode::OnePass::value) {
701+
trackChunk.push_back(temporaryTrack);
702+
} else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) {
703+
// nothing to do
704+
} else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) {
705+
trackChunk[offset] = temporaryTrack;
706+
} else {
707+
static_assert(false, "Unknown mode!");
708+
}
709+
return 1;
710+
};
711+
712+
const int nSeeds = static_cast<int>(finalSeeds.size());
713+
if (mTaskArena->max_concurrency() <= 1) {
714+
for (int iSeed{0}; iSeed < nSeeds; ++iSeed) {
715+
forSeed(PassMode::OnePass{}, iSeed);
716+
}
722717
} else {
723-
static_assert(false, "Unknown mode!");
724-
}
725-
return 1;
726-
};
718+
bounded_vector<int> perSeedCount(nSeeds + 1, 0, mMemoryPool.get());
719+
tbb::parallel_for(0, nSeeds, [&](const int iSeed) {
720+
perSeedCount[iSeed] = forSeed(PassMode::TwoPassCount{}, iSeed);
721+
});
722+
723+
std::exclusive_scan(perSeedCount.begin(), perSeedCount.end(), perSeedCount.begin(), 0);
724+
auto totalTracks{perSeedCount.back()};
725+
if (totalTracks == 0) {
726+
return;
727+
}
728+
trackChunk.resize(totalTracks);
727729

728-
const int nSeeds = static_cast<int>(trackSeeds.size());
729-
if (mTaskArena->max_concurrency() <= 1) {
730-
for (int iSeed{0}; iSeed < nSeeds; ++iSeed) {
731-
forSeed(PassMode::OnePass{}, iSeed);
730+
tbb::parallel_for(0, nSeeds, [&](const int iSeed) {
731+
if (perSeedCount[iSeed] == perSeedCount[iSeed + 1]) {
732+
return;
733+
}
734+
forSeed(PassMode::TwoPassInsert{}, iSeed, perSeedCount[iSeed]);
735+
});
732736
}
733-
} else {
734-
bounded_vector<int> perSeedCount(nSeeds + 1, 0, mMemoryPool.get());
735-
tbb::parallel_for(0, nSeeds, [&](const int iSeed) {
736-
perSeedCount[iSeed] = forSeed(PassMode::TwoPassCount{}, iSeed);
737-
});
737+
});
738+
if (!trackChunk.empty()) {
739+
tracks.reserve(tracks.size() + trackChunk.size());
740+
tracks.insert(tracks.end(), std::make_move_iterator(trackChunk.begin()), std::make_move_iterator(trackChunk.end()));
741+
}
742+
};
738743

739-
std::exclusive_scan(perSeedCount.begin(), perSeedCount.end(), perSeedCount.begin(), 0);
740-
auto totalTracks{perSeedCount.back()};
741-
if (totalTracks == 0) {
742-
return;
743-
}
744-
tracks.resize(totalTracks);
744+
for (int startLayer{mTrkParams[iteration].NeighboursPerRoad()}; startLayer >= startLevel - 1; --startLayer) {
745+
if ((mTrkParams[iteration].StartLayerMask & (1 << (startLayer + 2))) == 0) {
746+
continue;
747+
}
745748

746-
tbb::parallel_for(0, nSeeds, [&](const int iSeed) {
747-
if (perSeedCount[iSeed] == perSeedCount[iSeed + 1]) {
748-
return;
749+
bounded_vector<int> inputCellIds(mMemoryPool.get()), outputCellIds(mMemoryPool.get());
750+
bounded_vector<CellSeedN> inputCellSeeds(mMemoryPool.get()), outputCellSeeds(mMemoryPool.get());
751+
const bounded_vector<CellSeedN>* currentSeeds = &mTimeFrame->getCells()[startLayer];
752+
const bounded_vector<int>* currentIds = &inputCellIds;
753+
int layer = startLayer;
754+
int level = startLevel;
755+
756+
while (!currentSeeds->empty() && level > 1) {
757+
if (!mTrkParams[iteration].PNChunkSize) {
758+
processNeighbours(layer, level, *currentSeeds, *currentIds, outputCellSeeds, outputCellIds);
759+
if (level == 2) {
760+
fitSeeds(outputCellSeeds);
749761
}
750-
forSeed(PassMode::TwoPassInsert{}, iSeed, perSeedCount[iSeed]);
751-
});
762+
} else {
763+
for (int beginCell{0}; beginCell < static_cast<int>(currentSeeds->size()); beginCell += mTrkParams[iteration].PNChunkSize) {
764+
const int endCell = std::min(beginCell + mTrkParams[iteration].PNChunkSize, static_cast<int>(currentSeeds->size()));
765+
bounded_vector<CellSeedN> chunkSeeds(mMemoryPool.get());
766+
bounded_vector<int> chunkIds(mMemoryPool.get());
767+
processNeighbours(layer, level, *currentSeeds, *currentIds, chunkSeeds, chunkIds, beginCell, endCell);
768+
if (chunkSeeds.empty()) {
769+
continue;
770+
}
771+
if (level == 2) {
772+
fitSeeds(chunkSeeds);
773+
continue;
774+
}
775+
outputCellSeeds.reserve(outputCellSeeds.size() + chunkSeeds.size());
776+
outputCellIds.reserve(outputCellIds.size() + chunkIds.size());
777+
outputCellSeeds.insert(outputCellSeeds.end(), std::make_move_iterator(chunkSeeds.begin()), std::make_move_iterator(chunkSeeds.end()));
778+
outputCellIds.insert(outputCellIds.end(), std::make_move_iterator(chunkIds.begin()), std::make_move_iterator(chunkIds.end()));
779+
}
780+
}
781+
if (outputCellSeeds.empty() || level == 2) {
782+
break;
783+
}
784+
785+
--layer;
786+
--level;
787+
inputCellSeeds.swap(outputCellSeeds);
788+
inputCellIds.swap(outputCellIds);
789+
deepVectorClear(outputCellSeeds);
790+
deepVectorClear(outputCellIds);
791+
currentSeeds = &inputCellSeeds;
792+
currentIds = &inputCellIds;
752793
}
794+
}
753795

754-
deepVectorClear(trackSeeds);
796+
if (tracks.empty()) {
797+
continue;
798+
}
799+
800+
// sort by quality do to disambiguation
801+
mTaskArena->execute([&] {
755802
tbb::parallel_sort(tracks.begin(), tracks.end(), [](const auto& a, const auto& b) {
756803
return a.getChi2() < b.getChi2();
757804
});
758805
});
759806

807+
// create final tracks
760808
const float smallestROFHalf = mTimeFrame->getROFOverlapTableView().getClockLayer().mROFLength * 0.5f;
761809
for (auto& track : tracks) {
762810
int nShared = 0;

0 commit comments

Comments
 (0)