mirror of
https://github.com/PixarAnimationStudios/OpenSubdiv
synced 2025-01-17 03:30:06 +00:00
Faster, simpler stencil table construction.
This is a new implementation of the stencil table construction algorithm found in protoStencil.h. In local tests with production assets, the new algorithm is ~25% faster and significantly more stable, in terms of average performance In one asset test, generating stencils for level 10 adaptive refinement of BuzzLightyear was reduced from 18s to 13s.
This commit is contained in:
parent
330dd95e93
commit
909757ca9b
@ -37,12 +37,13 @@ set(SOURCE_FILES
|
||||
patchTableFactory.cpp
|
||||
ptexIndices.cpp
|
||||
stencilTableFactory.cpp
|
||||
stencilBuilder.cpp
|
||||
topologyRefiner.cpp
|
||||
topologyRefinerFactory.cpp
|
||||
)
|
||||
|
||||
set(PRIVATE_HEADER_FILES
|
||||
protoStencil.h
|
||||
stencilBuilder.h
|
||||
)
|
||||
|
||||
set(PUBLIC_HEADER_FILES
|
||||
|
@ -27,7 +27,6 @@
|
||||
|
||||
#include "../far/patchTableFactory.h"
|
||||
#include "../far/gregoryBasis.h"
|
||||
#include "../far/protoStencil.h"
|
||||
#include "../vtr/level.h"
|
||||
|
||||
namespace OpenSubdiv {
|
||||
|
@ -25,8 +25,9 @@
|
||||
#ifndef OPENSUBDIV3_FAR_GREGORY_BASIS_H
|
||||
#define OPENSUBDIV3_FAR_GREGORY_BASIS_H
|
||||
|
||||
#include "../far/protoStencil.h"
|
||||
#include "../vtr/level.h"
|
||||
#include "../far/types.h"
|
||||
#include "../far/stencilTable.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace OpenSubdiv {
|
||||
@ -59,7 +60,7 @@ public:
|
||||
template <class T, class U>
|
||||
void Evaluate(T const & controlValues, U values[20]) const {
|
||||
|
||||
Index const * indices = &_indices.at(0);
|
||||
Vtr::Index const * indices = &_indices.at(0);
|
||||
float const * weights = &_weights.at(0);
|
||||
|
||||
for (int i=0; i<20; ++i) {
|
||||
@ -85,7 +86,7 @@ public:
|
||||
_weights.reserve(RESERVED_ENTRY_SIZE);
|
||||
}
|
||||
|
||||
Point(Index idx, float weight = 1.0f) {
|
||||
Point(Vtr::Index idx, float weight = 1.0f) {
|
||||
_indices.reserve(RESERVED_ENTRY_SIZE);
|
||||
_weights.reserve(RESERVED_ENTRY_SIZE);
|
||||
_size = 1;
|
||||
@ -101,7 +102,7 @@ public:
|
||||
return _size;
|
||||
}
|
||||
|
||||
Index const * GetIndices() const {
|
||||
Vtr::Index const * GetIndices() const {
|
||||
return &_indices[0];
|
||||
}
|
||||
|
||||
@ -118,7 +119,7 @@ public:
|
||||
|
||||
Point & operator += (Point const & other) {
|
||||
for (int i=0; i<other._size; ++i) {
|
||||
Index idx = findIndex(other._indices[i]);
|
||||
Vtr::Index idx = findIndex(other._indices[i]);
|
||||
_weights[idx] += other._weights[i];
|
||||
}
|
||||
return *this;
|
||||
@ -126,7 +127,7 @@ public:
|
||||
|
||||
Point & operator -= (Point const & other) {
|
||||
for (int i=0; i<other._size; ++i) {
|
||||
Index idx = findIndex(other._indices[i]);
|
||||
Vtr::Index idx = findIndex(other._indices[i]);
|
||||
_weights[idx] -= other._weights[i];
|
||||
}
|
||||
return *this;
|
||||
@ -159,14 +160,14 @@ public:
|
||||
Point p(*this); return p-=other;
|
||||
}
|
||||
|
||||
void OffsetIndices(Index offset) {
|
||||
void OffsetIndices(Vtr::Index offset) {
|
||||
for (int i=0; i<_size; ++i) {
|
||||
_indices[i] += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Copy(int ** size, Index ** indices, float ** weights) const {
|
||||
memcpy(*indices, &_indices[0], _size*sizeof(Index));
|
||||
void Copy(int ** size, Vtr::Index ** indices, float ** weights) const {
|
||||
memcpy(*indices, &_indices[0], _size*sizeof(Vtr::Index));
|
||||
memcpy(*weights, &_weights[0], _size*sizeof(float));
|
||||
**size = _size;
|
||||
*indices += _size;
|
||||
@ -176,7 +177,7 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
int findIndex(Index idx) {
|
||||
int findIndex(Vtr::Index idx) {
|
||||
for (int i=0; i<_size; ++i) {
|
||||
if (_indices[i]==idx) {
|
||||
return i;
|
||||
@ -189,7 +190,7 @@ public:
|
||||
}
|
||||
|
||||
int _size;
|
||||
std::vector<Index> _indices;
|
||||
std::vector<Vtr::Index> _indices;
|
||||
std::vector<float> _weights;
|
||||
};
|
||||
|
||||
@ -201,11 +202,11 @@ public:
|
||||
//
|
||||
struct ProtoBasis {
|
||||
|
||||
ProtoBasis(Vtr::Level const & level, Index faceIndex, int fvarChannel=-1);
|
||||
ProtoBasis(Vtr::Level const & level, Vtr::Index faceIndex, int fvarChannel=-1);
|
||||
|
||||
int GetNumElements() const;
|
||||
|
||||
void Copy(int * sizes, Index * indices, float * weights) const;
|
||||
void Copy(int * sizes, Vtr::Index * indices, float * weights) const;
|
||||
void Copy(GregoryBasis* dest) const;
|
||||
|
||||
// Control Vertices based on :
|
||||
@ -247,7 +248,7 @@ private:
|
||||
|
||||
int _sizes[20];
|
||||
|
||||
std::vector<Index> _indices;
|
||||
std::vector<Vtr::Index> _indices;
|
||||
std::vector<float> _weights;
|
||||
};
|
||||
|
||||
|
@ -1,504 +0,0 @@
|
||||
//
|
||||
// Copyright 2013 Pixar
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "Apache License")
|
||||
// with the following modification; you may not use this file except in
|
||||
// compliance with the Apache License and the following modification to it:
|
||||
// Section 6. Trademarks. is deleted and replaced with:
|
||||
//
|
||||
// 6. Trademarks. This License does not grant permission to use the trade
|
||||
// names, trademarks, service marks, or product names of the Licensor
|
||||
// and its affiliates, except as required to comply with Section 4(c) of
|
||||
// the License and to reproduce the content of the NOTICE file.
|
||||
//
|
||||
// You may obtain a copy of the Apache License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the Apache License with the above modification is
|
||||
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the Apache License for the specific
|
||||
// language governing permissions and limitations under the Apache License.
|
||||
//
|
||||
|
||||
#ifndef OPENSUBDIV3_FAR_PROTOSTENCIL_H
|
||||
#define OPENSUBDIV3_FAR_PROTOSTENCIL_H
|
||||
|
||||
#include "../far/stencilTable.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace OpenSubdiv {
|
||||
namespace OPENSUBDIV_VERSION {
|
||||
|
||||
namespace Far {
|
||||
|
||||
//
|
||||
// Proto-stencil Pool Allocator classes
|
||||
//
|
||||
// Strategy: allocate up-front a data pool for supporting PROTOSTENCILS of a size
|
||||
// (maxsize) slightly above average. For the (rare) BIG_PROTOSTENCILS that
|
||||
// require more support vertices, switch to (slow) heap allocation.
|
||||
//
|
||||
template <typename PROTOSTENCIL, class BIG_PROTOSTENCIL>
|
||||
class Allocator {
|
||||
|
||||
public:
|
||||
|
||||
// Constructor
|
||||
Allocator(int maxSize, bool interpolateVarying=false) :
|
||||
_maxsize(maxSize), _interpolateVarying(interpolateVarying) { }
|
||||
|
||||
~Allocator() {
|
||||
clearBigStencils();
|
||||
}
|
||||
|
||||
// Returns the number of stencils in the allocator
|
||||
int GetNumStencils() const {
|
||||
return (int)_sizes.size();
|
||||
}
|
||||
|
||||
// Returns the total number of control vertices used by the all the stencils
|
||||
int GetNumVerticesTotal() const {
|
||||
int nverts=0;
|
||||
for (int i=0; i<GetNumStencils(); ++i) {
|
||||
nverts += _sizes[i];
|
||||
}
|
||||
return nverts;
|
||||
}
|
||||
|
||||
// Returns true if the pool allocator executes AddVaryingWithWeight
|
||||
// factorization
|
||||
bool GetInterpolateVarying() const {
|
||||
return _interpolateVarying;
|
||||
}
|
||||
|
||||
// Allocates storage for 'size' stencils with a fixed '_maxsize' supporting
|
||||
// basis of control-vertices
|
||||
void Resize(int numStencils) {
|
||||
clearBigStencils();
|
||||
int nelems = numStencils * _maxsize;
|
||||
_sizes.clear();
|
||||
_sizes.resize(numStencils);
|
||||
_indices.resize(nelems);
|
||||
_weights.resize(nelems);
|
||||
}
|
||||
|
||||
// Adds the contribution of a supporting vertex that was not yet
|
||||
// in the stencil
|
||||
void PushBackVertex(Index protoStencil, Index vert, float weight) {
|
||||
assert(weight!=0.0f);
|
||||
int & size = _sizes[protoStencil];
|
||||
Index idx = protoStencil*_maxsize;
|
||||
if (size < (_maxsize-1)) {
|
||||
idx += size;
|
||||
_indices[idx] = vert;
|
||||
_weights[idx] = weight;
|
||||
} else {
|
||||
BIG_PROTOSTENCIL * dst = 0;
|
||||
if (size==(_maxsize-1)) {
|
||||
dst = new BIG_PROTOSTENCIL(size, &_indices[idx], &_weights[idx]);
|
||||
assert(_bigStencils.find(protoStencil)==_bigStencils.end());
|
||||
_bigStencils[protoStencil] = dst;
|
||||
} else {
|
||||
assert(_bigStencils.find(protoStencil)!=_bigStencils.end());
|
||||
dst = _bigStencils[protoStencil];
|
||||
}
|
||||
dst->_indices.push_back(vert);
|
||||
dst->_weights.push_back(weight);
|
||||
}
|
||||
++size;
|
||||
}
|
||||
|
||||
// Returns the local index in 'stencil' of a given vertex index, or
|
||||
// INDEX_INVALID if the stencil does not contain this vertex
|
||||
int FindVertex(Index protoStencil, Index vert) {
|
||||
int size = _sizes[protoStencil];
|
||||
Index const * indices = GetIndices(protoStencil);
|
||||
for (int i=0; i<size; ++i) {
|
||||
if (indices[i]==vert) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return Vtr::INDEX_INVALID;
|
||||
}
|
||||
|
||||
// Returns true of the stencil does not fit in the pool allocator and
|
||||
// has been moved to the 'big' (slow) allocation pool
|
||||
bool IsBigStencil(Index protoStencil) const {
|
||||
assert(protoStencil<(int)_sizes.size());
|
||||
return _sizes[protoStencil]>=_maxsize;
|
||||
}
|
||||
|
||||
// Returns the size of a given proto-stencil
|
||||
int GetSize(Index protoStencil) const {
|
||||
assert(protoStencil<(int)_sizes.size());
|
||||
return _sizes[protoStencil];
|
||||
}
|
||||
|
||||
// Resolve memory pool and return a pointer to the indices of a given
|
||||
// proto-stencil
|
||||
Index * GetIndices(Index protoStencil) {
|
||||
if (not IsBigStencil(protoStencil)) {
|
||||
return &_indices[protoStencil*_maxsize];
|
||||
} else {
|
||||
assert(_bigStencils.find(protoStencil)!=_bigStencils.end());
|
||||
return &_bigStencils[protoStencil]->_indices[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve memory pool and return a pointer to the weights of a given
|
||||
// proto-stencil
|
||||
float * GetWeights(Index protoStencil) {
|
||||
if (not IsBigStencil(protoStencil)) {
|
||||
return &_weights[protoStencil*_maxsize];
|
||||
} else {
|
||||
assert(_bigStencils.find(protoStencil)!=_bigStencils.end());
|
||||
return &_bigStencils[protoStencil]->_weights[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the proto-stencil at a given index
|
||||
PROTOSTENCIL operator[] (Index protoStencil) {
|
||||
// If the allocator is empty, AddWithWeight() expects a coarse control
|
||||
// vertex instead of a stencil and we only need to pass the index
|
||||
return PROTOSTENCIL(protoStencil, this->GetNumStencils()>0 ? this : 0);
|
||||
}
|
||||
|
||||
// Returns the proto-stencil at a given index
|
||||
PROTOSTENCIL operator[] (Index protoStencil) const {
|
||||
// If the allocator is empty, AddWithWeight() expects a coarse control
|
||||
// vertex instead of a stencil and we only need to pass the index
|
||||
return PROTOSTENCIL(protoStencil, this->GetNumStencils()>0 ?
|
||||
const_cast<Allocator<PROTOSTENCIL, BIG_PROTOSTENCIL> *>(this) : 0);
|
||||
}
|
||||
|
||||
// Copy the proto-stencil out of the pool
|
||||
int CopyStencil(Index protoStencil,
|
||||
Index * indices, float * weights) {
|
||||
int size = GetSize(protoStencil);
|
||||
memcpy(indices, this->GetIndices(protoStencil), size*sizeof(Index));
|
||||
memcpy(weights, this->GetWeights(protoStencil), size*sizeof(float));
|
||||
return size;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// delete 'slow' memory pool
|
||||
void clearBigStencils() {
|
||||
typename BigStencilMap::iterator it;
|
||||
for (it=_bigStencils.begin(); it!=_bigStencils.end(); ++it) {
|
||||
delete it->second;
|
||||
}
|
||||
_bigStencils.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
int _maxsize; // max size of stencil that fits in the 'fast' pool
|
||||
|
||||
bool _interpolateVarying; // true for varying interpolation
|
||||
|
||||
std::vector<int> _sizes; // 'fast' memory pool
|
||||
std::vector<int> _indices;
|
||||
std::vector<float> _weights;
|
||||
|
||||
typedef std::map<int, BIG_PROTOSTENCIL *> BigStencilMap;
|
||||
BigStencilMap _bigStencils; // 'slow' memory pool
|
||||
};
|
||||
|
||||
//
|
||||
// Specialization of the Allocator for stencils with tangents that require
|
||||
// additional derivative weights.
|
||||
//
|
||||
template <typename PROTOSTENCIL, class BIG_PROTOSTENCIL>
|
||||
class LimitAllocator : public Allocator<PROTOSTENCIL, BIG_PROTOSTENCIL> {
|
||||
|
||||
public:
|
||||
|
||||
// Constructor
|
||||
LimitAllocator(int maxSize) :
|
||||
Allocator<PROTOSTENCIL, BIG_PROTOSTENCIL>(maxSize) { }
|
||||
|
||||
void Resize(int size) {
|
||||
Allocator<PROTOSTENCIL, BIG_PROTOSTENCIL>::Resize(size);
|
||||
int nelems = (int)this->_weights.size();
|
||||
_tan1Weights.resize(nelems);
|
||||
_tan2Weights.resize(nelems);
|
||||
}
|
||||
|
||||
void PushBackVertex(Index protoStencil,
|
||||
Index vert, float weight, float tan1Weight, float tan2Weight) {
|
||||
assert(weight!=0.0f or tan1Weight!=0.0f or tan2Weight!=0.0f);
|
||||
int & size = this->_sizes[protoStencil];
|
||||
Index idx = protoStencil*this->_maxsize;
|
||||
if (size < (this->_maxsize-1)) {
|
||||
idx += size;
|
||||
this->_indices[idx] = vert;
|
||||
this->_weights[idx] = weight;
|
||||
this->_tan1Weights[idx] = tan1Weight;
|
||||
this->_tan2Weights[idx] = tan2Weight;
|
||||
} else {
|
||||
BIG_PROTOSTENCIL * dst = 0;
|
||||
if (size==(this->_maxsize-1)) {
|
||||
dst = new BIG_PROTOSTENCIL(size,
|
||||
&this->_indices[idx], &this->_weights[idx],
|
||||
&this->_tan1Weights[idx], &this->_tan2Weights[idx]);
|
||||
assert(this->_bigStencils.find(protoStencil)==this->_bigStencils.end());
|
||||
this->_bigStencils[protoStencil] = dst;
|
||||
} else {
|
||||
assert(this->_bigStencils.find(protoStencil)!=this->_bigStencils.end());
|
||||
dst = this->_bigStencils[protoStencil];
|
||||
}
|
||||
dst->_indices.push_back(vert);
|
||||
dst->_weights.push_back(weight);
|
||||
dst->_tan1Weights.push_back(tan1Weight);
|
||||
dst->_tan2Weights.push_back(tan2Weight);
|
||||
}
|
||||
++size;
|
||||
}
|
||||
|
||||
float * GetTan1Weights(Index protoStencil) {
|
||||
if (not this->IsBigStencil(protoStencil)) {
|
||||
return &_tan1Weights[protoStencil*this->_maxsize];
|
||||
} else {
|
||||
assert(this->_bigStencils.find(protoStencil)!=this->_bigStencils.end());
|
||||
return &this->_bigStencils[protoStencil]->_tan1Weights[0];
|
||||
}
|
||||
}
|
||||
|
||||
float * GetTan2Weights(Index protoStencil) {
|
||||
if (not this->IsBigStencil(protoStencil)) {
|
||||
return &_tan2Weights[protoStencil*this->_maxsize];
|
||||
} else {
|
||||
assert(this->_bigStencils.find(protoStencil)!=this->_bigStencils.end());
|
||||
return &this->_bigStencils[protoStencil]->_tan2Weights[0];
|
||||
}
|
||||
}
|
||||
|
||||
PROTOSTENCIL operator[] (Index protoStencil) {
|
||||
assert(this->GetNumStencils()>0);
|
||||
return PROTOSTENCIL(protoStencil, this);
|
||||
}
|
||||
|
||||
void ClearStencil(Index protoStencil) {
|
||||
Allocator<PROTOSTENCIL, BIG_PROTOSTENCIL>::ClearStencil(protoStencil);
|
||||
memset(GetTan1Weights(protoStencil), 0, this->_sizes[protoStencil]*sizeof(float));
|
||||
memset(GetTan2Weights(protoStencil), 0, this->_sizes[protoStencil]*sizeof(float));
|
||||
}
|
||||
|
||||
int CopyLimitStencil(Index protoStencil,
|
||||
Index * indices, float * weights, float * tan1Weights, float * tan2Weights) {
|
||||
int size = Allocator<PROTOSTENCIL, BIG_PROTOSTENCIL>::CopyStencil(
|
||||
protoStencil, indices, weights);
|
||||
memcpy(tan1Weights, this->GetTan1Weights(protoStencil), size*sizeof(Index));
|
||||
memcpy(tan2Weights, this->GetTan2Weights(protoStencil), size*sizeof(float));
|
||||
return size;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<float> _tan1Weights,
|
||||
_tan2Weights;
|
||||
};
|
||||
|
||||
//
|
||||
// 'Big' Proto stencil classes
|
||||
//
|
||||
// When proto-stencils exceed _maxsize, fall back to dynamically allocated
|
||||
// "BigStencils" (with 'Limit' specialization to handle tangents)
|
||||
//
|
||||
struct BigStencil {
|
||||
|
||||
BigStencil(int size, Index const * indices,
|
||||
float const * weights) {
|
||||
_indices.reserve(size+5); _indices.resize(size);
|
||||
memcpy(&_indices.at(0), indices, size*sizeof(int));
|
||||
_weights.reserve(size+5); _weights.resize(size);
|
||||
memcpy(&_weights.at(0), weights, size*sizeof(float));
|
||||
}
|
||||
|
||||
std::vector<Index> _indices;
|
||||
std::vector<float> _weights;
|
||||
};
|
||||
struct BigLimitStencil : public BigStencil {
|
||||
|
||||
BigLimitStencil(int size, Index const * indices, float const * weights,
|
||||
float const * tan1Weights, float const * tan2Weights) :
|
||||
BigStencil(size, indices, weights) {
|
||||
|
||||
_tan1Weights.reserve(size+5); _tan1Weights.resize(size);
|
||||
memcpy(&_tan1Weights.at(0), tan1Weights, size*sizeof(float));
|
||||
_tan2Weights.reserve(size+5); _tan2Weights.resize(size);
|
||||
memcpy(&_tan2Weights.at(0), tan2Weights, size*sizeof(float));
|
||||
}
|
||||
|
||||
std::vector<float> _tan1Weights,
|
||||
_tan2Weights;
|
||||
};
|
||||
|
||||
//
|
||||
// ProtoStencils
|
||||
//
|
||||
// Proto-stencils are used to interpolate stencils from supporting vertices.
|
||||
// These stencils are backed by a pool allocator to allow for fast push-back
|
||||
// of contributing control-vertices weights & indices as they are discovered.
|
||||
//
|
||||
class ProtoStencil {
|
||||
|
||||
public:
|
||||
|
||||
ProtoStencil(Index id, Allocator<ProtoStencil, BigStencil> * alloc) :
|
||||
_id(id), _alloc(alloc) { }
|
||||
|
||||
void Clear() {
|
||||
// Clear() can only ever be called on an empty stencil: nothing to do
|
||||
assert(_alloc->GetSize(_id)==0);
|
||||
}
|
||||
|
||||
// Factorize from a proto-stencil allocator
|
||||
void AddWithWeight(ProtoStencil const & src, float weight) {
|
||||
|
||||
if(weight==0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (src._alloc) {
|
||||
// Stencil contribution
|
||||
int srcSize = src._alloc->GetSize(src._id);
|
||||
Index const * srcIndices = src._alloc->GetIndices(src._id);
|
||||
float const * srcWeights = src._alloc->GetWeights(src._id);
|
||||
|
||||
addWithWeight(weight, srcSize, srcIndices, srcWeights);
|
||||
} else {
|
||||
// Coarse vertex contribution
|
||||
Index n = _alloc->FindVertex(_id, src._id);
|
||||
if (Vtr::IndexIsValid(n)) {
|
||||
_alloc->GetWeights(_id)[n] += weight;
|
||||
assert(_alloc->GetWeights(_id)[n]>0.0f);
|
||||
} else {
|
||||
_alloc->PushBackVertex(_id, src._id, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Factorize from a finished stencil table
|
||||
void AddWithWeight(StencilTable const & table, Index idx, float weight) {
|
||||
|
||||
assert(idx<table.GetNumStencils());
|
||||
|
||||
if(weight==0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
int srcSize = table.GetSizes()[idx];
|
||||
Index offset = table.GetOffsets()[idx];
|
||||
Index const * srcIndices = &table.GetControlIndices()[offset];
|
||||
float const * srcWeights = &table.GetWeights()[offset];
|
||||
|
||||
addWithWeight(weight, srcSize, srcIndices, srcWeights);
|
||||
}
|
||||
|
||||
void AddVaryingWithWeight(ProtoStencil const & src, float weight) {
|
||||
if (_alloc->GetInterpolateVarying()) {
|
||||
AddWithWeight(src, weight);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void addWithWeight(float weight, int srcSize,
|
||||
Index const * srcIndices, float const * srcWeights) {
|
||||
|
||||
for (int i=0; i<srcSize; ++i) {
|
||||
|
||||
assert(srcWeights[i]!=0.0f);
|
||||
|
||||
float w = weight * srcWeights[i];
|
||||
if (w==0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Index vertIndex = srcIndices[i],
|
||||
n = _alloc->FindVertex(_id, vertIndex);
|
||||
if (Vtr::IndexIsValid(n)) {
|
||||
_alloc->GetWeights(_id)[n] += w;
|
||||
assert(_alloc->GetWeights(_id)[n]!=0.0f);
|
||||
} else {
|
||||
_alloc->PushBackVertex(_id, vertIndex, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Index _id;
|
||||
Allocator<ProtoStencil, BigStencil> * _alloc;
|
||||
};
|
||||
|
||||
typedef Allocator<ProtoStencil, BigStencil> StencilAllocator;
|
||||
|
||||
|
||||
//
|
||||
// ProtoLimitStencil
|
||||
//
|
||||
class ProtoLimitStencil {
|
||||
|
||||
public:
|
||||
|
||||
ProtoLimitStencil(Index id,
|
||||
LimitAllocator<ProtoLimitStencil, BigLimitStencil> * alloc) :
|
||||
_id(id), _alloc(alloc) { }
|
||||
|
||||
void Clear() {
|
||||
// Clear() can only ever be called on an empty stencil: nothing to do
|
||||
assert(_alloc->GetSize(_id)==0);
|
||||
}
|
||||
|
||||
void AddWithWeight(Stencil const & src,
|
||||
float weight, float tan1Weight, float tan2Weight) {
|
||||
|
||||
if(weight==0.0f and tan1Weight==0.0f and tan2Weight==0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
int srcSize = *src.GetSizePtr();
|
||||
Index const * srcIndices = src.GetVertexIndices();
|
||||
float const * srcWeights = src.GetWeights();
|
||||
|
||||
for (int i=0; i<srcSize; ++i) {
|
||||
|
||||
float w = srcWeights[i];
|
||||
if (w==0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Index vertIndex = srcIndices[i],
|
||||
n = _alloc->FindVertex(_id, vertIndex);
|
||||
if (Vtr::IndexIsValid(n)) {
|
||||
_alloc->GetWeights(_id)[n] += weight*w;
|
||||
_alloc->GetTan1Weights(_id)[n] += tan1Weight*w;
|
||||
_alloc->GetTan2Weights(_id)[n] += tan2Weight*w;
|
||||
|
||||
} else {
|
||||
_alloc->PushBackVertex(_id, vertIndex,
|
||||
weight*w, tan1Weight*w, tan2Weight*w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Index _id;
|
||||
LimitAllocator<ProtoLimitStencil, BigLimitStencil> * _alloc;
|
||||
};
|
||||
|
||||
typedef LimitAllocator<ProtoLimitStencil, BigLimitStencil> LimitStencilAllocator;
|
||||
|
||||
|
||||
|
||||
} // end namespace Far
|
||||
|
||||
} // end namespace OPENSUBDIV_VERSION
|
||||
} // end namespace OpenSubdiv
|
||||
|
||||
#endif // OPENSUBDIV3_FAR_PROTOSTENCIL_H
|
453
opensubdiv/far/stencilBuilder.cpp
Normal file
453
opensubdiv/far/stencilBuilder.cpp
Normal file
@ -0,0 +1,453 @@
|
||||
//
|
||||
// Copyright 2013 Pixar
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "Apache License")
|
||||
// with the following modification; you may not use this file except in
|
||||
// compliance with the Apache License and the following modification to it:
|
||||
// Section 6. Trademarks. is deleted and replaced with:
|
||||
//
|
||||
// 6. Trademarks. This License does not grant permission to use the trade
|
||||
// names, trademarks, service marks, or product names of the Licensor
|
||||
// and its affiliates, except as required to comply with Section 4(c) of
|
||||
// the License and to reproduce the content of the NOTICE file.
|
||||
//
|
||||
// You may obtain a copy of the Apache License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the Apache License with the above modification is
|
||||
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the Apache License for the specific
|
||||
// language governing permissions and limitations under the Apache License.
|
||||
//
|
||||
|
||||
#include "../far/stencilBuilder.h"
|
||||
#include "../far/topologyRefiner.h"
|
||||
|
||||
namespace OpenSubdiv {
|
||||
namespace OPENSUBDIV_VERSION {
|
||||
|
||||
namespace Far {
|
||||
namespace Internal {
|
||||
|
||||
struct PointDerivWeight {
|
||||
float p;
|
||||
float du;
|
||||
float dv;
|
||||
|
||||
PointDerivWeight()
|
||||
: p(0.0f), du(0.0f), dv(0.0f)
|
||||
{ }
|
||||
PointDerivWeight(float w)
|
||||
: p(w), du(w), dv(w)
|
||||
{ }
|
||||
PointDerivWeight(float w, float wDu, float wDv)
|
||||
: p(w), du(wDu), dv(wDv)
|
||||
{ }
|
||||
|
||||
friend PointDerivWeight operator*(PointDerivWeight lhs,
|
||||
PointDerivWeight const& rhs) {
|
||||
lhs.p *= rhs.p;
|
||||
lhs.du *= rhs.du;
|
||||
lhs.dv *= rhs.dv;
|
||||
return lhs;
|
||||
}
|
||||
PointDerivWeight& operator+=(PointDerivWeight const& rhs) {
|
||||
p += rhs.p;
|
||||
du += rhs.du;
|
||||
dv += rhs.dv;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// Stencil table constructor set.
|
||||
///
|
||||
class WeightTable {
|
||||
public:
|
||||
WeightTable(int coarseVerts,
|
||||
bool genCtrlVertStencils,
|
||||
bool compactWeights)
|
||||
: _size(0)
|
||||
, _lastOffset(0)
|
||||
, _coarseVertCount(coarseVerts)
|
||||
, _compactWeights(compactWeights)
|
||||
{
|
||||
// These numbers were chosen by profiling production assets at uniform
|
||||
// level 3.
|
||||
size_t n = std::max(coarseVerts,
|
||||
std::min(int(5*1024*1024),
|
||||
coarseVerts*2));
|
||||
_dests.reserve(n);
|
||||
_sources.reserve(n);
|
||||
_weights.reserve(n);
|
||||
|
||||
if (!genCtrlVertStencils)
|
||||
return;
|
||||
|
||||
// Generate trivial control vert stencils
|
||||
_sources.resize(coarseVerts);
|
||||
_weights.resize(coarseVerts);
|
||||
_dests.resize(coarseVerts);
|
||||
_indices.resize(coarseVerts);
|
||||
_sizes.resize(coarseVerts);
|
||||
|
||||
for (int i = 0; i < coarseVerts; i++) {
|
||||
_indices[i] = i;
|
||||
_sizes[i] = 1;
|
||||
_dests[i] = i;
|
||||
_sources[i] = i;
|
||||
_weights[i] = 1.0;
|
||||
}
|
||||
|
||||
_size = _sources.size();
|
||||
_lastOffset = _sources.size() - 1;
|
||||
}
|
||||
|
||||
template <class W, class WACCUM>
|
||||
void AddWithWeight(int src, int dest, W weight, WACCUM weights)
|
||||
{
|
||||
// Factorized stencils are expressed purely in terms of the control
|
||||
// mesh verts. Without this flattening, level_i's weights would point
|
||||
// to level_i-1, which would point to level_i-2, until the final level
|
||||
// points to the control verts.
|
||||
//
|
||||
// So here, we check if the incoming vert (src) is in the control mesh,
|
||||
// if it is, we can simply merge it without attempting to resolve it
|
||||
// first.
|
||||
if (src < _coarseVertCount) {
|
||||
merge(src, dest, weight, W(1.0), _lastOffset, _size, weights);
|
||||
return;
|
||||
}
|
||||
|
||||
// src is not in the control mesh, so resolve all contributing coarse
|
||||
// verts (src itself is made up of many control vert weights).
|
||||
//
|
||||
// Find the src stencil and number of contributing CVs.
|
||||
int len = _sizes[src];
|
||||
int start = _indices[src];
|
||||
|
||||
for (int i = start; i < start+len; i++) {
|
||||
// Invariant: by processing each level in order and each vertex in
|
||||
// dependent order, any src stencil vertex reference is guaranteed
|
||||
// to consist only of coarse verts: therefore resolving src verts
|
||||
// must yield verts in the coarse mesh.
|
||||
assert(_sources[i] < _coarseVertCount);
|
||||
|
||||
// Merge each of src's contributing verts into this stencil.
|
||||
merge(_sources[i], dest, weights.Get(i), weight,
|
||||
_lastOffset, _size, weights);
|
||||
}
|
||||
}
|
||||
|
||||
class PointDerivAccumulator {
|
||||
WeightTable* _tbl;
|
||||
public:
|
||||
PointDerivAccumulator(WeightTable* tbl) : _tbl(tbl)
|
||||
{ }
|
||||
void PushBack(PointDerivWeight weight) {
|
||||
_tbl->_weights.push_back(weight.p);
|
||||
_tbl->_duWeights.push_back(weight.du);
|
||||
_tbl->_dvWeights.push_back(weight.dv);
|
||||
}
|
||||
void Add(size_t i, PointDerivWeight weight) {
|
||||
_tbl->_weights[i] += weight.p;
|
||||
_tbl->_duWeights[i] += weight.du;
|
||||
_tbl->_dvWeights[i] += weight.dv;
|
||||
}
|
||||
PointDerivWeight Get(size_t index) {
|
||||
return PointDerivWeight(_tbl->_weights[index],
|
||||
_tbl->_duWeights[index],
|
||||
_tbl->_dvWeights[index]);
|
||||
}
|
||||
};
|
||||
PointDerivAccumulator GetPointDerivAccumulator() {
|
||||
return PointDerivAccumulator(this);
|
||||
};
|
||||
|
||||
class ScalarAccumulator {
|
||||
WeightTable* _tbl;
|
||||
public:
|
||||
ScalarAccumulator(WeightTable* tbl) : _tbl(tbl)
|
||||
{ }
|
||||
void PushBack(PointDerivWeight weight) {
|
||||
_tbl->_weights.push_back(weight.p);
|
||||
}
|
||||
void Add(size_t i, float w) {
|
||||
_tbl->_weights[i] += w;
|
||||
}
|
||||
float Get(size_t index) {
|
||||
return _tbl->_weights[index];
|
||||
}
|
||||
};
|
||||
ScalarAccumulator GetScalarAccumulator() {
|
||||
return ScalarAccumulator(this);
|
||||
};
|
||||
|
||||
std::vector<int> const&
|
||||
GetOffsets() const { return _indices; }
|
||||
|
||||
std::vector<int> const&
|
||||
GetSizes() const { return _sizes; }
|
||||
|
||||
std::vector<int> const&
|
||||
GetSources() const { return _sources; }
|
||||
|
||||
std::vector<float> const&
|
||||
GetWeights() const { return _weights; }
|
||||
|
||||
std::vector<float> const&
|
||||
GetDuWeights() const { return _duWeights; }
|
||||
|
||||
std::vector<float> const&
|
||||
GetDvWeights() const { return _dvWeights; }
|
||||
|
||||
private:
|
||||
|
||||
// Merge a vertex weight into the stencil table, if there is an existing
|
||||
// weight for a given source vertex it will be combined.
|
||||
//
|
||||
// PERFORMANCE: caution, this function is super hot.
|
||||
template <class W, class WACCUM>
|
||||
void merge(int src, int dst, W weight,
|
||||
// Delaying weight*factor multiplication hides memory latency of
|
||||
// accessing weight[i], yielding more stable performance.
|
||||
W weightFactor,
|
||||
// Similarly, passing offset & tableSize as params yields higher
|
||||
// performance than accessing the class members directly.
|
||||
int lastOffset, int tableSize, WACCUM weights)
|
||||
{
|
||||
// The lastOffset is the vertex we're currently processing, by
|
||||
// leveraging this we need not lookup the dest stencil size or offset.
|
||||
//
|
||||
// Additionally, if the client does not want the resulting verts
|
||||
// compacted, do not attempt to combine weights.
|
||||
if (_compactWeights and _dests[lastOffset] == dst) {
|
||||
|
||||
// tableSize is exactly _sources.size(), but using tableSize is
|
||||
// significantly faster.
|
||||
for (int i = lastOffset; i < tableSize; i++) {
|
||||
|
||||
// If we find an existing vertex that matches src, we need to
|
||||
// combine the weights to avoid duplicate entries for src.
|
||||
if (_sources[i] == src) {
|
||||
weights.Add(i, weight*weightFactor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We haven't seen src yet, insert it as a new vertex weight.
|
||||
add(src, dst, weight*weightFactor, weights);
|
||||
}
|
||||
|
||||
// Add a new vertex weight to the stencil table.
|
||||
template <class W, class WACCUM>
|
||||
void add(int src, int dst, W weight, WACCUM weights)
|
||||
{
|
||||
// The _dests array has num(weights) elements mapping each individual
|
||||
// element back to a specific stencil. The array is constructed in such
|
||||
// a way that the current stencil being built is always at the end of
|
||||
// the array, so if the dests array is empty or back() doesn't match
|
||||
// dst, then we just started building a new stencil.
|
||||
if (_dests.empty() or dst != _dests.back()) {
|
||||
// _indices and _sizes always have num(stencils) elements so that
|
||||
// stencils can be directly looked up by their index in these
|
||||
// arrays. So here, ensure that they are large enough to hold the
|
||||
// new stencil about to be built.
|
||||
if (dst+1 > (int)_indices.size()) {
|
||||
_indices.resize(dst+1);
|
||||
_sizes.resize(dst+1);
|
||||
}
|
||||
// Initialize the new stencil's meta-data (offset, size).
|
||||
_indices[dst] = _sources.size();
|
||||
_sizes[dst] = 0;
|
||||
// Keep track of where the current stencil begins, which lets us
|
||||
// avoid having to look it up later.
|
||||
_lastOffset = _sources.size();
|
||||
}
|
||||
// Cache the number of elements as an optimization, it's faster than
|
||||
// calling size() on any of the vectors.
|
||||
_size++;
|
||||
|
||||
// Increment the current stencil element size.
|
||||
_sizes[dst]++;
|
||||
// Track this element as belonging to the stencil "dst".
|
||||
_dests.push_back(dst);
|
||||
|
||||
// Store the actual stencil data.
|
||||
_sources.push_back(src);
|
||||
weights.PushBack(weight);
|
||||
}
|
||||
|
||||
// The following vectors are explicitly stored as non-interleaved elements
|
||||
// to reduce cache misses.
|
||||
|
||||
// Stencil to destination vertex map.
|
||||
std::vector<int> _dests;
|
||||
|
||||
// The actual stencil data.
|
||||
std::vector<int> _sources;
|
||||
std::vector<float> _weights;
|
||||
std::vector<float> _duWeights;
|
||||
std::vector<float> _dvWeights;
|
||||
|
||||
// Index data used to recover stencil-to-vertex mapping.
|
||||
std::vector<int> _indices;
|
||||
std::vector<int> _sizes;
|
||||
|
||||
// Acceleration members to avoid pointer chasing and reverse loops.
|
||||
int _size;
|
||||
int _lastOffset;
|
||||
int _coarseVertCount;
|
||||
bool _compactWeights;
|
||||
};
|
||||
|
||||
StencilBuilder::StencilBuilder(int coarseVertCount,
|
||||
bool isVarying,
|
||||
bool genCtrlVertStencils,
|
||||
bool compactWeights)
|
||||
: _weightTable(new WeightTable(coarseVertCount,
|
||||
genCtrlVertStencils,
|
||||
compactWeights))
|
||||
, _isVarying(isVarying)
|
||||
{
|
||||
}
|
||||
|
||||
StencilBuilder::~StencilBuilder()
|
||||
{
|
||||
delete _weightTable;
|
||||
}
|
||||
|
||||
size_t
|
||||
StencilBuilder::GetNumVerticesTotal() const
|
||||
{
|
||||
return _weightTable->GetWeights().size();
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
StencilBuilder::GetNumVertsInStencil(size_t stencilIndex) const
|
||||
{
|
||||
if (stencilIndex > _weightTable->GetSizes().size() - 1)
|
||||
return 0;
|
||||
|
||||
return (int)_weightTable->GetSizes()[stencilIndex];
|
||||
}
|
||||
|
||||
std::vector<int> const&
|
||||
StencilBuilder::GetStencilOffsets() const {
|
||||
return _weightTable->GetOffsets();
|
||||
}
|
||||
|
||||
std::vector<int> const&
|
||||
StencilBuilder::GetStencilSizes() const {
|
||||
return _weightTable->GetSizes();
|
||||
}
|
||||
|
||||
std::vector<int> const&
|
||||
StencilBuilder::GetStencilSources() const {
|
||||
return _weightTable->GetSources();
|
||||
}
|
||||
|
||||
std::vector<float> const&
|
||||
StencilBuilder::GetStencilWeights() const {
|
||||
return _weightTable->GetWeights();
|
||||
}
|
||||
|
||||
std::vector<float> const&
|
||||
StencilBuilder::GetStencilDuWeights() const {
|
||||
return _weightTable->GetDuWeights();
|
||||
}
|
||||
|
||||
std::vector<float> const&
|
||||
StencilBuilder::GetStencilDvWeights() const {
|
||||
return _weightTable->GetDvWeights();
|
||||
}
|
||||
|
||||
void
|
||||
StencilBuilder::Index::AddWithWeight(Index const & src, float weight)
|
||||
{
|
||||
if (_owner->_isVarying)
|
||||
return;
|
||||
// Ignore no-op weights.
|
||||
if (weight == 0)
|
||||
return;
|
||||
_owner->_weightTable->AddWithWeight(src._index, _index, weight,
|
||||
_owner->_weightTable->GetScalarAccumulator());
|
||||
}
|
||||
|
||||
void
|
||||
StencilBuilder::Index::AddWithWeight(Stencil const& src, float weight)
|
||||
{
|
||||
if(weight == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
int srcSize = *src.GetSizePtr();
|
||||
Vtr::Index const * srcIndices = src.GetVertexIndices();
|
||||
float const * srcWeights = src.GetWeights();
|
||||
|
||||
for (int i = 0; i < srcSize; ++i) {
|
||||
float w = srcWeights[i];
|
||||
if (w == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vtr::Index srcIndex = srcIndices[i];
|
||||
|
||||
float wgt = weight * w;
|
||||
_owner->_weightTable->AddWithWeight(srcIndex, _index, wgt,
|
||||
_owner->_weightTable->GetScalarAccumulator());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StencilBuilder::Index::AddWithWeight(Stencil const& src,
|
||||
float weight, float du, float dv)
|
||||
{
|
||||
if(weight == 0.0f and du == 0.0f and dv == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
int srcSize = *src.GetSizePtr();
|
||||
Vtr::Index const * srcIndices = src.GetVertexIndices();
|
||||
float const * srcWeights = src.GetWeights();
|
||||
|
||||
for (int i = 0; i < srcSize; ++i) {
|
||||
float w = srcWeights[i];
|
||||
if (w == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vtr::Index srcIndex = srcIndices[i];
|
||||
|
||||
PointDerivWeight wgt = PointDerivWeight(weight, du, dv) * w;
|
||||
_owner->_weightTable->AddWithWeight(srcIndex, _index, wgt,
|
||||
_owner->_weightTable->GetPointDerivAccumulator());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StencilBuilder::Index::AddVaryingWithWeight(Index const &src, float weight)
|
||||
{
|
||||
if (not _owner->_isVarying)
|
||||
return;
|
||||
// Ignore no-op weights.
|
||||
if (weight == 0)
|
||||
return;
|
||||
_owner->_weightTable->AddWithWeight(src._index, _index, weight,
|
||||
_owner->_weightTable->GetScalarAccumulator());
|
||||
}
|
||||
|
||||
void
|
||||
StencilBuilder::Index::AddFaceVaryingWithWeight(Index const &, float)
|
||||
{
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
} // end namespace Internal
|
||||
} // end namespace Far
|
||||
} // end namespace OPENSUBDIV_VERSION
|
||||
} // end namespace OpenSubdiv
|
||||
|
110
opensubdiv/far/stencilBuilder.h
Normal file
110
opensubdiv/far/stencilBuilder.h
Normal file
@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright 2013 Pixar
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "Apache License")
|
||||
// with the following modification; you may not use this file except in
|
||||
// compliance with the Apache License and the following modification to it:
|
||||
// Section 6. Trademarks. is deleted and replaced with:
|
||||
//
|
||||
// 6. Trademarks. This License does not grant permission to use the trade
|
||||
// names, trademarks, service marks, or product names of the Licensor
|
||||
// and its affiliates, except as required to comply with Section 4(c) of
|
||||
// the License and to reproduce the content of the NOTICE file.
|
||||
//
|
||||
// You may obtain a copy of the Apache License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the Apache License with the above modification is
|
||||
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the Apache License for the specific
|
||||
// language governing permissions and limitations under the Apache License.
|
||||
//
|
||||
|
||||
#ifndef OPENSUBDIV3_FAR_STENCILBUILDER_H
|
||||
#define OPENSUBDIV3_FAR_STENCILBUILDER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../version.h"
|
||||
#include "../far/stencilTable.h"
|
||||
|
||||
namespace OpenSubdiv {
|
||||
namespace OPENSUBDIV_VERSION {
|
||||
|
||||
namespace Far {
|
||||
namespace Internal {
|
||||
|
||||
class WeightTable;
|
||||
|
||||
class StencilBuilder {
|
||||
public:
|
||||
StencilBuilder(int coarseVertCount,
|
||||
bool isVarying=false,
|
||||
bool genCtrlVertStencils=true,
|
||||
bool compactWeights=true);
|
||||
~StencilBuilder();
|
||||
|
||||
// TODO: noncopyable.
|
||||
|
||||
size_t GetNumVerticesTotal() const;
|
||||
|
||||
int GetNumVertsInStencil(size_t stencilIndex) const;
|
||||
|
||||
// Mapping from stencil[i] to it's starting offset in the sources[] and weights[] arrays;
|
||||
std::vector<int> const& GetStencilOffsets() const;
|
||||
|
||||
// The number of contributing sources and weights in stencil[i]
|
||||
std::vector<int> const& GetStencilSizes() const;
|
||||
|
||||
// The absolute source vertex offsets.
|
||||
std::vector<int> const& GetStencilSources() const;
|
||||
|
||||
// The individual vertex weights, each weight is paired with one source.
|
||||
std::vector<float> const& GetStencilWeights() const;
|
||||
std::vector<float> const& GetStencilDuWeights() const;
|
||||
std::vector<float> const& GetStencilDvWeights() const;
|
||||
|
||||
// Vertex Facade.
|
||||
class Index {
|
||||
public:
|
||||
Index(StencilBuilder* owner, int index)
|
||||
: _owner(owner)
|
||||
, _index(index)
|
||||
{}
|
||||
|
||||
// Add with point/vertex weight only.
|
||||
void AddWithWeight(Index const & src, float weight);
|
||||
void AddWithWeight(Stencil const& src, float weight);
|
||||
|
||||
// Add with first derivative.
|
||||
void AddWithWeight(Stencil const& src,
|
||||
float weight, float du, float dv);
|
||||
|
||||
void AddVaryingWithWeight(Index const &, float);
|
||||
void AddFaceVaryingWithWeight(Index const &, float);
|
||||
|
||||
Index operator[](int index) const {
|
||||
return Index(_owner, index+_index);
|
||||
}
|
||||
|
||||
int GetOffset() const { return _index; }
|
||||
|
||||
void Clear() {/*nothing to do here*/}
|
||||
private:
|
||||
StencilBuilder* _owner;
|
||||
int _index;
|
||||
};
|
||||
|
||||
private:
|
||||
WeightTable* _weightTable;
|
||||
bool _isVarying;
|
||||
};
|
||||
|
||||
} // end namespace Internal
|
||||
} // end namespace Far
|
||||
} // end namespace OPENSUBDIV_VERSION
|
||||
} // end namespace OpenSubdiv
|
||||
|
||||
#endif // FAR_STENCILBUILDER_H
|
@ -30,13 +30,91 @@
|
||||
#include "../far/types.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenSubdiv {
|
||||
namespace OPENSUBDIV_VERSION {
|
||||
|
||||
namespace Far {
|
||||
|
||||
namespace {
|
||||
void
|
||||
copyStencilData(int numControlVerts,
|
||||
bool includeCoarseVerts,
|
||||
size_t firstOffset,
|
||||
std::vector<int> const* offsets,
|
||||
std::vector<int> * _offsets,
|
||||
std::vector<int> const* sizes,
|
||||
std::vector<int> * _sizes,
|
||||
std::vector<int> const* sources,
|
||||
std::vector<int> * _sources,
|
||||
std::vector<float> const* weights,
|
||||
std::vector<float> * _weights,
|
||||
std::vector<float> const* duWeights=NULL,
|
||||
std::vector<float> * _duWeights=NULL,
|
||||
std::vector<float> const* dvWeights=NULL,
|
||||
std::vector<float> * _dvWeights=NULL)
|
||||
{
|
||||
size_t off = includeCoarseVerts ? 0 : firstOffset;
|
||||
|
||||
_offsets->resize(offsets->size());
|
||||
_sizes->resize(sizes->size());
|
||||
_sources->resize(sources->size());
|
||||
_weights->resize(weights->size());
|
||||
if (_duWeights)
|
||||
_duWeights->resize(duWeights->size());
|
||||
if (_dvWeights)
|
||||
_dvWeights->resize(dvWeights->size());
|
||||
|
||||
// The stencils are probably not in order, so we must copy/sort them.
|
||||
// Note here that loop index 'i' represents stencil_i for vertex_i.
|
||||
int curOffset = 0;
|
||||
|
||||
size_t stencilCount = 0,
|
||||
weightCount = 0;
|
||||
|
||||
for (size_t i = off; i < offsets->size(); i++) {
|
||||
// Once we've copied out all the control verts, jump to the offset
|
||||
// where the actual stencils begin.
|
||||
if ((int)i == numControlVerts)
|
||||
i = firstOffset;
|
||||
|
||||
// Copy the stencil.
|
||||
int sz = (*sizes)[i];
|
||||
int off = (*offsets)[i];
|
||||
(*_offsets)[stencilCount] = curOffset;
|
||||
(*_sizes)[stencilCount] = sz;
|
||||
std::memcpy(&(*_sources)[curOffset],
|
||||
&(*sources)[off], sz*sizeof(int));
|
||||
std::memcpy(&(*_weights)[curOffset],
|
||||
&(*weights)[off], sz*sizeof(float));
|
||||
|
||||
if (_duWeights) {
|
||||
std::memcpy(&(*_duWeights)[curOffset],
|
||||
&(*duWeights)[off], sz*sizeof(float));
|
||||
}
|
||||
if (_dvWeights) {
|
||||
std::memcpy(&(*_dvWeights)[curOffset],
|
||||
&(*dvWeights)[off], sz*sizeof(float));
|
||||
}
|
||||
|
||||
curOffset += sz;
|
||||
stencilCount++;
|
||||
weightCount += sz;
|
||||
}
|
||||
|
||||
_offsets->resize(stencilCount);
|
||||
_sizes->resize(stencilCount);
|
||||
_sources->resize(weightCount);
|
||||
if (_duWeights)
|
||||
_duWeights->resize(weightCount);
|
||||
if (_dvWeights)
|
||||
_dvWeights->resize(weightCount);
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Vertex stencil descriptor
|
||||
///
|
||||
/// Allows access and manipulation of a single stencil in a StencilTable.
|
||||
@ -121,6 +199,23 @@ protected:
|
||||
/// control vertices.
|
||||
///
|
||||
class StencilTable {
|
||||
StencilTable(int numControlVerts,
|
||||
std::vector<int> const& offsets,
|
||||
std::vector<int> const& sizes,
|
||||
std::vector<int> const& sources,
|
||||
std::vector<float> const& weights,
|
||||
bool includeCoarseVerts,
|
||||
size_t firstOffset)
|
||||
: _numControlVertices(numControlVerts)
|
||||
{
|
||||
copyStencilData(numControlVerts,
|
||||
includeCoarseVerts,
|
||||
firstOffset,
|
||||
&offsets, &_offsets,
|
||||
&sizes, &_sizes,
|
||||
&sources, &_indices,
|
||||
&weights, &_weights);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@ -203,6 +298,9 @@ protected:
|
||||
|
||||
protected:
|
||||
StencilTable() : _numControlVertices(0) {}
|
||||
StencilTable(int numControlVerts)
|
||||
: _numControlVertices(numControlVerts)
|
||||
{ }
|
||||
|
||||
friend class StencilTableFactory;
|
||||
// XXX: temporarily, GregoryBasis class will go away.
|
||||
@ -281,6 +379,29 @@ class LimitStencilTable : public StencilTable {
|
||||
|
||||
public:
|
||||
|
||||
// TODO: share construction logic
|
||||
LimitStencilTable(int numControlVerts,
|
||||
std::vector<int> const& offsets,
|
||||
std::vector<int> const& sizes,
|
||||
std::vector<int> const& sources,
|
||||
std::vector<float> const& weights,
|
||||
std::vector<float> const& duWeights,
|
||||
std::vector<float> const& dvWeights,
|
||||
bool includeCoarseVerts,
|
||||
size_t firstOffset)
|
||||
: StencilTable(numControlVerts)
|
||||
{
|
||||
copyStencilData(numControlVerts,
|
||||
includeCoarseVerts,
|
||||
firstOffset,
|
||||
&offsets, &_offsets,
|
||||
&sizes, &_sizes,
|
||||
&sources, &_indices,
|
||||
&weights, &_weights,
|
||||
&duWeights, &_duWeights,
|
||||
&dvWeights, &_dvWeights);
|
||||
}
|
||||
|
||||
/// \brief Returns the 'u' derivative stencil interpolation weights
|
||||
std::vector<float> const & GetDuWeights() const {
|
||||
return _duWeights;
|
||||
|
@ -23,15 +23,16 @@
|
||||
//
|
||||
|
||||
#include "../far/stencilTableFactory.h"
|
||||
#include "../far/stencilBuilder.h"
|
||||
#include "../far/endCapGregoryBasisPatchFactory.h"
|
||||
#include "../far/patchTable.h"
|
||||
#include "../far/patchTableFactory.h"
|
||||
#include "../far/patchMap.h"
|
||||
#include "../far/protoStencil.h"
|
||||
#include "../far/topologyRefiner.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenSubdiv {
|
||||
namespace OPENSUBDIV_VERSION {
|
||||
@ -60,133 +61,52 @@ StencilTable const *
|
||||
StencilTableFactory::Create(TopologyRefiner const & refiner,
|
||||
Options options) {
|
||||
|
||||
StencilTable * result = new StencilTable;
|
||||
|
||||
// always initialize numControlVertices (useful for torus case)
|
||||
result->_numControlVertices = refiner.GetLevel(0).GetNumVertices();
|
||||
|
||||
int maxlevel = std::min(int(options.maxLevel), refiner.GetMaxLevel());
|
||||
if (maxlevel==0 and (not options.generateControlVerts)) {
|
||||
StencilTable * result = new StencilTable;
|
||||
result->_numControlVertices = refiner.GetLevel(0).GetNumVertices();
|
||||
return result;
|
||||
}
|
||||
|
||||
// 'maxsize' reflects the size of the default supporting basis factorized
|
||||
// in the stencils, with a little bit of head-room. Each subdivision scheme
|
||||
// has a set valence for 'regular' vertices, which drives the size of the
|
||||
// supporting basis of control-vertices. The goal is to reduce the number
|
||||
// of incidences where the pool allocator has to switch to dynamically
|
||||
// allocated heap memory when encountering extraordinary vertices that
|
||||
// require a larger supporting basis.
|
||||
//
|
||||
// The maxsize settings we use follow the assumption that the vast
|
||||
// majority of the vertices in a mesh are regular, and that the valence
|
||||
// of the extraordinary vertices is only higher by 1 edge.
|
||||
int maxsize = 0;
|
||||
bool interpolateVarying = false;
|
||||
switch (options.interpolationMode) {
|
||||
case INTERPOLATE_VERTEX: {
|
||||
Sdc::SchemeType type = refiner.GetSchemeType();
|
||||
switch (type) {
|
||||
case Sdc::SCHEME_BILINEAR : maxsize = 5; break;
|
||||
case Sdc::SCHEME_CATMARK : maxsize = 17; break;
|
||||
case Sdc::SCHEME_LOOP : maxsize = 10; break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
} break;
|
||||
case INTERPOLATE_VARYING: maxsize = 5; interpolateVarying=true; break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
std::vector<StencilAllocator> allocators(
|
||||
options.generateIntermediateLevels ? maxlevel+1 : 2,
|
||||
StencilAllocator(maxsize, interpolateVarying));
|
||||
|
||||
StencilAllocator * srcAlloc = &allocators[0],
|
||||
* dstAlloc = &allocators[1];
|
||||
bool interpolateVarying = options.interpolationMode==INTERPOLATE_VARYING;
|
||||
Internal::StencilBuilder builder(refiner.GetLevel(0).GetNumVertices(),
|
||||
interpolateVarying,
|
||||
/*genControlVerts*/ true,
|
||||
/*compactWeights*/ true);
|
||||
|
||||
//
|
||||
// Interpolate stencils for each refinement level using
|
||||
// TopologyRefiner::InterpolateLevel<>()
|
||||
//
|
||||
for (int level=1;level<=maxlevel; ++level) {
|
||||
|
||||
dstAlloc->Resize(refiner.GetLevel(level).GetNumVertices());
|
||||
|
||||
if (options.interpolationMode==INTERPOLATE_VERTEX) {
|
||||
refiner.Interpolate(level, *srcAlloc, *dstAlloc);
|
||||
Internal::StencilBuilder::Index srcIndex(&builder, 0);
|
||||
Internal::StencilBuilder::Index dstIndex(&builder,
|
||||
refiner.GetLevel(0).GetNumVertices());
|
||||
for (int level=1; level<=maxlevel; ++level) {
|
||||
if (not interpolateVarying) {
|
||||
refiner.Interpolate(level, srcIndex, dstIndex);
|
||||
} else {
|
||||
refiner.InterpolateVarying(level, *srcAlloc, *dstAlloc);
|
||||
refiner.InterpolateVarying(level, srcIndex, dstIndex);
|
||||
}
|
||||
|
||||
if (options.generateIntermediateLevels) {
|
||||
if (level<maxlevel) {
|
||||
if (options.factorizeIntermediateLevels) {
|
||||
srcAlloc = &allocators[level];
|
||||
} else {
|
||||
// if the stencils are dependent on the previous level of
|
||||
// subdivision, pass an empty allocator to treat all parent
|
||||
// vertices as control vertices
|
||||
assert(allocators[0].GetNumStencils()==0);
|
||||
}
|
||||
dstAlloc = &allocators[level+1];
|
||||
}
|
||||
} else {
|
||||
std::swap(srcAlloc, dstAlloc);
|
||||
}
|
||||
srcIndex = dstIndex;
|
||||
dstIndex = dstIndex[refiner.GetLevel(level).GetNumVertices()];
|
||||
}
|
||||
|
||||
// Copy stencils from the pool allocator into the table
|
||||
{
|
||||
// Add total number of stencils, weights & indices
|
||||
int nelems = 0, nstencils=0;
|
||||
if (options.generateIntermediateLevels) {
|
||||
for (int level=0; level<=maxlevel; ++level) {
|
||||
nstencils += allocators[level].GetNumStencils();
|
||||
nelems += allocators[level].GetNumVerticesTotal();
|
||||
}
|
||||
} else {
|
||||
nstencils = (int)srcAlloc->GetNumStencils();
|
||||
nelems = srcAlloc->GetNumVerticesTotal();
|
||||
}
|
||||
|
||||
// Allocate
|
||||
result->_numControlVertices = refiner.GetLevel(0).GetNumVertices();
|
||||
|
||||
if (options.generateControlVerts) {
|
||||
nstencils += result->_numControlVertices;
|
||||
nelems += result->_numControlVertices;
|
||||
}
|
||||
result->resize(nstencils, nelems);
|
||||
|
||||
// Copy stencils
|
||||
Stencil dst(&result->_sizes.at(0),
|
||||
&result->_indices.at(0), &result->_weights.at(0));
|
||||
|
||||
if (options.generateControlVerts) {
|
||||
generateControlVertStencils(result->_numControlVertices, dst);
|
||||
}
|
||||
|
||||
if (options.generateIntermediateLevels) {
|
||||
for (int level=1; level<=maxlevel; ++level) {
|
||||
for (int i=0; i<allocators[level].GetNumStencils(); ++i) {
|
||||
*dst._size = allocators[level].CopyStencil(i, dst._indices, dst._weights);
|
||||
dst.Next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<srcAlloc->GetNumStencils(); ++i) {
|
||||
*dst._size = srcAlloc->CopyStencil(i, dst._indices, dst._weights);
|
||||
dst.Next();
|
||||
}
|
||||
}
|
||||
|
||||
if (options.generateOffsets) {
|
||||
result->generateOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
size_t firstOffset = refiner.GetLevel(0).GetNumVertices();
|
||||
if (not options.generateIntermediateLevels)
|
||||
firstOffset = srcIndex.GetOffset();
|
||||
|
||||
// Copy stencils from the pool allocator into the tables
|
||||
// always initialize numControlVertices (useful for torus case)
|
||||
StencilTable * result =
|
||||
new StencilTable(refiner.GetLevel(0).GetNumVertices(),
|
||||
builder.GetStencilOffsets(),
|
||||
builder.GetStencilSizes(),
|
||||
builder.GetStencilSources(),
|
||||
builder.GetStencilWeights(),
|
||||
options.generateControlVerts,
|
||||
firstOffset);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -261,8 +181,8 @@ StencilTableFactory::Create(int numTables, StencilTable const ** tables) {
|
||||
StencilTable const *
|
||||
StencilTableFactory::AppendEndCapStencilTable(
|
||||
TopologyRefiner const &refiner,
|
||||
StencilTable const *baseStencilTable,
|
||||
StencilTable const *endCapStencilTable,
|
||||
StencilTable const * baseStencilTable,
|
||||
StencilTable const * endCapStencilTable,
|
||||
bool factorize) {
|
||||
|
||||
// factorize and append.
|
||||
@ -330,34 +250,37 @@ StencilTableFactory::AppendEndCapStencilTable(
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// copy all endcap stencils to proto stencils, and factorize if needed.
|
||||
int nEndCapStencils = endCapStencilTable->GetNumStencils();
|
||||
int nEndCapStencilsElements = 0;
|
||||
|
||||
// we exclude zero weight stencils. the resulting number of
|
||||
// stencils of endcap may be different from input.
|
||||
StencilAllocator allocator(16);
|
||||
allocator.Resize(nEndCapStencils);
|
||||
Internal::StencilBuilder builder(refiner.GetLevel(0).GetNumVertices(),
|
||||
/*isVarying*/ false,
|
||||
/*genControlVerts*/ false,
|
||||
/*compactWeights*/ factorize);
|
||||
Internal::StencilBuilder::Index origin(&builder, 0);
|
||||
Internal::StencilBuilder::Index dst = origin;
|
||||
Internal::StencilBuilder::Index srcIdx = origin;
|
||||
|
||||
for (int i = 0 ; i < nEndCapStencils; ++i) {
|
||||
Stencil src = endCapStencilTable->GetStencil(i);
|
||||
allocator[i].Clear();
|
||||
dst = origin[i];
|
||||
for (int j = 0; j < src.GetSize(); ++j) {
|
||||
Index index = src.GetVertexIndices()[j];
|
||||
float weight = src.GetWeights()[j];
|
||||
if (weight == 0.0) continue;
|
||||
|
||||
if (factorize) {
|
||||
allocator[i].AddWithWeight(*baseStencilTable,
|
||||
index + stencilsIndexOffset,
|
||||
weight);
|
||||
dst.AddWithWeight(
|
||||
baseStencilTable->GetStencil(index+stencilsIndexOffset),
|
||||
weight);
|
||||
} else {
|
||||
allocator.PushBackVertex(i,
|
||||
index + controlVertsIndexOffset,
|
||||
weight);
|
||||
srcIdx = origin[index + controlVertsIndexOffset];
|
||||
dst.AddWithWeight(srcIdx, weight);
|
||||
}
|
||||
}
|
||||
nEndCapStencilsElements += allocator.GetSize(i);
|
||||
nEndCapStencilsElements += builder.GetNumVertsInStencil(i);
|
||||
}
|
||||
|
||||
// create new stencil table
|
||||
@ -384,10 +307,11 @@ StencilTableFactory::AppendEndCapStencilTable(
|
||||
|
||||
// endcap stencils second
|
||||
for (int i = 0 ; i < nEndCapStencils; ++i) {
|
||||
int size = allocator.GetSize(i);
|
||||
int size = builder.GetNumVertsInStencil(i);
|
||||
int idx = builder.GetStencilOffsets()[i];
|
||||
for (int j = 0; j < size; ++j) {
|
||||
*indices++ = allocator.GetIndices(i)[j];
|
||||
*weights++ = allocator.GetWeights(i)[j];
|
||||
*indices++ = builder.GetStencilSources()[idx+j];
|
||||
*weights++ = builder.GetStencilWeights()[idx+j];
|
||||
}
|
||||
*sizes++ = size;
|
||||
}
|
||||
@ -416,7 +340,7 @@ LimitStencilTableFactory::Create(TopologyRefiner const & refiner,
|
||||
|
||||
bool uniform = refiner.IsUniform();
|
||||
|
||||
int maxlevel = refiner.GetMaxLevel(), maxsize=17;
|
||||
int maxlevel = refiner.GetMaxLevel();
|
||||
|
||||
StencilTable const * cvstencils = cvStencilsIn;
|
||||
if (not cvstencils) {
|
||||
@ -430,9 +354,9 @@ LimitStencilTableFactory::Create(TopologyRefiner const & refiner,
|
||||
options.generateControlVerts = true;
|
||||
options.generateOffsets = true;
|
||||
|
||||
// XXXX (manuelk) We could potentially save some mem-copies by not
|
||||
// instanciating the stencil table and work directly off the pool
|
||||
// allocators.
|
||||
// PERFORMANCE: We could potentially save some mem-copies by not
|
||||
// instanciating the stencil tables and work directly off the source
|
||||
// data.
|
||||
cvstencils = StencilTableFactory::Create(refiner, options);
|
||||
} else {
|
||||
// Sanity checks
|
||||
@ -489,35 +413,32 @@ LimitStencilTableFactory::Create(TopologyRefiner const & refiner,
|
||||
// Generate limit stencils for locations
|
||||
//
|
||||
|
||||
// Create a pool allocator to accumulate ProtoLimitStencils
|
||||
LimitStencilAllocator alloc(maxsize);
|
||||
alloc.Resize(numStencils);
|
||||
Internal::StencilBuilder builder(refiner.GetLevel(0).GetNumVertices(),
|
||||
/*isVarying*/ false,
|
||||
/*genControlVerts*/ false,
|
||||
/*compactWeights*/ true);
|
||||
Internal::StencilBuilder::Index origin(&builder, 0);
|
||||
Internal::StencilBuilder::Index dst = origin;
|
||||
|
||||
// XXXX (manuelk) we can make uniform (bilinear) stencils faster with a
|
||||
// dedicated code path that does not use PatchTable or the PatchMap
|
||||
float wP[20], wDs[20], wDt[20];
|
||||
|
||||
for (int i=0; i<(int)locationArrays.size(); ++i) {
|
||||
|
||||
for (size_t i=0; i<locationArrays.size(); ++i) {
|
||||
LocationArray const & array = locationArrays[i];
|
||||
|
||||
assert(array.ptexIdx>=0);
|
||||
|
||||
for (int j=0; j<array.numLocations; ++j) {
|
||||
|
||||
float s = array.s[j],
|
||||
t = array.t[j];
|
||||
|
||||
PatchMap::Handle const * handle = patchmap.FindPatch(array.ptexIdx, s, t);
|
||||
|
||||
PatchMap::Handle const * handle =
|
||||
patchmap.FindPatch(array.ptexIdx, s, t);
|
||||
if (handle) {
|
||||
|
||||
ConstIndexArray cvs = patchtable->GetPatchVertices(*handle);
|
||||
|
||||
patchtable->EvaluateBasis(*handle, s, t, wP, wDs, wDt);
|
||||
|
||||
StencilTable const & src = *cvstencils;
|
||||
ProtoLimitStencil dst = alloc[numLimitStencils];
|
||||
dst = origin[numLimitStencils];
|
||||
|
||||
dst.Clear();
|
||||
for (int k = 0; k < cvs.size(); ++k) {
|
||||
@ -540,30 +461,18 @@ LimitStencilTableFactory::Create(TopologyRefiner const & refiner,
|
||||
//
|
||||
// Copy the proto-stencils into the limit stencil table
|
||||
//
|
||||
LimitStencilTable * result = new LimitStencilTable;
|
||||
|
||||
int nelems = alloc.GetNumVerticesTotal();
|
||||
if (nelems>0) {
|
||||
|
||||
// Allocate
|
||||
result->resize(numLimitStencils, nelems);
|
||||
|
||||
// Copy stencils
|
||||
LimitStencil dst(&result->_sizes.at(0), &result->_indices.at(0),
|
||||
&result->_weights.at(0), &result->_duWeights.at(0),
|
||||
&result->_dvWeights.at(0));
|
||||
|
||||
for (int i = 0; i < numLimitStencils; ++i) {
|
||||
*dst._size = alloc.CopyLimitStencil(i, dst._indices, dst._weights,
|
||||
dst._duWeights, dst._dvWeights);
|
||||
dst.Next();
|
||||
}
|
||||
|
||||
// XXXX manuelk should offset creation be optional ?
|
||||
result->generateOffsets();
|
||||
}
|
||||
result->_numControlVertices = refiner.GetLevel(0).GetNumVertices();
|
||||
size_t firstOffset = refiner.GetLevel(0).GetNumVertices();
|
||||
|
||||
LimitStencilTable * result = new LimitStencilTable(
|
||||
refiner.GetLevel(0).GetNumVertices(),
|
||||
builder.GetStencilOffsets(),
|
||||
builder.GetStencilSizes(),
|
||||
builder.GetStencilSources(),
|
||||
builder.GetStencilWeights(),
|
||||
builder.GetStencilDuWeights(),
|
||||
builder.GetStencilDvWeights(),
|
||||
/*ctrlVerts*/false,
|
||||
firstOffset);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user