Compare commits

..
Author SHA1 Message Date
doriswu 5a903f58d9 Merge branch 'main' into exv/mi-sc
# Conflicts:
#	NEW_RELEASE_NOTES.md
#	filament/src/LocalProgramCache.cpp
#	filament/src/PostProcessManager.cpp
#	filament/src/PostProcessManager.h
#	filament/src/details/Material.cpp
2026-04-30 14:03:20 +08:00
Eliza Velasquez 8b5af6f515 Update documentation for Material::compile 2026-03-16 11:39:10 +09:00
Eliza Velasquez bd07193814 engine: address comments 2026-03-16 11:39:10 +09:00
Eliza Velasquez 842967a410 PostProcessManager: prepare programs per instance 2026-03-16 11:39:10 +09:00
Eliza Velasquez a4e6ec2450 android: fix build errors 2026-03-16 11:39:10 +09:00
Eliza Velasquez b6c551ac3c java: add bindings for MaterialInstance::compile() 2026-03-16 11:39:10 +09:00
Eliza Velasquez 038062ec77 engine: add MaterialInstance::compile() 2026-03-16 11:39:10 +09:00
Eliza Velasquez 5d6b62de3e engine: override spec constants on instance-basis
This commit adds `getConstant()` and `setConstant()` methods to
`MaterialInstance`.

When a material instance is created, it doesn't create or manage any spec
constants on its own, but instead relies on `Material`. The client can change
constants at any time, after which the necessary resources are copied and
maintained by `MaterialInstance`.

As part of this change, most internal rendering methods which depended on
`Material` have been refactored to instead depend on `MaterialInstance`.
2026-03-16 11:39:10 +09:00
58 changed files with 951 additions and 808 deletions
+10 -5
View File
@@ -474,12 +474,12 @@ if (NOT MSVC)
endif()
if (ANDROID OR WASM)
# On Android and WASM RELEASE builds, we omit unwind info to save space.
# On Android and WebGL RELEASE builds, we omit unwind info to save space.
# (We keep unwind info on iOS to allow readable stack traces in crash reports.)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-unwind-tables -fno-asynchronous-unwind-tables")
endif()
# With WASM, we disable RTTI because we pass emscripten::val back and forth
# With WebGL, we disable RTTI because we pass emscripten::val back and forth
# between C++ and JavaScript in order to efficiently access typed arrays, which are unbound.
# NOTE: This is not documented in emscripten so we should consider a different approach.
if (WASM)
@@ -603,7 +603,7 @@ if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
endif()
# Build with Metal support on non-WASM Apple platforms.
# Build with Metal support on non-WebGL Apple platforms.
if (APPLE AND NOT WASM)
option(FILAMENT_SUPPORTS_METAL "Include the Metal backend" ON)
else()
@@ -691,6 +691,11 @@ if (FILAMENT_SUPPORTS_WEBGPU)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a webgpu --variant-filter=stereo)
endif()
# Disable ESSL 1.0 code generation.
if (NOT FILAMENT_ENABLE_FEATURE_LEVEL_0)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -1)
endif()
# Enable debug info (preserves names in SPIR-V)
if (FILAMENT_ENABLE_MATDBG)
set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -d)
@@ -969,7 +974,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
add_subdirectory(${EXTERNAL}/spirv-headers)
endif()
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
if (FILAMENT_SUPPORTS_WEBGPU)
add_subdirectory(${EXTERNAL}/dawn/tnt/)
endif()
@@ -1021,7 +1026,7 @@ if (IS_HOST_PLATFORM)
add_subdirectory(${TOOLS}/specgen)
endif()
# Generate exported executables for cross-compiled builds (Android, WASM, and iOS)
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
if ((NOT CMAKE_CROSSCOMPILING AND NOT FILAMENT_IMPORT_PREBUILT_EXECUTABLES) OR FILAMENT_EXPORT_PREBUILT_EXECUTABLES)
export(TARGETS matc cmgen filamesh mipgen resgen uberz glslminifier FILE ${IMPORT_EXECUTABLES})
endif()
@@ -20,6 +20,8 @@
#include <filament/Texture.h>
#include <filament/TextureSampler.h>
#include "common/CallbackUtils.h"
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec2.h>
@@ -256,6 +258,69 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nGetConstantBool(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
jboolean result = instance->getConstant<bool>(name);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetConstantFloat(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
jfloat result = instance->getConstant<float>(name);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_google_android_filament_MaterialInstance_nGetConstantInt(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
jint result = instance->getConstant<int32_t>(name);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jboolean x) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant<bool>(name, x);
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantFloat(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jfloat x) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant<float>(name, x);
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantInt(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jint x) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant<int32_t>(name, x);
env->ReleaseStringUTFChars(name_, name);
}
// defined in TextureSampler.cpp
namespace filament::JniUtils {
TextureSampler from_long(jlong params) noexcept;
@@ -592,3 +657,17 @@ Java_com_google_android_filament_MaterialInstance_nGetTransparencyMode(JNIEnv*,
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
return (jint) instance->getTransparencyMode();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nCompile(JNIEnv *env, jclass clazz,
jlong nativeMaterialInstance, jint priority, jint variants, jobject handler, jobject runnable) {
MaterialInstance* materialInstance = (MaterialInstance*) nativeMaterialInstance;
JniCallback* jniCallback = JniCallback::make(env, handler, runnable);
materialInstance->compile(
(MaterialInstance::CompilerPriorityQueue) priority,
(UserVariantFilterBit) variants,
jniCallback->getHandler(), [jniCallback](MaterialInstance*){
JniCallback::postToJavaAndDestroy(jniCallback);
});
}
@@ -523,11 +523,19 @@ public class Material {
* for stereoscopic rendering. If an application is not planning to render in stereo, this bit
* should be turned off to avoid unnecessary material compilations.
*</p>
*<p>
* Note that it is possible to override specialization constants on a per-MaterialInstance basis
* (see {@link MaterialInstance#setConstant}). In that case, the programs compiled by a call to
* Material::compile() may not be reusable by that MaterialInstance. It's better to call
* MaterialInstance::compile() in cases where you intend to override specialization constants.
*</p>
* @param priority Which priority queue to use, LOW or HIGH.
* @param variants Variants to include to the compile command.
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
* @param callback callback called on the main thread when the compilation is done on
* by backend.
*
* @see MaterialInstance#compile
*/
public void compile(@NonNull CompilerPriorityQueue priority,
int variants,
@@ -18,6 +18,7 @@ package com.google.android.filament;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import com.google.android.filament.proguard.UsedByNative;
@@ -142,6 +143,28 @@ public class MaterialInstance {
return mMaterial;
}
/**
* Asynchronously ensures that a subset of this MaterialInstance's variants are compiled.
*
* <p>This function behaves identically to {@link Material#compile}, but takes into account
* the specific constants overridden by {@link #setConstant}.</p>
*
* @param priority Priority of the compile command.
* @param variants Variants to include to the compile command.
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
* @param callback callback called on the main thread when the compilation is done on
* by backend.
*
* @see Material#compile
* @see #setConstant
*/
public void compile(@NonNull Material.CompilerPriorityQueue priority,
int variants,
@Nullable Object handler,
@Nullable Runnable callback) {
nCompile(getNativeObject(), priority.ordinal(), variants, handler, callback);
}
/** @return the name associated with this instance */
@NonNull
public String getName() {
@@ -420,6 +443,69 @@ public class MaterialInstance {
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]);
}
/**
* Overrides a specialization constant of this material instance.
*
* @param name The name of the constant as defined in the material.
* @param value The value of the constant.
* @see Material.Builder#constant
*/
public void setConstant(@NonNull String name, boolean value) {
nSetConstantBool(getNativeObject(), name, value);
}
/**
* Overrides a specialization constant of this material instance.
*
* @param name The name of the constant as defined in the material.
* @param value The value of the constant.
* @see Material.Builder#constant
*/
public void setConstant(@NonNull String name, float value) {
nSetConstantFloat(getNativeObject(), name, value);
}
/**
* Overrides a specialization constant of this material instance.
*
* @param name The name of the constant as defined in the material.
* @param value The value of the constant.
* @see Material.Builder#constant
*/
public void setConstant(@NonNull String name, int value) {
nSetConstantInt(getNativeObject(), name, value);
}
/**
* Gets the value of a specialization constant by name.
*
* @param name The name of the constant as defined in the material.
* @return The value of the constant.
*/
public boolean getConstantBoolean(@NonNull String name) {
return nGetConstantBool(getNativeObject(), name);
}
/**
* Gets the value of a specialization constant by name.
*
* @param name The name of the constant as defined in the material.
* @return The value of the constant.
*/
public float getConstantFloat(@NonNull String name) {
return nGetConstantFloat(getNativeObject(), name);
}
/**
* Gets the value of a specialization constant by name.
*
* @param name The name of the constant as defined in the material.
* @return The value of the constant.
*/
public int getConstantInt(@NonNull String name) {
return nGetConstantInt(getNativeObject(), name);
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
@@ -955,6 +1041,17 @@ public class MaterialInstance {
@NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
private static native boolean nGetConstantBool(long nativeMaterialInstance, @NonNull String name);
private static native float nGetConstantFloat(long nativeMaterialInstance, @NonNull String name);
private static native int nGetConstantInt(long nativeMaterialInstance, @NonNull String name);
private static native void nSetConstantBool(long nativeMaterialInstance,
@NonNull String name, boolean x);
private static native void nSetConstantFloat(long nativeMaterialInstance,
@NonNull String name, float x);
private static native void nSetConstantInt(long nativeMaterialInstance,
@NonNull String name, int x);
private static native void nSetParameterTexture(long nativeMaterialInstance,
@NonNull String name, long nativeTexture, long sampler);
@@ -1018,4 +1115,5 @@ public class MaterialInstance {
private static native int nGetDepthFunc(long nativeMaterialInstance);
private static native void nSetTransparencyMode(long nativeMaterialInstance, int mode);
private static native int nGetTransparencyMode(long nativeMaterialInstance);
private static native void nCompile(long nativeMaterialInstance, int priority, int variants, Object handler, Runnable callback);
}
+1 -7
View File
@@ -368,11 +368,6 @@ function build_wasm_with_target {
ISSUE_CMAKE_ALWAYS=true
fi
if [[ "${WEBGPU_OPTION}" == *"-DFILAMENT_SUPPORTS_WEBGPU=ON"* ]]; then
WEBGPU_OPTION="${WEBGPU_OPTION} -DCMAKE_CXX_FLAGS=\"--use-port=emdawnwebgpu\""
WEBGPU_OPTION="${WEBGPU_OPTION} -DCMAKE_C_FLAGS=\"--use-port=emdawnwebgpu\""
fi
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
# Apply the emscripten environment within a subshell.
(
@@ -1032,8 +1027,7 @@ while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:Py:E" opt; do
echo "Consider using -c after changing this option to clear the Gradle cache."
;;
W)
WEBGPU_OPTION='-DFILAMENT_SUPPORTS_WEBGPU=ON'
WEBGPU_OPTION="-DFILAMENT_SUPPORTS_WEBGPU=ON"
WEBGPU_ANDROID_GRADLE_OPTION="-Pcom.google.android.filament.include-webgpu"
echo "Enable support for WebGPU(Experimental) in the core Filament library."
;;
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+3 -8
View File
@@ -358,11 +358,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
include/backend/platforms/WebGPUPlatformAndroid.h
src/webgpu/platform/WebGPUPlatformAndroid.cpp
)
elseif (WASM)
list(APPEND SRCS
include/backend/platforms/WebGPUPlatformWasm.h
src/webgpu/platform/WebGPUPlatformWasm.cpp
)
endif()
if (TNT_DEV)
@@ -436,7 +431,7 @@ if (FILAMENT_USE_ABSEIL_LOGGING)
target_link_libraries(${TARGET} PRIVATE absl::log)
endif()
# Android, iOS, and WASM do not use bluegl.
# Android, iOS, and WebGL do not use bluegl.
if(FILAMENT_SUPPORTS_OPENGL AND NOT IOS AND NOT ANDROID AND NOT WASM)
target_link_libraries(${TARGET} PRIVATE bluegl)
endif()
@@ -446,7 +441,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
endif()
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
if (FILAMENT_SUPPORTS_WEBGPU)
target_link_libraries(${TARGET} PRIVATE webgpu_dawn dawncpp_headers)
endif()
@@ -564,7 +559,7 @@ install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
install(TARGETS ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include)
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
if (FILAMENT_SUPPORTS_WEBGPU)
install(TARGETS webgpu_dawn ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
endif()
@@ -31,11 +31,6 @@
#endif
#include <webgpu/webgpu_cpp.h>
#if defined(__EMSCRIPTEN__)
// We need to polyfill some Dawn extensions that are not in Emscripten.
#include "WebGPUWasmPolyfill.h"
#endif
#include <cstdint>
#include <vector>
@@ -66,9 +61,9 @@ public:
// either returns a valid surface or panics
[[nodiscard]] virtual wgpu::Surface createSurface(void* nativeWindow, uint64_t flags) = 0;
// either returns a valid adapter or panics
[[nodiscard]] virtual wgpu::Adapter requestAdapter(wgpu::Surface const& surface);
[[nodiscard]] wgpu::Adapter requestAdapter(wgpu::Surface const& surface);
// either returns a valid device or panics
[[nodiscard]] virtual wgpu::Device requestDevice(wgpu::Adapter const& adapter);
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
struct Configuration {
wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined;
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2026 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_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWASM_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWASM_H
#include <backend/platforms/WebGPUPlatform.h>
namespace filament::backend {
class WebGPUPlatformWasm : public WebGPUPlatform {
public:
wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override;
wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override;
wgpu::Adapter requestAdapter(wgpu::Surface const& surface) override;
wgpu::Device requestDevice(wgpu::Adapter const& adapter) override;
static wgpu::Device sDevice;
protected:
std::vector<wgpu::RequestAdapterOptions> getAdapterOptions() override;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWASM_H
@@ -1,70 +0,0 @@
/*
* Copyright (C) 2026 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_FILAMENT_BACKEND_WEBGPU_WASM_POLYFILL_H
#define TNT_FILAMENT_BACKEND_WEBGPU_WASM_POLYFILL_H
#if defined(__EMSCRIPTEN__)
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace wgpu {
struct Extent2D {
uint32_t width = 0;
uint32_t height = 0;
operator Extent3D() const { return {width, height, 1}; }
};
struct Origin2D {
uint32_t x = 0;
uint32_t y = 0;
operator Origin3D() const { return {x, y, 0}; }
};
// b/508270158
enum class ComponentSwizzle : uint32_t {
Undefined = 0,
Zero = 1,
One = 2,
R = 3,
G = 4,
B = 5,
A = 6,
};
struct TextureComponentSwizzle {
ComponentSwizzle r = ComponentSwizzle::Undefined;
ComponentSwizzle g = ComponentSwizzle::Undefined;
ComponentSwizzle b = ComponentSwizzle::Undefined;
ComponentSwizzle a = ComponentSwizzle::Undefined;
};
struct DawnTogglesDescriptor : public ChainedStruct {
uint32_t enabledToggleCount = 0;
const char* const* enabledToggles = nullptr;
};
struct DawnAdapterPropertiesPowerPreference : public ChainedStructOut {
PowerPreference powerPreference = PowerPreference::Undefined;
};
} // namespace wgpu
#endif // __EMSCRIPTEN__
#endif // TNT_FILAMENT_BACKEND_WEBGPU_WASM_POLYFILL_H
-5
View File
@@ -30,8 +30,6 @@
#include "backend/platforms/WebGPUPlatformLinux.h"
#elif defined(WIN32)
#include "backend/platforms/WebGPUPlatformWindows.h"
#elif defined(__EMSCRIPTEN__)
#include "backend/platforms/WebGPUPlatformWasm.h"
#endif
#endif
@@ -154,8 +152,6 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
return new WebGPUPlatformLinux();
#elif defined(WIN32)
return new WebGPUPlatformWindows();
#elif defined(__EMSCRIPTEN__)
return new WebGPUPlatformWasm();
#else
return nullptr;
#endif
@@ -213,4 +209,3 @@ void PlatformFactory::destroy(Platform** platform) noexcept {
}
} // namespace filament::backend
@@ -19,10 +19,6 @@
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
+6 -36
View File
@@ -64,22 +64,6 @@
using namespace std::chrono_literals;
// https://issues.chromium.org/issues/507581790
// Manual polyfill pending upstream fix.
#if defined(__EMSCRIPTEN__)
#include <emscripten/em_js.h>
EM_JS(void, wgpuRenderPassEncoderSetImmediates, (WGPURenderPassEncoder passEncoder,
uint32_t offset, void const * data, size_t size), {
const encoder = WebGPU.Internals.jsObjects[passEncoder];
if (encoder && encoder.setImmediates) {
const buffer = HEAPU8.slice(data, data + size);
encoder.setImmediates(offset, buffer);
}
})
#endif
namespace filament::backend {
namespace {
@@ -163,10 +147,8 @@ void WebGPUDriver::terminate() {
}
void WebGPUDriver::tick(int) {
#if !defined(__EMSCRIPTEN__)
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
#endif
}
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -216,9 +198,7 @@ void WebGPUDriver::finish(int /* dummy */) {
// processed. Note that blocking with mReadPixelMapsCounter.waitForAllToFinish will only
// deadlock since we could not advance the counter.
while (!mReadPixelMapsCounter.isIdle()) {
#if !defined(__EMSCRIPTEN__)
mAdapter.GetInstance().ProcessEvents();
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
@@ -802,7 +782,7 @@ FenceStatus WebGPUDriver::fenceWait(FenceHandle fenceHandle, uint64_t const time
if (!fence) {
return FenceStatus::ERROR;
}
using namespace std::chrono;
auto now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
@@ -820,15 +800,15 @@ FenceStatus WebGPUDriver::fenceWait(FenceHandle fenceHandle, uint64_t const time
state = fence->getState();
return bool(state);
}, until);
if (status == FenceStatus::ERROR) {
return FenceStatus::ERROR;
}
if (status == FenceStatus::TIMEOUT_EXPIRED) {
return FenceStatus::TIMEOUT_EXPIRED;
}
auto duration_ns = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(steady_clock::now() - now)
.count());
@@ -857,11 +837,7 @@ bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
}
bool WebGPUDriver::isTextureSwizzleSupported() {
#if defined(__EMSCRIPTEN__)
return false;
#else
return mDevice.HasFeature(wgpu::FeatureName::TextureComponentSwizzle);
#endif
}
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
@@ -883,7 +859,7 @@ bool WebGPUDriver::isTextureFormatFilterable(TextureFormat format) {
if (isFp32ColorFormat(format)) {
return mDevice.HasFeature(wgpu::FeatureName::Float32Filterable);
}
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
isDepthFormat(format) || isStencilFormat(format)) {
return false;
}
@@ -1348,8 +1324,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
const uint8_t mipLevel = colorInfos[i].level;
const uint32_t arrayLayer = colorInfos[i].layer;
customColorViews[customColorViewCount] =
colorTexture->makeAttachmentTextureView(mipLevel, arrayLayer,
renderTarget->getLayerCount());
colorTexture->makeAttachmentTextureView(mipLevel, arrayLayer, renderTarget->getLayerCount());
if (msaaSidecarsRequired) {
const wgpu::TextureView msaaSidecarView{
colorTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
@@ -1508,12 +1483,7 @@ void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
} else if (std::holds_alternative<bool>(value)) {
data = std::get<bool>(value) ? 1 : 0;
}
#if defined(__EMSCRIPTEN__)
wgpuRenderPassEncoderSetImmediates(mRenderPassEncoder.Get(), index * sizeof(uint32_t), &data,
sizeof(uint32_t));
#else
mRenderPassEncoder.SetImmediates(index * sizeof(uint32_t), &data, sizeof(uint32_t));
#endif
}
void WebGPUDriver::insertEventMarker(char const* string) {
@@ -168,10 +168,8 @@ private:
wgpu::TextureFormat::Undefined //
}; // 32 : 328
};
#if !defined(__EMSCRIPTEN__)
static_assert(sizeof(RenderPipelineKey) == 360,
"RenderPipelineKey must not have implicit padding.");
#endif
static_assert(std::is_trivially_copyable<RenderPipelineKey>::value,
"RenderPipelineKey must be a trivially copyable POD for fast hashing.");
@@ -28,33 +28,6 @@
#include <array>
#include <cstring>
// https://issues.chromium.org/issues/507581790
// Manual polyfill pending upstream fix.
#if defined(__EMSCRIPTEN__)
#include <emscripten/em_js.h>
EM_JS(WGPUPipelineLayout, filamentCreatePipelineLayoutWithImmediateData,
(WGPUDevice devicePtr, WGPUPipelineLayoutDescriptor const* descriptor), {
const bglCount = HEAPU32[(descriptor >> 2) + 3];
const bglPtr = HEAPU32[(descriptor >> 2) + 4];
const bgls = [];
for (let i = 0; i < bglCount; ++i) {
bgls.push(WebGPU.getJsObject(HEAPU32[(bglPtr >> 2) + i]));
}
const desc = {
"label": WebGPU.makeStringFromOptionalStringView(descriptor + 4),
"bindGroupLayouts": bgls,
"immediateSize": HEAPU32[(descriptor >> 2) + 5],
};
const device = WebGPU.getJsObject(devicePtr);
const ptr = _emwgpuCreatePipelineLayout(0);
WebGPU.Internals.jsObjectInsert(ptr, device.createPipelineLayout(desc));
return ptr;
})
#endif
namespace filament::backend {
WebGPUPipelineLayoutCache::WebGPUPipelineLayoutCache(wgpu::Device const& device)
@@ -99,28 +72,13 @@ wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
wgpu::Limits supportedLimits{};
mDevice.GetLimits(&supportedLimits);
uint32_t immediateSize = supportedLimits.maxImmediateSize;
if (immediateSize == 0) {
// Fallback: WebGPU implementations that support 'chromium-experimental-immediate-data'
// might not expose 'maxImmediateSize' via JS limits yet, so we assume 64.
immediateSize = 64;
}
const wgpu::PipelineLayoutDescriptor descriptor{
.label = wgpu::StringView(request.label.c_str_safe()),
.bindGroupLayoutCount = request.bindGroupLayoutCount,
.bindGroupLayouts = request.bindGroupLayouts.data(),
.immediateSize = immediateSize,
.immediateSize = supportedLimits.maxImmediateSize,
};
const wgpu::PipelineLayout layout =
#if defined(__EMSCRIPTEN__)
wgpu::PipelineLayout::Acquire(
filamentCreatePipelineLayoutWithImmediateData(mDevice.Get(),
reinterpret_cast<WGPUPipelineLayoutDescriptor const*>(&descriptor)));
#else
mDevice.CreatePipelineLayout(&descriptor);
#endif
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(layout)
<< "Failed to create pipeline layout " << descriptor.label << ".";
return layout;
@@ -76,10 +76,8 @@ private:
uint8_t bindGroupLayoutCount{ 0 }; // 1 :32
uint8_t padding[7]{ 0 }; // 7 :33
};
#if !defined(__EMSCRIPTEN__)
static_assert(sizeof(PipelineLayoutKey) == 40,
"PipelineLayoutKey must not have implicit padding.");
#endif
static_assert(std::is_trivially_copyable<PipelineLayoutKey>::value,
"PipelineLayoutKey must be a trivially copyable POD for fast hashing.");
+111 -12
View File
@@ -43,6 +43,101 @@ namespace filament::backend {
namespace {
/**
* Given the source code of a [WGSL] WebGPU shader in text (string view to it) and the constants
* to be overwritten now (at runtime), returns updated [WGSL] shader code text, where the constants
* have been replaced.
* @param shaderLabel Something to call this shader for troubleshooting, error messaging, etc.
* @param shaderSource The original WGSL WebGPU shader code as text to be processed. This is a
* view to that text and this function does not change it; it is immutable.
* Instead, the processed version of this source is returned by the function.
* @param specConstants The constants to replace in the shader code, indexed by constant id.
* @return Processed version of the WGSL WebGPU shader code provided, where the constants have
* been replaced with the values provided by the specConstants parameter.
*/
[[nodiscard]] std::string replaceSpecConstants(std::string_view shaderLabel,
std::string_view shaderSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants) {
// this function is not expected to be called at all if no spec constants are to be replaced
assert_invariant(!specConstants.empty());
static constexpr std::string_view specConstantPrefix = "FILAMENT_SPEC_CONST_";
static constexpr size_t specConstantPrefixSize = specConstantPrefix.size();
const char* const sourceData = shaderSource.data();
std::stringstream processedShaderSource{};
size_t pos = 0;
while (pos < shaderSource.size()) {
const size_t posOfNextSpecConstant = shaderSource.find(specConstantPrefix, pos);
if (posOfNextSpecConstant == std::string::npos) {
// no more spec constants, so just stream the rest of the source code string
processedShaderSource << std::string_view(sourceData + pos, shaderSource.size() - pos);
break;
}
const size_t posOfId = posOfNextSpecConstant + specConstantPrefixSize;
const size_t posAfterId = shaderSource.find('_', posOfId);
FILAMENT_CHECK_POSTCONDITION(posAfterId != std::string::npos)
<< "malformed " << shaderLabel << ". Found spec constant prefix '"
<< specConstantPrefix << "' without an id or '_' after it.";
const std::string_view idStr =
std::string_view(sourceData + posOfId, posAfterId - posOfId);
const size_t posEndOfStatement = shaderSource.find(';', posAfterId);
FILAMENT_CHECK_POSTCONDITION(posEndOfStatement != std::string::npos)
<< "malformed " << shaderLabel << ". Found spec constant assignment with id "
<< idStr << " without a terminating ';' character?";
// this is a view into part of the statement, from after the id to the ';'
const std::string_view statementSegment =
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
size_t posOfEqual = statementSegment.find('=');
if (posOfEqual == std::string::npos) {
// Not an assignment, so we don't need to replace it.
processedShaderSource << std::string_view(sourceData + pos,
posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
continue;
}
posOfEqual += posAfterId; // position in original source overall, not just the segment
int constantId = 0;
char* endPtr;
errno = 0; // Clear errno before the call
long tempConstantId = std::strtol(idStr.data(), &endPtr, 10);
// Check for conversion errors
if (endPtr == idStr.data() || *endPtr != '\0' || errno == ERANGE) {
// Parsing failed, or conversion was out of range for 'long'
// or the string contained non-numeric characters after the number.
PANIC_POSTCONDITION("Invalid spec constant id '%s' in %s (not a valid integer or out "
"of range for 'long'?)",
idStr.data(), shaderLabel.data());
} else {
// Check if the parsed long value fits into an int
if (tempConstantId > std::numeric_limits<int>::max() ||
tempConstantId < std::numeric_limits<int>::min()) {
PANIC_POSTCONDITION(
"Invalid spec constant id '%s' in %s (value out of range for 'int')",
idStr.data(), shaderLabel.data());
} else {
constantId = static_cast<int>(tempConstantId);
}
}
const std::variant<int32_t, float, bool> newValue = specConstants[constantId];
// stream up to the equal sign...
processedShaderSource << std::string_view(sourceData + pos, posOfEqual + 1 - pos);
// stream the new value...
if (auto* v = std::get_if<int32_t>(&newValue)) {
processedShaderSource << " " << *v << "i";
} else if (auto* f = std::get_if<float>(&newValue)) {
processedShaderSource << " " << *f << "f";
} else if (auto* b = std::get_if<bool>(&newValue)) {
processedShaderSource << " " << ((*b) ? "true" : "false");
}
// end the statement...
processedShaderSource << ";";
// and skip to after the end of the statement in the original source and continue...
pos = posEndOfStatement + 1;
}
return processedShaderSource.str();
}
/**
* Creates a WebGPU shader module for a given "program" "stage", accounting for override constants.
* Effectively, this function is responsible for preprocessing the shader source and compiling it.
@@ -50,12 +145,14 @@ namespace {
* a shader module
* @param program The "program" to compile/create the shader, which includes the shader source
* @param stage The stage (e.g. vertex, fragment, etc.) to create the shader module
* @param specConstants Override constants to apply when creating/compiling the shader module.
* @return the proper WebGPU shader module compiled/created from the input parameters. This might
* wrap a null handle if the shader is not present (if the shader source is empty), such as
* a missing fragment or compute shader.
*/
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device const& device,
Program const& program, const ShaderStage stage) {
Program const& program, const ShaderStage stage,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants) {
const char* const programName = program.getName().c_str_safe();
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
shaderSource = program.getShadersSource();
@@ -67,18 +164,18 @@ namespace {
std::stringstream labelStream;
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
const auto label = labelStream.str();
const std::string processedShaderSource =
specConstants.empty()
? reinterpret_cast<const char*>(sourceBytes.data())
: replaceSpecConstants(label, reinterpret_cast<const char*>(sourceBytes.data()),
specConstants);
wgpu::ShaderSourceWGSL wgslDescriptor{};
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
wgslDescriptor.code = wgpu::StringView(processedShaderSource);
const wgpu::ShaderModuleDescriptor descriptor{
.nextInChain = &wgslDescriptor,
.label = label.data()
};
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
#if !defined(__EMSCRIPTEN__)
// TODO: We don't really need to wait for compilation info in production. It's helpful only
// for debugging.
const wgpu::Instance instance = device.GetAdapter().GetInstance();
// Synchronously compile the shader module.
@@ -147,7 +244,6 @@ namespace {
PANIC_POSTCONDITION("Timed out creating/compiling shader %s", descriptor.label.data);
break;
}
#endif
FILAMENT_CHECK_POSTCONDITION(shaderModule) << "Failed to create " << descriptor.label;
return shaderModule;
}
@@ -155,11 +251,14 @@ namespace {
}// namespace
WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
: HwProgram{ program.getName() } {
: HwProgram{ program.getName() } {
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants =
program.getSpecializationConstants();
// TODO: Consider creating/compiling these shaders in parallel.
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX);
fragmentShaderModule = createShaderModule(device, program, ShaderStage::FRAGMENT);
computeShaderModule = createShaderModule(device, program, ShaderStage::COMPUTE);
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
fragmentShaderModule =
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
computeShaderModule = createShaderModule(device, program, ShaderStage::COMPUTE, specConstants);
}
}// namespace filament::backend
@@ -81,9 +81,7 @@ void WebGPUQueueManager::finish() {
// This is similar to draining a work queue. We currently have no other way to force the "last"
// callback to be called.
while (mLatestSubmissionState->getStatus() == FenceStatus::TIMEOUT_EXPIRED) {
#if !defined(__EMSCRIPTEN__)
mDevice.GetAdapter().GetInstance().ProcessEvents();
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
@@ -392,9 +392,6 @@ WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureForm
case wgpu::TextureFormat::R16Snorm:
case wgpu::TextureFormat::RG16Snorm:
case wgpu::TextureFormat::RGBA16Snorm:
// Formats not available on WASM
#if !defined(__EMSCRIPTEN__)
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
@@ -403,7 +400,6 @@ WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureForm
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
case wgpu::TextureFormat::OpaqueYCbCrAndroid:
#endif
return ScalarSampleType::F32;
case wgpu::TextureFormat::Depth16Unorm:
case wgpu::TextureFormat::Depth24Plus:
@@ -67,12 +67,10 @@ template<typename WebGPUPrintable>
}
}
#if !defined(__EMSCRIPTEN__)
[[nodiscard]] static inline std::string_view powerPreferenceToString(
const wgpu::DawnAdapterPropertiesPowerPreference powerPreference) {
return powerPreferenceToString(powerPreference.powerPreference);
}
#endif
[[nodiscard]] constexpr std::string_view backendTypeToString(const wgpu::BackendType backendType) {
switch (backendType) {
@@ -233,7 +231,6 @@ template<typename WebGPUPrintable>
case wgpu::TextureFormat::R16Snorm: return "R16Snorm";
case wgpu::TextureFormat::RG16Snorm: return "RG16Snorm";
case wgpu::TextureFormat::RGBA16Snorm: return "RGBA16Snorm";
#if !defined(__EMSCRIPTEN__)
case wgpu::TextureFormat::R8BG8Biplanar420Unorm: return "R8BG8Biplanar420Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm: return "R10X6BG10X6Biplanar420Unorm";
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm: return "R8BG8A8Triplanar420Unorm";
@@ -242,7 +239,6 @@ template<typename WebGPUPrintable>
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm: return "R10X6BG10X6Biplanar422Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm: return "R10X6BG10X6Biplanar444Unorm";
case wgpu::TextureFormat::OpaqueYCbCrAndroid: return "OpaqueYCbCrAndroid";
#endif
default: return "Unknown";
}
}
@@ -387,12 +387,9 @@ wgpu::Texture WebGPUSwapChain::getCurrentTexture() {
}
void WebGPUSwapChain::present(DriverBase& driver) {
#if !defined(__EMSCRIPTEN__)
if (!isHeadless()) {
mSurface.Present();
}
#endif
if (mFrameScheduled.callback) {
driver.scheduleCallback(mFrameScheduled.handler,
[callback = mFrameScheduled.callback]() {
@@ -17,15 +17,11 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
#define TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
#include <webgpu/webgpu_cpp.h>
#include "DriverBase.h"
#include <backend/Platform.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <memory>
+10 -24
View File
@@ -41,14 +41,6 @@ namespace filament::backend {
namespace {
bool supportsTransientAttachment(wgpu::Device const& device) {
#if !defined(__EMSCRIPTEN__)
return device.HasFeature(wgpu::FeatureName::TransientAttachments);
#else
return false;
#endif
}
[[nodiscard]] constexpr wgpu::StringView getUserTextureLabel(const SamplerType target) {
// TODO will be helpful to get more useful info than this
switch (target) {
@@ -217,9 +209,9 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS,
supportsTransientAttachment(device)) },
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false,
supportsTransientAttachment(device)) },
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mDimension{ toWebGPUTextureViewDimension(samplerType) },
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) },
@@ -299,7 +291,10 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
mDefaultTextureView = makeTextureView(mDefaultMipLevel, levelCount, 0,
mDefaultBaseArrayLayer, src->getViewDimension());
} else {
wgpu::TextureViewDescriptor viewDesc{
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = mTexture.GetFormat(),
.dimension = src->getViewDimension(),
@@ -308,12 +303,6 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
.baseArrayLayer = 0,
.arrayLayerCount = mDefaultBaseArrayLayer,
};
// b/508270158
#if !defined(__EMSCRIPTEN__)
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
viewDesc.nextInChain = &swizzleDesc,
#endif
mDefaultTextureView = mTexture.CreateView(&viewDesc);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView)
<< "Failed to create swizzled Texture view";
@@ -338,18 +327,15 @@ WebGPUTexture::WebGPUTexture(const WebGPUTexture* src,
mDefaultBaseArrayLayer{ 0 },
mMsaaSidecarTexture{src->mMsaaSidecarTexture},
mSwizzle{ composeSwizzle(src->getSwizzle(), nextSwizzle) } {
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
wgpu::TextureViewDescriptor viewDesc{
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = mTexture.GetFormat(),
.dimension = src->getViewDimension(),
};
// b/508270158
#if !defined(__EMSCRIPTEN__)
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
viewDesc.nextInChain = &swizzleDesc,
#endif
mDefaultTextureView = mTexture.CreateView(&viewDesc);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView) << "Failed to create swizzled Texture view";
}
@@ -20,9 +20,6 @@
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
@@ -21,10 +21,6 @@
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <string_view>
@@ -601,7 +597,7 @@ namespace filament::backend {
}
wgpu::TextureUsage transientAttachmentNeeded{ wgpu::TextureUsage::None };
bool const useTransientAttachment =
const bool useTransientAttachment {
deviceSupportsTransientAttachments &&
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
@@ -612,12 +608,9 @@ namespace filament::backend {
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1)};
if (useTransientAttachment) {
#if !defined(__EMSCRIPTEN__)
transientAttachmentNeeded |= wgpu::TextureUsage::TransientAttachment;
#endif
}
// A texture that is a blit destination or render attachment will often need to be
@@ -225,73 +225,42 @@ struct AttributeInfo final {
* associated with a vertex buffer, and the slot each one belongs
*/
void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attributes,
const uint32_t deviceMaxVertexBuffers,
const uint8_t attributeCount, const uint32_t deviceMaxVertexBuffers,
std::vector<WebGPUVertexBufferInfo::WebGPUSlotBindingInfo>& outWebGPUSlotBindingInfos,
std::vector<wgpu::VertexBufferLayout>& outVertexBufferLayouts,
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT>& outAttributeInfos,
uint8_t& outActualAttributeCount) {
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT>& outAttributeInfos) {
uint8_t currentWebGPUSlotIndex = 0;
uint8_t currentAttributeIndex = 0;
outVertexBufferLayouts.reserve(MAX_VERTEX_BUFFER_COUNT);
outWebGPUSlotBindingInfos.reserve(outVertexBufferLayouts.capacity());
// Find a valid attribute to use as a dummy for unused slots (typically POSITION at index 0)
Attribute dummyAttribute{};
bool hasDummy = false;
for (uint32_t i = 0; i < attributes.size(); ++i) {
if (attributes[i].buffer != Attribute::BUFFER_UNUSED) {
dummyAttribute = attributes[i];
hasDummy = true;
break;
}
}
/*
* WebGPU Strict Validation Workaround:
*
* 1. Completeness: WebGPU requires ALL shader-declared attributes to exist in the Pipeline's
* VertexState, even if unused by a specific mesh variant (e.g., BONE_WEIGHTS).
* 2. Stride Boundary: Offset + Format_Size <= arrayStride.
*
* If we omit an unused attribute, WebGPU crashes (violates 1).
* If we pad it as Float32x4 (16 bytes) into Slot 0 (e.g., Position, stride 12), WebGPU crashes
* (violates 2):
*
* [ Slot 0 Stride: 12 bytes ]
* |-------POSITION--------|
* |------ BONE_WEIGHTS (Float32x4 = 16b) -----| <-- OVERFLOWS STRIDE!
*
* FIX: We alias unused attributes into Slot 0 as a 4-byte format (Float32 or Uint32).
*
* [ Slot 0 Stride: 12 bytes ]
* |-------POSITION--------|
* |-BONE-| <-- (Float32 = 4b) Safely fits inside the 12-byte stride!
*
* WebGPU safely promotes the 4-byte Float32 into a WGSL vec4<f32> as (x, 0.0, 0.0, 1.0).
*/
for (uint32_t attributeIndex = 0; attributeIndex < attributes.size(); ++attributeIndex) {
auto attribute = attributes[attributeIndex];
wgpu::VertexFormat vertexFormat;
const auto& attribute = attributes[attributeIndex];
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
if (!hasDummy) {
continue;
}
// HACK: Re-use the dummy buffer for disabled attributes to satisfy WebGPU validation.
// Filament's shaders expect vec4 or uvec4. We provide a dummy format.
const bool isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
vertexFormat = isInteger ? wgpu::VertexFormat::Uint8x4 : wgpu::VertexFormat::Unorm8x4;
attribute = dummyAttribute;
} else {
const bool isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
const bool isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
vertexFormat = getVertexFormat(attribute.type, isNormalized, isInteger);
// We ignore "unused" attributes, ones not associated with a buffer. If a shader
// references such an attribute we have a bug one way or another. Either the API/CPU
// will produce an error (best case scenario), where an attribute is referenced in the
// shader but not provided by the backend API (CPU-side), or the shader would be getting
// junk/undefined data from the GPU, since we do not have a valid buffer of data to
// provide to the shader/GPU.
continue;
}
const bool isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
const bool isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
const wgpu::VertexFormat vertexFormat =
getVertexFormat(attribute.type, isNormalized, isInteger);
uint8_t existingSlot = INVALID_SLOT_INDEX;
for (uint32_t slot = 0; slot < currentWebGPUSlotIndex; slot++) {
WebGPUVertexBufferInfo::WebGPUSlotBindingInfo const& info =
outWebGPUSlotBindingInfos[slot];
// We consider attributes to be in the same slot/layout only if they belong to the
// same buffer and are interleaved; they cannot belong to separate partitions in the
// same buffer, for example.
// For the attributes to be interleaved, the stride must match (among other things).
// The attribute offset being less than the slot's/layout's buffer offset indicates
// that it is in a separate partition before this slot/layout, thus not part of it.
// The difference from the attribute's offset and this slot's/layout's buffer offset
// being greater than the stride indicates it is in a separate partition after this
// slot/layout, thus not part of it.
if (info.sourceBufferIndex == attribute.buffer && info.stride == attribute.stride &&
attribute.offset >= info.bufferOffset &&
((attribute.offset - info.bufferOffset) < attribute.stride)) {
@@ -300,18 +269,26 @@ void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attr
}
}
if (existingSlot == INVALID_SLOT_INDEX) {
// New combination, use a new WebGPU slot
FILAMENT_CHECK_PRECONDITION(currentWebGPUSlotIndex < MAX_VERTEX_BUFFER_COUNT &&
currentWebGPUSlotIndex < deviceMaxVertexBuffers)
<< "Number of vertex buffer layouts must not exceed MAX_VERTEX_BUFFER_COUNT ("
<< MAX_VERTEX_BUFFER_COUNT << ") or the device limit ("
<< deviceMaxVertexBuffers << ")";
existingSlot = currentWebGPUSlotIndex++;
outWebGPUSlotBindingInfos.push_back({ .sourceBufferIndex = attribute.buffer,
outWebGPUSlotBindingInfos.push_back({
.sourceBufferIndex = attribute.buffer,
.bufferOffset = attribute.offset,
.stride = attribute.stride });
outVertexBufferLayouts.push_back(
{ .stepMode = wgpu::VertexStepMode::Vertex, .arrayStride = attribute.stride });
// we need to have a vertex buffer layout for each slot
outVertexBufferLayouts.push_back({
.stepMode = wgpu::VertexStepMode::Vertex,
.arrayStride = attribute.stride
// we do not know attributeCount or attributes at this time. We get those
// in a subsequent pass over the attributeInfos we collect in this loop.
});
}
// we don't use a designated initializer here because certain compilers can't handle it
outAttributeInfos[currentAttributeIndex++] = AttributeInfo(
existingSlot,
{
@@ -320,8 +297,9 @@ void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attr
attribute.offset - outWebGPUSlotBindingInfos[existingSlot].bufferOffset,
.shaderLocation = attributeIndex });
}
outActualAttributeCount = currentAttributeIndex;
FILAMENT_CHECK_POSTCONDITION(currentAttributeIndex == attributeCount)
<< "Using " << currentAttributeIndex << " attributes, but " << attributeCount
<< " where indicated.";
outVertexBufferLayouts.shrink_to_fit();
outWebGPUSlotBindingInfos.shrink_to_fit();
}
@@ -338,21 +316,21 @@ WebGPUVertexBufferInfo::WebGPUVertexBufferInfo(const uint8_t bufferCount,
<< ") exceeds Filament's MAX_VERTEX_ATTRIBUTE_COUNT limit ("
<< MAX_VERTEX_ATTRIBUTE_COUNT << ") and/or the device's limit ("
<< deviceLimits.maxVertexAttributes << ")";
mVertexAttributes.reserve(MAX_VERTEX_ATTRIBUTE_COUNT); // Reserve max as we might add dummies
mVertexAttributes.reserve(attributeCount);
if (attributeCount == 0) {
mVertexBufferLayouts.resize(0);
mWebGPUSlotBindingInfos.resize(0);
return; // should not be possible, but being defensive. nothing to do otherwise
}
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT> attributeInfos{};
uint8_t actualAttributeCount = 0;
createBufferLayoutsBindingSlotsAndAttributeInfos(attributes, deviceLimits.maxVertexBuffers,
mWebGPUSlotBindingInfos, mVertexBufferLayouts, attributeInfos, actualAttributeCount);
createBufferLayoutsBindingSlotsAndAttributeInfos(attributes, attributeCount,
deviceLimits.maxVertexBuffers, mWebGPUSlotBindingInfos, mVertexBufferLayouts,
attributeInfos);
// sort attribute infos by increasing slot (by increasing offset within each slot).
// We do this to ensure that attributes for the same slot/layout are contiguous in
// the vector, so the vertex buffer layout associated with these contiguous attributes
// can directly reference them in the mVertexAttributes vector below.
std::sort(attributeInfos.data(), attributeInfos.data() + actualAttributeCount,
std::sort(attributeInfos.data(), attributeInfos.data() + attributeCount,
[](AttributeInfo const& first, AttributeInfo const& second) {
if (first.slot < second.slot) {
return true;
@@ -366,7 +344,7 @@ WebGPUVertexBufferInfo::WebGPUVertexBufferInfo(const uint8_t bufferCount,
});
// populate mVertexAttributes and update mVertexBufferLayouts to reference the correct
// attributes in it (which will be contiguous in memory as ensured by the sorting above)...
for (uint32_t attributeIndex = 0; attributeIndex < actualAttributeCount; ++attributeIndex) {
for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; ++attributeIndex) {
AttributeInfo const& info = attributeInfos[attributeIndex];
mVertexAttributes.push_back(info.attribute);
if (mVertexBufferLayouts[info.slot].attributeCount == 0) {
@@ -57,29 +57,20 @@ namespace {
constexpr uint32_t MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE = 12u;
constexpr std::array REQUIRED_FEATURES = {
wgpu::FeatureName::TransientAttachments,
// Qualcomm 500 and 600 GPUs do not support this so it is not part of core webgpu spec. To
// support such devices, we will either need Filament to not attempt this, or find another
// workaround. https://github.com/gpuweb/gpuweb/issues/2648
wgpu::FeatureName::RG11B10UfloatRenderable,
// necessary for blit conversions of formats like RGBA32Float...
wgpu::FeatureName::Float32Filterable,
// Unsupported on WASM
#if !defined(__EMSCRIPTEN__)
wgpu::FeatureName::TransientAttachments,
#endif
};
constexpr std::array OPTIONAL_FEATURES = {
wgpu::FeatureName::CoreFeaturesAndLimits,
wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8,
// Unsupported on WASM
#if !defined(__EMSCRIPTEN__)
wgpu::FeatureName::TextureComponentSwizzle,
#endif
};
enum class LimitToValidate : uint8_t {
@@ -275,21 +266,12 @@ void printInstanceDetails(wgpu::Instance const& instance) {
#endif
dawnTogglesDescriptor.enabledToggleCount = toggles.size();
dawnTogglesDescriptor.enabledToggles = toggles.data();
#if defined(__EMSCRIPTEN__)
constexpr std::array<wgpu::InstanceFeatureName, 0> features;
#else
constexpr std::array features = {
wgpu::InstanceFeatureName::TimedWaitAny
};
#endif
const wgpu::InstanceFeatureName features[] = {wgpu::InstanceFeatureName::TimedWaitAny};
wgpu::InstanceDescriptor instanceDescriptor{
.nextInChain = &dawnTogglesDescriptor,
.requiredFeatureCount = features.size(),
.requiredFeatures = features.data(),
.requiredFeatures = features,
};
instanceDescriptor.requiredFeatureCount = 1;
wgpu::Instance instance = wgpu::CreateInstance(&instanceDescriptor);
FILAMENT_CHECK_POSTCONDITION(instance != nullptr) << "Unable to create WebGPU instance.";
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
@@ -369,8 +351,6 @@ struct AdapterDetails final {
: info(std::move(info)),
powerPreference(powerPreference),
adapter(std::move(adapter)) {}
AdapterDetails(AdapterDetails&& other) noexcept = default;
AdapterDetails& operator=(AdapterDetails&& other) noexcept {
adapter = std::exchange(other.adapter, nullptr);
info = std::exchange(other.info, {});
@@ -393,10 +373,8 @@ struct AdapterDetails final {
[[nodiscard]] std::string toString(AdapterDetails const& details) {
std::stringstream out;
out << adapterInfoToString(details.info);
#if !defined(__EMSCRIPTEN__)
out << " power preference " << powerPreferenceToString(details.powerPreference);
#endif
out << adapterInfoToString(details.info)
<< " power preference " << powerPreferenceToString(details.powerPreference);
return out.str();
}
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2026 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 <backend/platforms/WebGPUPlatformWasm.h>
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
#include <emscripten/html5.h>
#include <vector>
#include <cstdint>
extern "C" WGPUDevice emscripten_webgpu_get_device(void);
namespace filament::backend {
wgpu::Device WebGPUPlatformWasm::sDevice = nullptr;
std::vector<wgpu::RequestAdapterOptions> WebGPUPlatformWasm::getAdapterOptions() {
std::vector<wgpu::RequestAdapterOptions> requests;
requests.reserve(2);
requests.emplace_back(wgpu::RequestAdapterOptions{
.powerPreference = wgpu::PowerPreference::HighPerformance,
.forceFallbackAdapter = false,
.backendType = wgpu::BackendType::WebGPU,
});
requests.emplace_back(wgpu::RequestAdapterOptions{
.powerPreference = wgpu::PowerPreference::LowPower,
.forceFallbackAdapter = false,
.backendType = wgpu::BackendType::WebGPU,
});
return requests;
}
wgpu::Adapter WebGPUPlatformWasm::requestAdapter(wgpu::Surface const& surface) { return nullptr; }
wgpu::Device WebGPUPlatformWasm::requestDevice(wgpu::Adapter const& adapter) {
if (sDevice == nullptr) {
WGPUDevice device = emscripten_webgpu_get_device();
FILAMENT_CHECK_POSTCONDITION(device != nullptr)
<< "WebGPU device not initialized. Call Filament.initWebGPU() first.";
sDevice = wgpu::Device::Acquire(device);
}
return sDevice;
}
wgpu::Extent2D WebGPUPlatformWasm::getSurfaceExtent(void* nativeWindow) const {
const char* selector = static_cast<const char*>(nativeWindow);
int width = 0;
int height = 0;
emscripten_get_canvas_element_size(selector, &width, &height);
return wgpu::Extent2D{
.width = static_cast<uint32_t>(width),
.height = static_cast<uint32_t>(height),
};
}
wgpu::Surface WebGPUPlatformWasm::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvasSource{};
canvasSource.selector = static_cast<const char*>(nativeWindow);
const wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &canvasSource,
.label = "wasm_surface",
};
wgpu::Surface surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface.Get() != nullptr)
<< "Unable to create Wasm-backed surface.";
return surface;
}
} // namespace filament::backend
+9 -4
View File
@@ -139,10 +139,8 @@ public:
Builder& package(const void* UTILS_NONNULL payload, size_t size);
template<typename T>
using is_supported_constant_parameter_t = std::enable_if_t<
std::is_same_v<int32_t, T> ||
std::is_same_v<float, T> ||
std::is_same_v<bool, T>>;
using is_supported_constant_parameter_t =
MaterialInstance::is_supported_constant_parameter_t<T>;
/**
* Specialize a constant parameter specified in the material definition with a concrete
@@ -243,11 +241,18 @@ public:
* for stereoscopic rendering. If an application is not planning to render in stereo, this bit
* should be turned off to avoid unnecessary material compilations.
*
* Note that it is possible to override specialization constants on a per-MaterialInstance basis
* (@see MaterialInstance::setConstant). In that case, the programs compiled by a call to
* Material::compile() may not be reusable by that MaterialInstance. It's better to call
* MaterialInstance::compile() in cases where you intend to override specialization constants.
*
* @param priority Which priority queue to use, LOW or HIGH.
* @param variants Variants to include to the compile command.
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback callback called on the main thread when the compilation is done on
* by backend.
*
* @see Material::compile
*/
void compile(CompilerPriorityQueue priority,
UserVariantFilterMask variants,
@@ -94,6 +94,12 @@ public:
std::is_same_v<math::mat3f, T>
>;
template<typename T>
using is_supported_constant_parameter_t = std::enable_if_t<
std::is_same_v<int32_t, T> ||
std::is_same_v<float, T> ||
std::is_same_v<bool, T>>;
/**
* Creates a new MaterialInstance using another MaterialInstance as a template for initialization.
* The new MaterialInstance is an instance of the same Material of the template instance and
@@ -276,6 +282,91 @@ public:
return getParameter<T>(name, strlen(name));
}
/**
* Overrides a specialization constant of this material instance.
*
* @tparam T The type of the constant. Must be int32_t, float, or bool.
* @param name The name of the constant as defined in the material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @param value The value of the constant.
*
* @see Material::Builder::constant
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T value);
/** inline helper to provide the name as a null-terminated string literal */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(StringLiteral const name, T value) {
setConstant<T>(name.data, name.size, value);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, T value) {
setConstant<T>(name, strlen(name), value);
}
/**
* Gets the value of a specialization constant by name.
*
* @tparam T The type of the constant. Must be int32_t, float, or bool.
* @param name The name of the constant as defined in the material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @return The value of the constant.
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const;
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(StringLiteral const name) const {
return getConstant<T>(name.data, name.size);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name) const {
return getConstant<T>(name, strlen(name));
}
using CompilerPriorityQueue = backend::CompilerPriorityQueue;
/**
* Asynchronously ensures that a subset of this MaterialInstance's variants are compiled.
*
* This function behaves identically to Material::compile(), but takes into account the
* specific constants overridden by setConstant().
*
* @param priority Which priority queue to use, LOW or HIGH.
* @param variants Variants to include to the compile command.
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback callback called on the main thread when the compilation is done on
* by backend.
*
* @see Material::compile
* @see setConstant
*/
void compile(CompilerPriorityQueue priority,
UserVariantFilterMask variants,
backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>&& callback = {}) noexcept;
inline void compile(CompilerPriorityQueue priority,
UserVariantFilterBit variants,
backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>&& callback = {}) noexcept {
compile(priority, UserVariantFilterMask(variants), handler,
std::forward<utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>>(callback));
}
inline void compile(CompilerPriorityQueue priority,
backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>&& callback = {}) noexcept {
compile(priority, UserVariantFilterBit::ALL, handler,
std::forward<utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>>(callback));
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
+11 -8
View File
@@ -182,15 +182,11 @@ Program::SpecializationConstant LocalProgramCache::getConstantImpl(
std::string_view name) const noexcept {
assert_invariant(mMaterial != nullptr);
MaterialDefinition const& definition = mMaterial->getDefinition();
auto it = definition.specializationConstantsNameToIndex.find(name);
if (it != definition.specializationConstantsNameToIndex.cend()) {
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
}
auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex;
auto it = constants.find(name);
FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist";
CString name_cstring(name);
PANIC_PRECONDITION("No such constant exists: %s", name_cstring.c_str());
return {};
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
}
void LocalProgramCache::setConstants(
@@ -240,6 +236,13 @@ void LocalProgramCache::setConstants(
}
}
void LocalProgramCache::setConstants(
FixedCapacityVector<Program::SpecializationConstant> constants) noexcept {
assert_invariant(mMaterial != nullptr);
setConstantsImpl(std::move(constants));
}
void LocalProgramCache::setConstantsImpl(
FixedCapacityVector<Program::SpecializationConstant> constants) noexcept {
FEngine& engine = mMaterial->getEngine();
+7
View File
@@ -119,7 +119,14 @@ public:
std::pair<std::string_view, backend::Program::SpecializationConstant>>
constants) noexcept;
// Set constants list directly.
void setConstants(utils::FixedCapacityVector<backend::Program::SpecializationConstant>
constants) noexcept;
private:
// Apply any pending specialization constants. Invalidates programs as necessary.
void flushConstants() const;
backend::Handle<backend::HwProgram> prepareProgramSlow(backend::DriverApi& driver,
Variant const variant,
backend::CompilerPriorityQueue const priorityQueue) const noexcept;
+26
View File
@@ -233,6 +233,26 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const ch
// ------------------------------------------------------------------------------------------------
template<typename T, typename>
void MaterialInstance::setConstant(const char* name, size_t nameLength, T value) {
downcast(this)->setConstantImpl(std::string_view{name, nameLength}, value);
}
template UTILS_PUBLIC void MaterialInstance::setConstant<int32_t>(const char* name, size_t nameLength, int32_t value);
template UTILS_PUBLIC void MaterialInstance::setConstant<float>(const char* name, size_t nameLength, float value);
template UTILS_PUBLIC void MaterialInstance::setConstant<bool>(const char* name, size_t nameLength, bool value);
template<typename T, typename>
T MaterialInstance::getConstant(const char* name, size_t nameLength) const {
return downcast(this)->getConstantImpl<T>(std::string_view{name, nameLength});
}
template UTILS_PUBLIC int32_t MaterialInstance::getConstant<int32_t>(const char* name, size_t nameLength) const;
template UTILS_PUBLIC float MaterialInstance::getConstant<float>(const char* name, size_t nameLength) const;
template UTILS_PUBLIC bool MaterialInstance::getConstant<bool>(const char* name, size_t nameLength) const;
// ------------------------------------------------------------------------------------------------
Material const* MaterialInstance::getMaterial() const noexcept {
return downcast(this)->getMaterial();
}
@@ -403,4 +423,10 @@ void MaterialInstance::commit(Engine& engine) const {
downcast(this)->commit(downcast(engine));
}
void MaterialInstance::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask const variants, CallbackHandler* handler,
utils::Invocable<void(MaterialInstance*)>&& callback) noexcept {
downcast(this)->compile(priority, variants, handler, std::move(callback));
}
} // namespace filament
+74 -59
View File
@@ -186,12 +186,10 @@ void PostProcessManager::PostProcessMaterial::loadMaterial(FEngine& engine) cons
}
UTILS_NOINLINE
FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine,
DriverApi& driver, Variant::type_t const variant) const noexcept {
FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine) const noexcept {
if (UTILS_UNLIKELY(mSize)) {
loadMaterial(engine);
}
mMaterial->prepareProgram(driver, Variant{ variant }, CompilerPriorityQueue::CRITICAL);
return mMaterial;
}
@@ -249,6 +247,20 @@ void PostProcessManager::bindPerRenderableDescriptorSet(DriverApi& driver) const
{ { 0, 0 }, driver });
}
FMaterialInstance* PostProcessManager::getMaterialInstance(backend::DriverApi& driver,
FMaterial const* ma, Variant::type_t variant) const {
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma);
mi->prepareProgram(driver, Variant{ variant }, backend::CompilerPriorityQueue::CRITICAL);
return mi;
}
FMaterialInstance* PostProcessManager::getMaterialInstanceWithTag(backend::DriverApi& driver,
FMaterial const* ma, uint32_t tag, Variant::type_t variant) const {
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma, tag);
mi->prepareProgram(driver, Variant { variant }, backend::CompilerPriorityQueue::CRITICAL);
return mi;
}
UboManager* PostProcessManager::getUboManager() const noexcept {
return mEngine.getUboManager();
}
@@ -468,9 +480,10 @@ void PostProcessManager::unbindAllDescriptorSets(DriverApi& driver) noexcept {
UTILS_NOINLINE
PipelineState PostProcessManager::getPipelineState(
FMaterial const* const ma, Variant::type_t const variant) const noexcept {
FMaterialInstance const* const mi, Variant::type_t const variant) const noexcept {
FMaterial const* const ma = mi->getMaterial();
return {
.program = ma->getProgram(Variant{ variant }),
.program = mi->getProgram(Variant{ variant }),
.vertexBufferInfo = mFullScreenQuadVbih,
.pipelineLayout = {
.setLayout = {
@@ -478,7 +491,7 @@ PipelineState PostProcessManager::getPipelineState(
mPerRenderableDslh,
ma->getDescriptorSetLayout().getHandle()
}},
.rasterState = ma->getRasterState()
.rasterState = mi->getRasterState()
};
}
@@ -518,8 +531,7 @@ void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver,
PostProcessVariant const variant) const noexcept {
mi->commit(driver, getUboManager());
mi->use(driver);
FMaterial const* const ma = mi->getMaterial();
PipelineState const pipeline = getPipelineState(ma, variant);
PipelineState const pipeline = getPipelineState(mi, variant);
assert_invariant(
((out.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH)
@@ -636,12 +648,12 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
auto in = resources.getTexture(data.depth);
auto& material = getPostProcessMaterial("mipmapDepth");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterialInstance* const mi = getMaterialInstance(ma);
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
// Only the depth texture is changing in the material instance (no UBO updates),
// we do not move getMaterialInstance() inside the loop.
auto pipeline = getPipelineState(ma);
auto pipeline = getPipelineState(mi);
// The first mip already exists, so we process n-1 lods
for (size_t level = 0; level < levelCount - 1; level++) {
@@ -1001,14 +1013,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
#endif
auto& material = getPostProcessMaterial(materialName);
FMaterial* ma = material.getMaterial(mEngine, driver);
FMaterial* ma = material.getMaterial(mEngine);
ma->getPrograms().setConstants({
{ "useVisibilityBitmasks", options.gtao.useVisibilityBitmasks },
{ "linearThickness", options.gtao.linearThickness },
});
ma = material.getMaterial(mEngine, driver);
FMaterialInstance* const mi = getMaterialInstance(ma);
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
// Set AO type specific material parameters
switch (aoType) {
@@ -1084,7 +1095,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(ma);
auto pipeline = getPipelineState(mi);
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
assert_invariant(ssao.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH);
renderFullScreenQuad(ssao, pipeline, driver);
@@ -1194,8 +1205,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
auto& material = config.bentNormals ?
getPostProcessMaterial("bilateralBlurBentNormals") :
getPostProcessMaterial("bilateralBlur");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterialInstance* const mi = getMaterialInstance(ma);
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
mi->setParameter("ssao", ssao, { /* only reads level 0 */ });
mi->setParameter("axis", axis / float2{desc.width, desc.height});
mi->setParameter("kernel", kGaussianSamples, kGaussianCount);
@@ -1205,7 +1216,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(ma);
auto pipeline = getPipelineState(mi);
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
renderFullScreenQuad(blurred, pipeline, driver);
unbindAllDescriptorSets(driver);
@@ -1364,7 +1375,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
"separableGaussianBlur4L"sv : "separableGaussianBlur4"sv; break;
}
auto const& separableGaussianBlur = getPostProcessMaterial(materialName);
auto ma = separableGaussianBlur.getMaterial(mEngine, driver);
auto ma = separableGaussianBlur.getMaterial(mEngine);
const size_t kernelStorageSize = ma->reflect("kernel")->size;
float2 kernel[64];
@@ -1887,9 +1898,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
auto inOutCoc = resources.getTexture(data.inOutCoc);
auto const& material = getPostProcessMaterial("dofMipmap");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
auto const pipeline = getPipelineState(ma, variant);
auto const pipeline = getPipelineState(mi, variant);
for (size_t level = 0 ; level < mipmapCount - 1u ; level++) {
const float w = FTexture::valueForLevel(level, desc.width);
@@ -1897,7 +1909,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
auto const& out = resources.getRenderPassInfo(data.rp[level]);
auto inColor = driver.createTextureView(inOutColor, level, 1);
auto inCoc = driver.createTextureView(inOutCoc, level, 1);
FMaterialInstance* const mi = getMaterialInstance(ma);
// FIXME: is this necessary?
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
mi->setParameter("color", inColor, SamplerParams{
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -2426,9 +2439,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
auto const& outDesc = resources.getDescriptor(data.out);
auto const& material = getPostProcessMaterial("bloomUpsample");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterial const* const ma = material.getMaterial(mEngine);
auto pipeline = getPipelineState(ma);
auto pipeline = getPipelineState(getMaterialInstance(driver, ma));
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
@@ -2436,7 +2449,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
// Note that we wouldn't want to use the same instance for each pass since that
// would imply using the same UBOs, which implies synchronization across the
// passes.
FMaterialInstance* mi = getMaterialInstance(ma);
FMaterialInstance* mi = getMaterialInstance(driver, ma);
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i - 1]);
hwDstRT.params.flags.discardStart = TargetBufferFlags::NONE; // b/c we'll blend
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
@@ -2569,11 +2582,12 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
auto const& material = getPostProcessMaterial("colorGradingAsSubpass");
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
FMaterial const* const ma = material.getMaterial(mEngine);
// the UBO has been set and committed in colorGradingPrepareSubpass()
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
FMaterialInstance const* mi =
getMaterialInstanceWithTag(driver, ma, colorGradingConfig.translucent, variant);
mi->use(driver);
auto const pipeline = getPipelineState(ma, variant);
auto const pipeline = getPipelineState(mi, variant);
driver.nextSubpass();
driver.scissor(mi->getScissor());
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
@@ -2581,8 +2595,8 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomResolveOp const op) noexcept {
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
auto const ma = material.getMaterial(mEngine, driver, PostProcessVariant::OPAQUE);
auto* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
auto const ma = material.getMaterial(mEngine);
auto* const mi = getMaterialInstance(driver, ma, 0);
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
mi->commit(driver, getUboManager());
}
@@ -2592,12 +2606,12 @@ void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept {
bindPerRenderableDescriptorSet(driver);
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterial const* const ma = material.getMaterial(mEngine);
// the UBO has been set and committed in customResolvePrepareSubpass()
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
FMaterialInstance const* mi = getMaterialInstance(driver, ma, 0);
mi->use(driver);
auto const pipeline = getPipelineState(ma);
auto const pipeline = getPipelineState(mi);
driver.nextSubpass();
driver.scissor(mi->getScissor());
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
@@ -2633,8 +2647,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::customResolveUncompressPass(
void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver,
Variant::type_t variant) noexcept {
auto const& material = getPostProcessMaterial("clearDepth");
auto const ma = material.getMaterial(mEngine, driver, variant);
auto const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
auto const ma = material.getMaterial(mEngine);
auto const mi = getMaterialInstanceWithTag(driver, ma, 0, variant);
mi->commit(driver, getUboManager());
}
@@ -2649,13 +2663,13 @@ void PostProcessManager::clearAncillaryBuffers(DriverApi& driver,
bindPerRenderableDescriptorSet(driver);
auto const& material = getPostProcessMaterial("clearDepth");
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
FMaterial const* const ma = material.getMaterial(mEngine);
// the UBO has been set and committed in clearAncillaryBuffersPrepare()
FMaterialInstance const* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
FMaterialInstance const* const mi = getMaterialInstanceWithTag(driver, ma, 0, variant);
mi->use(driver);
auto pipeline = getPipelineState(ma, variant);
auto pipeline = getPipelineState(mi, variant);
pipeline.rasterState.depthFunc = RasterState::DepthFunc::A;
driver.scissor(mi->getScissor());
@@ -2665,7 +2679,7 @@ void PostProcessManager::clearAncillaryBuffers(DriverApi& driver,
void PostProcessManager::fogPrepare(DriverApi& driver) noexcept {
// ensures the material is loaded and material instance created
auto const& material = getPostProcessMaterial("fog");
FMaterial const* const ma = material.getMaterial(mEngine, driver, PostProcessVariant::OPAQUE);
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance const* mi = ma->getDefaultInstance();
mi->commit(driver, getUboManager());
}
@@ -2676,11 +2690,11 @@ void PostProcessManager::fog(DriverApi& driver) noexcept {
bindPerRenderableDescriptorSet(driver);
auto const& material = getPostProcessMaterial("fog");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance const* mi = ma->getDefaultInstance();
mi->use(driver);
auto pipeline = getPipelineState(ma, Variant::NO_VARIANT);
auto pipeline = getPipelineState(mi, Variant::NO_VARIANT);
driver.scissor(mi->getScissor());
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
}
@@ -2937,7 +2951,7 @@ void PostProcessManager::TaaJitterCamera(
void PostProcessManager::configureTemporalAntiAliasingMaterial(backend::DriverApi& driver,
TemporalAntiAliasingOptions const& taaOptions) noexcept {
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine, driver);
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine);
ma->getPrograms().setConstants({
{ "upscaling", taaOptions.upscaling > 1.0f },
{ "historyReprojection", taaOptions.historyReprojection },
@@ -2956,7 +2970,7 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
PostProcessMaterial const& material, FColorGrading const* colorGrading,
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
uint32_t const width, uint32_t const height) noexcept {
FMaterial* ma = material.getMaterial(mEngine, driver);
FMaterial* ma = material.getMaterial(mEngine);
ma->getPrograms().setConstants({
{ "isOneDimensional", colorGrading->isOneDimensional() },
{ "isLDR", colorGrading->isLDR() },
@@ -2965,8 +2979,9 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
PostProcessVariant const variant = colorGradingConfig.translucent
? PostProcessVariant::TRANSLUCENT
: PostProcessVariant::OPAQUE;
ma = material.getMaterial(mEngine, driver, variant);
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
ma = material.getMaterial(mEngine);
FMaterialInstance* mi =
getMaterialInstanceWithTag(driver, ma, colorGradingConfig.translucent, variant);
const SamplerParams params = SamplerParams{
.filterMag = SamplerMagFilter::LINEAR,
@@ -3078,9 +3093,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
PostProcessVariant const variant = colorGradingConfig.translucent ?
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance* mi = getMaterialInstance(ma);
FMaterialInstance* mi = getMaterialInstance(driver, ma);
mi->setParameter("color", color, SamplerParams{}); // nearest
mi->setParameter("depth", depth, SamplerParams{}); // nearest
mi->setParameter("history", history, SamplerParams{
@@ -3120,7 +3135,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
if (colorGradingConfig.asSubpass) {
out.params.subpassMask = 1;
}
auto const pipeline = getPipelineState(ma, variant);
auto const pipeline = getPipelineState(mi, variant);
driver.beginRenderPass(out.target, out.params);
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
@@ -3202,7 +3217,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(material.getMaterial(mEngine, driver), variant);
auto pipeline = getPipelineState(mi, variant);
if (mode == RcasMode::BLENDED) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
@@ -3295,7 +3310,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
auto out = resources.getRenderPassInfo();
auto pipeline = getPipelineState(material.getMaterial(mEngine, driver));
auto pipeline = getPipelineState(mi);
if (blended) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
@@ -3509,15 +3524,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
auto out = resources.getRenderPassInfo();
if (UTILS_UNLIKELY(twoPassesEASU)) {
auto pipeline0 = getPipelineState(splitEasuMaterial->getMaterial(mEngine, driver));
auto pipeline1 = getPipelineState(easuMaterial->getMaterial(mEngine, driver));
auto pipeline0 = getPipelineState(getMaterialInstance(mEngine, driver, *splitEasuMaterial));
auto pipeline1 = getPipelineState(getMaterialInstance(mEngine, driver, *easuMaterial));
pipeline1.rasterState.depthFunc = SamplerCompareFunc::NE;
driver.beginRenderPass(out.target, out.params);
driver.draw(pipeline0, mFullScreenQuadRph, 0, 3, 1);
driver.draw(pipeline1, mFullScreenQuadRph, 0, 3, 1);
driver.endRenderPass();
} else {
auto pipeline = getPipelineState(easuMaterial->getMaterial(mEngine, driver));
auto pipeline = getPipelineState(getMaterialInstance(mEngine, driver, *easuMaterial));
renderFullScreenQuad(out, pipeline, driver);
}
unbindAllDescriptorSets(driver);
@@ -3569,8 +3584,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
PostProcessMaterial const& material =
getPostProcessMaterial(layer ? "blitArray" : "blitLow");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
auto* mi = getMaterialInstance(ma);
FMaterial const* const ma = material.getMaterial(mEngine);
auto* mi = getMaterialInstance(driver, ma);
mi->setParameter("color", color, SamplerParams{
.filterMag = filterMag,
.filterMin = filterMin
@@ -3588,7 +3603,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(ma);
auto pipeline = getPipelineState(mi);
if (translucent) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
@@ -3886,10 +3901,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
// set uniforms
PostProcessMaterial const& material = getPostProcessMaterial("blitArray");
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterial const* const ma = material.getMaterial(mEngine);
// It should be ok to not move this getMaterialInstance to inside the loop, since
// this is a pass meant for debug.
auto* mi = getMaterialInstance(ma);
auto* mi = getMaterialInstance(driver, ma);
mi->setParameter("color", color, SamplerParams{
.filterMag = filterMag,
.filterMin = filterMin
@@ -3903,7 +3918,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(ma);
auto pipeline = getPipelineState(mi);
if (translucent) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
+40 -25
View File
@@ -360,13 +360,7 @@ public:
void terminate(FEngine& engine) noexcept;
FMaterial* getMaterial(FEngine& engine, backend::DriverApi& driver,
Variant::type_t variant) const noexcept;
FMaterial* getMaterial(FEngine& engine, backend::DriverApi& driver,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
return getMaterial(engine, driver, Variant::type_t(variant));
}
FMaterial* getMaterial(FEngine& engine) const noexcept;
private:
void loadMaterial(FEngine& engine) const noexcept;
@@ -393,11 +387,12 @@ public:
void bindPerRenderableDescriptorSet(backend::DriverApi& driver) const noexcept;
backend::PipelineState getPipelineState(FMaterial const* ma, Variant::type_t variant) const noexcept;
backend::PipelineState getPipelineState(FMaterialInstance const* mi,
Variant::type_t variant) const noexcept;
backend::PipelineState getPipelineState(FMaterial const* ma,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
return getPipelineState(ma, Variant::type_t(variant));
backend::PipelineState getPipelineState(FMaterialInstance const* mi,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
return getPipelineState(mi, Variant::type_t(variant));
}
void renderFullScreenQuad(FrameGraphResources::RenderPassInfo const& out,
@@ -427,27 +422,47 @@ public:
void resetForRender();
MaterialInstanceManager& getMaterialInstanceManager() noexcept {
return mMaterialInstanceManager;
}
// Helper to get a MaterialInstance from a FMaterial
// This currently just call FMaterial::getDefaultInstance().
FMaterialInstance* getMaterialInstance(FMaterial const* ma) {
return mMaterialInstanceManager.getMaterialInstance(ma);
}
// Helper to get a MaterialInstance from a PostProcessMaterial.
FMaterialInstance* getMaterialInstance(FEngine& engine, backend::DriverApi& driver,
PostProcessMaterial const& material, PostProcessVariant variant = PostProcessVariant::OPAQUE) {
FMaterial const* ma = material.getMaterial(engine, driver, variant);
return getMaterialInstance(ma);
}
static void unbindAllDescriptorSets(backend::DriverApi& driver) noexcept;
private:
// Helpers to get MaterialInstances.
//
// These funcions additionally ensure that the necessary shader programs are compiled via
// prepareProgram().
FMaterialInstance* getMaterialInstance(backend::DriverApi& driver, FMaterial const* ma,
Variant::type_t variant) const;
FMaterialInstance* getMaterialInstance(backend::DriverApi& driver, FMaterial const* ma,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
return getMaterialInstance(driver, ma, Variant::type_t(variant));
}
FMaterialInstance* getMaterialInstance(FEngine& engine, backend::DriverApi& driver,
PostProcessMaterial const& material,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
return getMaterialInstance(driver, material.getMaterial(engine), Variant::type_t(variant));
}
FMaterialInstance* getMaterialInstanceWithTag(backend::DriverApi& driver, FMaterial const* ma,
uint32_t tag, Variant::type_t variant) const;
FMaterialInstance* getMaterialInstanceWithTag(backend::DriverApi& driver, FMaterial const* ma,
uint32_t tag, PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
return getMaterialInstanceWithTag(driver, ma, tag, Variant::type_t(variant));
}
FMaterialInstance* getMaterialInstanceWithTag(FEngine& engine, backend::DriverApi& driver,
PostProcessMaterial const& material, uint32_t tag,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
return getMaterialInstanceWithTag(driver, material.getMaterial(engine), tag,
Variant::type_t(variant));
}
UboManager* getUboManager() const noexcept;
backend::RenderPrimitiveHandle mFullScreenQuadRph;
+7 -10
View File
@@ -243,8 +243,8 @@ void RenderPass::appendCommands(FEngine const& engine, backend::DriverApi& drive
// This must be done from the main thread.
for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) {
if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) {
auto ma = first->info.mi->getMaterial();
ma->prepareProgram(driver, first->info.materialVariant, CompilerPriorityQueue::CRITICAL);
first->info.mi->prepareProgram(driver, first->info.materialVariant,
CompilerPriorityQueue::CRITICAL);
}
}
}
@@ -426,16 +426,18 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
bool const isBlendingCommand = !hasScreenSpaceRefraction &&
(blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED);
RasterState rasterState = mi->getRasterState();
uint64_t keyDraw = cmdDraw.key;
keyDraw &= ~(PASS_MASK | BLENDING_MASK | MATERIAL_MASK);
keyDraw |= uint64_t(hasScreenSpaceRefraction ? Pass::REFRACT : Pass::COLOR);
keyDraw |= uint64_t(CustomCommand::PASS);
keyDraw |= mi->getSortingKey(); // already all set-up for direct or'ing
keyDraw |= makeField(variant.key, MATERIAL_VARIANT_KEY_MASK, MATERIAL_VARIANT_KEY_SHIFT);
keyDraw |= makeField(ma->getRasterState().alphaToCoverage, BLENDING_MASK, BLENDING_SHIFT);
keyDraw |= makeField(rasterState.alphaToCoverage, BLENDING_MASK, BLENDING_SHIFT);
cmdDraw.key = isBlendingCommand ? keyBlending : keyDraw;
cmdDraw.info.rasterState = ma->getRasterState();
cmdDraw.info.rasterState = rasterState;
// for SSR pass, the blending mode of opaques (including MASKED) must be off
// see Material.cpp.
@@ -446,10 +448,6 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
BlendFunction::ZERO : cmdDraw.info.rasterState.blendFunctionDstAlpha;
cmdDraw.info.rasterState.inverseFrontFaces = inverseFrontFaces;
cmdDraw.info.rasterState.culling = mi->getCullingMode();
cmdDraw.info.rasterState.colorWrite = mi->isColorWriteEnabled();
cmdDraw.info.rasterState.depthWrite = mi->isDepthWriteEnabled();
cmdDraw.info.rasterState.depthFunc = mi->getDepthFunc();
cmdDraw.info.rasterState.depthClamp = hasDepthClamp;
cmdDraw.info.materialVariant = variant;
// we keep "RasterState::colorWrite" to the value set by material (could be disabled)
@@ -1090,8 +1088,7 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
mi->use(driver, info.materialVariant);
}
assert_invariant(ma);
pipeline.program = ma->getProgram(info.materialVariant);
pipeline.program = mi->getProgram(info.materialVariant);
if (UTILS_UNLIKELY(memcmp(&pipeline, &currentPipeline, sizeof(PipelineState)) != 0)) {
currentPipeline = pipeline;
+5 -5
View File
@@ -636,7 +636,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::gaussianBlurSeparatedPass(
// get the material
auto const& separableGaussianBlur = ppm.getPostProcessMaterial("gaussian");
auto const ma = separableGaussianBlur.getMaterial(engine, driver);
auto const ma = separableGaussianBlur.getMaterial(engine);
// Generates half of a Gaussian kernel (center + one side)
auto generateGaussianWeights = [](std::array<float, 32>& weights,
@@ -685,7 +685,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::gaussianBlurSeparatedPass(
for (auto const mi : miList) {
mi->use(driver);
driver.scissor(mi->getScissor());
driver.draw(ppm.getPipelineState(ma), engine.getFullScreenRenderPrimitive(), 0, 3, 1);
driver.draw(ppm.getPipelineState(mi), engine.getFullScreenRenderPrimitive(), 0, 3, 1);
}
driver.endRenderPass();
@@ -733,12 +733,12 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::vsmMipmapPass(
ppm.bindPerRenderableDescriptorSet(driver);
auto& material = ppm.getPostProcessMaterial("vsmMipmap");
FMaterial const* const ma = material.getMaterial(engine, driver);
FMaterial const* const ma = material.getMaterial(engine);
auto const mi = ppm.getMaterialInstanceManager().getMaterialInstance(ma);
auto const pipeline = ppm.getPipelineState(ma);
auto const pipeline = ppm.getPipelineState(mi);
backend::Viewport const scissor = { 0, 0, dim, dim };
auto const mi = ppm.getMaterialInstanceManager().getMaterialInstance(ma);
mi->setParameter("color", in, SamplerParams{
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST
+1 -1
View File
@@ -1830,7 +1830,7 @@ void FEngine::compile(
CallbackHandler* handler,
Invocable<void(Material*)>&& callback) {
auto const variants = getMaterialCompileVariants(view, shadowReceiver, skinning);
material->compile(priority, variants, handler, std::move(callback));
const_cast<FMaterial*>(material)->compile(priority, variants, handler, std::move(callback));
}
// ------------------------------------------------------------------------------------------------
+15 -28
View File
@@ -170,8 +170,6 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition
DriverApi& driver = engine.getDriverApi();
mIsStereoSupported = driver.isStereoSupported();
mIsParallelShaderCompileSupported = driver.isParallelShaderCompileSupported();
mDepthPrecacheDisabled =
driver.isWorkaroundNeeded(Workaround::DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL);
mDefaultMaterial = engine.getDefaultMaterial();
@@ -240,44 +238,33 @@ filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
void FMaterial::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask variantSpec,
CallbackHandler* handler,
Invocable<void(Material*)>&& callback) const noexcept {
DriverApi& driver = mEngine.getDriverApi();
// Turn off the STE variant if stereo is not supported.
if (!mIsStereoSupported) {
variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE);
Invocable<void(Material*)>&& callback) noexcept {
FMaterialInstance* mi = getDefaultInstance();
if (callback) {
mi->compile(priority, variantSpec, handler,
[this, callback = std::move(callback)](MaterialInstance*) {
callback(this);
});
} else {
mi->compile(priority, variantSpec, handler, {});
}
UserVariantFilterMask const variantFilter = ~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
ShaderModel const shaderModel = mEngine.getShaderModel();
bool const isStereoSupported = mEngine.getDriverApi().isStereoSupported();
if (UTILS_LIKELY(mIsParallelShaderCompileSupported)) {
for (auto const variant: mDefinition.getVariants()) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (mDefinition.hasVariant(variant, shaderModel, isStereoSupported)) {
prepareProgram(driver, variant, priority);
}
}
}
}
compileAllPrograms(priority, handler, std::move(callback));
}
void FMaterial::compile(CompilerPriorityQueue const priority,
FixedCapacityVector<Variant> const& variants,
CallbackHandler* handler,
Invocable<void(Material*)>&& callback) const noexcept {
Invocable<void(Material*)>&& callback) noexcept {
DriverApi& driver = mEngine.getDriverApi();
FMaterialInstance* mi = getDefaultInstance();
ShaderModel const shaderModel = mEngine.getShaderModel();
bool const isStereoSupported = driver.isStereoSupported();
bool const isParallelShaderCompileSupported = driver.isParallelShaderCompileSupported();
if (UTILS_LIKELY(mIsParallelShaderCompileSupported)) {
if (UTILS_LIKELY(isParallelShaderCompileSupported)) {
for (auto const variant : variants) {
if (mDefinition.hasVariant(variant, shaderModel, isStereoSupported)) {
prepareProgram(driver, variant, priority);
mi->prepareProgram(driver, variant, priority);
}
}
}
@@ -287,7 +274,7 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
void FMaterial::compileAllPrograms(CompilerPriorityQueue const priority,
CallbackHandler* handler,
Invocable<void(Material*)>&& callback) const noexcept {
Invocable<void(Material*)>&& callback) noexcept {
DriverApi& driver = mEngine.getDriverApi();
if (callback) {
+6 -41
View File
@@ -106,12 +106,12 @@ public:
void compile(CompilerPriorityQueue priority,
UserVariantFilterMask variantSpec,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) const noexcept;
utils::Invocable<void(Material*)>&& callback) noexcept;
void compile(CompilerPriorityQueue priority,
utils::FixedCapacityVector<Variant> const& variants,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) const noexcept;
utils::Invocable<void(Material*)>&& callback) noexcept;
// Creates an instance of this material, specifying the batching mode.
FMaterialInstance* createInstance(const char* name) const noexcept;
@@ -130,40 +130,6 @@ public:
FEngine& getEngine() const noexcept { return mEngine; }
// prepareProgram creates the program for the material's given variant at the backend level.
// Must be called outside of backend render pass.
// Must be called before getProgram() below.
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
Variant const variant,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
return mPrograms.prepareProgram(driver, variant, priorityQueue);
}
// getProgram returns the backend program for the material's given variant.
// Must be called after prepareProgram().
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgram(Variant variant) const noexcept {
if (UTILS_UNLIKELY(mEngine.features.material.enable_fog_as_postprocess)) {
// if the fog as post-process feature is enabled, we need to proceed "as-if" the material
// didn't have the FOG variant bit.
if (getMaterialDomain() == MaterialDomain::SURFACE) {
BlendingMode const blendingMode = getBlendingMode();
bool const hasScreenSpaceRefraction = getRefractionMode() == RefractionMode::SCREEN_SPACE;
bool const isBlendingCommand = !hasScreenSpaceRefraction &&
(blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED);
if (!isBlendingCommand) {
variant.setFog(false);
}
}
}
#if FILAMENT_ENABLE_MATDBG
updateActiveProgramsForMatdbg(variant);
#endif
return mPrograms.getProgram(variant);
}
// MaterialInstance::use() binds descriptor sets before drawing. For shared variants,
// however, the material instance will call useShared() to bind the default material's sets
// instead.
@@ -310,6 +276,9 @@ public:
}
/** @}*/
// Called by getProgram() to update active program list for matdbg UI.
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
#endif
private:
@@ -318,15 +287,13 @@ private:
void compileAllPrograms(CompilerPriorityQueue priority,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) const noexcept;
utils::Invocable<void(Material*)>&& callback) noexcept;
MaterialDefinition const& mDefinition;
bool mIsDefaultMaterial = false;
bool mUseUboBatching = false;
bool mIsStereoSupported = false;
bool mIsParallelShaderCompileSupported = false;
bool mDepthPrecacheDisabled = false;
FMaterial const* mDefaultMaterial = nullptr;
@@ -341,8 +308,6 @@ private:
mutable utils::Mutex mPendingEditsLock;
std::unique_ptr<MaterialParser> mPendingEdits;
std::unique_ptr<MaterialParser> mEditedMaterialParser;
// Called by getProgram() to update active program list for matdbg UI.
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
void setPendingEdits(std::unique_ptr<MaterialParser> pendingEdits) noexcept;
bool hasPendingEdits() const noexcept;
void latchPendingEdits() noexcept;
+125
View File
@@ -17,6 +17,7 @@
#include <filament/MaterialInstance.h>
#include "RenderPass.h"
#include "MaterialParser.h"
#include "ds/DescriptorSetLayout.h"
@@ -131,6 +132,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mTextureParameters(other->mTextureParameters),
mDescriptorSet(other->mDescriptorSet.duplicate(
"MaterialInstance", mMaterial->getDescriptorSetLayout())),
mPrograms(other->mPrograms),
mPolygonOffset(other->mPolygonOffset),
mStencilState(other->mStencilState),
mMaskThreshold(other->mMaskThreshold),
@@ -207,6 +209,10 @@ void FMaterialInstance::terminate(FEngine& engine) {
if (ubHandle){
driver.destroyBufferObject(*ubHandle);
}
if (mPrograms.isInitialized()) {
mPrograms.terminate(engine);
}
}
void FMaterialInstance::commit(FEngine& engine) const {
@@ -264,6 +270,39 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage
// ------------------------------------------------------------------------------------------------
template<typename T>
void FMaterialInstance::setConstantImpl(std::string_view name, T value) {
auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex;
auto it = constants.find(name);
FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist";
if (UTILS_UNLIKELY(mPendingSpecializationConstants.empty())) {
mPendingSpecializationConstants =
FixedCapacityVector<backend::Program::SpecializationConstant>(
getPrograms().getSpecializationConstants());
}
uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
mPendingSpecializationConstants[id] = value;
}
template<typename T>
T FMaterialInstance::getConstantImpl(std::string_view name) const {
auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex;
auto it = constants.find(name);
FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist";
uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
if (UTILS_UNLIKELY(!mPendingSpecializationConstants.empty())) {
return std::get<T>(mPendingSpecializationConstants[id]);
}
return getPrograms().getConstant<T>(id);
}
// ------------------------------------------------------------------------------------------------
void FMaterialInstance::setParameter(std::string_view const name,
Handle<HwTexture> texture, SamplerParams const params) {
auto const binding = mMaterial->getSamplerBinding(name);
@@ -383,6 +422,15 @@ void FMaterialInstance::setTransparencyMode(TransparencyMode const mode) noexcep
mTransparencyMode = mode;
}
RasterState FMaterialInstance::getRasterState() const noexcept {
RasterState rs = mMaterial->getRasterState();
rs.culling = mCulling;
rs.depthWrite = mDepthWrite;
rs.depthFunc = mDepthFunc;
rs.colorWrite = mColorWrite;
return rs;
}
void FMaterialInstance::setDepthCulling(bool const enable) noexcept {
mDepthFunc = enable ? RasterState::DepthFunc::GE : RasterState::DepthFunc::A;
}
@@ -403,6 +451,51 @@ const char* FMaterialInstance::getName() const noexcept {
// ------------------------------------------------------------------------------------------------
void FMaterialInstance::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask variantSpec, CallbackHandler* handler,
Invocable<void(MaterialInstance*)>&& callback) noexcept {
FEngine& engine = mMaterial->getEngine();
DriverApi& driver = engine.getDriverApi();
MaterialDefinition const& definition = mMaterial->getDefinition();
bool const isStereoSupported = driver.isStereoSupported();
// Turn off the STE variant if stereo is not supported.
if (UTILS_LIKELY(!isStereoSupported)) {
variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE);
}
UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
ShaderModel const shaderModel = engine.getShaderModel();
if (UTILS_LIKELY(driver.isParallelShaderCompileSupported())) {
for (auto const variant: definition.getVariants()) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (definition.hasVariant(variant, shaderModel, isStereoSupported)) {
prepareProgram(driver, variant, priority);
}
}
}
}
if (callback) {
struct Callback {
Invocable<void(MaterialInstance*)> f;
MaterialInstance* m;
static void func(void* user) {
auto* const c = static_cast<Callback*>(user);
c->f(c->m);
delete c;
}
};
auto* const user = new (std::nothrow) Callback{ std::move(callback), this };
driver.compilePrograms(priority, handler, &Callback::func, user);
} else {
driver.compilePrograms(priority, nullptr, nullptr, nullptr);
}
}
void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
assert_invariant(mDescriptorSet.getHandle());
assert_invariant(!isUsingUboBatching() || BufferAllocator::isValid(getAllocationId()));
@@ -507,4 +600,36 @@ void FMaterialInstance::fixMissingSamplers() const {
}
}
LocalProgramCache const& FMaterialInstance::getPrograms() const noexcept {
return mPrograms.isInitialized() ? mPrograms : mMaterial->getPrograms();
}
void FMaterialInstance::flushSpecializationConstants() const noexcept {
if (mPendingSpecializationConstants.empty()) {
return;
}
if (!mPrograms.isInitialized()) {
mPrograms.initializeForMaterialInstance(mMaterial->getEngine(), *mMaterial);
}
mPrograms.setConstants(std::move(mPendingSpecializationConstants));
mPendingSpecializationConstants.clear();
}
#if FILAMENT_ENABLE_MATDBG
void FMaterialInstance::updateActiveProgramsForMatdbg(Variant const variant) const noexcept {
mMaterial->updateActiveProgramsForMatdbg(variant);
}
#endif // FILAMENT_ENABLE_MATDBG
template void FMaterialInstance::setConstantImpl<int32_t>(std::string_view name, int32_t value);
template void FMaterialInstance::setConstantImpl<float>(std::string_view name, float value);
template void FMaterialInstance::setConstantImpl<bool>(std::string_view name, bool value);
template int32_t FMaterialInstance::getConstantImpl<int32_t>(std::string_view name) const;
template float FMaterialInstance::getConstantImpl<float>(std::string_view name) const;
template bool FMaterialInstance::getConstantImpl<bool>(std::string_view name) const;
} // namespace filament
+52 -4
View File
@@ -18,7 +18,7 @@
#define TNT_FILAMENT_DETAILS_MATERIALINSTANCE_H
#include "downcast.h"
#include "LocalProgramCache.h"
#include "UniformBuffer.h"
#include "ds/DescriptorSet.h"
@@ -26,8 +26,6 @@
#include "details/BufferAllocator.h"
#include "details/Engine.h"
#include "private/backend/DriverApi.h"
#include <filament/MaterialInstance.h>
#include <private/filament/Variant.h>
@@ -67,7 +65,7 @@ public:
~FMaterialInstance() noexcept;
void terminate(FEngine& engine);
void commit(FEngine& engine) const;
void commit(FEngine::DriverApi& driver, UboManager* uboManager) const;
@@ -86,6 +84,32 @@ public:
UniformBuffer const& getUniformBuffer() const noexcept { return mUniforms; }
void compile(backend::CompilerPriorityQueue priority, UserVariantFilterMask variantSpec,
backend::CallbackHandler* handler,
utils::Invocable<void(MaterialInstance*)>&& callback) noexcept;
// prepareProgram creates the program for the material's given variant at the backend level.
// Must be called outside of backend render pass.
// Must be called before getProgram() below.
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
Variant const variant,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
flushSpecializationConstants();
return getPrograms().prepareProgram(driver, variant, priorityQueue);
}
// getProgram returns the backend program for the material's given variant.
// Must be called after prepareProgram().
//
// See also Material::getProgram().
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgram(Variant const variant) const noexcept {
#if FILAMENT_ENABLE_MATDBG
updateActiveProgramsForMatdbg(variant);
#endif
return getPrograms().getProgram(variant);
}
void setScissor(uint32_t const left, uint32_t const bottom, uint32_t const width, uint32_t const height) noexcept {
constexpr uint32_t maxvalu = std::numeric_limits<int32_t>::max();
mScissorRect = { int32_t(left), int32_t(bottom),
@@ -107,6 +131,8 @@ public:
bool hasScissor() const noexcept { return mHasScissor; }
backend::RasterState getRasterState() const noexcept;
backend::CullingMode getCullingMode() const noexcept { return mCulling; }
backend::CullingMode getShadowCullingMode() const noexcept { return mShadowCulling; }
@@ -254,11 +280,15 @@ public:
backend::Handle<backend::HwTexture> texture, backend::SamplerParams params);
using MaterialInstance::setParameter;
using MaterialInstance::setConstant;
private:
friend class FMaterial;
friend class MaterialInstance;
// Cannot inline since it inspects the FMaterial class.
LocalProgramCache const& getPrograms() const noexcept;
template<size_t Size>
void setParameterUntypedImpl(std::string_view name, const void* value);
@@ -277,6 +307,19 @@ private:
template<typename T>
T getParameterImpl(std::string_view name) const;
template<typename T>
void setConstantImpl(std::string_view name, T value);
template<typename T>
T getConstantImpl(std::string_view name) const;
void flushSpecializationConstants() const noexcept;
#if FILAMENT_ENABLE_MATDBG
// Called by getProgram() to update active program list for matdbg UI.
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
#endif
// keep these grouped, they're accessed together in the render-loop
FMaterial const* mMaterial = nullptr;
@@ -291,6 +334,11 @@ private:
mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms;
// HACK: Mutable so that prepareProgram() can update specialization constants.
mutable LocalProgramCache mPrograms;
mutable utils::FixedCapacityVector<backend::Program::SpecializationConstant>
mPendingSpecializationConstants;
backend::PolygonOffset mPolygonOffset{};
backend::StencilState mStencilState{};
+50
View File
@@ -19,6 +19,7 @@
#include <filament/Engine.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include "filament_test_resources.h"
@@ -167,3 +168,52 @@ TEST(Material, MaterialSettingInvalidApiLevelReturnsAnInvalidPackage) {
Engine::destroy(engine);
}
TEST(MaterialInstanceTest, SetConstant) {
Engine* engine = Engine::create(Engine::Backend::NOOP);
std::string shaderCode(R"(
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = vec4(1.0);
}
)");
filamat::MaterialBuilder builder;
builder.init();
builder.name("MaterialInstanceTest");
builder.material(shaderCode.c_str());
builder.constant("myFloat", filamat::MaterialBuilder::ConstantType::FLOAT, 1.0f);
builder.constant("myInt", filamat::MaterialBuilder::ConstantType::INT, 2);
builder.constant("myBool", filamat::MaterialBuilder::ConstantType::BOOL, false);
filamat::Package result = builder.build(engine->getJobSystem());
ASSERT_TRUE(result.isValid());
Material* material = Material::Builder()
.package(result.getData(), result.getSize())
.build(*engine);
ASSERT_NE(material, nullptr);
MaterialInstance* instance = material->createInstance();
ASSERT_NE(instance, nullptr);
// Verify default values
EXPECT_EQ(instance->getConstant<float>("myFloat"), 1.0f);
EXPECT_EQ(instance->getConstant<int32_t>("myInt"), 2);
EXPECT_EQ(instance->getConstant<bool>("myBool"), false);
// Set new values
instance->setConstant("myFloat", 3.0f);
instance->setConstant("myInt", 4);
instance->setConstant("myBool", true);
// Verify new values
EXPECT_EQ(instance->getConstant<float>("myFloat"), 3.0f);
EXPECT_EQ(instance->getConstant<int32_t>("myInt"), 4);
EXPECT_EQ(instance->getConstant<bool>("myBool"), true);
engine->destroy(instance);
engine->destroy(material);
Engine::destroy(engine);
}
@@ -138,7 +138,7 @@ constexpr size_t CONFIG_MINSPEC_UBO_SIZE = 16384;
// https://crbug.com/1348363 Lighting looks wrong with D3D11 but not OpenGL
// Note that __EMSCRIPTEN__ is not defined when running matc, but that's okay because we're
// actually using a specification constant.
#if defined(__EMSCRIPTEN__) && !defined(FILAMENT_SUPPORTS_WEBGPU)
#if defined(__EMSCRIPTEN__)
constexpr size_t CONFIG_MAX_INSTANCES = 8;
#else
constexpr size_t CONFIG_MAX_INSTANCES = 64;
@@ -881,6 +881,14 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst
static const char* types[] = { "int", "float", "bool" };
// Spec constants aren't fully supported in Tint,
// workaround until https://issues.chromium.org/issues/42250586 is resolved
if (mTargetApi == TargetApi::WEBGPU) {
std::string const variableName = "FILAMENT_SPEC_CONST_" + std::to_string(id) + "_" + name;
out << " const " << types[value.index()] << " " << variableName << " = " << constantString << ";\n";
out << types[value.index()] << " " << name << " = " << variableName << ";\n";
return out;
}
if (mTargetLanguage == MaterialBuilderBase::TargetLanguage::SPIRV) {
out << "layout (constant_id = " << id << ") const "
<< types[value.index()] << " " << name << " = " << constantString << ";\n";
+2 -1
View File
@@ -52,6 +52,8 @@ if (ANDROID AND FILAMENT_BUILD_FILAMAT)
tint_lang_spirv_intrinsic
tint_lang_spirv_ir
tint_lang_spirv_reader
tint_lang_spirv_reader_ast_lower
tint_lang_spirv_reader_ast_parser
tint_lang_spirv_reader_common
tint_lang_spirv_reader_lower
tint_lang_spirv_reader_parser
@@ -75,7 +77,6 @@ if (ANDROID AND FILAMENT_BUILD_FILAMAT)
tint_utils_rtti
tint_utils_strconv
tint_utils_symbol
tint_utils_system
tint_utils_text
tint_utils_text_generator
)
+1 -4
View File
@@ -17,9 +17,6 @@ function(build_material MAT_FILE TARGET_DIR MAT_NAME OUT_LIST)
if (NOT CMAKE_BUILD_TYPE MATCHES Release)
set(MATC_FLAGS -g ${MATC_FLAGS})
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
set(MATC_FLAGS -a webgpu ${MATC_FLAGS})
endif()
set(mat_src "${CMAKE_CURRENT_SOURCE_DIR}/${MAT_FILE}")
set(output_path "${SERVER_DIR}/${TARGET_DIR}/${MAT_NAME}.filamat")
add_custom_command(
@@ -90,7 +87,7 @@ function(add_envmap SOURCE TARGET TARGET_DIR OUT_LIST)
WORKING_DIRECTORY "${SERVER_DIR}/${TARGET_DIR}"
MAIN_DEPENDENCY ${source_envmap}
DEPENDS cmgen)
set(${OUT_LIST} ${${OUT_LIST}} ${target_skybox} ${target_skybox_tiny} ${target_envmap} PARENT_SCOPE)
endfunction()
+1 -2
View File
@@ -4,8 +4,7 @@ material {
color
],
shadingModel : unlit,
culling : none,
featureLevel: 0
culling : none
}
fragment {
-5
View File
@@ -31,7 +31,6 @@ set(COPTS "${COPTS} -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0")
set(LOPTS "${LOPTS} --bind")
set(LOPTS "${LOPTS} -s ALLOW_MEMORY_GROWTH=1")
set(LOPTS "${LOPTS} -s EXPORTED_FUNCTIONS=['_malloc','_free']")
set(LOPTS "${LOPTS} -s EXPORTED_RUNTIME_METHODS=['HEAP8','HEAP16','HEAP32','HEAPU8','HEAPU16','HEAPU32','HEAPF32','HEAPF64']")
set(LOPTS "${LOPTS} -s MODULARIZE=1")
set(LOPTS "${LOPTS} -s EXPORT_NAME=Filament")
set(LOPTS "${LOPTS} -s USE_WEBGL2=1")
@@ -39,10 +38,6 @@ set(LOPTS "${LOPTS} -s FULL_ES3")
set(LOPTS "${LOPTS} -s MIN_WEBGL_VERSION=2")
set(LOPTS "${LOPTS} -s MAX_WEBGL_VERSION=2")
if (FILAMENT_SUPPORTS_WEBGPU)
set(LOPTS "${LOPTS} --use-port=emdawnwebgpu")
endif()
foreach (JS_FILENAME ${EXTERN_POSTJS_SRC})
set(LOPTS "${LOPTS} --extern-post-js ${JS_FILENAME}")
endforeach()
+21 -41
View File
@@ -80,49 +80,36 @@ Filament.loadClassExtensions = function() {
/// options ::argument:: optional WebGL 2.0 context configuration
/// ::retval:: an instance of [Engine]
Filament.Engine.create = function (canvas, options, config) {
if (!canvas.id) {
canvas.id = 'filament-canvas-' + Math.random().toString(36).substr(2, 9);
}
const canvasId = '#' + canvas.id;
const defaults = {
majorVersion: 2,
minorVersion: 0,
antialias: false,
depth: true,
alpha: false
};
options = Object.assign(defaults, options);
const backend = (options && options.backend !== undefined) ?
options.backend : Filament.Backend.DEFAULT;
// Create the WebGL 2.0 context.
const ctx = canvas.getContext("webgl2", options);
if (backend !== Filament.Backend.WEBGPU) {
const defaults = {
majorVersion: 2,
minorVersion: 0,
antialias: false,
depth: true,
alpha: false
};
const glOptions = Object.assign(defaults, options);
// Enable all desired extensions by calling getExtension on each one.
ctx.getExtension('WEBGL_compressed_texture_s3tc');
ctx.getExtension('WEBGL_compressed_texture_s3tc_srgb');
ctx.getExtension('WEBGL_compressed_texture_astc');
ctx.getExtension('WEBGL_compressed_texture_etc');
// Create the WebGL 2.0 context.
const ctx = canvas.getContext("webgl2", glOptions);
// Enable all desired extensions by calling getExtension on each one.
ctx.getExtension('WEBGL_compressed_texture_s3tc');
ctx.getExtension('WEBGL_compressed_texture_s3tc_srgb');
ctx.getExtension('WEBGL_compressed_texture_astc');
ctx.getExtension('WEBGL_compressed_texture_etc');
// These transient globals are used temporarily during Engine construction.
window.filament_glOptions = glOptions;
window.filament_glContext = ctx;
}
// These transient globals are used temporarily during Engine construction.
window.filament_glOptions = options;
window.filament_glContext = ctx;
// Register the GL context with emscripten and create the Engine.
const defaultConfig = Filament.Engine.createDefaultConfig();
const finalConfig = Object.assign(defaultConfig, config);
const engine = Filament.Engine._create(backend, finalConfig);
const engine = Filament.Engine._create(finalConfig);
// Annotate the engine with the GL context to support multiple canvases.
if (backend !== Filament.Backend.WEBGPU) {
engine.context = window.filament_glContext;
engine.handle = window.filament_contextHandle;
}
engine.canvasId = canvasId;
engine.context = window.filament_glContext;
engine.handle = window.filament_contextHandle;
// Ensure that we do not pollute the global namespace.
delete window.filament_glOptions;
@@ -132,13 +119,6 @@ Filament.loadClassExtensions = function() {
return engine;
};
Filament.Engine.prototype.createSwapChain = function() {
if (this.canvasId) {
return this._createSwapChainForCanvas(this.canvasId);
}
return this._createSwapChain();
};
Filament.Engine.prototype.execute = function() {
window.filament_contextHandle = this.handle;
this._execute();
+1 -17
View File
@@ -26,22 +26,6 @@ import * as glm from "gl-matrix";
export as namespace Filament;
export function getSupportedFormatSuffix(desired: string): void;
/**
* Asynchronously initializes the WebGPU adapter and device.
* This must be awaited before initializing the Filament Engine with the WebGPU backend.
*/
export function initWebGPU(): Promise<void>;
export enum Backend {
DEFAULT,
OPENGL,
VULKAN,
METAL,
WEBGPU,
NOOP,
}
export function init(assets: string[], onready?: (() => void) | null): void;
export function fetch(assets: string[], onDone?: (() => void) | null, onFetched?: ((name: string) => void) | null): void;
export function clearAssetCache(): void;
@@ -558,7 +542,7 @@ interface Filamesh {
}
export class Engine {
public static create(canvas: HTMLCanvasElement, options?: { backend?: Backend }): Engine;
public static create(canvas: HTMLCanvasElement, contextOptions?: object): Engine;
public static destroy(engine: Engine): void;
public execute(): void;
public createCamera(entity: Entity): Camera;
+11 -21
View File
@@ -422,17 +422,15 @@ enum_<Engine::Config::ShaderLanguage>("ShaderLanguage")
/// Engine ::core class:: Central manager and resource owner.
class_<Engine>("Engine")
.class_function("_create", (Engine* (*)(backend::Backend, Engine::Config)) [] (backend::Backend backend, Engine::Config config) {
if (backend == backend::Backend::DEFAULT || backend == backend::Backend::OPENGL) {
EM_ASM_INT({
const options = window.filament_glOptions;
const context = window.filament_glContext;
const handle = GL.registerContext(context, options);
window.filament_contextHandle = handle;
GL.makeContextCurrent(handle);
});
}
return Engine::create(backend, nullptr, nullptr, &config);
.class_function("_create", (Engine* (*)(Engine::Config)) [] (Engine::Config config) {
EM_ASM_INT({
const options = window.filament_glOptions;
const context = window.filament_glContext;
const handle = GL.registerContext(context, options);
window.filament_contextHandle = handle;
GL.makeContextCurrent(handle);
});
return Engine::create(Engine::Backend::DEFAULT, nullptr, nullptr, &config);
}, allow_raw_pointers())
// Create a default Engine configuration. This is for internal use to ensure that engine
@@ -505,17 +503,9 @@ class_<Engine>("Engine")
/// createSwapChain ::method::
/// ::retval:: an instance of [SwapChain]
.function("_createSwapChain", (SwapChain* (*)(Engine*)) []
.function("createSwapChain", (SwapChain* (*)(Engine*)) []
(Engine* engine) { return engine->createSwapChain(nullptr); },
allow_raw_pointers())
.function("_createSwapChainForCanvas", (SwapChain* (*)(Engine*, std::string)) []
(Engine* engine, std::string canvasId) {
// Allocate on the heap because nativeWindow is passed asynchronously through the
// driver command buffer to the backend thread.
std::string* persistentCanvasId = new std::string(canvasId);
return engine->createSwapChain((void*)persistentCanvasId->c_str());
},
allow_raw_pointers())
/// destroySwapChain ::method::
/// swapChain ::argument:: an instance of [SwapChain]
.function("destroySwapChain", (void (*)(Engine*, SwapChain*)) []
@@ -2157,7 +2147,7 @@ class_<Ktx2Provider>("gltfio$Ktx2Provider")
class_<WebpProvider>("gltfio$WebpProvider")
.constructor(EMBIND_LAMBDA(WebpProvider, (Engine* engine), {
return WebpProvider { createWebpProvider(engine) };
}))
}))
.class_function("isWebpSupported", &isWebpSupported);
class_<AssetLoader>("gltfio$AssetLoader")
-1
View File
@@ -452,7 +452,6 @@ enum_<ktxreader::Ktx2Reader::Result>("Ktx2Reader$Result")
.value("OPENGL", backend::Backend::OPENGL)
.value("VULKAN", backend::Backend::VULKAN)
.value("METAL", backend::Backend::METAL)
.value("WEBGPU", backend::Backend::WEBGPU)
.value("NOOP", backend::Backend::NOOP);
}
-47
View File
@@ -14,53 +14,6 @@
* limitations under the License.
*/
// ---------------
// WebGPU Initialization
// ---------------
/// initWebGPU ::function:: Asynchronously initializes the WebGPU adapter and device.
/// This must be awaited before initializing the Filament Engine with the WebGPU backend.
/// ::retval:: Promise that resolves when WebGPU is ready.
Filament.initWebGPU = async function() {
if (!navigator.gpu) {
throw new Error("WebGPU is not supported by this browser.");
}
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
throw new Error("No appropriate WebGPU adapter found.");
}
const requiredFeatures = [];
const optionalFeatures = [
'immediates',
'chromium-experimental-immediate-data',
'rg11b10ufloat-renderable',
'float32-filterable',
'float32-blendable',
'depth-clip-control',
'depth32float-stencil8',
'texture-compression-bc',
'texture-compression-etc2',
'texture-compression-astc'
];
for (const feature of optionalFeatures) {
if (adapter.features.has(feature)) {
requiredFeatures.push(feature);
}
}
const requiredLimits = {};
for (const key in adapter.limits) {
requiredLimits[key] = adapter.limits[key];
}
if (adapter.limits.maxImmediateSize !== undefined) {
requiredLimits.maxImmediateSize = adapter.limits.maxImmediateSize;
} else if (requiredFeatures.includes('chromium-experimental-immediate-data')) {
requiredLimits.maxImmediateSize = 64;
}
const device = await adapter.requestDevice({ requiredFeatures, requiredLimits });
Filament.preinitializedWebGPUDevice = device;
};
// ---------------
// Buffer Wrappers
// ---------------
+1 -6
View File
@@ -66,12 +66,7 @@ Filament.init = (assets, onready) => {
// Emscripten creates a global function called "Filament" that returns a promise that
// resolves to a module. Here we replace the function with the module. Note that our
// TypeScript bindings assume that Filament is a namespace, not a function.
const moduleConfig = {};
if (Filament.preinitializedWebGPUDevice) {
moduleConfig.preinitializedWebGPUDevice = Filament.preinitializedWebGPUDevice;
}
Filament(moduleConfig).then(module => {
Filament().then(module => {
// Merge our extension functions into the emscripten module, not the other
// way around, because Emscripten potentially replaces the HEAPU8 views in