From 4e50e64888a1dbff8153641a15e75be095c9a5da Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 2 Jul 2025 15:32:40 -0700 Subject: [PATCH] new generatePrefilterMipmap.a library --- CMakeLists.txt | 1 + libs/generatePrefilterMipmap/CMakeLists.txt | 79 ++++++ .../generatePrefilterMipmap.h | 94 ++++++++ .../src/generatePrefilterMipmap.cpp | 228 ++++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 libs/generatePrefilterMipmap/CMakeLists.txt create mode 100644 libs/generatePrefilterMipmap/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h create mode 100644 libs/generatePrefilterMipmap/src/generatePrefilterMipmap.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bc64e5a79e..673b2b6c91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -786,6 +786,7 @@ add_subdirectory(${LIBRARIES}/filabridge) add_subdirectory(${LIBRARIES}/filaflat) add_subdirectory(${LIBRARIES}/filagui) add_subdirectory(${LIBRARIES}/filameshio) +add_subdirectory(${LIBRARIES}/generatePrefilterMipmap) add_subdirectory(${LIBRARIES}/gltfio) add_subdirectory(${LIBRARIES}/ibl) add_subdirectory(${LIBRARIES}/iblprefilter) diff --git a/libs/generatePrefilterMipmap/CMakeLists.txt b/libs/generatePrefilterMipmap/CMakeLists.txt new file mode 100644 index 0000000000..026f45613f --- /dev/null +++ b/libs/generatePrefilterMipmap/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.19) +project(filament C ASM) + +set(TARGET filament-generatePrefilterMipmap) +set(PUBLIC_HDR_DIR include) +set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR}) + +# ================================================================================================== +# Sources and headers +# ================================================================================================== +set(PUBLIC_HDRS + include/${TARGET}/generatePrefilterMipmap.h +) + +set(SRCS + src/generatePrefilterMipmap.cpp +) + +# ================================================================================================== +# Includes & target definition +# ================================================================================================== +# specify where our headers are +include_directories(${PUBLIC_HDR_DIR}) +include_directories(src) + +# we're building a library +add_library(${TARGET} STATIC ${PRIVATE_HDRS} ${PUBLIC_HDRS} ${SRCS}) + +# specify where the public headers of this library are +target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) +set_target_properties(${TARGET} PROPERTIES FOLDER Libs) + +# ================================================================================================== +# Dependencies +# ================================================================================================== + +target_link_libraries(${TARGET} PUBLIC math) +target_link_libraries(${TARGET} PUBLIC utils) +target_link_libraries(${TARGET} PUBLIC ibl) +target_link_libraries(${TARGET} PRIVATE filament) + +# ================================================================================================== +# Compiler flags +# ================================================================================================== +if (MSVC) + set(OPTIMIZATION_FLAGS + /fp:fast + ) +elseif(WEBGL) + # Avoid strict-vtable-pointers here, it is broken in WebAssembly. + set(OPTIMIZATION_FLAGS -fvisibility-inlines-hidden) +else() + set(OPTIMIZATION_FLAGS + -ffast-math + -fno-finite-math-only + -ffp-contract=fast + # TODO: aggressive vectorization is currently broken on Android + # -fslp-vectorize-aggressive + -fvisibility-inlines-hidden + -fstrict-vtable-pointers + ) +endif() + +target_compile_options(${TARGET} PRIVATE + ${FILAMENT_WARNINGS} + $<$:${OPTIMIZATION_FLAGS}> + $<$,$>:${DARWIN_OPTIMIZATION_FLAGS}> +) + +target_link_libraries(${TARGET} PRIVATE + $<$,$>:${LINUX_LINKER_OPTIMIZATION_FLAGS}> +) + +# ================================================================================================== +# Installation +# ================================================================================================== +set(INSTALL_TYPE ARCHIVE) +install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR}) +install(DIRECTORY ${PUBLIC_HDR_DIR}/${TARGET} DESTINATION include) diff --git a/libs/generatePrefilterMipmap/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h b/libs/generatePrefilterMipmap/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h new file mode 100644 index 0000000000..ab506c9f3e --- /dev/null +++ b/libs/generatePrefilterMipmap/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +namespace filament { + +namespace backend { +class PixelBufferDescriptor; +} + +class Engine; +class Texture; + +struct UTILS_PUBLIC FaceOffsets { + using size_type = size_t; + union { + struct { + size_type px; //!< +x face offset in bytes + size_type nx; //!< -x face offset in bytes + size_type py; //!< +y face offset in bytes + size_type ny; //!< -y face offset in bytes + size_type pz; //!< +z face offset in bytes + size_type nz; //!< -z face offset in bytes + }; + size_type offsets[6]; + }; + size_type operator[](size_t const n) const noexcept { return offsets[n]; } + size_type& operator[](size_t const n) { return offsets[n]; } + FaceOffsets() noexcept = default; + explicit FaceOffsets(size_type const faceSize) noexcept { + px = faceSize * 0; + nx = faceSize * 1; + py = faceSize * 2; + ny = faceSize * 3; + pz = faceSize * 4; + nz = faceSize * 5; + } + FaceOffsets(const FaceOffsets& rhs) noexcept { + px = rhs.px; + nx = rhs.nx; + py = rhs.py; + ny = rhs.ny; + pz = rhs.pz; + nz = rhs.nz; + } + FaceOffsets& operator=(const FaceOffsets& rhs) noexcept { + px = rhs.px; + nx = rhs.nx; + py = rhs.py; + ny = rhs.ny; + pz = rhs.pz; + nz = rhs.nz; + return *this; + } +}; + +/** + * Options for environment prefiltering into reflection map + * + * @see generatePrefilterMipmap() + */ +struct UTILS_PUBLIC PrefilterOptions { + uint16_t sampleCount = 8; //!< sample count used for filtering + bool mirror = true; //!< whether the environment must be mirrored +private: + UTILS_UNUSED uintptr_t reserved[3] = {}; +}; + +UTILS_PUBLIC +void generatePrefilterMipmap(Texture* UTILS_NONNULL texture, Engine& engine, + backend::PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets, + PrefilterOptions const* UTILS_NULLABLE options = nullptr); + + +} // namespace filament diff --git a/libs/generatePrefilterMipmap/src/generatePrefilterMipmap.cpp b/libs/generatePrefilterMipmap/src/generatePrefilterMipmap.cpp new file mode 100644 index 0000000000..999f501efe --- /dev/null +++ b/libs/generatePrefilterMipmap/src/generatePrefilterMipmap.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filament-generatePrefilterMipmap/generatePrefilterMipmap.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + + +#include +#include +#include +#include + +#include + +#include +#include + +namespace filament { + +using namespace backend; +using namespace utils; + +void generatePrefilterMipmap(Texture* const texture, Engine& engine, + PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets, + PrefilterOptions const* options) { + using namespace ibl; + using namespace backend; + using namespace math; + + const size_t size = texture->getWidth(); + const size_t stride = buffer.stride ? buffer.stride : size; + + /* validate input data */ + + FILAMENT_CHECK_PRECONDITION( + buffer.format == PixelDataFormat::RGB || buffer.format == PixelDataFormat::RGBA) + << "input data format must be RGB or RGBA"; + + FILAMENT_CHECK_PRECONDITION(buffer.type == PixelDataType::FLOAT || + buffer.type == PixelDataType::HALF || + buffer.type == PixelDataType::UINT_10F_11F_11F_REV) + << "input data type must be FLOAT, HALF or UINT_10F_11F_11F_REV"; + + /* validate texture */ + + FILAMENT_CHECK_PRECONDITION(!(size & (size - 1))) + << "input data cubemap dimensions must be a power-of-two"; + + FILAMENT_CHECK_PRECONDITION(!Texture::isTextureFormatCompressed(texture->getFormat())) + << "reflections texture cannot be compressed"; + + PrefilterOptions const defaultOptions; + options = options ? options : &defaultOptions; + + JobSystem& js = engine.getJobSystem(); + + auto generateMipmaps = [](JobSystem& js, + FixedCapacityVector& levels, FixedCapacityVector& images) { + Image temp; + const Cubemap& base(levels[0]); + size_t dim = base.getDimensions(); + size_t mipLevel = 0; + while (dim > 1) { + dim >>= 1u; + Cubemap dst = CubemapUtils::create(temp, dim); + const Cubemap& src(levels[mipLevel++]); + CubemapUtils::downsampleCubemapLevelBoxFilter(js, dst, src); + dst.makeSeamless(); + images.push_back(std::move(temp)); + levels.push_back(std::move(dst)); + } + }; + + + /* + * Create a Cubemap data structure + */ + + size_t bytesPerPixel = 0; + + switch (buffer.format) { + case PixelDataFormat::RGB: + bytesPerPixel = 3; + break; + case PixelDataFormat::RGBA: + bytesPerPixel = 4; + break; + default: + // this cannot happen due to the checks above + break; + } + + switch (buffer.type) { // NOLINT + case PixelDataType::FLOAT: + bytesPerPixel *= 4; + break; + case PixelDataType::HALF: + bytesPerPixel *= 2; + break; + default: + // this cannot happen due to the checks above + break; + } + assert_invariant(bytesPerPixel); + + Image temp; + Cubemap cml = CubemapUtils::create(temp, size); + for (size_t j = 0; j < 6; j++) { + Cubemap::Face const face = Cubemap::Face(j); + Image const& image = cml.getImageForFace(face); + for (size_t y = 0; y < size; y++) { + Cubemap::Texel* out = static_cast(image.getPixelRef(0, y)); + if (buffer.type == PixelDataType::FLOAT) { + float3 const* src = pointermath::add((float3 const*)buffer.buffer, faceOffsets[j]); + src = pointermath::add(src, y * stride * bytesPerPixel); + for (size_t x = 0; x < size; x++, out++) { + Cubemap::writeAt(out, *src); + src = pointermath::add(src, bytesPerPixel); + } + } else if (buffer.type == PixelDataType::HALF) { + half3 const* src = pointermath::add((half3 const*)buffer.buffer, faceOffsets[j]); + src = pointermath::add(src, y * stride * bytesPerPixel); + for (size_t x = 0; x < size; x++, out++) { + Cubemap::writeAt(out, *src); + src = pointermath::add(src, bytesPerPixel); + } + } else if (buffer.type == PixelDataType::UINT_10F_11F_11F_REV) { + // this doesn't depend on buffer.format + uint32_t const* src = reinterpret_cast( + static_cast(buffer.buffer) + + faceOffsets[j]) + y * stride; + for (size_t x = 0; x < size; x++, out++, src++) { + using fp10 = fp<0, 5, 5>; + using fp11 = fp<0, 5, 6>; + fp11 const r{ uint16_t( *src & 0x7FFu) }; + fp11 const g{ uint16_t((*src >> 11u) & 0x7FFu) }; + fp10 const b{ uint16_t((*src >> 22u) & 0x3FFu) }; + Cubemap::Texel const texel{ fp11::tof(r), fp11::tof(g), fp10::tof(b) }; + Cubemap::writeAt(out, texel); + } + } + } + } + + /* + * Create the mipmap chain + */ + + auto images = FixedCapacityVector::with_capacity(texture->getLevels()); + auto levels = FixedCapacityVector::with_capacity(texture->getLevels()); + + images.push_back(std::move(temp)); + levels.push_back(std::move(cml)); + + const float3 mirror = options->mirror ? float3{ -1, 1, 1 } : float3{ 1, 1, 1 }; + + // make the cubemap seamless + levels[0].makeSeamless(); + + // Now generate all the mipmap levels + generateMipmaps(js, levels, images); + + // Finally generate each pre-filtered mipmap level + const size_t baseExp = ctz(size); + size_t const numSamples = options->sampleCount; + const size_t numLevels = baseExp + 1; + for (ssize_t i = (ssize_t)baseExp; i >= 0; --i) { + const size_t dim = 1U << i; + const size_t level = baseExp - i; + const float lod = saturate(float(level) / float(numLevels - 1)); + const float linearRoughness = lod * lod; + + Image image; + Cubemap dst = CubemapUtils::create(image, dim); + CubemapIBL::roughnessFilter(js, dst, { levels.begin(), uint32_t(levels.size()) }, + linearRoughness, numSamples, mirror, true); + + PixelBufferDescriptor const pbd(image.getData(), image.getSize(), + PixelBufferDescriptor::PixelDataFormat::RGB, + PixelBufferDescriptor::PixelDataType::FLOAT, 1, 0, 0, image.getStride()); + + uintptr_t const base = uintptr_t(image.getData()); + for (size_t j = 0; j < 6; j++) { + Image const& faceImage = dst.getImageForFace(Cubemap::Face(j)); + auto offset = uintptr_t(faceImage.getData()) - base; + texture->setImage(engine, level, 0, 0, j, dim, dim, 1, + PixelBufferDescriptor::make((char*) image.getData() + offset, + dim * dim * 3 * sizeof(float), + PixelBufferDescriptor::PixelDataFormat::RGB, + PixelBufferDescriptor::PixelDataType::FLOAT, 1, + 0, 0, uint32_t(image.getStride()), + [j, &image](void const*, size_t) { + // free the buffer only when processing the last image + if (j == 5) { + image.detach(); + } + })); + } + } + + // no need to call the user callback because buffer is a reference, and it'll be destroyed + // by the caller (without being move()d here). +} + +} // namespace filament