20 #if !defined(__MITSUBA_RENDER_MIPMAP_H_)
21 #define __MITSUBA_RENDER_MIPMAP_H_
29 #include <boost/filesystem/fstream.hpp>
34 #define MTS_MIPMAP_BLOCKED 1
37 #define MTS_MIPMAP_LUT_SIZE 64
40 #define MTS_MIPMAP_CACHE_VERSION 0x01
43 #define MTS_MIPMAP_CACHE_ALIGNMENT 64
91 template <
typename Value,
typename QuantizedValue>
class TMIPMap :
public Object {
93 #if MTS_MIPMAP_BLOCKED == 1
162 Float maxAnisotropy = 20.0f,
163 fs::path cacheFilename = fs::path(),
164 uint64_t timestamp = 0,
165 Float maxValue = 1.0f,
167 : m_pixelFormat(pixelFormat), m_bcu(bcu), m_bcv(bcv), m_filterType(filterType),
168 m_weightLut(NULL), m_maxAnisotropy(maxAnisotropy) {
179 Array2DType::bufferSize(bitmap_->
getSize());
186 while (size.x > 1 || size.y > 1) {
187 size.x = std::max(1, (size.x + 1) / 2);
188 size.y = std::max(1, (size.y + 1) / 2);
189 cacheSize += Array2DType::bufferSize(size);
197 uint8_t *mmapData = NULL, *mmapPtr = NULL;
198 if (!cacheFilename.empty()) {
199 Log(
EInfo,
"Generating MIP map cache file \"%s\" ..", cacheFilename.string().c_str());
202 }
catch (std::runtime_error &e) {
203 Log(
EWarn,
"Unable to create MIP map cache file \"%s\" -- "
204 "retrying with a temporary file. Error message was: %s",
205 cacheFilename.string().c_str(), e.what());
206 m_mmap = MemoryMappedFile::createTemporary(cacheSize);
208 mmapData = mmapPtr = (
uint8_t *) m_mmap->getData();
213 m_sizeRatio =
new Vector2[m_levels];
218 m_pyramid[0].map(mmapPtr, bitmap_->
getSize());
219 mmapPtr += m_pyramid[0].getBufferSize();
221 m_pyramid[0].alloc(bitmap_->
getSize());
227 componentFormat, 1.0f, 1.0f, intent);
229 m_pyramid[0].cleanup();
230 m_pyramid[0].init((Value *) bitmap->getData(), m_minimum, m_maximum, m_average);
232 if (m_minimum.min() < 0) {
233 Log(
EWarn,
"The texture contains negative pixel values! These will be clamped!");
234 Value *value = (Value *) bitmap->getData();
236 for (
size_t i=0, count=bitmap->getPixelCount(); i<count; ++i)
237 (*value++).clampNegative();
239 m_pyramid[0].init((Value *) bitmap->getData(), m_minimum, m_maximum, m_average);
242 m_sizeRatio[0] =
Vector2(1, 1);
248 while (size.x > 1 || size.y > 1) {
250 size.x = std::max(1, (size.x + 1) / 2);
251 size.y = std::max(1, (size.y + 1) / 2);
255 m_pyramid[m_levels].map(mmapPtr, size);
256 mmapPtr += m_pyramid[m_levels].getBufferSize();
258 m_pyramid[m_levels].alloc(size);
261 bitmap = bitmap->resample(rfilter, bcu, bcv, size, 0.0f, maxValue);
262 m_pyramid[m_levels].cleanup();
263 m_pyramid[m_levels].init((Value *) bitmap->getData());
264 m_sizeRatio[m_levels] =
Vector2(
265 (
Float) size.x / (
Float) m_pyramid[0].getWidth(),
266 (
Float) size.y / (
Float) m_pyramid[0].getHeight());
294 getBufferSize()).c_str(), timer->getMilliseconds());
296 if (m_filterType ==
EEWA) {
320 : m_weightLut(NULL), m_maxAnisotropy(maxAnisotropy) {
323 Log(
EInfo,
"Mapped MIP map cache file \"%s\" into memory (%s).", cacheFilename.string().c_str(),
334 m_levels = (
int) header.
levels;
350 m_sizeRatio =
new Vector2[m_levels];
352 m_pyramid[0].map(mmapPtr, size);
353 mmapPtr += m_pyramid[0].getBufferSize();
354 m_sizeRatio[0] =
Vector2(1, 1);
359 while (size.x > 1 || size.y > 1) {
360 size.x = std::max(1, (size.x + 1) / 2);
361 size.y = std::max(1, (size.y + 1) / 2);
362 m_pyramid[level].map(mmapPtr, size);
364 (
Float) size.x / (
Float) m_pyramid[0].getWidth(),
365 (
Float) size.y / (
Float) m_pyramid[0].getHeight());
366 mmapPtr += m_pyramid[level++].getBufferSize();
368 Assert(level == m_levels);
371 if (m_filterType ==
EEWA) {
383 delete[] m_sizeRatio;
408 fs::ifstream is(path);
425 if (gamma != 0 && (
float) gamma != header.
gamma)
434 size_t expectedFileSize =
sizeof(
MIPMapHeader) + padding
435 + Array2DType::bufferSize(size);
438 while (size.x > 1 || size.y > 1) {
439 size.x = std::max(1, (size.x + 1) / 2);
440 size.y = std::max(1, (size.y + 1) / 2);
441 expectedFileSize += Array2DType::bufferSize(size);
445 return fs::file_size(path) == expectedFileSize;
451 for (
int i=0; i<m_levels; ++i)
452 size += m_pyramid[i].getBufferSize();
460 inline int getWidth()
const {
return getSize().x; }
482 return m_pyramid[level];
490 Bitmap::componentFormat<typename QuantizedValue::Scalar>(),
494 array.
copyTo((QuantizedValue *) result->getData());
504 const Vector2i &size = m_pyramid[level].getSize();
506 if (x < 0 || x >= size.x) {
509 case ReconstructionFilter::ERepeat:
513 case ReconstructionFilter::EClamp:
517 case ReconstructionFilter::EMirror:
521 x = 2*size.x - x - 1;
523 case ReconstructionFilter::EZero:
527 case ReconstructionFilter::EOne:
534 if (y < 0 || y >= size.y) {
537 case ReconstructionFilter::ERepeat:
541 case ReconstructionFilter::EClamp:
545 case ReconstructionFilter::EMirror:
549 y = 2*size.y - y - 1;
551 case ReconstructionFilter::EZero:
555 case ReconstructionFilter::EOne:
562 return Value(m_pyramid[level](x, y));
567 const Vector2i &size = m_pyramid[level].getSize();
576 if (EXPECT_NOT_TAKEN(!std::isfinite(uv.x) || !std::isfinite(uv.y))) {
577 Log(
EWarn,
"evalBilinear(): encountered a NaN!");
579 }
else if (EXPECT_NOT_TAKEN(level >= m_levels)) {
581 return evalBox(m_levels-1, uv);
585 const Vector2i &size = m_pyramid[level].getSize();
586 Float u = uv.x * size.x - 0.5f, v = uv.y * size.y - 0.5f;
589 Float dx1 = u - xPos, dx2 = 1.0f - dx1,
590 dy1 = v - yPos, dy2 = 1.0f - dy1;
592 return evalTexel(level, xPos, yPos) * dx2 * dy2
593 + evalTexel(level, xPos, yPos + 1) * dx2 * dy1
594 + evalTexel(level, xPos + 1, yPos) * dx1 * dy2
595 + evalTexel(level, xPos + 1, yPos + 1) * dx1 * dy1;
602 if (EXPECT_NOT_TAKEN(!std::isfinite(uv.x) || !std::isfinite(uv.y))) {
603 Log(
EWarn,
"evalGradientBilinear(): encountered a NaN!");
604 gradient[0] = gradient[1] = Value(0.0f);
606 }
else if (EXPECT_NOT_TAKEN(level >= m_levels)) {
607 evalGradientBilinear(m_levels-1, uv, gradient);
612 const Vector2i &size = m_pyramid[level].getSize();
613 Float u = uv.x * size.x - 0.5f, v = uv.y * size.y - 0.5f;
616 Float dx = u - xPos, dy = v - yPos;
618 const Value p00 = evalTexel(level, xPos, yPos);
619 const Value p10 = evalTexel(level, xPos+1, yPos);
620 const Value p01 = evalTexel(level, xPos, yPos+1);
621 const Value p11 = evalTexel(level, xPos+1, yPos+1);
622 Value tmp = p01 + p10 - p11;
624 gradient[0] = (p10 + p00*(dy-1) - tmp*dy) *
static_cast<Float> (size.x);
625 gradient[1] = (p01 + p00*(dx-1) - tmp*dx) *
static_cast<Float> (size.y);
631 return evalBox(0, uv);
633 return evalBilinear(0, uv);
636 const Vector2i &size = m_pyramid[0].getSize();
637 Float du0 = d0.x * size.x, dv0 = d0.y * size.y,
638 du1 = d1.x * size.x, dv1 = d1.y * size.y;
642 Float A = dv0*dv0 + dv1*dv1,
643 B = -2.0f * (du0*dv0 + du1*dv1),
644 C = du0*du0 + du1*du1,
649 Aprime = 0.5f * (A + C - root),
650 Cprime = 0.5f * (A + C + root),
651 majorRadius = Aprime != 0 ? std::sqrt(F / Aprime) : 0,
652 minorRadius = Cprime != 0 ? std::sqrt(F / Cprime) : 0;
654 if (m_filterType ==
ETrilinear || !(minorRadius > 0) || !(majorRadius > 0) || F < 0) {
662 return evalBilinear(0, uv);
665 Float a = level - ilevel;
666 return evalBilinear(ilevel, uv) * (1.0f - a)
667 + evalBilinear(ilevel+1, uv) * a;
672 if (minorRadius * m_maxAnisotropy < majorRadius) {
674 minorRadius = majorRadius / m_maxAnisotropy;
680 Float theta = 0.5f * std::atan(B / (A-C)), sinTheta, cosTheta;
683 Float a2 = majorRadius*majorRadius,
684 b2 = minorRadius*minorRadius,
685 sinTheta2 = sinTheta*sinTheta,
686 cosTheta2 = cosTheta*cosTheta,
687 sin2Theta = 2*sinTheta*cosTheta;
689 A = a2*cosTheta2 + b2*sinTheta2;
690 B = (a2-b2) * sin2Theta;
691 C = a2*sinTheta2 + b2*cosTheta2;
699 Float scale = 1.0f / F;
700 A *= scale; B *= scale; C *= scale;
705 int ilevel = (int) level;
706 Float a = level - ilevel;
709 if (majorRadius < 1 || !(A > 0 && C > 0))
710 return evalBilinear(ilevel, uv);
712 return evalEWA(ilevel, uv, A, B, C) * (1.0f-a) +
713 evalEWA(ilevel+1, uv, A, B, C) * a;
719 std::ostringstream oss;
720 oss <<
"TMIPMap[" << endl
721 <<
" pixelFormat = " << m_pixelFormat <<
"," << endl
722 <<
" size = " <<
memString(getBufferSize()) <<
"," << endl
723 <<
" levels = " << m_levels <<
"," << endl
724 <<
" cached = " << (m_mmap.get() ?
"yes" :
"no") <<
"," << endl
727 switch (m_filterType) {
728 case ENearest: oss <<
"nearest," << endl;
break;
729 case EBilinear: oss <<
"bilinear," << endl;
break;
730 case ETrilinear: oss <<
"trilinear," << endl;
break;
731 case EEWA: oss <<
"ewa," << endl;
break;
734 oss <<
" bc = [" << m_bcu <<
", " << m_bcv <<
"]," << endl
735 <<
" minimum = " << m_minimum.toString() <<
"," << endl
736 <<
" maximum = " << m_maximum.toString() <<
"," << endl
737 <<
" average = " << m_average.toString() << endl
766 if (EXPECT_NOT_TAKEN(!std::isfinite(A+B+C+uv.x+uv.y))) {
767 Log(
EWarn,
"evalEWA(): encountered a NaN!");
769 }
else if (EXPECT_NOT_TAKEN(level >= m_levels)) {
771 return evalBox(m_levels-1, uv);
775 const Vector2i &size = m_pyramid[level].getSize();
776 Float u = uv.x * size.x - 0.5f;
777 Float v = uv.y * size.y - 0.5f;
780 const Vector2 &ratio = m_sizeRatio[level];
781 A /= ratio.x * ratio.x;
782 B /= ratio.x * ratio.y;
783 C /= ratio.y * ratio.y;
786 Float invDet = 1.0f / (-B*B + 4.0f*A*C),
787 deltaU = 2.0f * std::sqrt(C * invDet),
788 deltaV = 2.0f * std::sqrt(A * invDet);
798 Float denominator = 0.0f;
802 for (
int vt = v0; vt <= v1; ++vt) {
805 Float q = As*uu0*uu0 + (Bs*uu0 + Cs*vv)*vv;
806 Float dq = As*(2*uu0 + 1) + Bs*vv;
808 for (
int ut = u0; ut <= u1; ++ut) {
811 if (qi < MTS_MIPMAP_LUT_SIZE) {
812 const Float weight = m_weightLut[(int) q];
813 result += evalTexel(level, ut, vt) * weight;
814 denominator += weight;
824 if (denominator == 0) {
827 return evalBilinear(level, uv);
833 return result / denominator;
838 EBoundaryCondition m_bcu, m_bcv;
841 Float m_maxAnisotropy;
843 Array2DType *m_pyramid;
850 template <
typename Value,
typename QuantizedValue>
851 Class *TMIPMap<Value, QuantizedValue>::m_theClass
852 =
new Class(
"MIPMap",
false,
"Object");
854 template <
typename Value,
typename QuantizedValue>
MTS_EXPORT_CORE float hypot2(float a, float b)
sqrt(a^2 + b^2) without range issues (like 'hypot' on compilers that support C99, single precision) ...
int getLevels() const
Return the number of mip-map levels.
Definition: mipmap.h:466
EBoundaryCondition
When resampling data to a different resolution using Resampler::resample(), this enumeration specifie...
Definition: rfilter.h:53
#define Epsilon
Definition: constants.h:28
std::string toString() const
Return a human-readable string representation.
Definition: mipmap.h:718
TMIPMap(Bitmap *bitmap_, Bitmap::EPixelFormat pixelFormat, Bitmap::EComponentFormat componentFormat, const ReconstructionFilter *rfilter, EBoundaryCondition bcu=ReconstructionFilter::ERepeat, EBoundaryCondition bcv=ReconstructionFilter::ERepeat, EMIPFilterType filterType=EEWA, Float maxAnisotropy=20.0f, fs::path cacheFilename=fs::path(), uint64_t timestamp=0, Float maxValue=1.0f, Spectrum::EConversionIntent intent=Spectrum::EReflectance)
Construct a new MIP map from the given bitmap.
Definition: mipmap.h:155
General-purpose bitmap class with read and write support for several common file formats.
Definition: bitmap.h:50
StatsCounter filteredLookups
size_t getBufferSize() const
Return the size of all buffers.
Definition: mipmap.h:449
int floorToInt(Scalar value)
Integer floor function (single precision)
Definition: math.h:100
void incrementBase(size_t amount=1)
Increment the base counter by the specified amount (only for use with EPercentage/EAverage) ...
Definition: statistics.h:145
StatsCounter clampedAnisotropy
#define MTS_MIPMAP_CACHE_VERSION
MIP map cache file version.
Definition: mipmap.h:40
static bool validateCacheFile(const fs::path &path, uint64_t timestamp, Bitmap::EPixelFormat pixelFormat, EBoundaryCondition bcu, EBoundaryCondition bcv, EMIPFilterType filterType, Float gamma)
Check if a MIP map cache is up-to-date and matches the desired configuration.
Definition: mipmap.h:405
int getHeight() const
Return the height of the represented texture.
Definition: mipmap.h:463
const Array2DType & getArray(int level=0) const
Return the blocked array used to store a given MIP level.
Definition: mipmap.h:481
const Vector2i & getSize() const
Return the bitmap's resolution in pixels.
Definition: bitmap.h:385
MIP map class with support for elliptically weighted averages.
Definition: mipmap.h:91
EPixelFormat
Definition: bitmap.h:61
Value evalBox(int level, const Point2 &uv) const
Evaluate the texture at the given resolution using a box filter.
Definition: mipmap.h:566
void evalGradientBilinear(int level, const Point2 &uv, Value *gradient) const
Evaluate the gradient of the texture at the given MIP level.
Definition: mipmap.h:601
Debug message, usually turned off.
Definition: formatter.h:30
TMIPMap(fs::path cacheFilename, Float maxAnisotropy=20.0f)
Construct a new MIP map from a previously created cache file.
Definition: mipmap.h:319
ref< Bitmap > expand()
Expand bitmask images.
Generic interface to separable image reconstruction filters.
Definition: rfilter.h:44
More relevant debug / information message.
Definition: formatter.h:31
Value evalTexel(int level, int x, int y) const
Return the texture value at a texel specified using integer coordinates, while accounting for boundar...
Definition: mipmap.h:503
EMIPFilterType
Specifies the desired antialiasing filter.
Definition: mipmap.h:54
Value evalEWA(int level, const Point2 &uv, Float A, Float B, Float C) const
Calculate the elliptically weighted average of a sample and associated Jacobian.
Definition: mipmap.h:764
EMIPFilterType getFilterType() const
Return the filter type that is used to pre-filter lookups.
Definition: mipmap.h:469
int32_t modulo(int32_t a, int32_t b)
Always-positive modulo function (assumes b > 0)
Definition: math.h:67
Basic cross-platform abstraction for memory mapped files.
Definition: mmap.h:32
Blocked generic 2D array data type.
Definition: barray.h:33
int getWidth() const
Return the width of the represented texture.
Definition: mipmap.h:460
Value evalBilinear(int level, const Point2 &uv) const
Evaluate the texture using fractional texture coordinates and bilinear interpolation. The desired MIP level must be specified.
Definition: mipmap.h:575
MTS_EXPORT_CORE void *__restrict allocAligned(size_t size)
Allocate an aligned region of memory.
Linear (i.e. non-blocked) generic 2D array data type.
Definition: barray.h:244
#define Log(level, fmt,...)
Write a Log message to the console (to be used within subclasses of Object)
Definition: logger.h:35
TVector2< Float > Vector2
Definition: fwd.h:106
ReconstructionFilter::EBoundaryCondition EBoundaryCondition
Shortcut.
Definition: mipmap.h:102
#define MTS_DECLARE_CLASS()
This macro must be used in the initial definition in classes that derive from Object.
Definition: class.h:158
Reference counting helper.
Definition: ref.h:40
Warning message.
Definition: formatter.h:32
EConversionIntent
When converting from RGB reflectance values to discretized color spectra, the following `intent' flag...
Definition: spectrum.h:673
BlockedArray< QuantizedValue > Array2DType
Use a blocked array to store MIP map data.
Definition: mipmap.h:95
Stores meta-information about Object instances.
Definition: class.h:43
MTS_EXPORT_CORE std::string memString(size_t size, bool precise=false)
Turn a memory size into a human-readable string.
int getHeight() const
Return the bitmap's height in pixels.
Definition: bitmap.h:394
Platform independent milli/micro/nanosecond timerThis class implements a simple cross-platform timer ...
Definition: timer.h:37
const Value & getMaximum() const
Get the component-wise minimum.
Definition: mipmap.h:475
MTS_EXPORT_CORE float log2(float value)
Base-2 logarithm (single precision)
General-purpose statistics counter.
Definition: statistics.h:94
const Value & getAverage() const
Get the component-wise average.
Definition: mipmap.h:478
int ceilToInt(Scalar value)
Integer ceil function (single precision)
Definition: math.h:103
StatsCounter avgEWASamples
int getWidth() const
Return the bitmap's width in pixels.
Definition: bitmap.h:391
#define MTS_MIPMAP_CACHE_ALIGNMENT
Make sure that the actual cache contents start on a cache line.
Definition: mipmap.h:43
ref< Bitmap > toBitmap(int level=0) const
Return a bitmap representation of the given level.
Definition: mipmap.h:486
No filtering, only bilinear interpolation.
Definition: mipmap.h:58
EComponentFormat
Supported per-component data formats.
Definition: bitmap.h:97
const Vector2i & getSize() const
Return the size of the underlying full resolution texture.
Definition: mipmap.h:457
MTS_EXPORT_CORE void freeAligned(void *ptr)
Free an aligned region of memory.
Value eval(const Point2 &uv, const Vector2 &d0, const Vector2 &d1) const
Perform a filtered texture lookup using the configured method.
Definition: mipmap.h:629
#define MTS_MIPMAP_LUT_SIZE
Look-up table size for a tabulated Gaussian filter.
Definition: mipmap.h:37
No filtering, nearest neighbor lookups.
Definition: mipmap.h:56
Parent of all Mitsuba classes.
Definition: object.h:38
void sincos(float theta, float *_sin, float *_cos)
Definition: math.h:228
Float getGamma() const
Return the bitmap's gamma identifier (-1: sRGB)
Definition: bitmap.h:1140
Basic trilinear filtering.
Definition: mipmap.h:60
void copyTo(AltValue *data) const
Copy the contents of the blocked array to a non-blocked destination buffer in row-major order...
Definition: barray.h:136
float fastexp(float value)
Definition: math.h:201
#define Assert(cond)
Assert that a condition is true (to be used inside of classes that derive from Object) ...
Definition: logger.h:73
const Value & getMinimum() const
Get the component-wise maximum at the zero level.
Definition: mipmap.h:472
~TMIPMap()
Release all memory.
Definition: mipmap.h:381
Elliptically weighted average.
Definition: mipmap.h:62
const Vector2i & getSize() const
Return the size of the array.
Definition: barray.h:165
Scalar clamp(Scalar value, Scalar min, Scalar max)
Generic clamping function.
Definition: math.h:51