Compare commits
52 Commits
ebridgewat
...
v1.9.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe23aa917d | ||
|
|
eb8a29a332 | ||
|
|
0626902530 | ||
|
|
042bfe2597 | ||
|
|
97133f3591 | ||
|
|
d06cc4390e | ||
|
|
6047d3235f | ||
|
|
2cda6e35bd | ||
|
|
8f8d51e17b | ||
|
|
6919e3b274 | ||
|
|
10bf944410 | ||
|
|
9a2f6fdb53 | ||
|
|
761977d385 | ||
|
|
21248f15b5 | ||
|
|
4f32817f6d | ||
|
|
cc9e05e711 | ||
|
|
419d68d4db | ||
|
|
8450232448 | ||
|
|
cc51726590 | ||
|
|
318e22af51 | ||
|
|
68ac87dc24 | ||
|
|
acb8f00075 | ||
|
|
06d9183aaa | ||
|
|
75af25419d | ||
|
|
f6b90d2a31 | ||
|
|
a3822f4af0 | ||
|
|
bcdad769ff | ||
|
|
be4fb4fdbb | ||
|
|
65394f6301 | ||
|
|
b0beee03bc | ||
|
|
fe1de41b8e | ||
|
|
a37b431e87 | ||
|
|
98107016b9 | ||
|
|
8bccfc2863 | ||
|
|
f54a0a3452 | ||
|
|
6778ab0624 | ||
|
|
269d636785 | ||
|
|
39862c91ce | ||
|
|
523f4026b4 | ||
|
|
a6bf162431 | ||
|
|
826e8d181c | ||
|
|
16dfadbba0 | ||
|
|
5cbb97551f | ||
|
|
defee767c3 | ||
|
|
9560318521 | ||
|
|
ef09feb048 | ||
|
|
39f323fe09 | ||
|
|
11b95304ea | ||
|
|
b7c30a7916 | ||
|
|
4cae48fc77 | ||
|
|
d1a93f0557 | ||
|
|
b93059fad7 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.9.9'
|
||||
implementation 'com.google.android.filament:filament-android:1.9.10'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -63,7 +63,7 @@ A much smaller alternative to `filamat-android` that can only generate OpenGL sh
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```
|
||||
pod 'Filament', '~> 1.9.9'
|
||||
pod 'Filament', '~> 1.9.10'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -5,9 +5,21 @@ A new header is inserted each time a *tag* is created.
|
||||
|
||||
## Next release (main branch)
|
||||
|
||||
## v.1.9.11
|
||||
|
||||
## v1.9.10
|
||||
|
||||
- Fix EXC_BAD_INSTRUCTION seen when using headless SwapChains on macOS with OpenGL.
|
||||
- Introduce libibl_lite library.
|
||||
- engine: Fix EXC_BAD_INSTRUCTION seen when using headless SwapChains on macOS with OpenGL.
|
||||
- engine: Add new callback API to SwapChain.
|
||||
- engine: Fix SwiftShader crash when using an IBL without a reflections texture.
|
||||
- filamat: Shrink internal Skybox material size.
|
||||
- filamat: improvements to generated material size.
|
||||
- filamat: silence spirv-opt warnings in release builds.
|
||||
- matc: Add fog variant filter.
|
||||
- matc: Fix crash when building mobile materials.
|
||||
- math: reduce template bloat for matrices.
|
||||
- Vulkan: robustness improvements.
|
||||
|
||||
## v1.9.9
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ add_library(utils STATIC IMPORTED)
|
||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||
|
||||
add_library(ibl STATIC IMPORTED)
|
||||
set_target_properties(ibl PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl.a)
|
||||
add_library(ibl-lite STATIC IMPORTED)
|
||||
set_target_properties(ibl-lite PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl-lite.a)
|
||||
|
||||
add_library(filaflat STATIC IMPORTED)
|
||||
set_target_properties(filaflat PROPERTIES IMPORTED_LOCATION
|
||||
@@ -92,7 +92,7 @@ target_link_libraries(filament-jni
|
||||
PRIVATE filaflat
|
||||
PRIVATE filabridge
|
||||
PRIVATE geometry
|
||||
PRIVATE ibl
|
||||
PRIVATE ibl-lite
|
||||
PRIVATE log
|
||||
PRIVATE GLESv3
|
||||
PRIVATE EGL
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.9.9
|
||||
VERSION_NAME=1.9.10
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -350,7 +350,7 @@ target_link_libraries(${TARGET} PUBLIC utils)
|
||||
target_link_libraries(${TARGET} PUBLIC geometry) # TODO: remove this dependency after deprecating VertexBuffer::populateTangentQuaternions
|
||||
target_link_libraries(${TARGET} PUBLIC filaflat)
|
||||
target_link_libraries(${TARGET} PUBLIC filabridge)
|
||||
target_link_libraries(${TARGET} PUBLIC ibl)
|
||||
target_link_libraries(${TARGET} PUBLIC ibl-lite)
|
||||
|
||||
if (FILAMENT_ENABLE_MATDBG)
|
||||
target_link_libraries(${TARGET} PUBLIC matdbg)
|
||||
|
||||
@@ -77,6 +77,10 @@ struct WGLSwapChain {
|
||||
};
|
||||
|
||||
Driver* PlatformWGL::createDriver(void* const sharedGLContext) noexcept {
|
||||
int result = 0;
|
||||
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
|
||||
int pixelFormat = 0;
|
||||
|
||||
mPfd = {
|
||||
sizeof(PIXELFORMATDESCRIPTOR),
|
||||
1,
|
||||
@@ -112,7 +116,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext) noexcept {
|
||||
goto error;
|
||||
}
|
||||
|
||||
int pixelFormat = ChoosePixelFormat(whdc, &mPfd);
|
||||
pixelFormat = ChoosePixelFormat(whdc, &mPfd);
|
||||
SetPixelFormat(whdc, pixelFormat, &mPfd);
|
||||
|
||||
// We need a tmp context to retrieve and call wglCreateContextAttribsARB.
|
||||
@@ -123,7 +127,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext) noexcept {
|
||||
goto error;
|
||||
}
|
||||
|
||||
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs =
|
||||
wglCreateContextAttribs =
|
||||
(PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
|
||||
mContext = wglCreateContextAttribs(whdc, (HGLRC) sharedGLContext, attribs);
|
||||
if (!mContext) {
|
||||
@@ -141,7 +145,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext) noexcept {
|
||||
goto error;
|
||||
}
|
||||
|
||||
int result = bluegl::bind();
|
||||
result = bluegl::bind();
|
||||
ASSERT_POSTCONDITION(!result, "Unable to load OpenGL entry points.");
|
||||
return OpenGLDriverFactory::create(this, sharedGLContext);
|
||||
|
||||
|
||||
@@ -595,7 +595,7 @@ void waitForIdle(VulkanContext& context) {
|
||||
}
|
||||
}
|
||||
if (nfences > 0) {
|
||||
vkWaitForFences(context.device, nfences, fences, VK_FALSE, ~0ull);
|
||||
vkWaitForFences(context.device, nfences, fences, VK_TRUE, UINT64_MAX);
|
||||
}
|
||||
|
||||
// Next flush the active command buffer and wait for it to finish.
|
||||
@@ -638,7 +638,7 @@ bool acquireSwapCommandBuffer(VulkanContext& context) {
|
||||
// Ensure that the previous submission of this command buffer has finished.
|
||||
auto& cmdfence = swap.commands.fence;
|
||||
if (cmdfence) {
|
||||
VkResult result = vkWaitForFences(context.device, 1, &cmdfence->fence, VK_FALSE, UINT64_MAX);
|
||||
VkResult result = vkWaitForFences(context.device, 1, &cmdfence->fence, VK_TRUE, UINT64_MAX);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkWaitForFences error.");
|
||||
}
|
||||
|
||||
@@ -685,7 +685,7 @@ void flushCommandBuffer(VulkanContext& context) {
|
||||
cmdfence->condition.notify_all();
|
||||
|
||||
// Restart the command buffer.
|
||||
error = vkWaitForFences(context.device, 1, &cmdfence->fence, VK_FALSE, UINT64_MAX);
|
||||
error = vkWaitForFences(context.device, 1, &cmdfence->fence, VK_TRUE, UINT64_MAX);
|
||||
ASSERT_POSTCONDITION(!error, "vkWaitForFences error.");
|
||||
error = vkResetFences(context.device, 1, &cmdfence->fence);
|
||||
ASSERT_POSTCONDITION(!error, "vkResetFences error.");
|
||||
@@ -720,7 +720,7 @@ VkCommandBuffer acquireWorkCommandBuffer(VulkanContext& context) {
|
||||
const VkCommandBufferBeginInfo binfo { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
||||
if (work.fence && work.fence->submitted) {
|
||||
work.fence->submitted = false;
|
||||
vkWaitForFences(context.device, 1, &work.fence->fence, VK_FALSE, UINT64_MAX);
|
||||
vkWaitForFences(context.device, 1, &work.fence->fence, VK_TRUE, UINT64_MAX);
|
||||
vkResetCommandBuffer(work.cmdbuffer, 0);
|
||||
vkBeginCommandBuffer(work.cmdbuffer, &binfo);
|
||||
}
|
||||
|
||||
@@ -757,7 +757,7 @@ FenceStatus VulkanDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
||||
if (cmdfence->swapChainDestroyed) {
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
VkResult result = vkWaitForFences(mContext.device, 1, &cmdfence->fence, VK_FALSE, timeout);
|
||||
VkResult result = vkWaitForFences(mContext.device, 1, &cmdfence->fence, VK_TRUE, timeout);
|
||||
return result == VK_SUCCESS ? FenceStatus::CONDITION_SATISFIED : FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
|
||||
|
||||
@@ -437,6 +437,10 @@ struct Config {
|
||||
size_t lutDimension;
|
||||
};
|
||||
|
||||
// Inside the FColorGrading constructor, TSAN sporadically detects a data race on the config struct;
|
||||
// the Filament thread writes and the Job thread reads. In practice there should be no data race, so
|
||||
// we force TSAN off to silence the warning.
|
||||
UTILS_NO_SANITIZE_THREAD
|
||||
FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
|
||||
@@ -237,7 +237,6 @@ void FEngine::init() {
|
||||
// 3 bands = 9 float3
|
||||
const float sh[9 * 3] = { 0.0f };
|
||||
mDefaultIbl = upcast(IndirectLight::Builder()
|
||||
.reflections(mDefaultIblTexture)
|
||||
.irradiance(3, reinterpret_cast<const float3*>(sh))
|
||||
.build(*this));
|
||||
|
||||
|
||||
@@ -398,10 +398,10 @@ void ShadowMap::computeShadowCameraDirectional(
|
||||
}
|
||||
|
||||
const mat4f F(mat4f::row_major_init {
|
||||
s.x, 0, 0, o.x,
|
||||
0, s.y, 0, o.y,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
s.x, 0.0f, 0.0f, o.x,
|
||||
0.0f, s.y, 0.0f, o.y,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f,
|
||||
});
|
||||
|
||||
|
||||
@@ -579,42 +579,42 @@ mat4f ShadowMap::getTextureCoordsMapping() const noexcept {
|
||||
// remapping from NDC to texture coordinates (i.e. [-1,1] -> [0, 1])
|
||||
// ([1, 0] for depth mapping)
|
||||
const mat4f Mt(mClipSpaceFlipped ? mat4f::row_major_init{
|
||||
0.5f, 0, 0, 0.5f,
|
||||
0, -0.5f, 0, 0.5f,
|
||||
0, 0, -0.5f, 0.5f,
|
||||
0, 0, 0, 1
|
||||
0.5f, 0.0f, 0.0f, 0.5f,
|
||||
0.0f, -0.5f, 0.0f, 0.5f,
|
||||
0.0f, 0.0f, -0.5f, 0.5f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
} : mat4f::row_major_init{
|
||||
0.5f, 0, 0, 0.5f,
|
||||
0, 0.5f, 0, 0.5f,
|
||||
0, 0, -0.5f, 0.5f,
|
||||
0, 0, 0, 1
|
||||
0.5f, 0.0f, 0.0f, 0.5f,
|
||||
0.0f, 0.5f, 0.0f, 0.5f,
|
||||
0.0f, 0.0f, -0.5f, 0.5f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
});
|
||||
|
||||
// the shadow map texture might be larger than the shadow map dimension, so we add a scaling
|
||||
// factor
|
||||
const float v = (float) mShadowMapLayout.textureDimension / mShadowMapLayout.atlasDimension;
|
||||
const mat4f Mv(mat4f::row_major_init{
|
||||
v, 0, 0, 0,
|
||||
0, v, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
v, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, v, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
});
|
||||
|
||||
// apply the 1-texel border viewport transform
|
||||
const float o = 1.0f / mShadowMapLayout.atlasDimension;
|
||||
const float s = 1.0f - 2.0f * (1.0f / mShadowMapLayout.textureDimension);
|
||||
const mat4f Mb(mat4f::row_major_init{
|
||||
s, 0, 0, o,
|
||||
0, s, 0, o,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
s, 0.0f, 0.0f, o,
|
||||
0.0f, s, 0.0f, o,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
});
|
||||
|
||||
const mat4f Mf = mTextureSpaceFlipped ? mat4f(mat4f::row_major_init{
|
||||
1, 0, 0, 0,
|
||||
0, -1, 0, 1,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, -1.0f, 0.0f, 1.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
}) : mat4f();
|
||||
|
||||
// Compute shadow-map texture access transform
|
||||
@@ -647,10 +647,10 @@ mat4f ShadowMap::warpFrustum(float n, float f) noexcept {
|
||||
const float A = (f + n) * d;
|
||||
const float B = -2 * n * f * d;
|
||||
const mat4f Wp(mat4f::row_major_init{
|
||||
n, 0, 0, 0,
|
||||
0, A, 0, B,
|
||||
0, 0, n, 0,
|
||||
0, 1, 0, 0
|
||||
n, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, A, 0.0f, B,
|
||||
0.0f, 0.0f, n, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f
|
||||
});
|
||||
return Wp;
|
||||
}
|
||||
@@ -735,10 +735,10 @@ void ShadowMap::intersectWithShadowCasters(Aabb& UTILS_RESTRICT lightFrustum,
|
||||
const float2 s = 2.0f / float2(lightFrustum.max.xy - lightFrustum.min.xy);
|
||||
const float2 o = -s * float2(lightFrustum.max.xy + lightFrustum.min.xy) * 0.5f;
|
||||
const mat4f F(mat4f::row_major_init {
|
||||
s.x, 0, 0, o.x,
|
||||
0, s.y, 0, o.y,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
s.x, 0.0f, 0.0f, o.x,
|
||||
0.0f, s.y, 0.0f, o.y,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f,
|
||||
});
|
||||
float3 wsLightFrustumCorners[8];
|
||||
const mat4f projection = F * lightView;
|
||||
@@ -1043,9 +1043,9 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF) const
|
||||
const float nsxsz = n * sx * sz;
|
||||
const float j = -(B * sy) / (nsxsz * dz * dz);
|
||||
const mat3f J(mat3f::row_major_init{
|
||||
j * dz * sz, j * X * sz, 0,
|
||||
0, j * nsxsz, 0,
|
||||
0, j * Z * sx, j * dz * sx
|
||||
j * dz * sz, j * X * sz, 0.0f,
|
||||
0.0f, j * nsxsz, 0.0f,
|
||||
0.0f, j * Z * sx, j * dz * sx
|
||||
});
|
||||
|
||||
float3 Jx = J[0] * ures;
|
||||
|
||||
@@ -301,13 +301,16 @@ void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaSc
|
||||
u.setUniform(offsetof(PerViewUib, iblRoughnessOneLevel), iblRoughnessOneLevel);
|
||||
u.setUniform(offsetof(PerViewUib, iblLuminance), intensity * exposure);
|
||||
u.setUniformArray(offsetof(PerViewUib, iblSH), ibl->getSH(), 9);
|
||||
if (ibl->getReflectionHwHandle()) {
|
||||
mPerViewSb.setSampler(PerViewSib::IBL_SPECULAR, {
|
||||
ibl->getReflectionHwHandle(), {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR
|
||||
}});
|
||||
|
||||
// We always sample from the reflection texture, so provide a dummy texture if necessary.
|
||||
backend::Handle<backend::HwTexture> reflection = ibl->getReflectionHwHandle();
|
||||
if (!reflection) {
|
||||
reflection = engine.getDummyCubemap()->getHwHandle();
|
||||
}
|
||||
mPerViewSb.setSampler(PerViewSib::IBL_SPECULAR, { reflection, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR
|
||||
}});
|
||||
|
||||
// Directional light (always at index 0)
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
@@ -24,7 +24,7 @@ material {
|
||||
vertexDomain : device,
|
||||
depthWrite : false,
|
||||
shadingModel : unlit,
|
||||
variantFilter : [ skinning, shadowReceiver ],
|
||||
variantFilter : [ skinning, shadowReceiver, vsm ],
|
||||
culling: none
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.9.9"
|
||||
spec.version = "1.9.10"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.9.9/filament-v1.9.9-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.9.10/filament-v1.9.10-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -52,6 +52,7 @@ set(PRIVATE_HDRS
|
||||
src/eiff/DictionarySpirvChunk.h
|
||||
src/eiff/MaterialSpirvChunk.h
|
||||
src/GLSLPostProcessor.h
|
||||
src/ShaderMinifier.h
|
||||
src/sca/ASTHelpers.h
|
||||
src/sca/GLSLTools.h
|
||||
src/sca/builtinResource.h)
|
||||
@@ -63,7 +64,8 @@ set(SRCS
|
||||
src/eiff/MaterialSpirvChunk.cpp
|
||||
src/sca/ASTHelpers.cpp
|
||||
src/sca/GLSLTools.cpp
|
||||
src/GLSLPostProcessor.cpp)
|
||||
src/GLSLPostProcessor.cpp
|
||||
src/ShaderMinifier.cpp)
|
||||
|
||||
# Sources and headers for filamat lite
|
||||
|
||||
|
||||
@@ -106,42 +106,8 @@ static std::string stringifySpvOptimizerMessage(spv_message_level_t level, const
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrinks the specified string and returns a new string as the result.
|
||||
* To shrink the string, this method performs the following transforms:
|
||||
* - Remove leading white spaces at the beginning of each line
|
||||
* - Remove empty lines
|
||||
*/
|
||||
static std::string shrinkString(const std::string& s) {
|
||||
size_t cur = 0;
|
||||
|
||||
std::string r;
|
||||
r.reserve(s.length());
|
||||
|
||||
while (cur < s.length()) {
|
||||
size_t pos = cur;
|
||||
size_t len = 0;
|
||||
|
||||
while (s[cur] != '\n') {
|
||||
cur++;
|
||||
len++;
|
||||
}
|
||||
|
||||
size_t newPos = s.find_first_not_of(" \t", pos);
|
||||
if (newPos == std::string::npos) newPos = pos;
|
||||
|
||||
r.append(s, newPos, len - (newPos - pos));
|
||||
r += '\n';
|
||||
|
||||
while (s[cur] == '\n') {
|
||||
cur++;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void SpvToMsl(const SpirvBlob* spirv, std::string* outMsl, const GLSLPostProcessor::Config& config) {
|
||||
void GLSLPostProcessor::spirvToToMsl(const SpirvBlob* spirv, std::string* outMsl,
|
||||
const GLSLPostProcessor::Config& config) const {
|
||||
CompilerMSL mslCompiler(*spirv);
|
||||
CompilerGLSL::Options options;
|
||||
mslCompiler.set_common_options(options);
|
||||
@@ -184,7 +150,7 @@ void SpvToMsl(const SpirvBlob* spirv, std::string* outMsl, const GLSLPostProcess
|
||||
}
|
||||
|
||||
*outMsl = mslCompiler.compile();
|
||||
*outMsl = shrinkString(*outMsl);
|
||||
*outMsl = mShaderMinifier.removeWhitespace(*outMsl);
|
||||
}
|
||||
|
||||
bool GLSLPostProcessor::process(const std::string& inputShader, Config const& config,
|
||||
@@ -245,7 +211,7 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co
|
||||
options.generateDebugInfo = mGenerateDebugInfo;
|
||||
GlslangToSpv(*program.getIntermediate(mShLang), *mSpirvOutput, &options);
|
||||
if (mMslOutput) {
|
||||
SpvToMsl(mSpirvOutput, mMslOutput, config);
|
||||
spirvToToMsl(mSpirvOutput, mMslOutput, config);
|
||||
}
|
||||
} else {
|
||||
utils::slog.e << "GLSL post-processor invoked with optimization level NONE"
|
||||
@@ -262,7 +228,13 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co
|
||||
}
|
||||
|
||||
if (mGlslOutput) {
|
||||
*mGlslOutput = shrinkString(*mGlslOutput);
|
||||
*mGlslOutput = mShaderMinifier.removeWhitespace(*mGlslOutput);
|
||||
|
||||
// In theory this should only be enabled for SIZE, but in practice we often use PERFORMANCE.
|
||||
if (mOptimization != MaterialBuilder::Optimization::NONE) {
|
||||
*mGlslOutput = mShaderMinifier.renameStructFields(*mGlslOutput);
|
||||
}
|
||||
|
||||
if (mPrintShaders) {
|
||||
utils::slog.i << *mGlslOutput << utils::io::endl;
|
||||
}
|
||||
@@ -313,7 +285,7 @@ void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader,
|
||||
}
|
||||
|
||||
if (mMslOutput) {
|
||||
SpvToMsl(mSpirvOutput, mMslOutput, config);
|
||||
spirvToToMsl(mSpirvOutput, mMslOutput, config);
|
||||
}
|
||||
|
||||
if (mGlslOutput) {
|
||||
@@ -339,7 +311,7 @@ void GLSLPostProcessor::fullOptimization(const TShader& tShader,
|
||||
}
|
||||
|
||||
if (mMslOutput) {
|
||||
SpvToMsl(&spirv, mMslOutput, config);
|
||||
spirvToToMsl(&spirv, mMslOutput, config);
|
||||
}
|
||||
|
||||
// Transpile back to GLSL
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#include "filamat/MaterialBuilder.h" // for MaterialBuilder:: enums
|
||||
|
||||
#include "ShaderMinifier.h"
|
||||
|
||||
#include <ShaderLang.h>
|
||||
|
||||
#include <spirv-tools/optimizer.hpp>
|
||||
@@ -78,6 +80,8 @@ private:
|
||||
static void registerPerformancePasses(spvtools::Optimizer& optimizer, Config const& config);
|
||||
|
||||
void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const;
|
||||
void spirvToToMsl(const SpirvBlob* spirv, std::string* outMsl,
|
||||
const GLSLPostProcessor::Config& config) const;
|
||||
|
||||
const MaterialBuilder::Optimization mOptimization;
|
||||
const bool mPrintShaders;
|
||||
@@ -86,6 +90,7 @@ private:
|
||||
SpirvBlob* mSpirvOutput = nullptr;
|
||||
std::string* mMslOutput = nullptr;
|
||||
EShLanguage mShLang = EShLangFragment;
|
||||
ShaderMinifier mShaderMinifier;
|
||||
int mLangVersion = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -640,6 +640,7 @@ bool MaterialBuilder::generateShaders(const std::vector<Variant>& variants, Chun
|
||||
const bool targetApiNeedsSpirv =
|
||||
(targetApi == TargetApi::VULKAN || targetApi == TargetApi::METAL);
|
||||
const bool targetApiNeedsMsl = targetApi == TargetApi::METAL;
|
||||
const bool targetApiNeedsGlsl = targetApi == TargetApi::OPENGL;
|
||||
std::vector<uint32_t>* pSpirv = targetApiNeedsSpirv ? &spirv : nullptr;
|
||||
std::string* pMsl = targetApiNeedsMsl ? &msl : nullptr;
|
||||
|
||||
@@ -678,6 +679,11 @@ bool MaterialBuilder::generateShaders(const std::vector<Variant>& variants, Chun
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string* pGlsl = nullptr;
|
||||
if (targetApiNeedsGlsl) {
|
||||
pGlsl = &shader;
|
||||
}
|
||||
|
||||
#ifndef FILAMAT_LITE
|
||||
|
||||
GLSLPostProcessor::Config config{
|
||||
@@ -690,7 +696,7 @@ bool MaterialBuilder::generateShaders(const std::vector<Variant>& variants, Chun
|
||||
config.glsl.subpassInputToColorLocation.emplace_back(0, 0);
|
||||
}
|
||||
|
||||
bool ok = postProcessor.process(shader, config, &shader, pSpirv, pMsl);
|
||||
bool ok = postProcessor.process(shader, config, pGlsl, pSpirv, pMsl);
|
||||
#else
|
||||
bool ok = true;
|
||||
#endif
|
||||
|
||||
332
libs/filamat/src/ShaderMinifier.cpp
Normal file
332
libs/filamat/src/ShaderMinifier.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 "ShaderMinifier.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filamat {
|
||||
|
||||
static bool isIdCharNondigit(char c) {
|
||||
return c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
static bool isIdChar(char c) {
|
||||
return isIdCharNondigit(c) || (c >= '0' && c <= '9');
|
||||
};
|
||||
|
||||
// Checks if a GLSL identifier lives at the given index in the given codeline.
|
||||
// If so, returns the identifier and moves the given index to point
|
||||
// to the first character after the identifier.
|
||||
static bool getId(std::string_view codeline, size_t* pindex, std::string_view* id) {
|
||||
size_t index = *pindex;
|
||||
if (index >= codeline.size()) {
|
||||
return false;
|
||||
}
|
||||
if (!isIdCharNondigit(codeline[index])) {
|
||||
return false;
|
||||
}
|
||||
++index;
|
||||
while (index < codeline.size() && isIdChar(codeline[index])) {
|
||||
++index;
|
||||
}
|
||||
*id = codeline.substr(*pindex, index - *pindex);
|
||||
*pindex = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if the given string lives anywhere after the given index in the given codeline.
|
||||
// If so, moves the given index to point to the first character after the string.
|
||||
static bool getString(std::string_view codeline, size_t* pindex, std::string_view s) {
|
||||
size_t index = codeline.find(s, *pindex);
|
||||
if (index == std::string_view::npos) {
|
||||
return false;
|
||||
}
|
||||
*pindex = index + s.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if the given character is at the last position of the string.
|
||||
static bool getLastChar(std::string_view codeline, size_t index, char c) {
|
||||
if (index != codeline.size() - 1) {
|
||||
return false;
|
||||
}
|
||||
return codeline[index] == c;
|
||||
}
|
||||
|
||||
// Checks if whitespace lives at the given position of a codeline; if so, updates the given index.
|
||||
static bool getWhitespace(std::string_view codeline, size_t* pindex) {
|
||||
size_t index = *pindex;
|
||||
while (index < codeline.size() && (codeline[index] == ' ' || codeline[index] == '\t')) {
|
||||
++index;
|
||||
}
|
||||
if (index == *pindex) {
|
||||
return false;
|
||||
}
|
||||
*pindex = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skips past an optional precision qualifier that is possibly surrounded by whitespace.
|
||||
static void ignorePrecision(std::string_view codeline, size_t* pindex) {
|
||||
static std::string tokens[3] = {"lowp", "mediump", "highp"};
|
||||
const size_t i = *pindex;
|
||||
getWhitespace(codeline, pindex);
|
||||
for (const auto& token : tokens) {
|
||||
const size_t n = token.size();
|
||||
if (codeline.substr(i, i + n) == token) {
|
||||
*pindex += n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
getWhitespace(codeline, pindex);
|
||||
}
|
||||
|
||||
// Checks if the given string lives has an array size at the given codeline.
|
||||
// If so, moves the given index to point to the first character after the array size.
|
||||
// Always returns true for convenient daisy-chaining in the parser.
|
||||
static bool ignoreArraySize(std::string_view codeline, size_t* pindex) {
|
||||
size_t index = *pindex;
|
||||
if (index >= codeline.size()) {
|
||||
return true;
|
||||
}
|
||||
if (codeline[index] != '[') {
|
||||
return true;
|
||||
}
|
||||
++index;
|
||||
while (index < codeline.size() && codeline[index] != ']') {
|
||||
++index;
|
||||
}
|
||||
*pindex = (index == codeline.size()) ? index : index + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void replaceAll(std::string& result, std::string_view from, std::string to) {
|
||||
size_t n = 0;
|
||||
while ((n = result.find(from, n)) != std::string::npos) {
|
||||
result.replace(n, from.size(), to);
|
||||
n += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
enum ParserState {
|
||||
OUTSIDE,
|
||||
STRUCT_OPEN,
|
||||
STRUCT_DEFN,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrinks the specified string and returns a new string as the result.
|
||||
* To shrink the string, this method performs the following transforms:
|
||||
* - Remove leading white spaces at the beginning of each line
|
||||
* - Remove empty lines
|
||||
*/
|
||||
std::string ShaderMinifier::removeWhitespace(const std::string& s) const {
|
||||
size_t cur = 0;
|
||||
|
||||
std::string r;
|
||||
r.reserve(s.length());
|
||||
|
||||
while (cur < s.length()) {
|
||||
size_t pos = cur;
|
||||
size_t len = 0;
|
||||
|
||||
while (s[cur] != '\n') {
|
||||
cur++;
|
||||
len++;
|
||||
}
|
||||
|
||||
size_t newPos = s.find_first_not_of(" \t", pos);
|
||||
if (newPos == std::string::npos) newPos = pos;
|
||||
|
||||
r.append(s, newPos, len - (newPos - pos));
|
||||
r += '\n';
|
||||
|
||||
while (s[cur] == '\n') {
|
||||
cur++;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniform block definitions can be quite big so this compresses them as follows.
|
||||
* First, the uniform struct definitions are found, new field names are generated, and a mapping
|
||||
* table is built. Second, all uses are replaced by applying the mapping table.
|
||||
*
|
||||
* The struct definition must be a sequence of tokens with the following pattern. This is fairly
|
||||
* constrained (e.g. no comments or nesting) but this is designed to operate on generated code.
|
||||
*
|
||||
* "uniform" TypeIdentifier
|
||||
* {
|
||||
* OptionalPrecQual TypeIdentifier FieldIdentifier OptionalArraySize ;
|
||||
* OptionalPrecQual TypeIdentifier FieldIdentifier OptionalArraySize ;
|
||||
* OptionalPrecQual TypeIdentifier FieldIdentifier OptionalArraySize ;
|
||||
* } StructIdentifier ;
|
||||
*/
|
||||
std::string ShaderMinifier::renameStructFields(const std::string& source) {
|
||||
std::string_view sv = source;
|
||||
size_t first = 0;
|
||||
mCodelines.clear();
|
||||
while (first < sv.size()) {
|
||||
const size_t second = sv.find_first_of('\n', first);
|
||||
if (first != second) {
|
||||
mCodelines.emplace_back(sv.substr(first, second - first));
|
||||
}
|
||||
if (second == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
first = second + 1;
|
||||
}
|
||||
buildFieldMapping();
|
||||
return applyFieldMapping();
|
||||
}
|
||||
|
||||
void ShaderMinifier::buildFieldMapping() {
|
||||
mStructFieldMap.clear();
|
||||
mStructDefnMap.clear();
|
||||
std::string currentStructPrefix;
|
||||
std::vector<std::string_view> currentStructFields;
|
||||
ParserState state = OUTSIDE;
|
||||
for (std::string_view codeline : mCodelines) {
|
||||
size_t cursor = 0;
|
||||
std::string_view typeId;
|
||||
std::string_view fieldName;
|
||||
switch (state) {
|
||||
case OUTSIDE:
|
||||
if (getString(codeline, &cursor, "uniform") &&
|
||||
getWhitespace(codeline, &cursor) &&
|
||||
getId(codeline, &cursor, &typeId) &&
|
||||
cursor == codeline.size()) {
|
||||
currentStructPrefix = std::string(typeId) + ".";
|
||||
state = STRUCT_OPEN;
|
||||
}
|
||||
continue;
|
||||
case STRUCT_OPEN:
|
||||
state = getLastChar(codeline, 0, '{') ? STRUCT_DEFN : OUTSIDE;
|
||||
continue;
|
||||
case STRUCT_DEFN: {
|
||||
std::string_view structName;
|
||||
if (getString(codeline, &cursor, "}")) {
|
||||
if (!getWhitespace(codeline, &cursor) ||
|
||||
!getId(codeline, &cursor, &structName) ||
|
||||
!getLastChar(codeline, cursor, ';')) {
|
||||
break;
|
||||
}
|
||||
const std::string structNamePrefix = std::string(structName) + ".";
|
||||
std::string generatedFieldName = "a";
|
||||
for (auto field : currentStructFields) {
|
||||
const std::string sField(field);
|
||||
const std::string key = structNamePrefix + sField;
|
||||
const std::string val = structNamePrefix + generatedFieldName;
|
||||
mStructFieldMap.push_back({key, val});
|
||||
mStructDefnMap[currentStructPrefix + sField] = generatedFieldName;
|
||||
if (generatedFieldName[0] == 'z') {
|
||||
generatedFieldName = "a" + generatedFieldName;
|
||||
} else {
|
||||
generatedFieldName[0]++;
|
||||
}
|
||||
}
|
||||
currentStructFields.clear();
|
||||
state = OUTSIDE;
|
||||
break;
|
||||
}
|
||||
ignorePrecision(codeline, &cursor);
|
||||
if (!getId(codeline, &cursor, &typeId) ||
|
||||
!getWhitespace(codeline, &cursor) ||
|
||||
!getId(codeline, &cursor, &fieldName) ||
|
||||
!ignoreArraySize(codeline, &cursor) ||
|
||||
!getLastChar(codeline, cursor, ';')) {
|
||||
break;
|
||||
}
|
||||
currentStructFields.push_back(fieldName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort keys from longest to shortest because we want to replace "fogColorFromIbl" before
|
||||
// replacing "fogColor".
|
||||
const auto& compare = [](const RenameEntry& a, const RenameEntry& b) {
|
||||
return a.first.length() > b.first.length();
|
||||
};
|
||||
std::sort(mStructFieldMap.begin(), mStructFieldMap.end(), compare);
|
||||
}
|
||||
|
||||
std::string ShaderMinifier::applyFieldMapping() const {
|
||||
std::string result;
|
||||
ParserState state = OUTSIDE;
|
||||
std::string currentStructPrefix;
|
||||
for (std::string_view codeline : mCodelines) {
|
||||
std::string modified(codeline);
|
||||
std::string_view fieldName;
|
||||
std::string_view structName;
|
||||
std::string_view typeId;
|
||||
size_t cursor = 0;
|
||||
switch (state) {
|
||||
case OUTSIDE: {
|
||||
if (getString(codeline, &cursor, "uniform") &&
|
||||
getWhitespace(codeline, &cursor) &&
|
||||
getId(codeline, &cursor, &typeId) &&
|
||||
cursor == codeline.size()) {
|
||||
currentStructPrefix = std::string(typeId) + ".";
|
||||
state = STRUCT_OPEN;
|
||||
break;
|
||||
}
|
||||
for (const auto& key : mStructFieldMap) {
|
||||
replaceAll(modified, key.first, key.second);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STRUCT_OPEN:
|
||||
state = getLastChar(codeline, 0, '{') ? STRUCT_DEFN : OUTSIDE;
|
||||
break;
|
||||
case STRUCT_DEFN: {
|
||||
if (getString(codeline, &cursor, "}") &&
|
||||
getWhitespace(codeline, &cursor) &&
|
||||
getId(codeline, &cursor, &structName) &&
|
||||
getLastChar(codeline, cursor, ';')) {
|
||||
state = OUTSIDE;
|
||||
break;
|
||||
}
|
||||
ignorePrecision(codeline, &cursor);
|
||||
if (!getId(codeline, &cursor, &typeId) ||
|
||||
!getWhitespace(codeline, &cursor) ||
|
||||
!getId(codeline, &cursor, &fieldName) ||
|
||||
!ignoreArraySize(codeline, &cursor) ||
|
||||
!getLastChar(codeline, cursor, ';')) {
|
||||
break;
|
||||
}
|
||||
std::string key = currentStructPrefix + std::string(fieldName);
|
||||
auto iter = mStructDefnMap.find(key);
|
||||
if (iter == mStructDefnMap.end()) {
|
||||
utils::slog.e << "ShaderMinifier error: " << key << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
replaceAll(modified, fieldName, iter->second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
result += modified + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace filamat
|
||||
51
libs/filamat/src/ShaderMinifier.h
Normal file
51
libs/filamat/src/ShaderMinifier.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef TNT_SHADERMINIFIER_H
|
||||
#define TNT_SHADERMINIFIER_H
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace filamat {
|
||||
|
||||
// Simple minifier for monolithic GLSL or MSL strings.
|
||||
//
|
||||
// Note that we already use a third party minifier, but it applies only to GLSL fragments.
|
||||
// This custom minifier is designed for generated code such as uniform structs.
|
||||
class ShaderMinifier {
|
||||
public:
|
||||
std::string removeWhitespace(const std::string& source) const;
|
||||
std::string renameStructFields(const std::string& source);
|
||||
|
||||
private:
|
||||
using RenameEntry = std::pair<std::string, std::string>;
|
||||
|
||||
void buildFieldMapping();
|
||||
std::string applyFieldMapping() const;
|
||||
|
||||
// These fields do not need to be members, but they allow clients to reduce malloc churn
|
||||
// by persisting the minifier object.
|
||||
std::vector<std::string_view> mCodelines;
|
||||
std::vector<RenameEntry> mStructFieldMap;
|
||||
std::unordered_map<std::string, std::string> mStructDefnMap;
|
||||
};
|
||||
|
||||
} // namespace filamat
|
||||
|
||||
#endif //TNT_SHADERMINIFIER_H
|
||||
@@ -118,7 +118,7 @@ inline filament::math::mat3f matrixFromUvTransform(const float offset[2], float
|
||||
float sy = scale[1];
|
||||
float c = cos(rotation);
|
||||
float s = sin(rotation);
|
||||
return filament::math::mat3f(sx * c, sx * s, tx, -sy * s, sy * c, ty, 0, 0, 1);
|
||||
return filament::math::mat3f(sx * c, sx * s, tx, -sy * s, sy * c, ty, 0.0f, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
} // namespace gltfio
|
||||
|
||||
@@ -35,16 +35,22 @@ include_directories(${PUBLIC_HDR_DIR})
|
||||
|
||||
add_library(${TARGET} ${PUBLIC_HDRS} ${PRIVATE_HDRS} ${SRCS})
|
||||
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
|
||||
|
||||
target_link_libraries(${TARGET} math utils)
|
||||
|
||||
add_library(${TARGET}-lite ${PUBLIC_HDRS} ${PRIVATE_HDRS} ${SRCS})
|
||||
target_compile_definitions(${TARGET}-lite PUBLIC -DFILAMENT_IBL_LITE=1)
|
||||
target_include_directories(${TARGET}-lite PUBLIC ${PUBLIC_HDR_DIR})
|
||||
target_link_libraries(${TARGET}-lite math utils)
|
||||
|
||||
# ==================================================================================================
|
||||
# Compile options and optimizations
|
||||
# ==================================================================================================
|
||||
if (MSVC)
|
||||
target_compile_options(${TARGET} PRIVATE /fp:fast)
|
||||
target_compile_options(${TARGET}-lite PRIVATE /fp:fast)
|
||||
else()
|
||||
target_compile_options(${TARGET} PRIVATE -ffast-math)
|
||||
target_compile_options(${TARGET}-lite PRIVATE -ffast-math)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -52,4 +58,5 @@ endif()
|
||||
# Installation
|
||||
# ==================================================================================================
|
||||
install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR})
|
||||
install(TARGETS ${TARGET}-lite ARCHIVE DESTINATION lib/${DIST_DIR})
|
||||
install(DIRECTORY ${PUBLIC_HDR_DIR}/ibl DESTINATION include)
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include <math/vec3.h>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -40,7 +39,7 @@ class Image;
|
||||
*/
|
||||
class CubemapIBL {
|
||||
public:
|
||||
using Progress = std::function<void(size_t, float)>;
|
||||
typedef void (*Progress)(size_t, float, void*);
|
||||
|
||||
/**
|
||||
* Computes a roughness LOD using prefiltered importance sampling GGX
|
||||
@@ -54,7 +53,7 @@ public:
|
||||
static void roughnessFilter(
|
||||
utils::JobSystem& js, Cubemap& dst, const std::vector<Cubemap>& levels,
|
||||
float linearRoughness, size_t maxNumSamples, math::float3 mirror, bool prefilter,
|
||||
Progress updater = {});
|
||||
Progress updater = nullptr, void* userdata = nullptr);
|
||||
|
||||
//! Computes the "DFG" term of the "split-sum" approximation and stores it in a 2D image
|
||||
static void DFG(utils::JobSystem& js, Image& dst, bool multiscatter, bool cloth);
|
||||
@@ -72,7 +71,7 @@ public:
|
||||
* @see CubemapSH
|
||||
*/
|
||||
static void diffuseIrradiance(utils::JobSystem& js, Cubemap& dst, const std::vector<Cubemap>& levels,
|
||||
size_t maxNumSamples = 1024, Progress updater = {});
|
||||
size_t maxNumSamples = 1024, Progress updater = nullptr, void* userdata = nullptr);
|
||||
|
||||
// for debugging. ignore.
|
||||
static void brdf(utils::JobSystem& js, Cubemap& dst, float linearRoughness);
|
||||
|
||||
@@ -65,18 +65,6 @@ public:
|
||||
ReduceProc<STATE> reduce = [](STATE&) {},
|
||||
const STATE& prototype = STATE());
|
||||
|
||||
//! Converts equirectangular Image to a Cubemap
|
||||
static void equirectangularToCubemap(utils::JobSystem& js, Cubemap& dst, const Image& src);
|
||||
|
||||
//! Converts a Cubemap to an equirectangular Image
|
||||
static void cubemapToEquirectangular(utils::JobSystem& js, Image& dst, const Cubemap& src);
|
||||
|
||||
//! Converts a Cubemap to an octahedron
|
||||
static void cubemapToOctahedron(utils::JobSystem& js, Image& dst, const Cubemap& src);
|
||||
|
||||
//! Converts horizontal or vertical cross Image to a Cubemap
|
||||
static void crossToCubemap(utils::JobSystem& js, Cubemap& dst, const Image& src);
|
||||
|
||||
//! clamps image to acceptable range
|
||||
static void clamp(Image& src);
|
||||
|
||||
@@ -88,21 +76,41 @@ public:
|
||||
//! Return the name of a face (suitable for a file name)
|
||||
static const char* getFaceName(Cubemap::Face face);
|
||||
|
||||
//! computes the solid angle of a pixel of a face of a cubemap
|
||||
static float solidAngle(size_t dim, size_t u, size_t v);
|
||||
|
||||
//! Sets a Cubemap faces from a cross image
|
||||
static void setAllFacesFromCross(Cubemap& cm, const Image& image);
|
||||
|
||||
private:
|
||||
|
||||
//move these into cmgen?
|
||||
static void setFaceFromCross(Cubemap& cm, Cubemap::Face face, const Image& image);
|
||||
static Image createCubemapImage(size_t dim, bool horizontal = true);
|
||||
|
||||
#ifndef FILAMENT_IBL_LITE
|
||||
|
||||
public:
|
||||
|
||||
//! Converts horizontal or vertical cross Image to a Cubemap
|
||||
static void crossToCubemap(utils::JobSystem& js, Cubemap& dst, const Image& src);
|
||||
|
||||
//! Converts equirectangular Image to a Cubemap
|
||||
static void equirectangularToCubemap(utils::JobSystem& js, Cubemap& dst, const Image& src);
|
||||
|
||||
//! Converts a Cubemap to an equirectangular Image
|
||||
static void cubemapToEquirectangular(utils::JobSystem& js, Image& dst, const Cubemap& src);
|
||||
|
||||
//! Converts a Cubemap to an octahedron
|
||||
static void cubemapToOctahedron(utils::JobSystem& js, Image& dst, const Cubemap& src);
|
||||
|
||||
//! mirror the cubemap in the horizontal direction
|
||||
static void mirrorCubemap(utils::JobSystem& js, Cubemap& dst, const Cubemap& src);
|
||||
|
||||
//! computes the solid angle of a pixel of a face of a cubemap
|
||||
static float solidAngle(size_t dim, size_t u, size_t v);
|
||||
|
||||
//! generates a UV grid in the cubemap -- useful for debugging.
|
||||
static void generateUVGrid(utils::JobSystem& js, Cubemap& cml, size_t gridFrequencyX, size_t gridFrequencyY);
|
||||
|
||||
private:
|
||||
static void setFaceFromCross(Cubemap& cm, Cubemap::Face face, const Image& image);
|
||||
static Image createCubemapImage(size_t dim, bool horizontal = true);
|
||||
#endif
|
||||
|
||||
friend class CubemapIBL;
|
||||
};
|
||||
|
||||
@@ -295,7 +295,7 @@ static float UTILS_UNUSED VisibilityAshikhmin(float NoV, float NoL, float /*a*/)
|
||||
void CubemapIBL::roughnessFilter(
|
||||
utils::JobSystem& js, Cubemap& dst, const std::vector<Cubemap>& levels,
|
||||
float linearRoughness, size_t maxNumSamples, math::float3 mirror, bool prefilter,
|
||||
Progress updater)
|
||||
Progress updater, void* userdata)
|
||||
{
|
||||
const float numSamples = maxNumSamples;
|
||||
const float inumSamples = 1.0f / numSamples;
|
||||
@@ -311,7 +311,7 @@ void CubemapIBL::roughnessFilter(
|
||||
(CubemapUtils::EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
|
||||
if (UTILS_UNLIKELY(updater)) {
|
||||
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
updater(0, (float)p / ((float) dim * 6.0f));
|
||||
updater(0, (float)p / ((float) dim * 6.0f), userdata);
|
||||
}
|
||||
const Cubemap& cm = levels[0];
|
||||
for (size_t x = 0; x < dim; ++x, ++data) {
|
||||
@@ -417,7 +417,7 @@ void CubemapIBL::roughnessFilter(
|
||||
Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
|
||||
if (UTILS_UNLIKELY(updater)) {
|
||||
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
updater(0, (float) p / ((float) dim * 6.0f));
|
||||
updater(0, (float) p / ((float) dim * 6.0f), userdata);
|
||||
}
|
||||
mat3 R;
|
||||
const size_t numSamples = cache.size();
|
||||
@@ -543,7 +543,7 @@ void CubemapIBL::roughnessFilter(
|
||||
*/
|
||||
|
||||
void CubemapIBL::diffuseIrradiance(JobSystem& js, Cubemap& dst, const std::vector<Cubemap>& levels,
|
||||
size_t maxNumSamples, CubemapIBL::Progress updater)
|
||||
size_t maxNumSamples, CubemapIBL::Progress updater, void* userdata)
|
||||
{
|
||||
const float numSamples = maxNumSamples;
|
||||
const float inumSamples = 1.0f / numSamples;
|
||||
@@ -595,7 +595,7 @@ void CubemapIBL::diffuseIrradiance(JobSystem& js, Cubemap& dst, const std::vecto
|
||||
|
||||
if (updater) {
|
||||
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
updater(0, (float)p / ((float) dim * 6.0f));
|
||||
updater(0, (float)p / ((float) dim * 6.0f), userdata);
|
||||
}
|
||||
|
||||
mat3 R;
|
||||
|
||||
@@ -70,6 +70,114 @@ void CubemapUtils::highlight(Image& src) {
|
||||
}
|
||||
}
|
||||
|
||||
void CubemapUtils::downsampleCubemapLevelBoxFilter(JobSystem& js, Cubemap& dst, const Cubemap& src) {
|
||||
size_t scale = src.getDimensions() / dst.getDimensions();
|
||||
processSingleThreaded<EmptyState>(dst, js,
|
||||
[&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
|
||||
const Image& image(src.getImageForFace(f));
|
||||
for (size_t x = 0; x < dim; ++x, ++data) {
|
||||
Cubemap::writeAt(data, Cubemap::filterAtCenter(image, x * scale, y * scale));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Area of a cube face's quadrant projected onto a sphere
|
||||
*
|
||||
* 1 +---+----------+
|
||||
* | | |
|
||||
* |---+----------|
|
||||
* | |(x,y) |
|
||||
* | | |
|
||||
* | | |
|
||||
* -1 +---+----------+
|
||||
* -1 1
|
||||
*
|
||||
*
|
||||
* The quadrant (-1,1)-(x,y) is projected onto the unit sphere
|
||||
*
|
||||
*/
|
||||
static inline float sphereQuadrantArea(float x, float y) {
|
||||
return std::atan2(x*y, std::sqrt(x*x + y*y + 1));
|
||||
}
|
||||
|
||||
float CubemapUtils::solidAngle(size_t dim, size_t u, size_t v) {
|
||||
const float iDim = 1.0f / dim;
|
||||
float s = ((u + 0.5f) * 2 * iDim) - 1;
|
||||
float t = ((v + 0.5f) * 2 * iDim) - 1;
|
||||
const float x0 = s - iDim;
|
||||
const float y0 = t - iDim;
|
||||
const float x1 = s + iDim;
|
||||
const float y1 = t + iDim;
|
||||
float solidAngle = sphereQuadrantArea(x0, y0) -
|
||||
sphereQuadrantArea(x0, y1) -
|
||||
sphereQuadrantArea(x1, y0) +
|
||||
sphereQuadrantArea(x1, y1);
|
||||
return solidAngle;
|
||||
}
|
||||
|
||||
Cubemap CubemapUtils::create(Image& image, size_t dim, bool horizontal) {
|
||||
Cubemap cm(dim);
|
||||
Image temp(CubemapUtils::createCubemapImage(dim, horizontal));
|
||||
CubemapUtils::setAllFacesFromCross(cm, temp);
|
||||
std::swap(image, temp);
|
||||
return cm;
|
||||
}
|
||||
|
||||
void CubemapUtils::setFaceFromCross(Cubemap& cm, Cubemap::Face face, const Image& image) {
|
||||
size_t dim = cm.getDimensions() + 2; // 2 extra per image, for seamlessness
|
||||
size_t x = 0;
|
||||
size_t y = 0;
|
||||
switch (face) {
|
||||
case Cubemap::Face::NX:
|
||||
x = 0, y = dim;
|
||||
break;
|
||||
case Cubemap::Face::PX:
|
||||
x = 2 * dim, y = dim;
|
||||
break;
|
||||
case Cubemap::Face::NY:
|
||||
x = dim, y = 2 * dim;
|
||||
break;
|
||||
case Cubemap::Face::PY:
|
||||
x = dim, y = 0;
|
||||
break;
|
||||
case Cubemap::Face::NZ:
|
||||
x = 3 * dim, y = dim;
|
||||
break;
|
||||
case Cubemap::Face::PZ:
|
||||
x = dim, y = dim;
|
||||
break;
|
||||
}
|
||||
Image subImage;
|
||||
subImage.subset(image, x + 1, y + 1, dim - 2, dim - 2);
|
||||
cm.setImageForFace(face, subImage);
|
||||
}
|
||||
|
||||
void CubemapUtils::setAllFacesFromCross(Cubemap& cm, const Image& image) {
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NX, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PX, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NY, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PY, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NZ, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PZ, image);
|
||||
}
|
||||
|
||||
Image CubemapUtils::createCubemapImage(size_t dim, bool horizontal) {
|
||||
// always allocate 2 extra column and row / face, to allow the cubemap to be "seamless"
|
||||
size_t width = 4 * (dim + 2);
|
||||
size_t height = 3 * (dim + 2);
|
||||
if (!horizontal) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
|
||||
Image image(width, height);
|
||||
memset(image.getData(), 0, image.getBytesPerRow() * height);
|
||||
return image;
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_IBL_LITE
|
||||
|
||||
void CubemapUtils::equirectangularToCubemap(JobSystem& js, Cubemap& dst, const Image& src) {
|
||||
const size_t width = src.getWidth();
|
||||
const size_t height = src.getHeight();
|
||||
@@ -242,71 +350,6 @@ void CubemapUtils::crossToCubemap(JobSystem& js, Cubemap& dst, const Image& src)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void CubemapUtils::downsampleCubemapLevelBoxFilter(JobSystem& js, Cubemap& dst, const Cubemap& src) {
|
||||
size_t scale = src.getDimensions() / dst.getDimensions();
|
||||
processSingleThreaded<EmptyState>(dst, js,
|
||||
[&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
|
||||
const Image& image(src.getImageForFace(f));
|
||||
for (size_t x = 0; x < dim; ++x, ++data) {
|
||||
Cubemap::writeAt(data, Cubemap::filterAtCenter(image, x * scale, y * scale));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void CubemapUtils::setFaceFromCross(Cubemap& cm, Cubemap::Face face, const Image& image) {
|
||||
size_t dim = cm.getDimensions() + 2; // 2 extra per image, for seamlessness
|
||||
size_t x = 0;
|
||||
size_t y = 0;
|
||||
switch (face) {
|
||||
case Cubemap::Face::NX:
|
||||
x = 0, y = dim;
|
||||
break;
|
||||
case Cubemap::Face::PX:
|
||||
x = 2 * dim, y = dim;
|
||||
break;
|
||||
case Cubemap::Face::NY:
|
||||
x = dim, y = 2 * dim;
|
||||
break;
|
||||
case Cubemap::Face::PY:
|
||||
x = dim, y = 0;
|
||||
break;
|
||||
case Cubemap::Face::NZ:
|
||||
x = 3 * dim, y = dim;
|
||||
break;
|
||||
case Cubemap::Face::PZ:
|
||||
x = dim, y = dim;
|
||||
break;
|
||||
}
|
||||
Image subImage;
|
||||
subImage.subset(image, x + 1, y + 1, dim - 2, dim - 2);
|
||||
cm.setImageForFace(face, subImage);
|
||||
}
|
||||
|
||||
void CubemapUtils::setAllFacesFromCross(Cubemap& cm, const Image& image) {
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NX, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PX, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NY, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PY, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NZ, image);
|
||||
CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PZ, image);
|
||||
}
|
||||
|
||||
Image CubemapUtils::createCubemapImage(size_t dim, bool horizontal) {
|
||||
// always allocate 2 extra column and row / face, to allow the cubemap to be "seamless"
|
||||
size_t width = 4 * (dim + 2);
|
||||
size_t height = 3 * (dim + 2);
|
||||
if (!horizontal) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
|
||||
Image image(width, height);
|
||||
memset(image.getData(), 0, image.getBytesPerRow() * height);
|
||||
return image;
|
||||
}
|
||||
|
||||
const char* CubemapUtils::getFaceName(Cubemap::Face face) {
|
||||
switch (face) {
|
||||
case Cubemap::Face::NX: return "nx";
|
||||
@@ -318,14 +361,6 @@ const char* CubemapUtils::getFaceName(Cubemap::Face face) {
|
||||
}
|
||||
}
|
||||
|
||||
Cubemap CubemapUtils::create(Image& image, size_t dim, bool horizontal) {
|
||||
Cubemap cm(dim);
|
||||
Image temp(CubemapUtils::createCubemapImage(dim, horizontal));
|
||||
CubemapUtils::setAllFacesFromCross(cm, temp);
|
||||
std::swap(image, temp);
|
||||
return cm;
|
||||
}
|
||||
|
||||
void CubemapUtils::mirrorCubemap(JobSystem& js, Cubemap& dst, const Cubemap& src) {
|
||||
processSingleThreaded<EmptyState>(dst, js,
|
||||
[&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
|
||||
@@ -358,42 +393,7 @@ void CubemapUtils::generateUVGrid(JobSystem& js, Cubemap& cml, size_t gridFreque
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Area of a cube face's quadrant projected onto a sphere
|
||||
*
|
||||
* 1 +---+----------+
|
||||
* | | |
|
||||
* |---+----------|
|
||||
* | |(x,y) |
|
||||
* | | |
|
||||
* | | |
|
||||
* -1 +---+----------+
|
||||
* -1 1
|
||||
*
|
||||
*
|
||||
* The quadrant (-1,1)-(x,y) is projected onto the unit sphere
|
||||
*
|
||||
*/
|
||||
static inline float sphereQuadrantArea(float x, float y) {
|
||||
return std::atan2(x*y, std::sqrt(x*x + y*y + 1));
|
||||
}
|
||||
|
||||
float CubemapUtils::solidAngle(size_t dim, size_t u, size_t v) {
|
||||
const float iDim = 1.0f / dim;
|
||||
float s = ((u + 0.5f) * 2 * iDim) - 1;
|
||||
float t = ((v + 0.5f) * 2 * iDim) - 1;
|
||||
const float x0 = s - iDim;
|
||||
const float y0 = t - iDim;
|
||||
const float x1 = s + iDim;
|
||||
const float y1 = t + iDim;
|
||||
float solidAngle = sphereQuadrantArea(x0, y0) -
|
||||
sphereQuadrantArea(x0, y1) -
|
||||
sphereQuadrantArea(x1, y0) +
|
||||
sphereQuadrantArea(x1, y1);
|
||||
return solidAngle;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ibl
|
||||
} // namespace filament
|
||||
|
||||
@@ -181,7 +181,7 @@ public:
|
||||
Iterator(Iterator const& rhs) noexcept = default;
|
||||
Iterator& operator=(Iterator const& rhs) = default;
|
||||
|
||||
const reference operator*() const { return { soa, index }; }
|
||||
reference operator*() const { return { soa, index }; }
|
||||
reference operator*() { return { soa, index }; }
|
||||
reference operator[](size_t n) { return *(*this + n); }
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ public:
|
||||
Zip2Iterator(Zip2Iterator const& rhs) noexcept = default;
|
||||
Zip2Iterator& operator=(Zip2Iterator const& rhs) = default;
|
||||
|
||||
const value_type operator*() const { return { *mIt.first, *mIt.second }; }
|
||||
reference operator*() { return { *mIt.first, *mIt.second }; }
|
||||
value_type operator*() const { return { *mIt.first, *mIt.second }; }
|
||||
reference operator*() { return { *mIt.first, *mIt.second }; }
|
||||
|
||||
const value_type operator[](size_t n) const { return *(*this + n); }
|
||||
reference operator[](size_t n) { return *(*this + n); }
|
||||
|
||||
@@ -64,6 +64,14 @@
|
||||
# define UTILS_PRIVATE
|
||||
#endif
|
||||
|
||||
#define UTILS_NO_SANITIZE_THREAD
|
||||
#if defined(__has_feature)
|
||||
# if __has_feature(thread_sanitizer)
|
||||
# undef UTILS_NO_SANITIZE_THREAD
|
||||
# define UTILS_NO_SANITIZE_THREAD __attribute__((no_sanitize("thread")))
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* helps the compiler's optimizer predicting branches
|
||||
*/
|
||||
|
||||
@@ -683,11 +683,11 @@ int main(int argc, char* argv[]) {
|
||||
Cubemap blurred = CubemapUtils::create(image, dim);
|
||||
CubemapIBL::roughnessFilter(js, blurred, levels, linear_roughness, g_num_samples,
|
||||
float3{ 1, 1, 1 }, !g_ibl_no_prefilter,
|
||||
[&updater, quiet = g_quiet](size_t index, float v) {
|
||||
if (!quiet) {
|
||||
updater.update(index, v);
|
||||
[](size_t index, float v, void* userdata) {
|
||||
if (!g_quiet) {
|
||||
((ProgressUpdater*) userdata)->update(index, v);
|
||||
}
|
||||
});
|
||||
}, &updater);
|
||||
if (!g_quiet) {
|
||||
updater.stop();
|
||||
std::cout << "Extract faces..." << std::endl;
|
||||
@@ -978,11 +978,11 @@ void iblRoughnessPrefilter(
|
||||
}
|
||||
CubemapIBL::roughnessFilter(js, dst, levels, roughness, numSamples,
|
||||
float3{ 1, 1, 1 }, prefilter,
|
||||
[&updater, quiet = g_quiet](size_t index, float v) {
|
||||
if (!quiet) {
|
||||
updater.update(index, v);
|
||||
[](size_t index, float v, void* userdata) {
|
||||
if (!g_quiet) {
|
||||
((ProgressUpdater*) userdata)->update(index, v);
|
||||
}
|
||||
});
|
||||
}, &updater);
|
||||
if (!g_quiet) {
|
||||
updater.stop();
|
||||
}
|
||||
@@ -1067,11 +1067,11 @@ void iblDiffuseIrradiance(utils::JobSystem& js, const utils::Path& iname,
|
||||
updater.start();
|
||||
}
|
||||
CubemapIBL::diffuseIrradiance(js, dst, levels, numSamples,
|
||||
[&updater, quiet = g_quiet](size_t index, float v) {
|
||||
if (!quiet) {
|
||||
updater.update(index, v);
|
||||
[](size_t index, float v, void* userdata) {
|
||||
if (!g_quiet) {
|
||||
((ProgressUpdater*) userdata)->update(index, v);
|
||||
}
|
||||
});
|
||||
}, &updater);
|
||||
if (!g_quiet) {
|
||||
updater.stop();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.9.9",
|
||||
"version": "1.9.10",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user