Mitsuba Renderer  0.5.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
mipmap.h
Go to the documentation of this file.
1 /*
2  This file is part of Mitsuba, a physically based rendering system.
3 
4  Copyright (c) 2007-2014 by Wenzel Jakob and others.
5 
6  Mitsuba is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License Version 3
8  as published by the Free Software Foundation.
9 
10  Mitsuba is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #pragma once
20 #if !defined(__MITSUBA_RENDER_MIPMAP_H_)
21 #define __MITSUBA_RENDER_MIPMAP_H_
22 
23 #include <mitsuba/core/bitmap.h>
24 #include <mitsuba/core/rfilter.h>
25 #include <mitsuba/core/barray.h>
26 #include <mitsuba/core/mmap.h>
27 #include <mitsuba/core/timer.h>
29 #include <boost/filesystem/fstream.hpp>
30 
32 
33 /// Use a blocked array to store MIP map data (slightly faster)
34 #define MTS_MIPMAP_BLOCKED 1
35 
36 /// Look-up table size for a tabulated Gaussian filter
37 #define MTS_MIPMAP_LUT_SIZE 64
38 
39 /// MIP map cache file version
40 #define MTS_MIPMAP_CACHE_VERSION 0x01
41 
42 /// Make sure that the actual cache contents start on a cache line
43 #define MTS_MIPMAP_CACHE_ALIGNMENT 64
44 
45 /* Some statistics counters */
46 namespace stats {
51 };
52 
53 /// Specifies the desired antialiasing filter
55  /// No filtering, nearest neighbor lookups
56  ENearest = 0,
57  /// No filtering, only bilinear interpolation
58  EBilinear = 1,
59  /// Basic trilinear filtering
61  /// Elliptically weighted average
62  EEWA = 3
63 };
64 
65 /**
66  * \brief MIP map class with support for elliptically weighted averages
67  *
68  * This class stores a precomputed collection of images that provide
69  * a hierarchy of resolution levels of an input image. These are used
70  * to prefilter texture lookups at render time, reducing aliasing
71  * artifacts. The implementation here supports non-power-of two images,
72  * different data types and quantizations thereof, as well as the
73  * computation of elliptically weighted averages that account for the
74  * anisotropy of texture lookups in UV space.
75  *
76  * Generating good mip maps is costly, and therefore this class provides
77  * the means to cache them on disk if desired.
78  *
79  * \tparam Value
80  * This class can be parameterized to yield MIP map classes for
81  * RGB values, color spectra, or just plain floats. This parameter
82  * specifies the underlying type.
83  *
84  * \tparam QuantizedValue
85  * If desired, the MIP map can store its contents using a quantized
86  * representation (e.g. half precision). In that case, this type
87  * denotes the associated data type.
88  *
89  * \ingroup librender
90  */
91 template <typename Value, typename QuantizedValue> class TMIPMap : public Object {
92 public:
93 #if MTS_MIPMAP_BLOCKED == 1
94  /// Use a blocked array to store MIP map data
96 #else
97  /// Use a non-blocked array to store MIP map data
99 #endif
100 
101  /// Shortcut
103 
104  /**
105  * \brief Construct a new MIP map from the given bitmap
106  *
107  * \param bitmap
108  * An arbitrary input bitmap that will (if necessary) be
109  * transformed into a representation that is compatible
110  * with the desired MIP map pixel format.
111  *
112  * \ref pixelFormat
113  * A pixel format that is compatible with the
114  * \c Value and \c QuantizedValue types
115  *
116  * \ref componentFormat
117  * A component format that is compatible with the
118  * \c Value type.
119  *
120  * \param rfilter
121  * An image reconstruction filter that is used to create
122  * progressively lower-resolution versions of the input image.
123  *
124  * \param bcu
125  * Specifies how to handle texture lookups outside of the
126  * horizontal range [0, 1]
127  *
128  * \param bcv
129  * Specifies how to handle texture lookups outside of the
130  * vertical range [0, 1]
131  *
132  * \param filterType
133  * Denotes the desired filter type to use for lookups;
134  * the default is to use elliptically weighted averages.
135  *
136  * \param maxAnisotropy
137  * Denotes the highest tolerated anisotropy of the lookup
138  * kernel. This is necessary to bound the computational
139  * cost of filtered lookups.
140  *
141  * \param cacheFilename
142  * Optional filename of a memory-mapped cache file that is used to keep
143  * MIP map data out of core, and to avoid having to load and
144  * downsample textures over and over again in subsequent Mitsuba runs.
145  *
146  * \param maxValue
147  * Maximum image value. This is used to clamp out-of-range values
148  * that occur during the image resampling process
149  *
150  * \param intent
151  * When an RGB image is transformed into a spectral representation,
152  * this parameter specifies what conversion method should be used.
153  * See \ref Spectrum::EConversionIntent for further details.
154  */
155  TMIPMap(Bitmap *bitmap_,
156  Bitmap::EPixelFormat pixelFormat,
157  Bitmap::EComponentFormat componentFormat,
158  const ReconstructionFilter *rfilter,
159  EBoundaryCondition bcu = ReconstructionFilter::ERepeat,
160  EBoundaryCondition bcv = ReconstructionFilter::ERepeat,
161  EMIPFilterType filterType = EEWA,
162  Float maxAnisotropy = 20.0f,
163  fs::path cacheFilename = fs::path(),
164  uint64_t timestamp = 0,
165  Float maxValue = 1.0f,
166  Spectrum::EConversionIntent intent = Spectrum::EReflectance)
167  : m_pixelFormat(pixelFormat), m_bcu(bcu), m_bcv(bcv), m_filterType(filterType),
168  m_weightLut(NULL), m_maxAnisotropy(maxAnisotropy) {
169 
170  /* Keep track of time */
171  ref<Timer> timer = new Timer();
172 
173  /* Compute the size of the MIP map cache file (for now
174  assuming that one should be created) */
175  size_t padding = sizeof(MIPMapHeader) % MTS_MIPMAP_CACHE_ALIGNMENT;
176  if (padding)
177  padding = MTS_MIPMAP_CACHE_ALIGNMENT - padding;
178  size_t cacheSize = sizeof(MIPMapHeader) + padding +
179  Array2DType::bufferSize(bitmap_->getSize());
180 
181  /* 1. Determine the number of MIP levels. The following
182  code also handles non-power-of-2 input. */
183  m_levels = 1;
184  if (m_filterType != ENearest && m_filterType != EBilinear) {
185  Vector2i size = 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);
190  ++m_levels;
191  }
192  }
193 
194  stats::mipStorage += cacheSize;
195 
196  /* Potentially create a MIP map cache file */
197  uint8_t *mmapData = NULL, *mmapPtr = NULL;
198  if (!cacheFilename.empty()) {
199  Log(EInfo, "Generating MIP map cache file \"%s\" ..", cacheFilename.string().c_str());
200  try {
201  m_mmap = new MemoryMappedFile(cacheFilename, cacheSize);
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);
207  }
208  mmapData = mmapPtr = (uint8_t *) m_mmap->getData();
209  }
210 
211  /* 2. Store the base image in a suitable memory layout */
212  m_pyramid = new Array2DType[m_levels];
213  m_sizeRatio = new Vector2[m_levels];
214 
215  /* Allocate memory for the first MIP map level */
216  if (mmapPtr) {
217  mmapPtr += sizeof(MIPMapHeader) + padding;
218  m_pyramid[0].map(mmapPtr, bitmap_->getSize());
219  mmapPtr += m_pyramid[0].getBufferSize();
220  } else {
221  m_pyramid[0].alloc(bitmap_->getSize());
222  }
223 
224  /* Initialize the first mip map level and extract some general
225  information (i.e. the minimum, maximum, and average texture value) */
226  ref<Bitmap> bitmap = bitmap_->expand()->convert(pixelFormat,
227  componentFormat, 1.0f, 1.0f, intent);
228 
229  m_pyramid[0].cleanup();
230  m_pyramid[0].init((Value *) bitmap->getData(), m_minimum, m_maximum, m_average);
231 
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();
235 
236  for (size_t i=0, count=bitmap->getPixelCount(); i<count; ++i)
237  (*value++).clampNegative();
238 
239  m_pyramid[0].init((Value *) bitmap->getData(), m_minimum, m_maximum, m_average);
240  }
241 
242  m_sizeRatio[0] = Vector2(1, 1);
243 
244  /* 3. Progressively downsample until only a 1x1 image is left */
245  if (m_filterType != ENearest && m_filterType != EBilinear) {
246  Vector2i size = bitmap_->getSize();
247  m_levels = 1;
248  while (size.x > 1 || size.y > 1) {
249  /* Compute the size of the next downsampled layer */
250  size.x = std::max(1, (size.x + 1) / 2);
251  size.y = std::max(1, (size.y + 1) / 2);
252 
253  /* Either allocate memory or index into the memory map file */
254  if (mmapPtr) {
255  m_pyramid[m_levels].map(mmapPtr, size);
256  mmapPtr += m_pyramid[m_levels].getBufferSize();
257  } else {
258  m_pyramid[m_levels].alloc(size);
259  }
260 
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());
267 
268  ++m_levels;
269  }
270  }
271 
272  if (mmapData) {
273  /* If a cache file was requested, create a header that
274  describes the current MIP map configuration */
275  MIPMapHeader header;
276  memcpy(header.identifier, "MIP", 3);
278  header.pixelFormat = (uint8_t) m_pixelFormat;
279  header.levels = (uint8_t) m_levels;
280  header.bcu = (uint8_t) bcu;
281  header.bcv = (uint8_t) bcv;
282  header.filterType = (uint8_t) m_filterType;
283  header.gamma = (float) bitmap_->getGamma();
284  header.width = bitmap_->getWidth();
285  header.height = bitmap_->getHeight();
286  header.timestamp = timestamp;
287  header.minimum = m_minimum;
288  header.maximum = m_maximum;
289  header.average = m_average;
290  memcpy(mmapData, &header, sizeof(MIPMapHeader));
291  }
292 
293  Log(EDebug, "Created %s of MIP maps in %i ms", memString(
294  getBufferSize()).c_str(), timer->getMilliseconds());
295 
296  if (m_filterType == EEWA) {
297  m_weightLut = static_cast<Float *>(allocAligned(sizeof(Float) * MTS_MIPMAP_LUT_SIZE));
298  for (int i=0; i<MTS_MIPMAP_LUT_SIZE; ++i) {
299  Float r2 = (Float) i / (Float) (MTS_MIPMAP_LUT_SIZE-1);
300  m_weightLut[i] = math::fastexp(-2.0f * r2) - math::fastexp(-2.0f);
301  }
302  }
303  }
304 
305  /**
306  * \brief Construct a new MIP map from a previously created cache file
307  *
308  * \param cacheFilename
309  * Filename of a memory-mapped cache file that is used to keep
310  * MIP map data out of core, and to avoid having to load and
311  * downsample textures over and over again in subsequent Mitsuba runs.
312  *
313  * \param maxAnisotropy
314  * Denotes the highest tolerated anisotropy of the lookup
315  * kernel. This is necessary to bound the computational
316  * cost of filtered lookups. This parameter is independent of the
317  * cache file that was previously created.
318  */
319  TMIPMap(fs::path cacheFilename, Float maxAnisotropy = 20.0f)
320  : m_weightLut(NULL), m_maxAnisotropy(maxAnisotropy) {
321  m_mmap = new MemoryMappedFile(cacheFilename);
322  uint8_t *mmapPtr = (uint8_t *) m_mmap->getData();
323  Log(EInfo, "Mapped MIP map cache file \"%s\" into memory (%s).", cacheFilename.string().c_str(),
324  memString(m_mmap->getSize()).c_str());
325 
326  stats::mipStorage += m_mmap->getSize();
327 
328  /* Load the file header, and run some santity checks */
329  MIPMapHeader header;
330  memcpy(&header, mmapPtr, sizeof(MIPMapHeader));
331  Assert(header.identifier[0] == 'M' && header.identifier[1] == 'I'
332  && header.identifier[2] == 'P' && header.version == MTS_MIPMAP_CACHE_VERSION);
333  m_pixelFormat = (Bitmap::EPixelFormat) header.pixelFormat;
334  m_levels = (int) header.levels;
335  m_bcu = (EBoundaryCondition) header.bcu;
336  m_bcv = (EBoundaryCondition) header.bcv;
337  m_filterType = (EMIPFilterType) header.filterType;
338  m_minimum = header.minimum;
339  m_maximum = header.maximum;
340  m_average = header.average;
341 
342  /* Move the pointer to the beginning of the MIP map data */
343  size_t padding = sizeof(MIPMapHeader) % MTS_MIPMAP_CACHE_ALIGNMENT;
344  if (padding)
345  padding = MTS_MIPMAP_CACHE_ALIGNMENT - padding;
346  mmapPtr += sizeof(MIPMapHeader) + padding;
347 
348  /* Map the highest resolution level */
349  m_pyramid = new Array2DType[m_levels];
350  m_sizeRatio = new Vector2[m_levels];
351  Vector2i size(header.width, header.height);
352  m_pyramid[0].map(mmapPtr, size);
353  mmapPtr += m_pyramid[0].getBufferSize();
354  m_sizeRatio[0] = Vector2(1, 1);
355 
356  if (m_filterType != ENearest && m_filterType != EBilinear) {
357  /* Map the remainder of the image pyramid */
358  int level = 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);
363  m_sizeRatio[level] = Vector2(
364  (Float) size.x / (Float) m_pyramid[0].getWidth(),
365  (Float) size.y / (Float) m_pyramid[0].getHeight());
366  mmapPtr += m_pyramid[level++].getBufferSize();
367  }
368  Assert(level == m_levels);
369  }
370 
371  if (m_filterType == EEWA) {
372  m_weightLut = static_cast<Float *>(allocAligned(sizeof(Float) * MTS_MIPMAP_LUT_SIZE));
373  for (int i=0; i<MTS_MIPMAP_LUT_SIZE; ++i) {
374  Float r2 = (Float) i / (Float) (MTS_MIPMAP_LUT_SIZE-1);
375  m_weightLut[i] = math::fastexp(-2.0f * r2) - math::fastexp(-2.0f);
376  }
377  }
378  }
379 
380  /// Release all memory
382  delete[] m_pyramid;
383  delete[] m_sizeRatio;
384  if (m_weightLut)
385  freeAligned(m_weightLut);
386  }
387 
388  /**
389  * \brief Check if a MIP map cache is up-to-date and matches the
390  * desired configuration
391  *
392  * \param path
393  * File system path of the MIP map cache
394  * \param timestamp
395  * Timestamp of the original texture file
396  * \param bcu
397  * Horizontal boundary condition used when creating the MIP map pyramid
398  * \param bcv
399  * Vertical boundary condition used when creating the MIP map pyramid
400  * \param gamma
401  * If nonzero, it is verified that the provided gamma value
402  * matches that of the cache file.
403  * \return \c true if the texture file is good for use
404  */
405  static bool validateCacheFile(const fs::path &path, uint64_t timestamp,
406  Bitmap::EPixelFormat pixelFormat, EBoundaryCondition bcu,
407  EBoundaryCondition bcv, EMIPFilterType filterType, Float gamma) {
408  fs::ifstream is(path);
409  if (!is.good())
410  return false;
411 
412  MIPMapHeader header;
413  is.read((char *) &header, sizeof(MIPMapHeader));
414  if (is.fail())
415  return false;
416 
417  if (header.identifier[0] != 'M' || header.identifier[1] != 'I'
418  || header.identifier[2] != 'P' || header.version != MTS_MIPMAP_CACHE_VERSION
419  || header.timestamp != timestamp
420  || header.bcu != (uint8_t) bcu || header.bcv != (uint8_t) bcv
421  || header.pixelFormat != (uint8_t) pixelFormat
422  || header.filterType != (uint8_t) filterType)
423  return false;
424 
425  if (gamma != 0 && (float) gamma != header.gamma)
426  return false;
427 
428  /* Sanity check on the expected file size */
429  size_t padding = sizeof(MIPMapHeader) % MTS_MIPMAP_CACHE_ALIGNMENT;
430  if (padding)
431  padding = MTS_MIPMAP_CACHE_ALIGNMENT - padding;
432 
433  Vector2i size(header.width, header.height);
434  size_t expectedFileSize = sizeof(MIPMapHeader) + padding
435  + Array2DType::bufferSize(size);
436 
437  if (filterType != ENearest && filterType != EBilinear) {
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);
442  }
443  }
444 
445  return fs::file_size(path) == expectedFileSize;
446  }
447 
448  /// Return the size of all buffers
449  size_t getBufferSize() const {
450  size_t size = 0;
451  for (int i=0; i<m_levels; ++i)
452  size += m_pyramid[i].getBufferSize();
453  return size;
454  }
455 
456  /// Return the size of the underlying full resolution texture
457  inline const Vector2i &getSize() const { return m_pyramid[0].getSize(); }
458 
459  /// Return the width of the represented texture
460  inline int getWidth() const { return getSize().x; }
461 
462  /// Return the height of the represented texture
463  inline int getHeight() const { return getSize().y; }
464 
465  /// Return the number of mip-map levels
466  inline int getLevels() const { return m_levels; }
467 
468  /// Return the filter type that is used to pre-filter lookups
469  inline EMIPFilterType getFilterType() const { return m_filterType; }
470 
471  /// Get the component-wise maximum at the zero level
472  inline const Value &getMinimum() const { return m_minimum; }
473 
474  /// Get the component-wise minimum
475  inline const Value &getMaximum() const { return m_maximum; }
476 
477  /// Get the component-wise average
478  inline const Value &getAverage() const { return m_average; }
479 
480  /// Return the blocked array used to store a given MIP level
481  inline const Array2DType &getArray(int level = 0) const {
482  return m_pyramid[level];
483  }
484 
485  /// Return a bitmap representation of the given level
486  ref<Bitmap> toBitmap(int level = 0) const {
487  const Array2DType &array = m_pyramid[level];
488  ref<Bitmap> result = new Bitmap(
489  m_pixelFormat,
490  Bitmap::componentFormat<typename QuantizedValue::Scalar>(),
491  array.getSize()
492  );
493 
494  array.copyTo((QuantizedValue *) result->getData());
495 
496  return result;
497  }
498 
499  /**
500  * \brief Return the texture value at a texel specified using integer
501  * coordinates, while accounting for boundary conditions
502  */
503  inline Value evalTexel(int level, int x, int y) const {
504  const Vector2i &size = m_pyramid[level].getSize();
505 
506  if (x < 0 || x >= size.x) {
507  /* Encountered an out of bounds access -- determine what to do */
508  switch (m_bcu) {
509  case ReconstructionFilter::ERepeat:
510  // Assume that the input repeats in a periodic fashion
511  x = math::modulo(x, size.x);
512  break;
513  case ReconstructionFilter::EClamp:
514  // Clamp to the outermost sample position
515  x = math::clamp(x, 0, size.x - 1);
516  break;
517  case ReconstructionFilter::EMirror:
518  // Assume that the input is mirrored along the boundary
519  x = math::modulo(x, 2*size.x);
520  if (x >= size.x)
521  x = 2*size.x - x - 1;
522  break;
523  case ReconstructionFilter::EZero:
524  // Assume that the input function is zero
525  // outside of the defined domain
526  return Value(0.0f);
527  case ReconstructionFilter::EOne:
528  // Assume that the input function is equal
529  // to one outside of the defined domain
530  return Value(1.0f);
531  }
532  }
533 
534  if (y < 0 || y >= size.y) {
535  /* Encountered an out of bounds access -- determine what to do */
536  switch (m_bcv) {
537  case ReconstructionFilter::ERepeat:
538  // Assume that the input repeats in a periodic fashion
539  y = math::modulo(y, size.y);
540  break;
541  case ReconstructionFilter::EClamp:
542  // Clamp to the outermost sample position
543  y = math::clamp(y, 0, size.y - 1);
544  break;
545  case ReconstructionFilter::EMirror:
546  // Assume that the input is mirrored along the boundary
547  y = math::modulo(y, 2*size.y);
548  if (y >= size.y)
549  y = 2*size.y - y - 1;
550  break;
551  case ReconstructionFilter::EZero:
552  // Assume that the input function is zero
553  // outside of the defined domain
554  return Value(0.0f);
555  case ReconstructionFilter::EOne:
556  // Assume that the input function is equal
557  // to one outside of the defined domain
558  return Value(1.0f);
559  }
560  }
561 
562  return Value(m_pyramid[level](x, y));
563  }
564 
565  /// Evaluate the texture at the given resolution using a box filter
566  inline Value evalBox(int level, const Point2 &uv) const {
567  const Vector2i &size = m_pyramid[level].getSize();
568  return evalTexel(level, math::floorToInt(uv.x*size.x), math::floorToInt(uv.y*size.y));
569  }
570 
571  /**
572  * \brief Evaluate the texture using fractional texture coordinates and
573  * bilinear interpolation. The desired MIP level must be specified
574  */
575  inline Value evalBilinear(int level, const Point2 &uv) const {
576  if (EXPECT_NOT_TAKEN(!std::isfinite(uv.x) || !std::isfinite(uv.y))) {
577  Log(EWarn, "evalBilinear(): encountered a NaN!");
578  return Value(0.0f);
579  } else if (EXPECT_NOT_TAKEN(level >= m_levels)) {
580  /* The lookup is larger than the entire texture */
581  return evalBox(m_levels-1, uv);
582  }
583 
584  /* Convert to fractional pixel coordinates on the specified level */
585  const Vector2i &size = m_pyramid[level].getSize();
586  Float u = uv.x * size.x - 0.5f, v = uv.y * size.y - 0.5f;
587 
588  int xPos = math::floorToInt(u), yPos = math::floorToInt(v);
589  Float dx1 = u - xPos, dx2 = 1.0f - dx1,
590  dy1 = v - yPos, dy2 = 1.0f - dy1;
591 
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;
596  }
597 
598  /**
599  * \brief Evaluate the gradient of the texture at the given MIP level
600  */
601  inline void evalGradientBilinear(int level, const Point2 &uv, Value *gradient) const {
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);
605  return;
606  } else if (EXPECT_NOT_TAKEN(level >= m_levels)) {
607  evalGradientBilinear(m_levels-1, uv, gradient);
608  return;
609  }
610 
611  /* Convert to fractional pixel coordinates on the specified level */
612  const Vector2i &size = m_pyramid[level].getSize();
613  Float u = uv.x * size.x - 0.5f, v = uv.y * size.y - 0.5f;
614 
615  int xPos = math::floorToInt(u), yPos = math::floorToInt(v);
616  Float dx = u - xPos, dy = v - yPos;
617 
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;
623 
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);
626  }
627 
628  /// \brief Perform a filtered texture lookup using the configured method
629  Value eval(const Point2 &uv, const Vector2 &d0, const Vector2 &d1) const {
630  if (m_filterType == ENearest)
631  return evalBox(0, uv);
632  else if (m_filterType == EBilinear)
633  return evalBilinear(0, uv);
634 
635  /* Convert into texel coordinates */
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;
639 
640  /* Turn the texture-space Jacobian into the coefficients of an
641  implicitly defined ellipse. */
642  Float A = dv0*dv0 + dv1*dv1,
643  B = -2.0f * (du0*dv0 + du1*dv1),
644  C = du0*du0 + du1*du1,
645  F = A*C - B*B*0.25f;
646 
647  /* Compute the major and minor radii */
648  Float root = math::hypot2(A-C, B),
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;
653 
654  if (m_filterType == ETrilinear || !(minorRadius > 0) || !(majorRadius > 0) || F < 0) {
655  /* Determine a suitable mip map level, while preferring
656  blurring over aliasing */
657  Float level = math::log2(std::max(majorRadius, Epsilon));
658  int ilevel = math::floorToInt(level);
659 
660  if (ilevel < 0) {
661  /* Bilinear interpolation (lookup is smaller than 1 pixel) */
662  return evalBilinear(0, uv);
663  } else {
664  /* Trilinear interpolation between two mipmap levels */
665  Float a = level - ilevel;
666  return evalBilinear(ilevel, uv) * (1.0f - a)
667  + evalBilinear(ilevel+1, uv) * a;
668  }
669  } else {
670  /* Artificially enlarge ellipses that are too skinny
671  to avoid having to compute excessively many samples */
672  if (minorRadius * m_maxAnisotropy < majorRadius) {
673  /* Enlarge the minor radius, which will cause extra blurring */
674  minorRadius = majorRadius / m_maxAnisotropy;
675 
676  /* We need to find the coefficients of the adjusted ellipse, which
677  unfortunately involves expensive trig and arctrig functions.
678  Fortunately, this is somewhat of a corner case and won't
679  happen overly often in practice. */
680  Float theta = 0.5f * std::atan(B / (A-C)), sinTheta, cosTheta;
681  math::sincos(theta, &sinTheta, &cosTheta);
682 
683  Float a2 = majorRadius*majorRadius,
684  b2 = minorRadius*minorRadius,
685  sinTheta2 = sinTheta*sinTheta,
686  cosTheta2 = cosTheta*cosTheta,
687  sin2Theta = 2*sinTheta*cosTheta;
688 
689  A = a2*cosTheta2 + b2*sinTheta2;
690  B = (a2-b2) * sin2Theta;
691  C = a2*sinTheta2 + b2*cosTheta2;
692  F = a2*b2;
693 
695  }
697 
698  /* Switch to normalized coefficients */
699  Float scale = 1.0f / F;
700  A *= scale; B *= scale; C *= scale;
701 
702  /* Determine a suitable MIP map level, such that the filter
703  covers a reasonable amount of pixels */
704  Float level = std::max((Float) 0.0f, math::log2(minorRadius));
705  int ilevel = (int) level;
706  Float a = level - ilevel;
707 
708  /* Switch to bilinear interpolation, be wary of round-off errors */
709  if (majorRadius < 1 || !(A > 0 && C > 0))
710  return evalBilinear(ilevel, uv);
711  else
712  return evalEWA(ilevel, uv, A, B, C) * (1.0f-a) +
713  evalEWA(ilevel+1, uv, A, B, C) * a;
714  }
715  }
716 
717  /// Return a human-readable string representation
718  std::string toString() const {
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
725  << " filterType = ";
726 
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;
732  }
733 
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
738  << "]";
739  return oss.str();
740  }
741 
743 protected:
744  /// Header file for MIP map cache files
745  struct MIPMapHeader {
746  char identifier[3];
750  uint8_t bcu:4;
751  uint8_t bcv:4;
753  float gamma;
754  int width;
755  int height;
756  uint64_t timestamp;
757  Value minimum;
758  Value maximum;
759  Value average;
760  };
761 
762 
763  /// Calculate the elliptically weighted average of a sample and associated Jacobian
764  Value evalEWA(int level, const Point2 &uv, Float A, Float B, Float C) const {
765  Assert(A > 0);
766  if (EXPECT_NOT_TAKEN(!std::isfinite(A+B+C+uv.x+uv.y))) {
767  Log(EWarn, "evalEWA(): encountered a NaN!");
768  return Value(0.0f);
769  } else if (EXPECT_NOT_TAKEN(level >= m_levels)) {
770  /* The lookup is larger than the entire texture */
771  return evalBox(m_levels-1, uv);
772  }
773 
774  /* Convert to fractional pixel coordinates on the specified level */
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;
778 
779  /* Do the same to the ellipse coefficients */
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;
784 
785  /* Compute the ellipse's bounding box in texture space */
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);
789  int u0 = math::ceilToInt(u - deltaU), u1 = math::floorToInt(u + deltaU);
790  int v0 = math::ceilToInt(v - deltaV), v1 = math::floorToInt(v + deltaV);
791 
792  /* Scale the coefficients by the size of the Gaussian lookup table */
793  Float As = A * MTS_MIPMAP_LUT_SIZE,
794  Bs = B * MTS_MIPMAP_LUT_SIZE,
795  Cs = C * MTS_MIPMAP_LUT_SIZE;
796 
797  Value result(0.0f);
798  Float denominator = 0.0f;
799  Float ddq = 2*As, uu0 = (Float) u0 - u;
800  int nSamples = 0;
801 
802  for (int vt = v0; vt <= v1; ++vt) {
803  const Float vv = (Float) vt - v;
804 
805  Float q = As*uu0*uu0 + (Bs*uu0 + Cs*vv)*vv;
806  Float dq = As*(2*uu0 + 1) + Bs*vv;
807 
808  for (int ut = u0; ut <= u1; ++ut) {
809  if (q < (Float) MTS_MIPMAP_LUT_SIZE) {
810  uint32_t qi = (uint32_t) q;
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;
815  ++nSamples;
816  }
817  }
818 
819  q += dq;
820  dq += ddq;
821  }
822  }
823 
824  if (denominator == 0) {
825  /* The filter did not cover any samples..
826  Revert to bilinear interpolation */
827  return evalBilinear(level, uv);
828  } else {
829  stats::avgEWASamples += nSamples;
831  }
832 
833  return result / denominator;
834  }
835 private:
836  ref<MemoryMappedFile> m_mmap;
837  Bitmap::EPixelFormat m_pixelFormat;
838  EBoundaryCondition m_bcu, m_bcv;
839  EMIPFilterType m_filterType;
840  Float *m_weightLut;
841  Float m_maxAnisotropy;
842  Vector2 *m_sizeRatio;
843  Array2DType *m_pyramid;
844  int m_levels;
845  Value m_minimum;
846  Value m_maximum;
847  Value m_average;
848 };
849 
850 template <typename Value, typename QuantizedValue>
851  Class *TMIPMap<Value, QuantizedValue>::m_theClass
852  = new Class("MIPMap", false, "Object");
853 
854 template <typename Value, typename QuantizedValue>
856  return m_theClass;
857 }
858 
860 
861 #endif /* __MITSUBA_RENDER_MIPMAP_H_ */
MTS_EXPORT_CORE float hypot2(float a, float b)
sqrt(a^2 + b^2) without range issues (like &#39;hypot&#39; 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
uint8_t pixelFormat
Definition: mipmap.h:748
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
char identifier[3]
Definition: mipmap.h:746
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
uint64_t timestamp
Definition: mipmap.h:756
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&#39;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
uint8_t levels
Definition: mipmap.h:749
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
#define MTS_NAMESPACE_BEGIN
Definition: platform.h:137
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 &gt; 0)
Definition: math.h:67
uint8_t bcv
Definition: mipmap.h:751
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.
StatsCounter mipStorage
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
Definition: fwd.h:99
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&#39; 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
Value average
Definition: mipmap.h:759
int width
Definition: mipmap.h:754
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&#39;s height in pixels.
Definition: bitmap.h:394
uint8_t filterType
Definition: mipmap.h:752
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&#39;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
int height
Definition: mipmap.h:755
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
uint8_t version
Definition: mipmap.h:747
Value minimum
Definition: mipmap.h:757
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
#define MTS_EXPORT_RENDER
Definition: platform.h:109
void sincos(float theta, float *_sin, float *_cos)
Definition: math.h:228
Float getGamma() const
Return the bitmap&#39;s gamma identifier (-1: sRGB)
Definition: bitmap.h:1140
Value maximum
Definition: mipmap.h:758
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
Header file for MIP map cache files.
Definition: mipmap.h:745
Definition: fwd.h:95
#define MTS_NAMESPACE_END
Definition: platform.h:138
uint8_t bcu
Definition: mipmap.h:750
~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
float gamma
Definition: mipmap.h:753
Scalar clamp(Scalar value, Scalar min, Scalar max)
Generic clamping function.
Definition: math.h:51