new generatePrefilterMipmap.a library

This commit is contained in:
Mathias Agopian
2025-07-07 14:16:16 -07:00
committed by Mathias Agopian
parent 27f7b198a6
commit 4e50e64888
4 changed files with 402 additions and 0 deletions
+1
View File
@@ -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)
@@ -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}
$<$<CONFIG:Release>:${OPTIMIZATION_FLAGS}>
$<$<AND:$<PLATFORM_ID:Darwin>,$<CONFIG:Release>>:${DARWIN_OPTIMIZATION_FLAGS}>
)
target_link_libraries(${TARGET} PRIVATE
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${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)
@@ -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 <utils/compiler.h>
#include <stddef.h>
#include <stdint.h>
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
@@ -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 <filament/Engine.h>
#include <filament/Texture.h>
#include <backend/DriverEnums.h>
#include <backend/PixelBufferDescriptor.h>
#include <ibl/Cubemap.h>
#include <ibl/CubemapIBL.h>
#include <ibl/CubemapUtils.h>
#include <ibl/Image.h>
#include <utils/compiler.h>
#include <utils/FixedCapacityVector.h>
#include <utils/JobSystem.h>
#include <utils/Panic.h>
#include <utility>
#include <stddef.h>
#include <stdint.h>
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<Cubemap>& levels, FixedCapacityVector<Image>& 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<Cubemap::Texel*>(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<uint32_t const*>(
static_cast<char const*>(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<Image>::with_capacity(texture->getLevels());
auto levels = FixedCapacityVector<Cubemap>::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