/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2012 by Wenzel Jakob and others.
Mitsuba is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Mitsuba is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#if defined(_MSC_VER)
#pragma warning(disable : 4231) // nonstandard extension used : 'extern' before template explicit instantiation
#endif
#include
#include
#include
#include
#include
MTS_NAMESPACE_BEGIN
/*!\plugin{tiledhdrfilm}{Tiled high dynamic range film}
* \order{2}
* \parameters{
* \parameter{width, height}{\Integer}{
* Width and height of the camera sensor in pixels
* \default{768, 576}
* }
* \parameter{cropOffsetX, cropOffsetY, cropWidth, cropHeight}{\Integer}{
* These parameters can optionally be provided to select a sub-rectangle
* of the output. In this case, Mitsuba will only render the requested
* regions. \default{Unused}
* }
* \parameter{pixelFormat}{\String}{Specifies the desired pixel format
* for OpenEXR output images. The options are \code{luminance},
* \code{luminanceAlpha}, \code{rgb}, \code{rgba}, \code{xyz},
* \code{xyza}, \code{spectrum}, and \code{spectrumAlpha}. In the latter two cases,
* the number of written channels depends on the value assigned to
* \code{SPECTRUM\_SAMPLES} during compilation (see Section~\ref{sec:compiling}
* for details)
* \default{\code{rgb}}
* }
* \parameter{componentFormat}{\String}{Specifies the desired floating
* point component format used for the output. The options are
* \code{float16}, \code{float32}, or \code{uint32}
* \default{\code{float16}}
* }
*
* \parameter{\Unnamed}{\RFilter}{Reconstruction filter that should
* be used by the film. \default{\code{gaussian}, a windowed Gaussian filter}}
* }
*
* This plugin implements a camera film that stores the captured image
* as a \emph{tiled} high dynamic-range OpenEXR file. It is very similar to
* \pluginref{hdrfilm}, the main difference being that it does not keep
* the rendered image in memory. Instead, image tiles are directly written
* to disk as they are being rendered, which enables renderings of extremely
* large output images that would otherwise not fit into memory (e.g.
* 100K$\times$100K).
*
* When the image can fit into memory, usage of this plugin is discouraged:
* due to the extra overhead of tracking image tiles, the rendering process
* will be slower, and the output files also generally do not compress as
* well as those produced by \pluginref{hdrfilm}.
*
* Based on the provided parameter values, the film will either write a luminance,
* luminance/alpha, RGB(A), XYZ(A) tristimulus, or spectrum/spectrum-alpha-based
* bitmap having a \code{float16}, \code{float32}, or \code{uint32}-based
* internal representation. The default is RGB and \code{float16}.
* Note that the spectral output options only make sense when using a
* custom compiled Mitsuba distribution that has spectral rendering
* enabled. This is not the case for the downloadable release builds.
*
* When RGB output is selected, the measured spectral power distributions are
* converted to linear RGB based on the CIE 1931 XYZ color matching curves and
* the ITU-R Rec. BT.709 primaries with a D65 white point.
*
* \remarks{
* \item This film is only meant for command line-based rendering. When
* used with \texttt{mtsgui}, the preview image will be black.
* \item This plugin is slower than \pluginref{hdrfilm}, and therefore should
* only be used when the output image is too large to fit into system memory.
* }
*/
class TiledHDRFilm : public Film {
public:
TiledHDRFilm(const Properties &props) : Film(props), m_output(NULL), m_frameBuffer(NULL) {
std::vector pixelFormats = tokenize(boost::to_lower_copy(
props.getString("pixelFormat", "rgb")), " ,");
std::vector channelNames = tokenize(
props.getString("channelNames", ""), ", ");
std::string componentFormat = boost::to_lower_copy(
props.getString("componentFormat", "float16"));
if (pixelFormats.empty())
Log(EError, "At least one pixel format must be specified!");
if (pixelFormats.size() != 1 && channelNames.size() != pixelFormats.size())
Log(EError, "Number of channel names must match the number of specified pixel formats!");
for (size_t i=0; i coverage = Spectrum::getBinCoverage(i);
m_channelNames.push_back(name + formatString("%.2f-%.2fnm", coverage.first, coverage.second));
}
} else if (pixelFormat == "spectrumalpha") {
m_pixelFormats.push_back(Bitmap::ESpectrumAlpha);
for (int i=0; i coverage = Spectrum::getBinCoverage(i);
m_channelNames.push_back(name + formatString("%.2f-%.2fnm", coverage.first, coverage.second));
}
m_channelNames.push_back(name + "A");
} else {
Log(EError, "The \"pixelFormat\" parameter must either be equal to "
"\"luminance\", \"luminanceAlpha\", \"rgb\", \"rgba\", \"xyz\", \"xyza\", "
"\"spectrum\", or \"spectrumAlpha\"!");
}
}
for (size_t i=0; ireadUInt());
for (size_t i=0; ireadUInt();
m_channelNames.resize((size_t) stream->readUInt());
for (size_t i=0; ireadString();
m_componentFormat = (Bitmap::EComponentFormat) stream->readUInt();
}
virtual ~TiledHDRFilm() {
develop(NULL, 0);
}
void serialize(Stream *stream, InstanceManager *manager) const {
Film::serialize(stream, manager);
for (size_t i=0; iwriteUInt(m_pixelFormats[i]);
stream->writeUInt((uint32_t) m_channelNames.size());
for (size_t i=0; iwriteString(m_channelNames[i]);
stream->writeUInt(m_componentFormat);
}
void setDestinationFile(const fs::path &destFile, uint32_t blockSize) {
if (m_output)
develop(NULL, 0);
fs::path filename = destFile;
std::string extension = boost::to_lower_copy(filename.extension().string());
if (extension != ".exr")
filename.replace_extension(".exr");
Log(EInfo, "Commencing creation of a tiled EXR image at \"%s\" ..", filename.string().c_str());
Imf::Header header(m_size.x, m_size.y);
header.setTileDescription(Imf::TileDescription(blockSize, blockSize, Imf::ONE_LEVEL));
header.insert("generated-by", Imf::StringAttribute("Mitsuba version " MTS_VERSION));
if (m_pixelFormats.size() == 1) {
/* Write a chromaticity tag when this is possible */
Bitmap::EPixelFormat pixelFormat = m_pixelFormats[0];
if (pixelFormat == Bitmap::EXYZ || pixelFormat == Bitmap::EXYZA) {
Imf::addChromaticities(header, Imf::Chromaticities(
Imath::V2f(1.0f, 0.0f),
Imath::V2f(0.0f, 1.0f),
Imath::V2f(0.0f, 0.0f),
Imath::V2f(1.0f/3.0f, 1.0f/3.0f)));
} else if (pixelFormat == Bitmap::ERGB || pixelFormat == Bitmap::ERGBA) {
Imf::addChromaticities(header, Imf::Chromaticities());
}
}
Imf::PixelType compType;
size_t compStride;
if (m_componentFormat == Bitmap::EFloat16) {
compType = Imf::HALF;
compStride = 2;
} else if (m_componentFormat == Bitmap::EFloat32) {
compType = Imf::FLOAT;
compStride = 4;
} else if (m_componentFormat == Bitmap::EUInt32) {
compType = Imf::UINT;
compStride = 4;
} else {
Log(EError, "Invalid component type (must be "
"float16, float32, or uint32)");
return;
}
Imf::ChannelList &channels = header.channels();
for (size_t i=0; isetChannelNames(m_channelNames);
}
char *ptr = (char *) m_tile->getUInt8Data();
for (size_t i=0; iinsert(m_channelNames[i].c_str(),
Imf::Slice(compType, ptr, m_pixelStride, m_rowStride));
ptr += compStride;
}
m_output->setFrameBuffer(*m_frameBuffer);
m_peakUsage = 0;
}
void put(const ImageBlock *block) {
Assert(m_output != NULL);
if ((block->getOffset().x % m_blockSize) != 0 ||
(block->getOffset().y % m_blockSize) != 0)
Log(EError, "Encountered an unaligned block!");
if (block->getSize().x > m_blockSize ||
block->getSize().y > m_blockSize)
Log(EError, "Encountered an oversized block!");
int x = block->getOffset().x / (int) m_blockSize;
int y = block->getOffset().y / (int) m_blockSize;
/* Create two copies: a clean one, and one that is used for accumulation */
ref copy1, copy2;
if (m_freeBlocks.size() > 0) {
copy1 = m_freeBlocks.back();
block->copyTo(copy1);
m_freeBlocks.pop_back();
} else {
copy1 = block->clone();
copy1->incRef();
++m_peakUsage;
}
if (m_freeBlocks.size() > 0) {
copy2 = m_freeBlocks.back();
block->copyTo(copy2);
m_freeBlocks.pop_back();
} else {
copy2 = block->clone();
copy2->incRef();
++m_peakUsage;
}
uint32_t idx = (uint32_t) x + (uint32_t) y * m_blocksH;
m_origBlocks[idx] = copy1;
m_mergedBlocks[idx] = copy2;
for (int yo = -1; yo <= 1; ++yo)
for (int xo = -1; xo <= 1; ++xo)
potentiallyWrite(x + xo, y + yo);
}
void setBitmap(const Bitmap *bitmap, Float multiplier) {
Log(EError, "setBitmap(): Global image updates are permitted by this film, "
"which operates strictly on tiles! Please either switch to a compatible "
"rendering technique or use a non-tiled film. (e.g. 'hdrfilm')");
}
void addBitmap(const Bitmap *bitmap, Float multiplier) {
Log(EError, "addBitmap(): Global image updates are permitted by this film, "
"which operates strictly on tiles! Please either switch to a compatible "
"rendering technique or use a non-tiled film. (e.g. 'hdrfilm')");
}
void potentiallyWrite(int x, int y) {
if (x < 0 || y < 0 || x >= m_blocksH || y >= m_blocksV)
return;
uint32_t idx = (uint32_t) x + (uint32_t) y * m_blocksH;
std::map::iterator it = m_origBlocks.find(idx);
if (it == m_origBlocks.end())
return;
ImageBlock *origBlock = it->second;
if (origBlock == NULL)
return;
/* This could be accelerated using some counters */
for (int yo = -1; yo <= 1; ++yo) {
for (int xo = -1; xo <= 1; ++xo) {
int xp = x + xo, yp = y + yo;
if (xp < 0 || yp < 0 || xp >= m_blocksH || yp >= m_blocksV
|| (xp == x && yp == y))
continue;
uint32_t idx2 = (uint32_t) xp + (uint32_t) yp * m_blocksH;
if (m_origBlocks.find(idx2) == m_origBlocks.end())
return; /* Not all neighboring blocks are there yet */
}
}
ImageBlock *mergedBlock = m_mergedBlocks[idx];
if (mergedBlock == NULL)
return;
/* All neighboring blocks are there -- join overlapping regions */
for (int yo = -1; yo <= 1; ++yo) {
for (int xo = -1; xo <= 1; ++xo) {
int xp = x + xo, yp = y + yo;
if (xp < 0 || yp < 0 || xp >= m_blocksH || yp >= m_blocksV
|| (xp == x && yp == y))
continue;
uint32_t idx2 = (uint32_t) xp + (uint32_t) yp * m_blocksH;
ImageBlock *origBlock2 = m_origBlocks[idx2];
ImageBlock *mergedBlock2 = m_mergedBlocks[idx2];
if (!origBlock2 || !mergedBlock2)
continue;
mergedBlock->put(origBlock2);
mergedBlock2->put(origBlock);
}
}
const Bitmap *source = mergedBlock->getBitmap();
size_t sourceBpp = source->getBytesPerPixel();
size_t targetBpp = m_tile->getBytesPerPixel();
const uint8_t *sourceData = source->getUInt8Data()
+ mergedBlock->getBorderSize() * sourceBpp * (1 + source->getWidth());
uint8_t *targetData = m_tile->getUInt8Data();
const FormatConverter *cvt = FormatConverter::getInstance(
std::make_pair(Bitmap::EFloat, m_tile->getComponentFormat())
);
for (int i=0; iconvert(source->getPixelFormat(), 1.0f, sourceData,
m_tile->getPixelFormat(), m_tile->getGamma(), targetData,
m_tile->getWidth());
else
Bitmap::convertMultiSpectrumAlphaWeight(source, sourceData,
m_tile, targetData, m_pixelFormats, m_componentFormat, m_tile->getWidth());
sourceData += source->getWidth() * sourceBpp;
targetData += m_tile->getWidth() * targetBpp;
}
/* Commit to disk */
size_t ptrOffset = mergedBlock->getOffset().x * m_pixelStride +
mergedBlock->getOffset().y * m_rowStride;
for (Imf::FrameBuffer::Iterator it = m_frameBuffer->begin();
it != m_frameBuffer->end(); ++it)
it.slice().base -= ptrOffset;
m_output->setFrameBuffer(*m_frameBuffer);
m_output->writeTile(x, y);
for (Imf::FrameBuffer::Iterator it = m_frameBuffer->begin();
it != m_frameBuffer->end(); ++it)
it.slice().base += ptrOffset;
/* Release the block */
m_freeBlocks.push_back(origBlock);
m_freeBlocks.push_back(mergedBlock);
m_origBlocks[idx] = NULL;
m_mergedBlocks[idx] = NULL;
}
bool develop(const Point2i &sourceOffset, const Vector2i &size,
const Point2i &targetOffset, Bitmap *target) const {
target->fillRect(targetOffset, size, Spectrum(0.0f));
return false; /* Not supported by the tiled EXR film! */
}
void develop(const Scene *scene, Float renderTime) {
if (m_output) {
Log(EInfo, "Closing EXR file (%u tiles in total, peak memory usage: %u tiles)..",
m_blocksH * m_blocksV, m_peakUsage);
delete m_output;
delete m_frameBuffer;
m_output = NULL;
m_frameBuffer = NULL;
m_tile = NULL;
for (std::vector::iterator it = m_freeBlocks.begin();
it != m_freeBlocks.end(); ++it)
(*it)->decRef();
m_freeBlocks.clear();
for (std::map::iterator it = m_origBlocks.begin();
it != m_origBlocks.end(); ++it) {
if ((*it).second)
(*it).second->decRef();
}
m_origBlocks.clear();
for (std::map::iterator it = m_mergedBlocks.begin();
it != m_mergedBlocks.end(); ++it) {
if ((*it).second)
(*it).second->decRef();
}
m_mergedBlocks.clear();
}
}
void clear() { /* Do nothing */ }
bool hasAlpha() const {
for (size_t i=0; i m_pixelFormats;
std::vector m_channelNames;
Bitmap::EComponentFormat m_componentFormat;
std::vector m_freeBlocks;
std::map m_origBlocks, m_mergedBlocks;
Imf::TiledOutputFile *m_output;
Imf::FrameBuffer *m_frameBuffer;
ref m_tile;
size_t m_pixelStride, m_rowStride;
int m_blocksH, m_blocksV, m_peakUsage;
int m_blockSize;
};
MTS_IMPLEMENT_CLASS_S(TiledHDRFilm, false, Film)
MTS_EXPORT_PLUGIN(TiledHDRFilm, "Tiled high dynamic range film");
MTS_NAMESPACE_END