// // Copyright (C) Pixar. All rights reserved. // // This license governs use of the accompanying software. If you // use the software, you accept this license. If you do not accept // the license, do not use the software. // // 1. Definitions // The terms "reproduce," "reproduction," "derivative works," and // "distribution" have the same meaning here as under U.S. // copyright law. A "contribution" is the original software, or // any additions or changes to the software. // A "contributor" is any person or entity that distributes its // contribution under this license. // "Licensed patents" are a contributor's patent claims that read // directly on its contribution. // // 2. Grant of Rights // (A) Copyright Grant- Subject to the terms of this license, // including the license conditions and limitations in section 3, // each contributor grants you a non-exclusive, worldwide, // royalty-free copyright license to reproduce its contribution, // prepare derivative works of its contribution, and distribute // its contribution or any derivative works that you create. // (B) Patent Grant- Subject to the terms of this license, // including the license conditions and limitations in section 3, // each contributor grants you a non-exclusive, worldwide, // royalty-free license under its licensed patents to make, have // made, use, sell, offer for sale, import, and/or otherwise // dispose of its contribution in the software or derivative works // of the contribution in the software. // // 3. Conditions and Limitations // (A) No Trademark License- This license does not grant you // rights to use any contributor's name, logo, or trademarks. // (B) If you bring a patent claim against any contributor over // patents that you claim are infringed by the software, your // patent license from such contributor to the software ends // automatically. // (C) If you distribute any portion of the software, you must // retain all copyright, patent, trademark, and attribution // notices that are present in the software. // (D) If you distribute any portion of the software in source // code form, you may do so only under this license by including a // complete copy of this license with your distribution. If you // distribute any portion of the software in compiled or object // code form, you may only do so under a license that complies // with this license. // (E) The software is licensed "as-is." You bear the risk of // using it. The contributors give no express warranties, // guarantees or conditions. You may have additional consumer // rights under your local laws which this license cannot change. // To the extent permitted under your local laws, the contributors // exclude the implied warranties of merchantability, fitness for // a particular purpose and non-infringement. // #include "../osd/ptexTextureLoader.h" #include #include #include #include #include namespace OpenSubdiv { namespace OPENSUBDIV_VERSION { // block : atomic texture unit, points to the texels contained in a face // // |-----------------------| |-----------------------| // | (u,v) | | (u,v) | // | | | | // | | | | // | Block 0 | | Block 1 | // | | | | // | vres | + | vres | ... // | | | | // | | | | // | | | | // | | | | // | ures | | ures | // |-----------------------| |-----------------------| // struct OsdPtexTextureLoader::block { int idx; // PTex face index unsigned short u, v; // location in memory pages Ptex::Res current, // current resolution of the block native; // native resolution of the block // comparison operator : true when the current texel area of "b" is greater than "a" static bool currentAreaSort(block const * a, block const * b) { int darea = a->current.ulog2 * a->current.vlog2 - b->current.ulog2 * b->current.vlog2; if (darea==0) return a->current.ulog2 < b->current.ulog2; else return darea < 0; } // returns a "distance" metric from the native texel resolution int8_t distanceFromNative( ) const { int8_t udist = native.ulog2-current.ulog2, vdist = native.vlog2-current.vlog2; return udist * udist + vdist * vdist; } // desirability predicates for resolution scaling optimizations static bool downsizePredicate( block const * b0, block const * b1 ) { int8_t d0 = b0->distanceFromNative(), d1 = b1->distanceFromNative(); if (d0==d1) return (b0->current.ulog2 * b0->current.vlog2) < (b1->current.ulog2 * b1->current.vlog2); else return d0 < d1; } static bool upsizePredicate( block const * b0, block const * b1 ) { int8_t d0 = b0->distanceFromNative(), d1 = b1->distanceFromNative(); if (d0==d1) return (b0->current.ulog2 * b0->current.vlog2) < (b1->current.ulog2 * b1->current.vlog2); else return d0 > d1; } friend std::ostream & operator <<(std::ostream &s, block const & b); }; // page : a handle on a single page of the GL texture array that contains the // packed PTex texels. Pages populate "empty" slots with "blocks" of // texels. // Note : pages are square, because i said so... // // |--------------------------| |------------|-------------| // | | |............|.............| // | | |............|.............| // | | |............|.............| // | | |.... B 0 ...|.... B 1 ..../ // | | |............|.............| // | | |............|.............| // | | |............|.............| // | Empty Page | |------------|-------------| // | | packed => |..........................| // | | |..........................| // | | |..........................| // | | |.......... B 2 ...........| // | | |..........................| // | | |..........................| // | | |..........................| // |--------------------------| |--------------------------| // struct OsdPtexTextureLoader::page { //---------------------------------------------------------------- // slot : rectangular block of available texels in a page struct slot { unsigned short u, v, ures, vres; slot( unsigned short size ) : u(0), v(0), ures(size), vres(size) { } slot( unsigned short iu, unsigned short iv, unsigned short iures, unsigned short ivres ) : u(iu), v(iv), ures(iures), vres(ivres) { } // true if a block can fit in this slot bool fits( block const * b, int gutterWidth ) { return ( (b->current.u()+2*gutterWidth)<=ures ) && ((b->current.v()+2*gutterWidth)<=vres); } }; //---------------------------------------------------------------- typedef std::list blist; blist blocks; typedef std::list slist; slist slots; // construct a page with a single empty slot the size of the page page( unsigned short pagesize ) { slots.push_back( slot( pagesize) ); } // true if there is no empty texels in the page (ie. no slots left) bool isFull( ) const { return slots.size()==0; } // true when the block "b" is successfully added to this page : // // |--------------------------| |------------|-------------| // | | |............| | // | | |............| | // | | |.... B .....| Right Slot | // | | |............| | // | | |............| | // | | |------------|-------------| // | Original Slot | ==> | | // | | | | // | | | Bottom Slot | // | | | | // | | | | // |--------------------------| |--------------------------| // bool addBlock( block * b, int gutterWidth ) { for (slist::iterator i=slots.begin(); i!=slots.end(); ++i) { if (i->fits( b, gutterWidth )) { blocks.push_back( b ); int w = gutterWidth, w2 = 2*w; b->u=i->u + w; b->v=i->v + w; // add new slot to the right if (i->ures > (b->current.u()+w2)) { slots.push_front( slot( i->u+b->current.u()+w2, i->v, i->ures-b->current.u()-w2, b->current.v()+w2)); } // add new slot to the bottom if (i->vres > (b->current.v()+w2)) { slots.push_back( slot( i->u, i->v+b->current.v()+w2, i->ures, i->vres-b->current.v()-w2 )); } slots.erase( i ); return true; } } return false; } friend std::ostream & operator <<(std::ostream &s, const page & p); }; OsdPtexTextureLoader::OsdPtexTextureLoader( PtexTexture * p, int gutterWidth, int pageMargin) : _ptex(p), _indexBuffer( NULL ), _layoutBuffer( NULL ), _texelBuffer(NULL), _gutterWidth(gutterWidth), _pageMargin(pageMargin) { _bpp = p->numChannels() * Ptex::DataSize( p->dataType() ); _txn = 0; int nf = p->numFaces(); _blocks.clear(); _blocks.resize( nf ); for (int i=0; igetFaceInfo(i); _blocks[i].idx=i; _blocks[i].current=_blocks[i].native=f.res; _txn += f.res.u() * f.res.v(); } _txc = _txn; } OsdPtexTextureLoader::~OsdPtexTextureLoader() { ClearPages(); } const unsigned long int OsdPtexTextureLoader::GetNumBlocks( ) const { return (unsigned long int)_blocks.size(); } const unsigned long int OsdPtexTextureLoader::GetNumPages( ) const { return (unsigned long int)_pages.size(); } // attempt to re-size per-face resolutions to hit the uncompressed texel // memory use requirement void OsdPtexTextureLoader::OptimizeResolution( unsigned long int memrec ) { unsigned long int txrec = memrec / _bpp; if (txrec==_txc) return; else { unsigned long int txcur = _txc; if (_blocks.size()==0) return; std::vector blocks( _blocks.size() ); for (unsigned long int i=0; i0) && (txcur>txrec) ) { unsigned long int txsaved = txcur; // start stealing from largest to smallest down for (int i=(int)blocks.size()-1; i>=0; --i) { block * b = blocks[i]; // we have already hit rock bottom resolution... skip this block if (b->current.ulog2==0 || b->current.vlog2==0) continue; unsigned short ures = (1<<(unsigned)(b->current.ulog2-1)), vres = (1<<(unsigned)(b->current.vlog2-1)); int diff = b->current.size() - ures * vres; // we are about to overshoot the limit with our big blocks : // skip until we find something smaller if ( ((unsigned long int)diff>txcur) || ((txcur-diff)current.ulog2--; b->current.vlog2--; txcur-=diff; } // couldn't scavenge anymore even from smallest faces : time to bail out. if (txsaved==txcur) break; } _txc = txcur; } else { // increasing footprint -------------------------------------- // blocks that have already been resized heavily will be considered first std::sort(blocks.begin(), blocks.end(), block::upsizePredicate ); while ( (txcur < _txn) && (txcur < txrec) ) { unsigned long int txsaved = txcur; // start adding back to the largest faces first for (int i=0; i<(int)blocks.size(); ++i) { block * b = blocks[i]; // already at native resolution... nothing to be done if (b->current == b->native) continue; unsigned short ures = (1<<(unsigned)(b->current.ulog2+1)), vres = (1<<(unsigned)(b->current.vlog2+1)); int diff = ures * vres - b->current.size(); // we are about to overshoot the limit with our big blocks : // skip until we find something smaller if ( (txcur + diff) > txrec ) continue; b->current.ulog2++; b->current.vlog2++; txcur+=diff; } // couldn't scavenge anymore even from smallest faces : time to bail out. if (txsaved==txcur) break; } _txc = txcur; } } } // greedy packing of blocks into pages void OsdPtexTextureLoader::OptimizePacking( int maxnumpages ) { if (_blocks.size()==0) return; // generate a vector of pointers to the blocks ------------------- std::vector blocks( _blocks.size() ); for (unsigned long int i=0; icurrent.u()); _pagesize = std::max(_pagesize, (unsigned short)blocks[i]->current.v()); } // note: at least 2*GUTTER_WIDTH of margin required for each page to fit _pagesize += (unsigned short)GetPageMargin(); // grow the pagesize to make sure the optimization will not exceed the maximum // number of pages allowed for (int npages=_txc/(_pagesize*_pagesize); npages>maxnumpages; _pagesize<<=1) npages = _txc/(_pagesize*_pagesize ); ClearPages( ); // save some memory allocation time : guess the number of pages from the // number of texels _pages.reserve( _txc / (_pagesize*_pagesize) + 1 ); // pack blocks into slots ---------------------------------------- for (unsigned long int i=0, firstslot=0; i<_blocks.size(); ++i ) { block * b = blocks[i]; // traverse existing pages for a suitable slot --------------- bool added=false; for( unsigned long int p=firstslot; p<_pages.size(); ++p ) if( (added=_pages[p]->addBlock( b, GetGutterWidth() )) == true ) { break; } // if none was found : start new page if( !added ) { page * p = new page( _pagesize ); p->addBlock(b, GetGutterWidth()); _pages.push_back( p ); } // adjust the page flag to the first page with open slots if( (_pages.size()>(firstslot+1)) && (_pages[firstslot+1]->isFull()) ) ++firstslot; } } // resample border texels for guttering // static int resampleBorder(PtexTexture * ptex, int face, int edgeId, unsigned char *result, int dstLength, int bpp, float srcStart=0.0f, float srcEnd=1.0f) { const Ptex::FaceInfo & pf = ptex->getFaceInfo(face); PtexFaceData * data = ptex->getData(face); int edgeLength = (edgeId==0||edgeId==2) ? pf.res.u() : pf.res.v(); int srcOffset = (int)(srcStart*edgeLength); int srcLength = (int)((srcEnd-srcStart)*edgeLength); // if dstLength < 0, returns as original resolution without scaling if (dstLength < 0) dstLength = srcLength; unsigned char *border = new unsigned char[bpp*srcLength]; // order of the result will be flipped to match adjacent pixel order for(int i=0;igetPixel(u, v, &border[i*bpp]); } // nearest resample to fit dstLength for(int i=0;igetFaceInfo(face); // copy adjacent borders int adjface = fi.adjface(edge); if(adjface != -1) { int ae = fi.adjedge(edge); if (!fi.isSubface() && ptex->getFaceInfo(adjface).isSubface()) { /* nonsubface -> subface (1:0.5) see http://ptex.us/adjdata.html for more detail +------------------+ | face | +--------edge------+ | adj face | | +----------+-------+ */ resampleBorder(ptex, adjface, ae, border, length/2, bpp); const Ptex::FaceInfo &sfi1 = ptex->getFaceInfo(adjface); adjface = sfi1.adjface((ae+3)%4); ae = (sfi1.adjedge((ae+3)%4)+3)%4; resampleBorder(ptex, adjface, ae, border+(length/2*bpp), length/2, bpp); } else if (fi.isSubface() && !ptex->getFaceInfo(adjface).isSubface()) { /* subface -> nonsubface (0.5:1). two possible configuration case 1 case 2 +----------+----------+ +----------+----------+--------+ | face | B | | | face | B | +---edge---+----------+ +----------+--edge----+--------+ |0.0 0.5 1.0| |0.0 0.5 1.0| | adj face | | adj face | +---------------------+ +---------------------+ */ int Bf = fi.adjface((edge+1)%4); int Be = fi.adjedge((edge+1)%4); int f = ptex->getFaceInfo(Bf).adjface((Be+1)%4); int e = ptex->getFaceInfo(Bf).adjedge((Be+1)%4); if(f == adjface && e == ae) // case 1 resampleBorder(ptex, adjface, ae, border, length, bpp, 0.0, 0.5); else // case 2 resampleBorder(ptex, adjface, ae, border, length, bpp, 0.5, 1.0); } else { /* ordinary case (1:1 match) +------------------+ | face | +--------edge------+ | adj face | +----------+-------+ */ resampleBorder(ptex, adjface, ae, border, length, bpp); } } else { /* border edge. duplicate itself +-----------------+ | face | +-------edge------+ */ resampleBorder(ptex, face, edge, border, length, bpp); flipBuffer(border, length, bpp); } } // get corner pixel by traversing all adjacent faces around vertex // static bool getCornerPixel(PtexTexture *ptex, float *resultPixel, int numchannels, int face, int edge, int bpp, unsigned char *lineBuffer) { const Ptex::FaceInfo &fi = ptex->getFaceInfo(face); /* see http://ptex.us/adjdata.html Figure 2 for the reason of conditions edge==1 and 3 */ if (fi.isSubface() && edge == 3) { /* in T-vertex case, this function sets 'D' pixel value to *resultPixel and returns false gutter line | +------+-------+ | | | | D|C |<-- gutter line | *-------+ | B|A [2] | | |[3] [1]| | | [0] | +------+-------+ */ int adjface = fi.adjface(edge); if (adjface != -1 and !ptex->getFaceInfo(adjface).isSubface()) { int length = resampleBorder(ptex, adjface, fi.adjedge(edge), lineBuffer, /*dstLength=*/-1, bpp, 0.0f, 1.0f); /* then lineBuffer contains |-------DB-------| 0 ^ length-1 length/2-1 */ Ptex::ConvertToFloat(resultPixel, lineBuffer + bpp*(length/2-1), ptex->dataType(), numchannels); return true; } } if (fi.isSubface() && edge == 1) { /* gutter line | +------+-------+ | | [3] | | |[0] [2]| | B|A [1] | | *-------+ | D|C |<-- gutter line | | | +------+-------+ note: here we're focusing on vertex A which corresponds to the edge 1, but the edge 0 is an adjacent edge to get D pixel. */ int adjface = fi.adjface(0); if (adjface != -1 and !ptex->getFaceInfo(adjface).isSubface()) { int length = resampleBorder(ptex, adjface, fi.adjedge(0), lineBuffer, /*dstLength=*/-1, bpp, 0.0f, 1.0f); /* then lineBuffer contains |-------BD-------| 0 ^ length-1 length/2 */ Ptex::ConvertToFloat(resultPixel, lineBuffer + bpp*(length/2), ptex->dataType(), numchannels); return true; } } int currentFace = face; int currentEdge = edge; int uv[4][2] = {{0,0}, {1,0}, {1,1}, {0,1}}; float *pixel = (float*)alloca(sizeof(float)*numchannels); float *accumPixel = (float*)alloca(sizeof(float)*numchannels); // clear accum pixel memset(accumPixel, 0, sizeof(float)*numchannels); bool clockWise = true; int valence = 0; do { valence++; Ptex::FaceInfo info = ptex->getFaceInfo(currentFace); ptex->getPixel(currentFace, uv[currentEdge][0] * (info.res.u()-1), uv[currentEdge][1] * (info.res.v()-1), pixel, 0, numchannels); for (int j = 0; j < numchannels; ++j) { accumPixel[j] += pixel[j]; if (valence == 3) { resultPixel[j] = pixel[j]; } } // next face if (clockWise) { currentFace = info.adjface(currentEdge); currentEdge = info.adjedge(currentEdge); currentEdge = (currentEdge+1)%4; } else { currentFace = info.adjface((currentEdge+3)%4); currentEdge = info.adjedge((currentEdge+3)%4); } if (currentFace == -1) { // border case. if (clockWise) { // reset position and restart counter clock wise Ptex::FaceInfo sinfo = ptex->getFaceInfo(face); currentFace = sinfo.adjface((edge+3)%4); currentEdge = sinfo.adjedge((edge+3)%4); clockWise = false; } else { // end break; } } } while(currentFace != face); if (valence == 4) { return true; } // non-4 valence. let's average and return false; for (int j = 0; j < numchannels; ++j) { resultPixel[j] = accumPixel[j]/valence; } return false; } // sample neighbor pixels and populate around blocks static void guttering(PtexTexture *_ptex, OsdPtexTextureLoader::block *b, unsigned char *pptr, int bpp, int pagesize, int stride, int gwidth) { unsigned char * lineBuffer = new unsigned char[pagesize * bpp]; for(int w=0; wcurrent.u() : b->current.v(); // XXX: for now, sample same edge regardless of gutter depth sampleNeighbor(_ptex, lineBuffer, b->idx, edge, len, bpp); unsigned char *s = lineBuffer, *d; for(int j=0;jv-1-w) + bpp*(b->u+j); break; case Ptex::e_right: d += stride*(b->v+j) + bpp*(b->u+b->current.u()+w); break; case Ptex::e_top: d += stride*(b->v+b->current.v()+w) + bpp*(b->u+len-j-1); break; case Ptex::e_left: d += stride*(b->v+len-j-1) + bpp*(b->u-1-w); break; } for(int k=0; knumChannels(); float *accumPixel = new float[numchannels]; int uv[4][2] = {{-1,-1}, {1,-1}, {1,1}, {-1,1}}; for (int edge=0; edge<4; edge++) { int du = (b->u+gwidth*uv[edge][0]); int dv = (b->v+gwidth*uv[edge][1]); /* There are 3 cases when filling a corner pixel on gutter. case 1: Regular 4 valence We already have correct 'B' and 'C' pixels by edge resampling above. so here only one more pixel 'D' is needed, and it will be placed on the gutter corner. +-----+-----+ | | |<-current | B|A | +-----*-----+ | D|C | | | | +-----+-----+ case 2: T-vertex case (note that this doesn't mean 3 valence) If the current face comes from non-quad root face, there could be a T-vertex on its corner. Just like case 1, need to fill border corner with pixel 'D'. +-----+-----+ | | |<-current | B|A | | *-----+ | D|C | | | | +-----+-----+ case 3: Other than 4 valence case (everything else, including boundary) Since guttering pixels are placed on the border of each ptex faces, It's not possible to store more than 4 pixels at a coner for a reasonable interpolation. In this case, we need to average all corner pixels and overwrite with an averaged value, so that every face vertex picks the same value. +---+---+ | | |<-current | B|A | +---*---| | D/E\C | | / \ | |/ \| +-------+ */ if (getCornerPixel(_ptex, accumPixel, numchannels, b->idx, edge, bpp, lineBuffer)) { // case 1 and case 2 if (edge==1||edge==2) du += b->current.u()-gwidth; if (edge==2||edge==3) dv += b->current.v()-gwidth; for (int u=0; udataType(), numchannels); } } } else { // case 3 if (edge==1||edge==2) du += b->current.u()-gwidth-1; if (edge==2||edge==3) dv += b->current.v()-gwidth-1; // set accumPixel to 4 corners // .. over (gwidth+1)x(gwidth+1) pixels for each corner for (int u=0; u<=gwidth; ++u) { for (int v=0; v<=gwidth; ++v) { unsigned char *d = pptr + (dv+u)*stride + (du+v)*bpp; Ptex::ConvertFromFloat(d, accumPixel, _ptex->dataType(), numchannels); } } } } delete[] lineBuffer; delete[] accumPixel; } // prepares the data for the texture samplers used by the GLSL tables to render // PTex texels bool OsdPtexTextureLoader::GenerateBuffers( ) { if (_pages.size()==0) return false; // populate the page index lookup texture ------------------------ _indexBuffer = new unsigned int[ _blocks.size() ]; for (unsigned long int i=0; i<_pages.size(); ++i) { page * p = _pages[i]; for (page::blist::iterator j=p->blocks.begin(); j!=p->blocks.end(); ++j) _indexBuffer[ (*j)->idx ] = i; } // populate the layout lookup texture ---------------------------- float * lptr = _layoutBuffer = new float[ 4 * _blocks.size() ]; for (unsigned long int i=0; i<_blocks.size(); ++ i) { // normalize coordinates by pagesize resolution ! *lptr++ = (float) _blocks[i].u / (float) _pagesize; *lptr++ = (float) _blocks[i].v / (float) _pagesize; *lptr++ = (float) _blocks[i].current.u() / (float) _pagesize; *lptr++ = (float) _blocks[i].current.v() / (float) _pagesize; } // populate the texels ------------------------------------------- int stride = _bpp * _pagesize, pagestride = stride * _pagesize; unsigned char * pptr = _texelBuffer = new unsigned char[ pagestride * _pages.size() ]; for (unsigned long int i=0; i<_pages.size(); i++) { page * p = _pages[i]; for (page::blist::iterator b=p->blocks.begin(); b!=p->blocks.end(); ++b) { _ptex->getData( (*b)->idx, pptr + stride*(*b)->v + _bpp*(*b)->u, stride, (*b)->current ); if(GetGutterWidth() > 0) guttering(_ptex, *b, pptr, _bpp, _pagesize, stride, GetGutterWidth()); } pptr += pagestride; } return true; } void OsdPtexTextureLoader::ClearBuffers( ) { delete [] _indexBuffer; delete [] _layoutBuffer; delete [] _texelBuffer; } // returns a ratio of texels wasted in the final GPU texture : anything under 5% // is pretty good compared to our previous solution... float OsdPtexTextureLoader::EvaluateWaste( ) const { unsigned long int wasted=0; for( unsigned long int i=0; i<_pages.size(); i++ ) { page * p = _pages[i]; for( page::slist::iterator s=p->slots.begin(); s!=p->slots.end(); ++s ) wasted += s->ures * s->vres; } return (float)((double)wasted/(double)_txc); } void OsdPtexTextureLoader::ClearPages( ) { for( unsigned long int i=0; i<_pages.size(); i++ ) delete _pages[i]; _pages.clear(); } void OsdPtexTextureLoader::PrintBlocks() const { for( unsigned long int i=0; i<_blocks.size(); ++i ) std::cout<<_blocks[i]<u<<" "<v<<" "<ures<<" "<vres<<"} "; s<<" }\n"; s<<" blocks {"; for (OsdPtexTextureLoader::page::blist::const_iterator i=p.blocks.begin(); i!=p.blocks.end(); ++i) s<<" "<< **i; s<<" }\n"; s<<"}"; return s; } } // end namespace OPENSUBDIV_VERSION } // end namespace OpenSubdiv