mirror of
https://github.com/PixarAnimationStudios/OpenSubdiv
synced 2024-11-25 13:00:07 +00:00
cc6c0925a3
- change error codes from situational to general (fatal / coding / run-time...) - pull error functions from Osd into Far - add a templated topology validation reporting system to Far::TopologyRefinerFactory - fix fallout on rest of code-base
941 lines
33 KiB
C++
941 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/ptexTextureLoader.h"
|
|
#include "../far/error.h"
|
|
|
|
#include <Ptexture.h>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <string.h>
|
|
#include <list>
|
|
|
|
namespace OpenSubdiv {
|
|
namespace OPENSUBDIV_VERSION {
|
|
|
|
namespace Osd {
|
|
|
|
// 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 PtexTextureLoader::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 = (int8_t)(native.ulog2-current.ulog2),
|
|
vdist = (int8_t)(native.vlog2-current.vlog2);
|
|
|
|
return (int8_t)(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 PtexTextureLoader::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=(unsigned short)(i->u + w);
|
|
b->v=(unsigned short)(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);
|
|
};
|
|
|
|
PtexTextureLoader::PtexTextureLoader( 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;
|
|
}
|
|
|
|
PtexTextureLoader::~PtexTextureLoader()
|
|
{
|
|
ClearPages();
|
|
}
|
|
|
|
unsigned long int
|
|
PtexTextureLoader::GetNumBlocks( ) const {
|
|
return (unsigned long int)_blocks.size();
|
|
}
|
|
|
|
unsigned long int
|
|
PtexTextureLoader::GetNumPages( ) const {
|
|
return (unsigned long int)_pages.size();
|
|
}
|
|
|
|
// attempt to re-size per-face resolutions to hit the uncompressed texel
|
|
// memory use requirement
|
|
void
|
|
PtexTextureLoader::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 = (unsigned short)(1<<(b->current.ulog2-1)),
|
|
vres = (unsigned short)(1<<(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 = (unsigned short)(1<<(b->current.ulog2+1)),
|
|
vres = (unsigned short)(1<<(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
|
|
PtexTextureLoader::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) {
|
|
Far::Warning("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, PtexTextureLoader::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
|
|
PtexTextureLoader::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
|
|
PtexTextureLoader::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
|
|
PtexTextureLoader::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
|
|
PtexTextureLoader::ClearPages( )
|
|
{ for( unsigned long int i=0; i<_pages.size(); i++ )
|
|
delete _pages[i];
|
|
_pages.clear();
|
|
}
|
|
|
|
void
|
|
PtexTextureLoader::PrintBlocks() const
|
|
{ for( unsigned long int i=0; i<_blocks.size(); ++i )
|
|
std::cout<<_blocks[i]<<std::endl;
|
|
}
|
|
|
|
void
|
|
PtexTextureLoader::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 PtexTextureLoader::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 PtexTextureLoader::page & p)
|
|
{
|
|
s<<"page {\n";
|
|
s<<" slots {";
|
|
for (PtexTextureLoader::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 (PtexTextureLoader::page::blist::const_iterator i=p.blocks.begin(); i!=p.blocks.end(); ++i)
|
|
s<<" "<< **i;
|
|
s<<" }\n";
|
|
|
|
s<<"}";
|
|
return s;
|
|
}
|
|
|
|
} // end namespace Osd
|
|
|
|
} // end namespace OPENSUBDIV_VERSION
|
|
} // end namespace OpenSubdiv
|
|
|