diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 7a24e0b023..89d11032bd 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -281,7 +281,8 @@ enum class PixelDataType : uint8_t { INT, HALF, FLOAT, - COMPRESSED + COMPRESSED, + UINT_10F_11F_11F_REV, }; enum class CompressedPixelDataType : uint16_t { diff --git a/filament/backend/include/backend/PixelBufferDescriptor.h b/filament/backend/include/backend/PixelBufferDescriptor.h index 727ac1a228..550388f9d1 100644 --- a/filament/backend/include/backend/PixelBufferDescriptor.h +++ b/filament/backend/include/backend/PixelBufferDescriptor.h @@ -107,6 +107,7 @@ public: case PixelDataType::UINT: case PixelDataType::INT: case PixelDataType::FLOAT: + case PixelDataType::UINT_10F_11F_11F_REV: bpp *= 4; break; } diff --git a/filament/backend/src/opengl/GLUtils.h b/filament/backend/src/opengl/GLUtils.h index 6442d5f279..7464318adc 100644 --- a/filament/backend/src/opengl/GLUtils.h +++ b/filament/backend/src/opengl/GLUtils.h @@ -262,6 +262,7 @@ constexpr inline GLenum getType(backend::PixelDataType type) noexcept { case PixelDataType::INT: return GL_INT; case PixelDataType::HALF: return GL_HALF_FLOAT; case PixelDataType::FLOAT: return GL_FLOAT; + case PixelDataType::UINT_10F_11F_11F_REV: return GL_UNSIGNED_INT_10F_11F_11F_REV; case PixelDataType::COMPRESSED: return 0; // should never happen } } diff --git a/libs/image/include/image/ColorTransform.h b/libs/image/include/image/ColorTransform.h index 4a778d9116..6d87475625 100644 --- a/libs/image/include/image/ColorTransform.h +++ b/libs/image/include/image/ColorTransform.h @@ -24,11 +24,25 @@ #include #include #include +#include #include namespace image { +template +uint32_t linearToRGB_10_11_11_REV(const T& linear) { + using fp11 = filament::math::fp<0, 5, 6>; + using fp10 = filament::math::fp<0, 5, 5>; + fp11 r = fp11::fromf(linear[0]); + fp11 g = fp11::fromf(linear[1]); + fp10 b = fp10::fromf(linear[2]); + uint32_t ir = r.bits & 0x7FF; + uint32_t ig = g.bits & 0x7FF; + uint32_t ib = b.bits & 0x3FF; + return (ib << 22) | (ig << 11) | ir; +} + template inline filament::math::float4 linearToRGBM(const T& linear) { using filament::math::float4; @@ -210,6 +224,26 @@ std::unique_ptr fromLinearToRGBM(const LinearImage& image) { return dst; } +// Creates a 3-channel RGB_10_11_11_REV image from a f32 image. +// The source image can have three or more channels, but only the first three are honored. +std::unique_ptr fromLinearToRGB_10_11_11_REV(const LinearImage& image) { + using namespace filament::math; + size_t w = image.getWidth(); + size_t h = image.getHeight(); + UTILS_UNUSED_IN_RELEASE size_t channels = image.getChannels(); + assert(channels >= 3); + std::unique_ptr dst(new uint8_t[w * h * sizeof(uint32_t)]); + uint8_t* d = dst.get(); + for (size_t y = 0; y < h; ++y) { + for (size_t x = 0; x < w; ++x, d += sizeof(uint32_t)) { + auto src = image.get((uint32_t)x, (uint32_t)y); + uint32_t v = linearToRGB_10_11_11_REV(*src); + *reinterpret_cast(d) = v; + } + } + return dst; +} + // Creates a packed single-channel integer-based image from a floating-point image. // For example if T is uint8_t, then this performs a transformation from [0,1] to [0,255]. template diff --git a/libs/image/include/image/KtxUtility.h b/libs/image/include/image/KtxUtility.h index 3fc6f5090c..97d35578b3 100644 --- a/libs/image/include/image/KtxUtility.h +++ b/libs/image/include/image/KtxUtility.h @@ -151,7 +151,7 @@ namespace KtxUtility { * @param srgb Forces the KTX-specified format into an SRGB format if possible * @param rgbm Interpret alpha as an HDR multiplier */ - inline Texture* createTexture(Engine* engine, KtxBundle* ktx, bool srgb, bool rgbm) { + inline Texture* createTexture(Engine* engine, KtxBundle* ktx, bool srgb, bool rgbm = false) { auto freeKtx = [] (void* userdata) { KtxBundle* ktx = (KtxBundle*) userdata; delete ktx; @@ -218,6 +218,7 @@ namespace KtxUtility { case KtxBundle::UNSIGNED_SHORT: return PixelDataType::USHORT; case KtxBundle::HALF_FLOAT: return PixelDataType::HALF; case KtxBundle::FLOAT: return PixelDataType::FLOAT; + case KtxBundle::R11F_G11F_B10F: return PixelDataType::UINT_10F_11F_11F_REV; } return (PixelDataType) 0xff; } @@ -230,6 +231,7 @@ namespace KtxUtility { case KtxBundle::RGB: return PixelDataFormat::RGB; case KtxBundle::RGBA: return rgbm ? PixelDataFormat::RGBM : PixelDataFormat::RGBA; + case KtxBundle::R11F_G11F_B10F: return PixelDataFormat::RGB; } return (PixelDataFormat) 0xff; } diff --git a/libs/imageio/include/imageio/ImageEncoder.h b/libs/imageio/include/imageio/ImageEncoder.h index 35eea71cd2..f9c9c93ff9 100644 --- a/libs/imageio/include/imageio/ImageEncoder.h +++ b/libs/imageio/include/imageio/ImageEncoder.h @@ -40,6 +40,7 @@ public: // Default: 16 bit DDS_LINEAR, // 8-bit, 16-bit or 32-bit linear RGB, 1, 2 or 3 channels // Default: 16 bit + RGB_10_11_11_REV, // RGBA PNG file, but containing 11_11_10 data }; // Consumes linear floating-point data, returns false if unable to encode. diff --git a/libs/imageio/src/ImageEncoder.cpp b/libs/imageio/src/ImageEncoder.cpp index 988a649511..33985708cd 100644 --- a/libs/imageio/src/ImageEncoder.cpp +++ b/libs/imageio/src/ImageEncoder.cpp @@ -50,9 +50,10 @@ namespace image { class PNGEncoder : public ImageEncoder::Encoder { public: enum class PixelFormat { - sRGB, // 8-bits sRGB - RGBM, // 8-bits RGBM - LINEAR_RGB, // 8-bits RGB + sRGB, // 8-bits sRGB + RGBM, // 8-bits RGBM + LINEAR_RGB, // 8-bits RGB + RGB_10_11_11_REV, }; static PNGEncoder* create(std::ostream& stream, PixelFormat format = PixelFormat::sRGB); @@ -197,6 +198,9 @@ bool ImageEncoder::encode(std::ostream& stream, Format format, const LinearImage case Format::PNG_LINEAR: encoder.reset(PNGEncoder::create(stream, PNGEncoder::PixelFormat::LINEAR_RGB)); break; + case Format::RGB_10_11_11_REV: + encoder.reset(PNGEncoder::create(stream, PNGEncoder::PixelFormat::RGB_10_11_11_REV)); + break; case Format::HDR: encoder.reset(HDREncoder::create(stream)); break; @@ -232,6 +236,8 @@ ImageEncoder::Format ImageEncoder::chooseFormat(const std::string& name, bool fo if (ext == "rgbm") return Format::PNG; + if (ext == "rgb32f") return Format::RGB_10_11_11_REV; + if (ext == "hdr") return Format::HDR; if (ext == "psd") return Format::PSD; @@ -249,6 +255,8 @@ std::string ImageEncoder::chooseExtension(ImageEncoder::Format format) { case Format::PNG: case Format::PNG_LINEAR: return ".png"; + case Format::RGB_10_11_11_REV: + return ".rgb32f"; case Format::RGBM: return ".rgbm"; case Format::HDR: @@ -295,6 +303,7 @@ int PNGEncoder::chooseColorType(const LinearImage& image) const { case 3: switch (mFormat) { case PixelFormat::RGBM: + case PixelFormat::RGB_10_11_11_REV: return PNG_COLOR_TYPE_RGBA; default: return PNG_COLOR_TYPE_RGB; @@ -305,6 +314,7 @@ int PNGEncoder::chooseColorType(const LinearImage& image) const { uint32_t PNGEncoder::getChannelsCount() const { switch (mFormat) { case PixelFormat::RGBM: + case PixelFormat::RGB_10_11_11_REV: return 4; default: return 3; @@ -313,10 +323,21 @@ uint32_t PNGEncoder::getChannelsCount() const { bool PNGEncoder::encode(const LinearImage& image) { size_t srcChannels = image.getChannels(); - if ((mFormat == PixelFormat::RGBM && srcChannels != 3) || - (srcChannels != 1 && srcChannels != 3)) { - std::cerr << "Cannot encode PNG: " << srcChannels << " channels." << std::endl; - return false; + + switch (mFormat) { + case PixelFormat::RGBM: + case PixelFormat::RGB_10_11_11_REV: + if (srcChannels != 3) { + std::cerr << "Cannot encode PNG: " << srcChannels << " channels." << std::endl; + return false; + } + break; + default: + if (srcChannels != 1 && srcChannels != 3) { + std::cerr << "Cannot encode PNG: " << srcChannels << " channels." << std::endl; + return false; + } + break; } try { @@ -329,7 +350,7 @@ bool PNGEncoder::encode(const LinearImage& image) { 8, chooseColorType(image), PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - if (mFormat == PixelFormat::LINEAR_RGB) { + if (mFormat == PixelFormat::LINEAR_RGB || mFormat == PixelFormat::RGB_10_11_11_REV) { png_set_gAMA(mPNG, mInfo, 1.0); } else { png_set_sRGB_gAMA_and_cHRM(mPNG, mInfo, PNG_sRGB_INTENT_PERCEPTUAL); @@ -354,6 +375,9 @@ bool PNGEncoder::encode(const LinearImage& image) { case PixelFormat::LINEAR_RGB: data = fromLinearToRGB(image); break; + case PixelFormat::RGB_10_11_11_REV: + data = fromLinearToRGB_10_11_11_REV(image); + break; } } diff --git a/samples/app/IBL.cpp b/samples/app/IBL.cpp index f47e98b2e6..d515376885 100644 --- a/samples/app/IBL.cpp +++ b/samples/app/IBL.cpp @@ -78,8 +78,8 @@ bool IBL::loadFromKtx(const std::string& prefix) { KtxBundle* iblKtx = createKtx(iblPath); KtxBundle* skyKtx = createKtx(skyPath); - mSkyboxTexture = KtxUtility::createTexture(&mEngine, skyKtx, false, true); - mTexture = KtxUtility::createTexture(&mEngine, iblKtx, false, true); + mSkyboxTexture = KtxUtility::createTexture(&mEngine, skyKtx, false); + mTexture = KtxUtility::createTexture(&mEngine, iblKtx, false); std::istringstream shstring(iblKtx->getMetadata("sh")); for (float3& band : mBands) { @@ -149,7 +149,7 @@ bool IBL::loadCubemapLevel(filament::Texture** texture, const utils::Path& path, { // this is just a scope to avoid variable name hidding below int w, h; - std::string faceName = levelPrefix + faceSuffix[0] + ".rgbm"; + std::string faceName = levelPrefix + faceSuffix[0] + ".rgb32f"; Path facePath(Path::concat(path, faceName)); if (!facePath.exists()) { std::cerr << "The face " << faceName << " does not exist" << std::endl; @@ -172,20 +172,19 @@ bool IBL::loadCubemapLevel(filament::Texture** texture, const utils::Path& path, .width((uint32_t)size) .height((uint32_t)size) .levels((uint8_t)numLevels) - .format(Texture::InternalFormat::RGBA8) - .rgbm(true) + .format(Texture::InternalFormat::R11F_G11F_B10F) .sampler(Texture::Sampler::SAMPLER_CUBEMAP) .build(mEngine); } } - // RGBM encoding: 4 bytes per pixel - const size_t faceSize = size * size * 4; + // RGB_10_11_11_REV encoding: 4 bytes per pixel + const size_t faceSize = size * size * sizeof(uint32_t); Texture::FaceOffsets offsets; Texture::PixelBufferDescriptor buffer( malloc(faceSize * 6), faceSize * 6, - Texture::Format::RGBM, Texture::Type::UBYTE, + Texture::Format::RGB, Texture::Type::UINT_10F_11F_11F_REV, (Texture::PixelBufferDescriptor::Callback) &free); bool success = true; @@ -194,7 +193,7 @@ bool IBL::loadCubemapLevel(filament::Texture** texture, const utils::Path& path, for (size_t j = 0; j < 6; j++) { offsets[j] = faceSize * j; - std::string faceName = levelPrefix + faceSuffix[j] + ".rgbm"; + std::string faceName = levelPrefix + faceSuffix[j] + ".rgb32f"; Path facePath(Path::concat(path, faceName)); if (!facePath.exists()) { std::cerr << "The face " << faceName << " does not exist" << std::endl; @@ -216,7 +215,9 @@ bool IBL::loadCubemapLevel(filament::Texture** texture, const utils::Path& path, success = false; break; } - memcpy(p + offsets[j], data, size_t(w * h * 4)); + + memcpy(p + offsets[j], data, w * h * sizeof(uint32_t)); + stbi_image_free(data); } diff --git a/tools/cmgen/src/cmgen.cpp b/tools/cmgen/src/cmgen.cpp index 0d2d17d17b..26dd1dd384 100644 --- a/tools/cmgen/src/cmgen.cpp +++ b/tools/cmgen/src/cmgen.cpp @@ -148,9 +148,9 @@ static void printUsage(char* name) { " Quiet mode. Suppress all non-error output\n\n" " --type=[cubemap|equirect|octahedron|ktx], -t [cubemap|equirect|octahedron|ktx]\n" " Specify output type (default: cubemap)\n\n" - " --format=[exr|hdr|psd|rgbm|png|dds|ktx], -f [exr|hdr|psd|rgbm|png|dds|ktx]\n" + " --format=[exr|hdr|psd|rgbm|rgb32f|png|dds|ktx], -f [exr|hdr|psd|rgbm|rgb32f|png|dds|ktx]\n" " Specify output file format. ktx implies -type=ktx.\n" - " KTX files are always encoded with 4-channel RGBM data\n\n" + " KTX files are always encoded with 3-channel RGB_10_11_11_REV data\n\n" " --compression=COMPRESSION, -c COMPRESSION\n" " Format specific compression:\n" " KTX:\n" @@ -184,7 +184,7 @@ static void printUsage(char* name) { " Generate irradiance SH for shader code\n\n" "\n" "Private use only:\n" - " --ibl-dfg=filename.[exr|hdr|psd|png|rgbm|dds|h|hpp|c|cpp|inc|txt]\n" + " --ibl-dfg=filename.[exr|hdr|psd|png|rgbm|rgb32f|dds|h|hpp|c|cpp|inc|txt]\n" " Compute the IBL DFG LUT\n\n" " --ibl-dfg-multiscatter\n" " If --ibl-dfg is set, computes the DFG for multi-scattering GGX\n\n" @@ -196,7 +196,7 @@ static void printUsage(char* name) { " Diffuse irradiance into \n\n" " --sh=bands\n" " SH decomposition of input cubemap\n\n" - " --sh-output=filename.[exr|hdr|psd|rgbm|png|dds|txt]\n" + " --sh-output=filename.[exr|hdr|psd|rgbm|rgb32f|png|dds|txt]\n" " SH output format. The filename extension determines the output format\n\n" " --sh-irradiance, -i\n" " Irradiance SH coefficients\n\n" @@ -296,6 +296,10 @@ static int handleCommandLineArgments(int argc, char* argv[]) { g_format = ImageEncoder::Format::RGBM; format_specified = true; } + if (arg == "rgb32f") { + g_format = ImageEncoder::Format::RGB_10_11_11_REV; + format_specified = true; + } if (arg == "exr") { g_format = ImageEncoder::Format::EXR; format_specified = true; @@ -410,7 +414,7 @@ static int handleCommandLineArgments(int argc, char* argv[]) { } if (g_deploy && !format_specified) { - g_format = ImageEncoder::Format::RGBM; + g_format = ImageEncoder::Format::RGB_10_11_11_REV; } if (num_sh_bands && g_sh_compute) { @@ -825,11 +829,11 @@ void iblRoughnessPrefilter(const utils::Path& iname, KtxBundle container((uint32_t) numLevels, 1, true); container.info() = { .endianness = KtxBundle::ENDIAN_DEFAULT, - .glType = KtxBundle::UNSIGNED_BYTE, + .glType = KtxBundle::R11F_G11F_B10F, .glTypeSize = 1, - .glFormat = KtxBundle::RGBA, - .glInternalFormat = KtxBundle::RGBA8, - .glBaseInternalFormat = KtxBundle::RGBA, + .glFormat = KtxBundle::R11F_G11F_B10F, + .glInternalFormat = KtxBundle::R11F_G11F_B10F, + .glBaseInternalFormat = KtxBundle::R11F_G11F_B10F, .pixelWidth = 1U << baseExp, .pixelHeight = 1U << baseExp, .pixelDepth = 0, @@ -1057,11 +1061,11 @@ void extractCubemapFaces(const utils::Path& iname, const Cubemap& cm, const util KtxBundle container(1, 1, true); container.info() = { .endianness = KtxBundle::ENDIAN_DEFAULT, - .glType = KtxBundle::UNSIGNED_BYTE, + .glType = KtxBundle::R11F_G11F_B10F, .glTypeSize = 1, - .glFormat = KtxBundle::RGBA, - .glInternalFormat = KtxBundle::RGBA8, - .glBaseInternalFormat = KtxBundle::RGBA, + .glFormat = KtxBundle::R11F_G11F_B10F, + .glInternalFormat = KtxBundle::R11F_G11F_B10F, + .glBaseInternalFormat = KtxBundle::R11F_G11F_B10F, .pixelWidth = dim, .pixelHeight = dim, .pixelDepth = 0, @@ -1141,7 +1145,9 @@ static void exportKtxFaces(KtxBundle& container, uint32_t miplevel, const Cubema // The glInternalFormat field is the only field that specifies the actual format. info.glTypeSize = 1; info.glFormat = 0; - info.glBaseInternalFormat = KtxBundle::RGBA; + // FIXME: not sure this is always correct to use RGB here, does this work with HDR formats? + info.glBaseInternalFormat = KtxBundle::RGB; + info.glInternalFormat = KtxBundle::RGB; } const uint32_t dim = (const uint32_t) cm.getDimensions(); @@ -1160,13 +1166,13 @@ static void exportKtxFaces(KtxBundle& container, uint32_t miplevel, const Cubema LinearImage image = toLinearImage(cm.getImageForFace(face)); if (compression.type != CompressionConfig::INVALID) { - CompressedTexture tex = compressTexture(compression, fromLinearToRGBM(image)); + CompressedTexture tex = compressTexture(compression, image); container.setBlob(blobIndex, tex.data.get(), tex.size); info.glInternalFormat = (uint32_t) tex.format; continue; } - auto uintData = fromLinearToRGBM(image); + auto uintData = fromLinearToRGB_10_11_11_REV(image); container.setBlob(blobIndex, uintData.get(), dim * dim * 4); } }