Compare commits

..
Author SHA1 Message Date
Powei Feng d49eb5e111 webgpu: add initial WebGPU support on Emscripten
This commit introduces the initial implementation for WebGPU on the
web via Emscripten. It enables the WebGPU backend in Filament when
compiling for WebAssembly, allowing both WebGL and WebGPU to be
shipped in the same filament.wasm binary.

Major changes:
- Build & CMake: Configured the WASM build to append the linker flag
  `--use-port=emdawnwebgpu` and enable the WebGPU backend alongside WebGL.
- Platform Bridge: Added `WebGPUPlatformWasm`, connecting Filament's
  WebGPU backend to the browser via `emscripten_webgpu_get_device()`.
- JS Bindings: Updated `utilities.js`, `extensions.js`, and `wasmloader.js`
  to support asynchronous WebGPU adapter and device initialization
  (`initWebGPU()`), skipping WebGL initialization when WEBGPU is selected.
- Workarounds: Integrated polyfills for Emscripten Dawn limitations,
  including bypassing buggy pipeline layout bindings for `immediateSize`
  and disabling unsupported WASM features like `TransientAttachments`.
2026-05-01 17:02:08 -07:00
Powei Feng 8812a35cbd webgpu: Clean up spec constants and fix validation for unused attributes (#9933)
- WebGPUProgram.cpp: Remove legacy replaceSpecConstants string injection
  since WebGPU handles specialization constants natively.
- WebGPUVertexBufferInfo.cpp: Pad unused vertex attributes safely into
  slot 0 using 4-byte formats to satisfy WebGPU stride validation.
- CodeGenerator.cpp: Remove legacy fallback generating 'const' structs
  for specialization constants.
- ShaderGenerator.cpp: Unset unused skinning/morphing attributes
  to prevent WebGPU validation crashes.
- dawn/tnt/CMakeLists.txt: Fix JNI linker error by dropping missing
  Tint AST reader libraries and adding tint_utils_system.

This commit fixes combination use of ubershader and skinning.
2026-04-30 19:08:25 +00:00
Filament Bot 35d7b819e1 [automated] Updating /docs due to commit 9b2959d
Full commit hash is 9b2959d3a4

DOCS_ALLOW_DIRECT_EDITS
2026-04-30 18:30:04 +00:00
Powei Feng 9b2959d3a4 web: fix redball and fl0 examples (#9942)
- Remove conditional forgotten by #9897
   Fixes cube_fl0 example
 - Add byte array view methods that are no longer exported by
   default in emscripten 5.0.4 (#9921)
   Fixes redball tutorial
2026-04-30 17:58:16 +00:00
58 changed files with 810 additions and 953 deletions
+5 -10
View File
@@ -474,12 +474,12 @@ if (NOT MSVC)
endif()
if (ANDROID OR WASM)
# On Android and WebGL RELEASE builds, we omit unwind info to save space.
# On Android and WASM 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 WebGL, we disable RTTI because we pass emscripten::val back and forth
# With WASM, 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-WebGL Apple platforms.
# Build with Metal support on non-WASM Apple platforms.
if (APPLE AND NOT WASM)
option(FILAMENT_SUPPORTS_METAL "Include the Metal backend" ON)
else()
@@ -691,11 +691,6 @@ 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)
@@ -974,7 +969,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
add_subdirectory(${EXTERNAL}/spirv-headers)
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
add_subdirectory(${EXTERNAL}/dawn/tnt/)
endif()
@@ -1026,7 +1021,7 @@ if (IS_HOST_PLATFORM)
add_subdirectory(${TOOLS}/specgen)
endif()
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
# Generate exported executables for cross-compiled builds (Android, WASM, 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,8 +20,6 @@
#include <filament/Texture.h>
#include <filament/TextureSampler.h>
#include "common/CallbackUtils.h"
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec2.h>
@@ -258,69 +256,6 @@ 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;
@@ -657,17 +592,3 @@ 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,19 +523,11 @@ 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,7 +18,6 @@ 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;
@@ -143,28 +142,6 @@ 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() {
@@ -443,69 +420,6 @@ 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.
*
@@ -1041,17 +955,6 @@ 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);
@@ -1115,5 +1018,4 @@ 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);
}
+7 -1
View File
@@ -368,6 +368,11 @@ 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.
(
@@ -1027,7 +1032,8 @@ 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.
+8 -3
View File
@@ -358,6 +358,11 @@ 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)
@@ -431,7 +436,7 @@ if (FILAMENT_USE_ABSEIL_LOGGING)
target_link_libraries(${TARGET} PRIVATE absl::log)
endif()
# Android, iOS, and WebGL do not use bluegl.
# Android, iOS, and WASM do not use bluegl.
if(FILAMENT_SUPPORTS_OPENGL AND NOT IOS AND NOT ANDROID AND NOT WASM)
target_link_libraries(${TARGET} PRIVATE bluegl)
endif()
@@ -441,7 +446,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
target_link_libraries(${TARGET} PRIVATE webgpu_dawn dawncpp_headers)
endif()
@@ -559,7 +564,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)
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
install(TARGETS webgpu_dawn ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
endif()
@@ -31,6 +31,11 @@
#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>
@@ -61,9 +66,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]] wgpu::Adapter requestAdapter(wgpu::Surface const& surface);
[[nodiscard]] virtual wgpu::Adapter requestAdapter(wgpu::Surface const& surface);
// either returns a valid device or panics
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
[[nodiscard]] virtual wgpu::Device requestDevice(wgpu::Adapter const& adapter);
struct Configuration {
wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined;
@@ -0,0 +1,40 @@
/*
* 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
@@ -0,0 +1,70 @@
/*
* 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,6 +30,8 @@
#include "backend/platforms/WebGPUPlatformLinux.h"
#elif defined(WIN32)
#include "backend/platforms/WebGPUPlatformWindows.h"
#elif defined(__EMSCRIPTEN__)
#include "backend/platforms/WebGPUPlatformWasm.h"
#endif
#endif
@@ -152,6 +154,8 @@ 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
@@ -209,3 +213,4 @@ void PlatformFactory::destroy(Platform** platform) noexcept {
}
} // namespace filament::backend
@@ -19,6 +19,10 @@
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
+36 -6
View File
@@ -64,6 +64,22 @@
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 {
@@ -147,8 +163,10 @@ void WebGPUDriver::terminate() {
}
void WebGPUDriver::tick(int) {
#if !defined(__EMSCRIPTEN__)
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
#endif
}
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -198,7 +216,9 @@ 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));
}
}
@@ -782,7 +802,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();
@@ -800,15 +820,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());
@@ -837,7 +857,11 @@ 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) {
@@ -859,7 +883,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;
}
@@ -1324,7 +1348,8 @@ 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(
@@ -1483,7 +1508,12 @@ 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,8 +168,10 @@ 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,6 +28,33 @@
#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)
@@ -72,13 +99,28 @@ 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 = supportedLimits.maxImmediateSize,
.immediateSize = immediateSize,
};
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
const wgpu::PipelineLayout layout =
#if defined(__EMSCRIPTEN__)
wgpu::PipelineLayout::Acquire(
filamentCreatePipelineLayoutWithImmediateData(mDevice.Get(),
reinterpret_cast<WGPUPipelineLayoutDescriptor const*>(&descriptor)));
#else
mDevice.CreatePipelineLayout(&descriptor);
#endif
FILAMENT_CHECK_POSTCONDITION(layout)
<< "Failed to create pipeline layout " << descriptor.label << ".";
return layout;
@@ -76,8 +76,10 @@ 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.");
+12 -111
View File
@@ -43,101 +43,6 @@ 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.
@@ -145,14 +50,12 @@ 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,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants) {
Program const& program, const ShaderStage stage) {
const char* const programName = program.getName().c_str_safe();
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
shaderSource = program.getShadersSource();
@@ -164,18 +67,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(processedShaderSource);
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
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.
@@ -244,6 +147,7 @@ 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;
}
@@ -251,14 +155,11 @@ namespace {
}// namespace
WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
: HwProgram{ program.getName() } {
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants =
program.getSpecializationConstants();
: HwProgram{ program.getName() } {
// TODO: Consider creating/compiling these shaders in parallel.
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
fragmentShaderModule =
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
computeShaderModule = createShaderModule(device, program, ShaderStage::COMPUTE, specConstants);
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX);
fragmentShaderModule = createShaderModule(device, program, ShaderStage::FRAGMENT);
computeShaderModule = createShaderModule(device, program, ShaderStage::COMPUTE);
}
}// namespace filament::backend
@@ -81,7 +81,9 @@ 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,6 +392,9 @@ 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:
@@ -400,6 +403,7 @@ 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,10 +67,12 @@ 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) {
@@ -231,6 +233,7 @@ 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";
@@ -239,6 +242,7 @@ 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,9 +387,12 @@ 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,11 +17,15 @@
#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>
+24 -10
View File
@@ -41,6 +41,14 @@ 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) {
@@ -209,9 +217,9 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS,
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
supportsTransientAttachment(device)) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false,
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
supportsTransientAttachment(device)) },
mDimension{ toWebGPUTextureViewDimension(samplerType) },
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) },
@@ -291,10 +299,7 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
mDefaultTextureView = makeTextureView(mDefaultMipLevel, levelCount, 0,
mDefaultBaseArrayLayer, src->getViewDimension());
} else {
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
wgpu::TextureViewDescriptor viewDesc{
.label = "swizzled_texture_view",
.format = mTexture.GetFormat(),
.dimension = src->getViewDimension(),
@@ -303,6 +308,12 @@ 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";
@@ -327,15 +338,18 @@ WebGPUTexture::WebGPUTexture(const WebGPUTexture* src,
mDefaultBaseArrayLayer{ 0 },
mMsaaSidecarTexture{src->mMsaaSidecarTexture},
mSwizzle{ composeSwizzle(src->getSwizzle(), nextSwizzle) } {
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
wgpu::TextureViewDescriptor viewDesc{
.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,6 +20,9 @@
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
@@ -21,6 +21,10 @@
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <string_view>
@@ -597,7 +601,7 @@ namespace filament::backend {
}
wgpu::TextureUsage transientAttachmentNeeded{ wgpu::TextureUsage::None };
const bool useTransientAttachment {
bool const useTransientAttachment =
deviceSupportsTransientAttachments &&
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
@@ -608,9 +612,12 @@ 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,42 +225,73 @@ struct AttributeInfo final {
* associated with a vertex buffer, and the slot each one belongs
*/
void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attributes,
const uint8_t attributeCount, const uint32_t deviceMaxVertexBuffers,
const uint32_t deviceMaxVertexBuffers,
std::vector<WebGPUVertexBufferInfo::WebGPUSlotBindingInfo>& outWebGPUSlotBindingInfos,
std::vector<wgpu::VertexBufferLayout>& outVertexBufferLayouts,
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT>& outAttributeInfos) {
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT>& outAttributeInfos,
uint8_t& outActualAttributeCount) {
uint8_t currentWebGPUSlotIndex = 0;
uint8_t currentAttributeIndex = 0;
outVertexBufferLayouts.reserve(MAX_VERTEX_BUFFER_COUNT);
outWebGPUSlotBindingInfos.reserve(outVertexBufferLayouts.capacity());
for (uint32_t attributeIndex = 0; attributeIndex < attributes.size(); ++attributeIndex) {
const auto& attribute = attributes[attributeIndex];
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
// 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;
// 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;
}
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);
}
/*
* 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;
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);
}
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)) {
@@ -269,26 +300,18 @@ 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 });
// 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.
});
outVertexBufferLayouts.push_back(
{ .stepMode = wgpu::VertexStepMode::Vertex, .arrayStride = attribute.stride });
}
// we don't use a designated initializer here because certain compilers can't handle it
outAttributeInfos[currentAttributeIndex++] = AttributeInfo(
existingSlot,
{
@@ -297,9 +320,8 @@ void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attr
attribute.offset - outWebGPUSlotBindingInfos[existingSlot].bufferOffset,
.shaderLocation = attributeIndex });
}
FILAMENT_CHECK_POSTCONDITION(currentAttributeIndex == attributeCount)
<< "Using " << currentAttributeIndex << " attributes, but " << attributeCount
<< " where indicated.";
outActualAttributeCount = currentAttributeIndex;
outVertexBufferLayouts.shrink_to_fit();
outWebGPUSlotBindingInfos.shrink_to_fit();
}
@@ -316,21 +338,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(attributeCount);
mVertexAttributes.reserve(MAX_VERTEX_ATTRIBUTE_COUNT); // Reserve max as we might add dummies
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{};
createBufferLayoutsBindingSlotsAndAttributeInfos(attributes, attributeCount,
deviceLimits.maxVertexBuffers, mWebGPUSlotBindingInfos, mVertexBufferLayouts,
attributeInfos);
uint8_t actualAttributeCount = 0;
createBufferLayoutsBindingSlotsAndAttributeInfos(attributes, deviceLimits.maxVertexBuffers,
mWebGPUSlotBindingInfos, mVertexBufferLayouts, attributeInfos, actualAttributeCount);
// 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() + attributeCount,
std::sort(attributeInfos.data(), attributeInfos.data() + actualAttributeCount,
[](AttributeInfo const& first, AttributeInfo const& second) {
if (first.slot < second.slot) {
return true;
@@ -344,7 +366,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 < attributeCount; ++attributeIndex) {
for (uint32_t attributeIndex = 0; attributeIndex < actualAttributeCount; ++attributeIndex) {
AttributeInfo const& info = attributeInfos[attributeIndex];
mVertexAttributes.push_back(info.attribute);
if (mVertexBufferLayouts[info.slot].attributeCount == 0) {
@@ -57,20 +57,29 @@ 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 {
@@ -266,12 +275,21 @@ void printInstanceDetails(wgpu::Instance const& instance) {
#endif
dawnTogglesDescriptor.enabledToggleCount = toggles.size();
dawnTogglesDescriptor.enabledToggles = toggles.data();
const wgpu::InstanceFeatureName features[] = {wgpu::InstanceFeatureName::TimedWaitAny};
#if defined(__EMSCRIPTEN__)
constexpr std::array<wgpu::InstanceFeatureName, 0> features;
#else
constexpr std::array features = {
wgpu::InstanceFeatureName::TimedWaitAny
};
#endif
wgpu::InstanceDescriptor instanceDescriptor{
.nextInChain = &dawnTogglesDescriptor,
.requiredFeatures = features,
.requiredFeatureCount = features.size(),
.requiredFeatures = features.data(),
};
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)
@@ -351,6 +369,8 @@ 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, {});
@@ -373,8 +393,10 @@ struct AdapterDetails final {
[[nodiscard]] std::string toString(AdapterDetails const& details) {
std::stringstream out;
out << adapterInfoToString(details.info)
<< " power preference " << powerPreferenceToString(details.powerPreference);
out << adapterInfoToString(details.info);
#if !defined(__EMSCRIPTEN__)
out << " power preference " << powerPreferenceToString(details.powerPreference);
#endif
return out.str();
}
@@ -0,0 +1,85 @@
/*
* 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
+4 -9
View File
@@ -139,8 +139,10 @@ public:
Builder& package(const void* UTILS_NONNULL payload, size_t size);
template<typename T>
using is_supported_constant_parameter_t =
MaterialInstance::is_supported_constant_parameter_t<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>>;
/**
* Specialize a constant parameter specified in the material definition with a concrete
@@ -241,18 +243,11 @@ 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,12 +94,6 @@ 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
@@ -282,91 +276,6 @@ 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.
*
+8 -11
View File
@@ -182,11 +182,15 @@ Program::SpecializationConstant LocalProgramCache::getConstantImpl(
std::string_view name) const noexcept {
assert_invariant(mMaterial != nullptr);
auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex;
auto it = constants.find(name);
FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist";
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);
}
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
CString name_cstring(name);
PANIC_PRECONDITION("No such constant exists: %s", name_cstring.c_str());
return {};
}
void LocalProgramCache::setConstants(
@@ -236,13 +240,6 @@ 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,14 +119,7 @@ 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,26 +233,6 @@ 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();
}
@@ -423,10 +403,4 @@ 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
+59 -74
View File
@@ -186,10 +186,12 @@ void PostProcessManager::PostProcessMaterial::loadMaterial(FEngine& engine) cons
}
UTILS_NOINLINE
FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine) const noexcept {
FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine,
DriverApi& driver, Variant::type_t const variant) const noexcept {
if (UTILS_UNLIKELY(mSize)) {
loadMaterial(engine);
}
mMaterial->prepareProgram(driver, Variant{ variant }, CompilerPriorityQueue::CRITICAL);
return mMaterial;
}
@@ -247,20 +249,6 @@ 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();
}
@@ -480,10 +468,9 @@ void PostProcessManager::unbindAllDescriptorSets(DriverApi& driver) noexcept {
UTILS_NOINLINE
PipelineState PostProcessManager::getPipelineState(
FMaterialInstance const* const mi, Variant::type_t const variant) const noexcept {
FMaterial const* const ma = mi->getMaterial();
FMaterial const* const ma, Variant::type_t const variant) const noexcept {
return {
.program = mi->getProgram(Variant{ variant }),
.program = ma->getProgram(Variant{ variant }),
.vertexBufferInfo = mFullScreenQuadVbih,
.pipelineLayout = {
.setLayout = {
@@ -491,7 +478,7 @@ PipelineState PostProcessManager::getPipelineState(
mPerRenderableDslh,
ma->getDescriptorSetLayout().getHandle()
}},
.rasterState = mi->getRasterState()
.rasterState = ma->getRasterState()
};
}
@@ -531,7 +518,8 @@ void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver,
PostProcessVariant const variant) const noexcept {
mi->commit(driver, getUboManager());
mi->use(driver);
PipelineState const pipeline = getPipelineState(mi, variant);
FMaterial const* const ma = mi->getMaterial();
PipelineState const pipeline = getPipelineState(ma, variant);
assert_invariant(
((out.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH)
@@ -648,12 +636,12 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
auto in = resources.getTexture(data.depth);
auto& material = getPostProcessMaterial("mipmapDepth");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterialInstance* const mi = getMaterialInstance(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(mi);
auto pipeline = getPipelineState(ma);
// The first mip already exists, so we process n-1 lods
for (size_t level = 0; level < levelCount - 1; level++) {
@@ -1013,13 +1001,14 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
#endif
auto& material = getPostProcessMaterial(materialName);
FMaterial* ma = material.getMaterial(mEngine);
FMaterial* ma = material.getMaterial(mEngine, driver);
ma->getPrograms().setConstants({
{ "useVisibilityBitmasks", options.gtao.useVisibilityBitmasks },
{ "linearThickness", options.gtao.linearThickness },
});
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
ma = material.getMaterial(mEngine, driver);
FMaterialInstance* const mi = getMaterialInstance(ma);
// Set AO type specific material parameters
switch (aoType) {
@@ -1095,7 +1084,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(mi);
auto pipeline = getPipelineState(ma);
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
assert_invariant(ssao.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH);
renderFullScreenQuad(ssao, pipeline, driver);
@@ -1205,8 +1194,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
auto& material = config.bentNormals ?
getPostProcessMaterial("bilateralBlurBentNormals") :
getPostProcessMaterial("bilateralBlur");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterialInstance* const mi = getMaterialInstance(ma);
mi->setParameter("ssao", ssao, { /* only reads level 0 */ });
mi->setParameter("axis", axis / float2{desc.width, desc.height});
mi->setParameter("kernel", kGaussianSamples, kGaussianCount);
@@ -1216,7 +1205,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(mi);
auto pipeline = getPipelineState(ma);
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
renderFullScreenQuad(blurred, pipeline, driver);
unbindAllDescriptorSets(driver);
@@ -1375,7 +1364,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
"separableGaussianBlur4L"sv : "separableGaussianBlur4"sv; break;
}
auto const& separableGaussianBlur = getPostProcessMaterial(materialName);
auto ma = separableGaussianBlur.getMaterial(mEngine);
auto ma = separableGaussianBlur.getMaterial(mEngine, driver);
const size_t kernelStorageSize = ma->reflect("kernel")->size;
float2 kernel[64];
@@ -1898,10 +1887,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
auto inOutCoc = resources.getTexture(data.inOutCoc);
auto const& material = getPostProcessMaterial("dofMipmap");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
auto const pipeline = getPipelineState(mi, variant);
auto const pipeline = getPipelineState(ma, variant);
for (size_t level = 0 ; level < mipmapCount - 1u ; level++) {
const float w = FTexture::valueForLevel(level, desc.width);
@@ -1909,8 +1897,7 @@ 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);
// FIXME: is this necessary?
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
FMaterialInstance* const mi = getMaterialInstance(ma);
mi->setParameter("color", inColor, SamplerParams{
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -2439,9 +2426,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);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
auto pipeline = getPipelineState(getMaterialInstance(driver, ma));
auto pipeline = getPipelineState(ma);
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
@@ -2449,7 +2436,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(driver, ma);
FMaterialInstance* mi = getMaterialInstance(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;
@@ -2582,12 +2569,11 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
auto const& material = getPostProcessMaterial("colorGradingAsSubpass");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
// the UBO has been set and committed in colorGradingPrepareSubpass()
FMaterialInstance const* mi =
getMaterialInstanceWithTag(driver, ma, colorGradingConfig.translucent, variant);
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
mi->use(driver);
auto const pipeline = getPipelineState(mi, variant);
auto const pipeline = getPipelineState(ma, variant);
driver.nextSubpass();
driver.scissor(mi->getScissor());
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
@@ -2595,8 +2581,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);
auto* const mi = getMaterialInstance(driver, ma, 0);
auto const ma = material.getMaterial(mEngine, driver, PostProcessVariant::OPAQUE);
auto* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
mi->commit(driver, getUboManager());
}
@@ -2606,12 +2592,12 @@ void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept {
bindPerRenderableDescriptorSet(driver);
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
// the UBO has been set and committed in customResolvePrepareSubpass()
FMaterialInstance const* mi = getMaterialInstance(driver, ma, 0);
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
mi->use(driver);
auto const pipeline = getPipelineState(mi);
auto const pipeline = getPipelineState(ma);
driver.nextSubpass();
driver.scissor(mi->getScissor());
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
@@ -2647,8 +2633,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);
auto const mi = getMaterialInstanceWithTag(driver, ma, 0, variant);
auto const ma = material.getMaterial(mEngine, driver, variant);
auto const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
mi->commit(driver, getUboManager());
}
@@ -2663,13 +2649,13 @@ void PostProcessManager::clearAncillaryBuffers(DriverApi& driver,
bindPerRenderableDescriptorSet(driver);
auto const& material = getPostProcessMaterial("clearDepth");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
// the UBO has been set and committed in clearAncillaryBuffersPrepare()
FMaterialInstance const* const mi = getMaterialInstanceWithTag(driver, ma, 0, variant);
FMaterialInstance const* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
mi->use(driver);
auto pipeline = getPipelineState(mi, variant);
auto pipeline = getPipelineState(ma, variant);
pipeline.rasterState.depthFunc = RasterState::DepthFunc::A;
driver.scissor(mi->getScissor());
@@ -2679,7 +2665,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);
FMaterial const* const ma = material.getMaterial(mEngine, driver, PostProcessVariant::OPAQUE);
FMaterialInstance const* mi = ma->getDefaultInstance();
mi->commit(driver, getUboManager());
}
@@ -2690,11 +2676,11 @@ void PostProcessManager::fog(DriverApi& driver) noexcept {
bindPerRenderableDescriptorSet(driver);
auto const& material = getPostProcessMaterial("fog");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
FMaterialInstance const* mi = ma->getDefaultInstance();
mi->use(driver);
auto pipeline = getPipelineState(mi, Variant::NO_VARIANT);
auto pipeline = getPipelineState(ma, Variant::NO_VARIANT);
driver.scissor(mi->getScissor());
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
}
@@ -2951,7 +2937,7 @@ void PostProcessManager::TaaJitterCamera(
void PostProcessManager::configureTemporalAntiAliasingMaterial(backend::DriverApi& driver,
TemporalAntiAliasingOptions const& taaOptions) noexcept {
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine);
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine, driver);
ma->getPrograms().setConstants({
{ "upscaling", taaOptions.upscaling > 1.0f },
{ "historyReprojection", taaOptions.historyReprojection },
@@ -2970,7 +2956,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);
FMaterial* ma = material.getMaterial(mEngine, driver);
ma->getPrograms().setConstants({
{ "isOneDimensional", colorGrading->isOneDimensional() },
{ "isLDR", colorGrading->isLDR() },
@@ -2979,9 +2965,8 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
PostProcessVariant const variant = colorGradingConfig.translucent
? PostProcessVariant::TRANSLUCENT
: PostProcessVariant::OPAQUE;
ma = material.getMaterial(mEngine);
FMaterialInstance* mi =
getMaterialInstanceWithTag(driver, ma, colorGradingConfig.translucent, variant);
ma = material.getMaterial(mEngine, driver, variant);
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
const SamplerParams params = SamplerParams{
.filterMag = SamplerMagFilter::LINEAR,
@@ -3093,9 +3078,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
PostProcessVariant const variant = colorGradingConfig.translucent ?
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
FMaterialInstance* mi = getMaterialInstance(driver, ma);
FMaterialInstance* mi = getMaterialInstance(ma);
mi->setParameter("color", color, SamplerParams{}); // nearest
mi->setParameter("depth", depth, SamplerParams{}); // nearest
mi->setParameter("history", history, SamplerParams{
@@ -3135,7 +3120,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
if (colorGradingConfig.asSubpass) {
out.params.subpassMask = 1;
}
auto const pipeline = getPipelineState(mi, variant);
auto const pipeline = getPipelineState(ma, variant);
driver.beginRenderPass(out.target, out.params);
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
@@ -3217,7 +3202,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(mi, variant);
auto pipeline = getPipelineState(material.getMaterial(mEngine, driver), variant);
if (mode == RcasMode::BLENDED) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
@@ -3310,7 +3295,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
auto out = resources.getRenderPassInfo();
auto pipeline = getPipelineState(mi);
auto pipeline = getPipelineState(material.getMaterial(mEngine, driver));
if (blended) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
@@ -3524,15 +3509,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
auto out = resources.getRenderPassInfo();
if (UTILS_UNLIKELY(twoPassesEASU)) {
auto pipeline0 = getPipelineState(getMaterialInstance(mEngine, driver, *splitEasuMaterial));
auto pipeline1 = getPipelineState(getMaterialInstance(mEngine, driver, *easuMaterial));
auto pipeline0 = getPipelineState(splitEasuMaterial->getMaterial(mEngine, driver));
auto pipeline1 = getPipelineState(easuMaterial->getMaterial(mEngine, driver));
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(getMaterialInstance(mEngine, driver, *easuMaterial));
auto pipeline = getPipelineState(easuMaterial->getMaterial(mEngine, driver));
renderFullScreenQuad(out, pipeline, driver);
}
unbindAllDescriptorSets(driver);
@@ -3584,8 +3569,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
PostProcessMaterial const& material =
getPostProcessMaterial(layer ? "blitArray" : "blitLow");
FMaterial const* const ma = material.getMaterial(mEngine);
auto* mi = getMaterialInstance(driver, ma);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
auto* mi = getMaterialInstance(ma);
mi->setParameter("color", color, SamplerParams{
.filterMag = filterMag,
.filterMin = filterMin
@@ -3603,7 +3588,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(mi);
auto pipeline = getPipelineState(ma);
if (translucent) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
@@ -3901,10 +3886,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
// set uniforms
PostProcessMaterial const& material = getPostProcessMaterial("blitArray");
FMaterial const* const ma = material.getMaterial(mEngine);
FMaterial const* const ma = material.getMaterial(mEngine, driver);
// It should be ok to not move this getMaterialInstance to inside the loop, since
// this is a pass meant for debug.
auto* mi = getMaterialInstance(driver, ma);
auto* mi = getMaterialInstance(ma);
mi->setParameter("color", color, SamplerParams{
.filterMag = filterMag,
.filterMin = filterMin
@@ -3918,7 +3903,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(mi);
auto pipeline = getPipelineState(ma);
if (translucent) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
+25 -40
View File
@@ -360,7 +360,13 @@ public:
void terminate(FEngine& engine) noexcept;
FMaterial* getMaterial(FEngine& engine) const 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));
}
private:
void loadMaterial(FEngine& engine) const noexcept;
@@ -387,12 +393,11 @@ public:
void bindPerRenderableDescriptorSet(backend::DriverApi& driver) const noexcept;
backend::PipelineState getPipelineState(FMaterialInstance const* mi,
Variant::type_t variant) const noexcept;
backend::PipelineState getPipelineState(FMaterial const* ma, Variant::type_t variant) const noexcept;
backend::PipelineState getPipelineState(FMaterialInstance const* mi,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
return getPipelineState(mi, Variant::type_t(variant));
backend::PipelineState getPipelineState(FMaterial const* ma,
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
return getPipelineState(ma, Variant::type_t(variant));
}
void renderFullScreenQuad(FrameGraphResources::RenderPassInfo const& out,
@@ -422,47 +427,27 @@ 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;
+10 -7
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))) {
first->info.mi->prepareProgram(driver, first->info.materialVariant,
CompilerPriorityQueue::CRITICAL);
auto ma = first->info.mi->getMaterial();
ma->prepareProgram(driver, first->info.materialVariant, CompilerPriorityQueue::CRITICAL);
}
}
}
@@ -426,18 +426,16 @@ 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(rasterState.alphaToCoverage, BLENDING_MASK, BLENDING_SHIFT);
keyDraw |= makeField(ma->getRasterState().alphaToCoverage, BLENDING_MASK, BLENDING_SHIFT);
cmdDraw.key = isBlendingCommand ? keyBlending : keyDraw;
cmdDraw.info.rasterState = rasterState;
cmdDraw.info.rasterState = ma->getRasterState();
// for SSR pass, the blending mode of opaques (including MASKED) must be off
// see Material.cpp.
@@ -448,6 +446,10 @@ 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)
@@ -1088,7 +1090,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
mi->use(driver, info.materialVariant);
}
pipeline.program = mi->getProgram(info.materialVariant);
assert_invariant(ma);
pipeline.program = ma->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);
auto const ma = separableGaussianBlur.getMaterial(engine, driver);
// 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(mi), engine.getFullScreenRenderPrimitive(), 0, 3, 1);
driver.draw(ppm.getPipelineState(ma), 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);
auto const mi = ppm.getMaterialInstanceManager().getMaterialInstance(ma);
FMaterial const* const ma = material.getMaterial(engine, driver);
auto const pipeline = ppm.getPipelineState(mi);
auto const pipeline = ppm.getPipelineState(ma);
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);
const_cast<FMaterial*>(material)->compile(priority, variants, handler, std::move(callback));
material->compile(priority, variants, handler, std::move(callback));
}
// ------------------------------------------------------------------------------------------------
+28 -15
View File
@@ -170,6 +170,8 @@ 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();
@@ -238,33 +240,44 @@ filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
void FMaterial::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask variantSpec,
CallbackHandler* handler,
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, {});
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);
}
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) noexcept {
Invocable<void(Material*)>&& callback) const 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(isParallelShaderCompileSupported)) {
if (UTILS_LIKELY(mIsParallelShaderCompileSupported)) {
for (auto const variant : variants) {
if (mDefinition.hasVariant(variant, shaderModel, isStereoSupported)) {
mi->prepareProgram(driver, variant, priority);
prepareProgram(driver, variant, priority);
}
}
}
@@ -274,7 +287,7 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
void FMaterial::compileAllPrograms(CompilerPriorityQueue const priority,
CallbackHandler* handler,
Invocable<void(Material*)>&& callback) noexcept {
Invocable<void(Material*)>&& callback) const noexcept {
DriverApi& driver = mEngine.getDriverApi();
if (callback) {
+41 -6
View File
@@ -106,12 +106,12 @@ public:
void compile(CompilerPriorityQueue priority,
UserVariantFilterMask variantSpec,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept;
utils::Invocable<void(Material*)>&& callback) const noexcept;
void compile(CompilerPriorityQueue priority,
utils::FixedCapacityVector<Variant> const& variants,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept;
utils::Invocable<void(Material*)>&& callback) const noexcept;
// Creates an instance of this material, specifying the batching mode.
FMaterialInstance* createInstance(const char* name) const noexcept;
@@ -130,6 +130,40 @@ 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.
@@ -276,9 +310,6 @@ public:
}
/** @}*/
// Called by getProgram() to update active program list for matdbg UI.
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
#endif
private:
@@ -287,13 +318,15 @@ private:
void compileAllPrograms(CompilerPriorityQueue priority,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept;
utils::Invocable<void(Material*)>&& callback) const noexcept;
MaterialDefinition const& mDefinition;
bool mIsDefaultMaterial = false;
bool mUseUboBatching = false;
bool mIsStereoSupported = false;
bool mIsParallelShaderCompileSupported = false;
bool mDepthPrecacheDisabled = false;
FMaterial const* mDefaultMaterial = nullptr;
@@ -308,6 +341,8 @@ 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,7 +17,6 @@
#include <filament/MaterialInstance.h>
#include "RenderPass.h"
#include "MaterialParser.h"
#include "ds/DescriptorSetLayout.h"
@@ -132,7 +131,6 @@ 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),
@@ -209,10 +207,6 @@ void FMaterialInstance::terminate(FEngine& engine) {
if (ubHandle){
driver.destroyBufferObject(*ubHandle);
}
if (mPrograms.isInitialized()) {
mPrograms.terminate(engine);
}
}
void FMaterialInstance::commit(FEngine& engine) const {
@@ -270,39 +264,6 @@ 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);
@@ -422,15 +383,6 @@ 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;
}
@@ -451,51 +403,6 @@ 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()));
@@ -600,36 +507,4 @@ 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
+4 -52
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,6 +26,8 @@
#include "details/BufferAllocator.h"
#include "details/Engine.h"
#include "private/backend/DriverApi.h"
#include <filament/MaterialInstance.h>
#include <private/filament/Variant.h>
@@ -65,7 +67,7 @@ public:
~FMaterialInstance() noexcept;
void terminate(FEngine& engine);
void commit(FEngine& engine) const;
void commit(FEngine::DriverApi& driver, UboManager* uboManager) const;
@@ -84,32 +86,6 @@ 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),
@@ -131,8 +107,6 @@ 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; }
@@ -280,15 +254,11 @@ 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);
@@ -307,19 +277,6 @@ 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;
@@ -334,11 +291,6 @@ 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,7 +19,6 @@
#include <filament/Engine.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include "filament_test_resources.h"
@@ -168,52 +167,3 @@ 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__)
#if defined(__EMSCRIPTEN__) && !defined(FILAMENT_SUPPORTS_WEBGPU)
constexpr size_t CONFIG_MAX_INSTANCES = 8;
#else
constexpr size_t CONFIG_MAX_INSTANCES = 64;
@@ -881,14 +881,6 @@ 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";
+1 -2
View File
@@ -52,8 +52,6 @@ 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
@@ -77,6 +75,7 @@ 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
)
+4 -1
View File
@@ -17,6 +17,9 @@ 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(
@@ -87,7 +90,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()
+2 -1
View File
@@ -4,7 +4,8 @@ material {
color
],
shadingModel : unlit,
culling : none
culling : none,
featureLevel: 0
}
fragment {
+5
View File
@@ -31,6 +31,7 @@ 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")
@@ -38,6 +39,10 @@ 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()
+41 -21
View File
@@ -80,36 +80,49 @@ Filament.loadClassExtensions = function() {
/// options ::argument:: optional WebGL 2.0 context configuration
/// ::retval:: an instance of [Engine]
Filament.Engine.create = function (canvas, options, config) {
const defaults = {
majorVersion: 2,
minorVersion: 0,
antialias: false,
depth: true,
alpha: false
};
options = Object.assign(defaults, options);
if (!canvas.id) {
canvas.id = 'filament-canvas-' + Math.random().toString(36).substr(2, 9);
}
const canvasId = '#' + canvas.id;
// Create the WebGL 2.0 context.
const ctx = canvas.getContext("webgl2", options);
const backend = (options && options.backend !== undefined) ?
options.backend : Filament.Backend.DEFAULT;
// 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');
if (backend !== Filament.Backend.WEBGPU) {
const defaults = {
majorVersion: 2,
minorVersion: 0,
antialias: false,
depth: true,
alpha: false
};
const glOptions = Object.assign(defaults, options);
// These transient globals are used temporarily during Engine construction.
window.filament_glOptions = options;
window.filament_glContext = ctx;
// 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;
}
// 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(finalConfig);
const engine = Filament.Engine._create(backend, finalConfig);
// Annotate the engine with the GL context to support multiple canvases.
engine.context = window.filament_glContext;
engine.handle = window.filament_contextHandle;
if (backend !== Filament.Backend.WEBGPU) {
engine.context = window.filament_glContext;
engine.handle = window.filament_contextHandle;
}
engine.canvasId = canvasId;
// Ensure that we do not pollute the global namespace.
delete window.filament_glOptions;
@@ -119,6 +132,13 @@ 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();
+17 -1
View File
@@ -26,6 +26,22 @@ 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;
@@ -542,7 +558,7 @@ interface Filamesh {
}
export class Engine {
public static create(canvas: HTMLCanvasElement, contextOptions?: object): Engine;
public static create(canvas: HTMLCanvasElement, options?: { backend?: Backend }): Engine;
public static destroy(engine: Engine): void;
public execute(): void;
public createCamera(entity: Entity): Camera;
+21 -11
View File
@@ -422,15 +422,17 @@ enum_<Engine::Config::ShaderLanguage>("ShaderLanguage")
/// Engine ::core class:: Central manager and resource owner.
class_<Engine>("Engine")
.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);
.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);
}, allow_raw_pointers())
// Create a default Engine configuration. This is for internal use to ensure that engine
@@ -503,9 +505,17 @@ 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*)) []
@@ -2147,7 +2157,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,6 +452,7 @@ 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,6 +14,53 @@
* 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
// ---------------
+6 -1
View File
@@ -66,7 +66,12 @@ 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.
Filament().then(module => {
const moduleConfig = {};
if (Filament.preinitializedWebGPUDevice) {
moduleConfig.preinitializedWebGPUDevice = Filament.preinitializedWebGPUDevice;
}
Filament(moduleConfig).then(module => {
// Merge our extension functions into the emscripten module, not the other
// way around, because Emscripten potentially replaces the HEAPU8 views in