Skip to content

Commit 617eeed

Browse files
authored
Improve make_texture for very large images (#3829)
`make_texture()` already took a configuration option "maketx:forcefloat" (defaults to 1), which if set to 0 disables the conversion to a float buffer for intermediate computation. For `oiiotool -otex`, there was no way to specify this. This patch allows it to be disabled with the new optional modifier `-otex:forcefloat=0`. Also, in make_texture, this option only controlled the initial conversion of the whole buffer to float, but the downsized levels all were float either way. This patch now uses the forcefloat config option to also control the buffers used for downsizing. Disabling forcefloat like this has a speed penalty (the downsizing math on float buffers is faster than going in and out of float for every indvidual value) and loses some precision in the process of repeatedly downsizing to generate the MIP levels. But it can sure save a lot of memory if you have a ginormous uint8 image and want to avod the 4x balooning of memory. I don't recommend disabling forcefloat except for images that can't fit into memory for the texture creation without it. But when this is the right hammer to use, it can be the difference between being able to create the texture, or not. Some other improvements: * `oiiotool -otex` wasn't outputting ANY status output during texture output as maketx does, and for that matter, -v and -debug were not being influencing the behavior of make_texture at all despite it having such support. So fix to pass down the right flags. Now there are proper verbose status and debug messages when requested. * Eliminate useless call to is_monochrome by calling it only when the previously-computed pixel stats indicate that the average value of all the channels is the same (if they don't have the same average, there's no way the pixel-by-pixel values will be the same in all channels for all pixels). This saves an extra traversal of all the pixel memory during texture creation. * Use synchronized/flushed status messages so users can see them as they are issued for very long maketx tasks. This also included some conversion from old iostream stuff to fmt-based print. * For verbose/debug, more fine-grained timing for make_texture, in particular, print the time spent downsizing and writing each MIP level.
1 parent 32a361a commit 617eeed

File tree

2 files changed

+91
-68
lines changed

2 files changed

+91
-68
lines changed

src/libOpenImageIO/maketexture.cpp

Lines changed: 83 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr<ImageBuf>& img,
615615
double& stat_miptime, size_t& peak_mem)
616616
{
617617
using OIIO::pvt::errorfmt;
618+
using OIIO::Strutil::sync::print; // Be sure to use synchronized one
618619
bool envlatlmode = (mode == ImageBufAlgo::MakeTxEnvLatl);
619620
bool orig_was_overscan = (img->spec().x || img->spec().y || img->spec().z
620621
|| img->spec().full_x || img->spec().full_y
@@ -710,11 +711,18 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr<ImageBuf>& img,
710711
return false;
711712
}
712713

713-
stat_writetime += writetimer();
714+
double wtime = writetimer();
715+
stat_writetime += wtime;
716+
if (verbose) {
717+
size_t mem = Sysutil::memory_used(true);
718+
peak_mem = std::max(peak_mem, mem);
719+
print(outstream, " {:-15s} ({}) write {}\n", formatres(outspec),
720+
Strutil::memformat(mem), Strutil::timeintervalformat(wtime, 2));
721+
}
714722

715723
if (mipmap) { // Mipmap levels:
716724
if (verbose)
717-
outstream << " Mipmapping...\n" << std::flush;
725+
print(outstream, " Mipmapping...\n");
718726
std::vector<std::string> mipimages;
719727
std::string mipimages_unsplit = configspec.get_string_attribute(
720728
"maketx:mipimages");
@@ -794,16 +802,16 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr<ImageBuf>& img,
794802
return false;
795803
}
796804
if (verbose) {
797-
outstream << " Downsampling filter \""
798-
<< filter->name()
799-
<< "\" width = " << filter->width();
800-
if (sharpen > 0.0f) {
801-
outstream << ", sharpening " << sharpen << " with "
802-
<< sharpenfilt << " unsharp mask "
803-
<< (sharpen_first ? "before" : "after")
804-
<< " the resize";
805-
}
806-
outstream << "\n";
805+
print(outstream,
806+
" Downsampling filter \"{}\" width = {}",
807+
filter->name(), filter->width());
808+
if (sharpen > 0.0f)
809+
print(
810+
outstream,
811+
", sharpening {} with {} unsharp mask {} the resize",
812+
sharpen, sharpenfilt,
813+
(sharpen_first ? "before" : "after"));
814+
print(outstream, "\n");
807815
}
808816
if (do_highlight_compensation)
809817
ImageBufAlgo::rangecompress(*img, *img);
@@ -838,7 +846,8 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr<ImageBuf>& img,
838846
if (clamp_half)
839847
ImageBufAlgo::clamp(*small, *small, -HALF_MAX, HALF_MAX, true);
840848

841-
stat_miptime += miptimer();
849+
double this_miptime = miptimer();
850+
stat_miptime += this_miptime;
842851
outspec = smallspec;
843852
outspec.set_format(outputdatatype);
844853
if (envlatlmode && src_samples_border)
@@ -863,22 +872,23 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr<ImageBuf>& img,
863872
out->close();
864873
return false;
865874
}
866-
stat_writetime += writetimer();
875+
double wtime = writetimer();
876+
stat_writetime += wtime;
867877
if (verbose) {
868878
size_t mem = Sysutil::memory_used(true);
869879
peak_mem = std::max(peak_mem, mem);
870-
outstream << Strutil::sprintf(" %-15s (%s)",
871-
formatres(smallspec),
872-
Strutil::memformat(mem))
873-
<< std::endl;
880+
print(outstream, " {:-15s} ({}) downres {} write {}\n",
881+
formatres(smallspec), Strutil::memformat(mem),
882+
Strutil::timeintervalformat(this_miptime, 2),
883+
Strutil::timeintervalformat(wtime, 2));
874884
}
875885
std::swap(img, small);
876886
}
877887
}
878888

879889
if (verbose)
880-
outstream << " Wrote file: " << outputfilename << " ("
881-
<< Strutil::memformat(Sysutil::memory_used(true)) << ")\n";
890+
print(outstream, " Wrote file: {} ({})\n", outputfilename,
891+
Strutil::memformat(Sysutil::memory_used(true)));
882892
writetimer.reset();
883893
writetimer.start();
884894
if (!out->close()) {
@@ -965,6 +975,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
965975
const ImageSpec& _configspec, std::ostream* outstream_ptr)
966976
{
967977
using OIIO::pvt::errorfmt;
978+
using OIIO::Strutil::sync::print; // Be sure to use synchronized one
968979
OIIO_ASSERT(mode >= 0 && mode < ImageBufAlgo::_MakeTxLast);
969980
double stat_readtime = 0;
970981
double stat_writetime = 0;
@@ -974,15 +985,14 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
974985
size_t peak_mem = 0;
975986
Timer alltime;
976987

977-
#define STATUS(task, timer) \
978-
{ \
979-
size_t mem = Sysutil::memory_used(true); \
980-
peak_mem = std::max(peak_mem, mem); \
981-
if (verbose) \
982-
outstream << Strutil::sprintf(" %-25s %s (%s)\n", task, \
983-
Strutil::timeintervalformat(timer, \
984-
2), \
985-
Strutil::memformat(mem)); \
988+
#define STATUS(task, timer) \
989+
{ \
990+
size_t mem = Sysutil::memory_used(true); \
991+
peak_mem = std::max(peak_mem, mem); \
992+
if (verbose) \
993+
print(outstream, " {:-25s} {} ({})\n", task, \
994+
Strutil::timeintervalformat(timer, 2), \
995+
Strutil::memformat(mem)); \
986996
}
987997

988998
ImageSpec configspec = _configspec;
@@ -1273,11 +1283,13 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
12731283
bool constant_color_detect = configspec.get_int_attribute(
12741284
"maketx:constant_color_detect");
12751285
bool opaque_detect = configspec.get_int_attribute("maketx:opaque_detect");
1286+
bool monochrome_detect = configspec.get_int_attribute(
1287+
"maketx:monochrome_detect");
12761288
bool compute_average_color
12771289
= configspec.get_int_attribute("maketx:compute_average", 1);
12781290
ImageBufAlgo::PixelStats pixel_stats;
12791291
bool compute_stats = (constant_color_detect || opaque_detect
1280-
|| compute_average_color);
1292+
|| compute_average_color || monochrome_detect);
12811293
if (compute_stats) {
12821294
pixel_stats = ImageBufAlgo::computePixelStats(*src);
12831295
}
@@ -1335,14 +1347,21 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
13351347
std::swap(src, newsrc); // N.B. the old src will delete
13361348
}
13371349

1338-
// If requested - and we're a monochrome image - drop the extra channels
1339-
if (configspec.get_int_attribute("maketx:monochrome_detect")
1340-
&& nchannels <= 0 && src->nchannels() == 3
1341-
&& src->spec().alpha_channel < 0 && // RGB only
1342-
ImageBufAlgo::isMonochrome(*src)) {
1350+
// If requested - and we're a monochrome image - drop the extra channels.
1351+
// In addition to only doing this for RGB images (3 channels, no alpha),
1352+
// we also check the stat averages are the same for all three channels (if
1353+
// the channel averages are not identical, they surely cannot be the same
1354+
// for all pixels, so there is no point wasting the time of the call to
1355+
// isMonochrome().
1356+
if (monochrome_detect && nchannels <= 0 && src->nchannels() == 3
1357+
&& src->spec().alpha_channel < 0
1358+
&& pixel_stats.avg[0] == pixel_stats.avg[1]
1359+
&& pixel_stats.avg[0] == pixel_stats.avg[2]
1360+
&& ImageBufAlgo::isMonochrome(*src)) {
13431361
if (verbose)
1344-
outstream
1345-
<< " Monochrome image detected. Converting to single channel texture.\n";
1362+
print(
1363+
outstream,
1364+
" Monochrome image detected. Converting to single channel texture.\n");
13461365
std::shared_ptr<ImageBuf> newsrc(new ImageBuf(src->spec()));
13471366
ImageBufAlgo::channels(*newsrc, *src, 1, NULL, NULL, NULL, true);
13481367
newsrc->specmod().default_channel_names();
@@ -1354,7 +1373,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
13541373
if ((nchannels > 0) && (nchannels != src->nchannels())) {
13551374
if (verbose)
13561375
outstream << " Overriding number of channels to " << nchannels
1357-
<< "\n";
1376+
<< std::endl;
13581377
std::shared_ptr<ImageBuf> newsrc(new ImageBuf(src->spec()));
13591378
ImageBufAlgo::channels(*newsrc, *src, nchannels, NULL, NULL, NULL,
13601379
true);
@@ -1655,14 +1674,6 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
16551674
STATUS("color convert", stat_colorconverttime);
16561675
}
16571676

1658-
// Force float for the sake of the ImageBuf math.
1659-
// Also force float if we do not allow for the pixel shift,
1660-
// since resize_block_ requires floating point buffers.
1661-
const int allow_shift = configspec.get_int_attribute(
1662-
"maketx:allow_pixel_shift");
1663-
if (configspec.get_int_attribute("maketx:forcefloat", 1) || !allow_shift)
1664-
dstspec.set_format(TypeDesc::FLOAT);
1665-
16661677
// Handle resize to power of two, if called for
16671678
if (configspec.get_int_attribute("maketx:resize") && !shadowmode) {
16681679
dstspec.width = ceil2(dstspec.width);
@@ -1684,6 +1695,15 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
16841695
|| Strutil::iends_with(outputfilename, ".exr")))
16851696
do_resize = true;
16861697

1698+
// Force float for the sake of the ImageBuf math.
1699+
// Also force float if we do not allow for the pixel shift,
1700+
// since resize_block_ requires floating point buffers.
1701+
const int allow_shift = configspec.get_int_attribute(
1702+
"maketx:allow_pixel_shift");
1703+
if (configspec.get_int_attribute("maketx:forcefloat", 1)
1704+
|| (do_resize && !allow_shift))
1705+
dstspec.set_format(TypeDesc::FLOAT);
1706+
16871707
if (orig_was_overscan && out && !out->supports("displaywindow")) {
16881708
errorfmt(
16891709
"Format \"{}\" does not support separate display "
@@ -1705,13 +1725,16 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
17051725
toplevel = src;
17061726
} else if (!do_resize) {
17071727
// Need format conversion, but no resize -- just copy the pixels
1728+
if (verbose)
1729+
print(outstream, " Copying for format conversion from {} to {}\n",
1730+
src->spec().format, dstspec.format);
17081731
toplevel.reset(new ImageBuf(dstspec));
17091732
toplevel->copy_pixels(*src);
17101733
} else {
17111734
// Resize
17121735
if (verbose)
1713-
outstream << " Resizing image to " << dstspec.width << " x "
1714-
<< dstspec.height << std::endl;
1736+
print(outstream, " Resizing image to {} x {}\n", dstspec.width,
1737+
dstspec.height);
17151738
string_view resize_filter(filtername);
17161739
if (Strutil::istarts_with(resize_filter, "unsharp-"))
17171740
resize_filter = "lanczos3";
@@ -1888,28 +1911,22 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
18881911
if (verbose || configspec.get_int_attribute("maketx:runstats")
18891912
|| configspec.get_int_attribute("maketx:stats")) {
18901913
double all = alltime();
1891-
Strutil::fprintf(outstream, "maketx run time (seconds): %5.2f\n", all);
1892-
Strutil::fprintf(outstream, " file read: %5.2f\n",
1893-
stat_readtime);
1894-
Strutil::fprintf(outstream, " file write: %5.2f\n",
1895-
stat_writetime);
1896-
Strutil::fprintf(outstream, " initial resize: %5.2f\n",
1897-
stat_resizetime);
1898-
Strutil::fprintf(outstream, " hash: %5.2f\n",
1899-
stat_hashtime);
1900-
Strutil::fprintf(outstream, " pixelstats: %5.2f\n",
1901-
stat_pixelstatstime);
1902-
Strutil::fprintf(outstream, " mip computation: %5.2f\n", stat_miptime);
1903-
Strutil::fprintf(outstream, " color convert: %5.2f\n",
1904-
stat_colorconverttime);
1905-
Strutil::fprintf(
1914+
print(outstream, "maketx run time (seconds): {:5.2f}\n", all);
1915+
print(outstream, " file read: {:5.2f}\n", stat_readtime);
1916+
print(outstream, " file write: {:5.2f}\n", stat_writetime);
1917+
print(outstream, " initial resize: {:5.2f}\n", stat_resizetime);
1918+
print(outstream, " hash: {:5.2f}\n", stat_hashtime);
1919+
print(outstream, " pixelstats: {:5.2f}\n", stat_pixelstatstime);
1920+
print(outstream, " mip computation: {:5.2f}\n", stat_miptime);
1921+
print(outstream, " color convert: {:5.2f}\n", stat_colorconverttime);
1922+
print(
19061923
outstream,
1907-
" unaccounted: %5.2f (%5.2f %5.2f %5.2f %5.2f %5.2f)\n",
1924+
" unaccounted: {:5.2f} ({:5.2f} {:5.2f} {:5.2f} {:5.2f} {:5.2f})\n",
19081925
all - stat_readtime - stat_writetime - stat_resizetime
19091926
- stat_hashtime - stat_miptime,
19101927
misc_time_1, misc_time_2, misc_time_3, misc_time_4, misc_time_5);
1911-
Strutil::fprintf(outstream, "maketx peak memory used: %s\n",
1912-
Strutil::memformat(peak_mem));
1928+
print(outstream, "maketx peak memory used: {}\n",
1929+
Strutil::memformat(peak_mem));
19131930
}
19141931

19151932
#undef STATUS

src/oiiotool/oiiotool.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5533,6 +5533,9 @@ prep_texture_config(ImageSpec& configspec, ParamValueList& fileoptions)
55335533
fileoptions.get_float("uvslopes_scale", 0.0f));
55345534
if (fileoptions.contains("handed"))
55355535
configspec.attribute("handed", fileoptions.get_string("handed"));
5536+
if (fileoptions.contains("forcefloat"))
5537+
configspec.attribute("maketx:forcefloat",
5538+
fileoptions.get_int("forcefloat"));
55365539

55375540
// The default values here should match the initialized values
55385541
// in src/maketx/maketx.cpp
@@ -5818,8 +5821,11 @@ output_file(int /*argc*/, const char* argv[])
58185821
mode = ImageBufAlgo::MakeTxBumpWithSlopes;
58195822
// if (lightprobemode)
58205823
// mode = ImageBufAlgo::MakeTxEnvLatlFromLightProbe;
5821-
ok = ImageBufAlgo::make_texture(mode, (*ir)(0, 0), filename,
5822-
configspec);
5824+
if (ot.verbose || ot.debug)
5825+
configspec.attribute("maketx:verbose", 1);
5826+
ok = ImageBufAlgo::make_texture(mode, (*ir)(0, 0), filename, configspec,
5827+
ot.verbose || ot.debug ? &std::cout
5828+
: nullptr);
58235829
if (!ok) {
58245830
ot.errorfmt(command, "Could not make texture: {}",
58255831
OIIO::geterror());

0 commit comments

Comments
 (0)