mirror of
https://github.com/PixarAnimationStudios/OpenSubdiv
synced 2024-11-23 12:10:08 +00:00
3ae50d1c50
New text: 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.
937 lines
33 KiB
C++
937 lines
33 KiB
C++
//
|
|
// 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 "../osd/error.h"
|
|
#include "../osd/ptexTextureLoader.h"
|
|
|
|
#include <Ptexture.h>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <string.h>
|
|
#include <list>
|
|
|
|
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<block *> blist;
|
|
blist blocks;
|
|
|
|
typedef std::list<slot> 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; i<nf; ++i) {
|
|
const Ptex::FaceInfo & f = p->getFaceInfo(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();
|
|
}
|
|
|
|
unsigned long int
|
|
OsdPtexTextureLoader::GetNumBlocks( ) const {
|
|
return (unsigned long int)_blocks.size();
|
|
}
|
|
|
|
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<block *> blocks( _blocks.size() );
|
|
for (unsigned long int i=0; i<blocks.size(); ++i)
|
|
blocks[i] = &(_blocks[i]);
|
|
|
|
// reducing footprint ----------------------------------------
|
|
if (txrec < _txc) {
|
|
|
|
// blocks that have already been resized heavily will be considered last
|
|
std::sort(blocks.begin(), blocks.end(), block::downsizePredicate );
|
|
|
|
while ( (txcur>0) && (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)<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;
|
|
} 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<block *> blocks( _blocks.size() );
|
|
for (unsigned long int i=0; i<blocks.size(); ++i)
|
|
blocks[i] = &(_blocks[i]);
|
|
|
|
// intro-sort blocks from largest to smallest (helps minimizing waste with
|
|
// greedy packing)
|
|
std::sort(blocks.rbegin(), blocks.rend(), block::currentAreaSort );
|
|
|
|
// compute page size ---------------------------------------------
|
|
// page size is set to the largest edge of the largest block : this is the
|
|
// smallest possible page size, which should minimize the texels wasted on
|
|
// the "last page" when the smallest blocks are being packed.
|
|
_pagesize = 0;
|
|
// also, find the max native edge length which will be used to allocate temporary
|
|
// buffers of guttering
|
|
for (unsigned long int i=0; i<blocks.size(); ++i) {
|
|
_pagesize = std::max(_pagesize, (unsigned short)blocks[i]->current.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;i<srcLength; ++i) {
|
|
int u = 0, v = 0;
|
|
if(edgeId==Ptex::e_bottom) {
|
|
u = edgeLength-1-(i+srcOffset);
|
|
v = 0;
|
|
} else if(edgeId==Ptex::e_right) {
|
|
u = pf.res.u()-1;
|
|
v = edgeLength-1-(i+srcOffset);
|
|
} else if(edgeId==Ptex::e_top) {
|
|
u = i+srcOffset;
|
|
v = pf.res.v()-1;
|
|
} else if(edgeId==Ptex::e_left) {
|
|
u = 0;
|
|
v = i+srcOffset;
|
|
}
|
|
data->getPixel(u, v, &border[i*bpp]);
|
|
}
|
|
|
|
// nearest resample to fit dstLength
|
|
for(int i=0;i<dstLength;++i) {
|
|
for(int j=0; j<bpp; j++) {
|
|
result[i*bpp+j] = border[(i*srcLength/dstLength)*bpp+j];
|
|
}
|
|
}
|
|
|
|
delete[] border;
|
|
|
|
return srcLength;
|
|
}
|
|
|
|
// flip order of pixel buffer
|
|
static void
|
|
flipBuffer(unsigned char *buffer, int length, int bpp)
|
|
{
|
|
for(int i=0; i<length/2; ++i){
|
|
for(int j=0; j<bpp; j++){
|
|
std::swap(buffer[i*bpp+j], buffer[(length-1-i)*bpp+j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// sample neighbor face's edge
|
|
static void
|
|
sampleNeighbor(PtexTexture * ptex, unsigned char *border, int face, int edge, int length, int bpp)
|
|
{
|
|
const Ptex::FaceInfo &fi = ptex->getFaceInfo(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++;
|
|
|
|
if (valence > 255) {
|
|
OsdWarning("High valence detected in %s : invalid adjacency around "
|
|
"face %d", ptex->path(), face);
|
|
break;
|
|
}
|
|
|
|
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; w<gwidth; ++w) {
|
|
for(int edge=0; edge<4; edge++) {
|
|
|
|
int len = (edge==0 or edge==2) ? b->current.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;j<len;++j) {
|
|
d = pptr;
|
|
switch(edge) {
|
|
case Ptex::e_bottom:
|
|
d += stride*(b->v-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; k<bpp; k++)
|
|
*d++ = *s++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// fix corner pixels
|
|
int numchannels = _ptex->numChannels();
|
|
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; 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);
|
|
}
|
|
}
|
|
} 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]<<std::endl;
|
|
}
|
|
|
|
void
|
|
OsdPtexTextureLoader::PrintPages() const
|
|
{ for( unsigned long int i=0; i<_pages.size(); ++i )
|
|
std::cout<<*(_pages[i])<<std::endl;
|
|
}
|
|
|
|
std::ostream & operator <<(std::ostream &s, const OsdPtexTextureLoader::block & b)
|
|
{ s<<"block "<<b.idx<<" = { ";
|
|
s<<"native=("<<b.native.u()<<","<<b.native.v()<<") ";
|
|
s<<"current=("<<b.current.u()<<","<<b.current.v()<<") ";
|
|
s<<"}";
|
|
return s;
|
|
}
|
|
|
|
std::ostream & operator <<(std::ostream &s, const OsdPtexTextureLoader::page & p)
|
|
{
|
|
s<<"page {\n";
|
|
s<<" slots {";
|
|
for (OsdPtexTextureLoader::page::slist::const_iterator i=p.slots.begin(); i!=p.slots.end(); ++i)
|
|
s<<" { "<<i->u<<" "<<i->v<<" "<<i->ures<<" "<<i->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
|
|
|