Compare commits

..

2 Commits

Author SHA1 Message Date
Mathias Agopian
547f1f7e32 utils: Secure EntityManager and hide implementation details
Move the `mGens` array and `isAlive()` implementation from `EntityManager`
to `EntityManagerImpl` to hide implementation details and maintain a clean
public header. Make `isAlive()` strictly thread-safe by acquiring the
`mFreeListLock`.

isAlive() was relying on undefined behavior, and the comment about
the memory barrier being provided by the lock was misleading (wrong)
because the lock only provided the "release", but isAlive() didn't
have an "acquire". 

Better safe than sorry, we now have to acquire the lock to test isAlive().
2026-05-01 11:32:59 -07:00
dependabot[bot]
fd0a0c4781 build(deps): bump uuid (#9927)
Bumps the npm_and_yarn group with 1 update in the /build/common/upload-release-assets directory: [uuid](https://github.com/uuidjs/uuid).


Removes `uuid`

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-30 22:35:17 +00:00
36 changed files with 177 additions and 622 deletions

View File

@@ -474,12 +474,12 @@ if (NOT MSVC)
endif()
if (ANDROID OR WASM)
# On Android and WASM RELEASE builds, we omit unwind info to save space.
# On Android and WebGL RELEASE builds, we omit unwind info to save space.
# (We keep unwind info on iOS to allow readable stack traces in crash reports.)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-unwind-tables -fno-asynchronous-unwind-tables")
endif()
# With WASM, we disable RTTI because we pass emscripten::val back and forth
# With WebGL, we disable RTTI because we pass emscripten::val back and forth
# between C++ and JavaScript in order to efficiently access typed arrays, which are unbound.
# NOTE: This is not documented in emscripten so we should consider a different approach.
if (WASM)
@@ -603,7 +603,7 @@ if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
endif()
# Build with Metal support on non-WASM Apple platforms.
# Build with Metal support on non-WebGL Apple platforms.
if (APPLE AND NOT WASM)
option(FILAMENT_SUPPORTS_METAL "Include the Metal backend" ON)
else()
@@ -969,7 +969,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
add_subdirectory(${EXTERNAL}/spirv-headers)
endif()
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
if (FILAMENT_SUPPORTS_WEBGPU)
add_subdirectory(${EXTERNAL}/dawn/tnt/)
endif()
@@ -1021,7 +1021,7 @@ if (IS_HOST_PLATFORM)
add_subdirectory(${TOOLS}/specgen)
endif()
# Generate exported executables for cross-compiled builds (Android, WASM, and iOS)
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
if ((NOT CMAKE_CROSSCOMPILING AND NOT FILAMENT_IMPORT_PREBUILT_EXECUTABLES) OR FILAMENT_EXPORT_PREBUILT_EXECUTABLES)
export(TARGETS matc cmgen filamesh mipgen resgen uberz glslminifier FILE ${IMPORT_EXECUTABLES})
endif()

View File

@@ -368,11 +368,6 @@ function build_wasm_with_target {
ISSUE_CMAKE_ALWAYS=true
fi
if [[ "${WEBGPU_OPTION}" == *"-DFILAMENT_SUPPORTS_WEBGPU=ON"* ]]; then
WEBGPU_OPTION="${WEBGPU_OPTION} -DCMAKE_CXX_FLAGS=\"--use-port=emdawnwebgpu\""
WEBGPU_OPTION="${WEBGPU_OPTION} -DCMAKE_C_FLAGS=\"--use-port=emdawnwebgpu\""
fi
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
# Apply the emscripten environment within a subshell.
(
@@ -1032,8 +1027,7 @@ while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:Py:E" opt; do
echo "Consider using -c after changing this option to clear the Gradle cache."
;;
W)
WEBGPU_OPTION='-DFILAMENT_SUPPORTS_WEBGPU=ON'
WEBGPU_OPTION="-DFILAMENT_SUPPORTS_WEBGPU=ON"
WEBGPU_ANDROID_GRADLE_OPTION="-Pcom.google.android.filament.include-webgpu"
echo "Enable support for WebGPU(Experimental) in the core Filament library."
;;

View File

@@ -13,12 +13,20 @@
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/glob": {
@@ -38,6 +46,11 @@
"tunnel": "^0.0.6"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"node_modules/@octokit/auth-token": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
@@ -257,24 +270,24 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==",
"license": "ISC"
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
}
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": {
"@actions/io": "^1.0.1"
}
},
"@actions/glob": {
@@ -294,6 +307,11 @@
"tunnel": "^0.0.6"
}
},
"@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"@octokit/auth-token": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
@@ -444,11 +462,6 @@
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
}

View File

@@ -358,11 +358,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
include/backend/platforms/WebGPUPlatformAndroid.h
src/webgpu/platform/WebGPUPlatformAndroid.cpp
)
elseif (WASM)
list(APPEND SRCS
include/backend/platforms/WebGPUPlatformWasm.h
src/webgpu/platform/WebGPUPlatformWasm.cpp
)
endif()
if (TNT_DEV)
@@ -436,7 +431,7 @@ if (FILAMENT_USE_ABSEIL_LOGGING)
target_link_libraries(${TARGET} PRIVATE absl::log)
endif()
# Android, iOS, and WASM do not use bluegl.
# Android, iOS, and WebGL do not use bluegl.
if(FILAMENT_SUPPORTS_OPENGL AND NOT IOS AND NOT ANDROID AND NOT WASM)
target_link_libraries(${TARGET} PRIVATE bluegl)
endif()
@@ -446,7 +441,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
endif()
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
if (FILAMENT_SUPPORTS_WEBGPU)
target_link_libraries(${TARGET} PRIVATE webgpu_dawn dawncpp_headers)
endif()
@@ -564,7 +559,7 @@ install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
install(TARGETS ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include)
if (FILAMENT_SUPPORTS_WEBGPU AND NOT WASM)
if (FILAMENT_SUPPORTS_WEBGPU)
install(TARGETS webgpu_dawn ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
endif()

View File

@@ -31,11 +31,6 @@
#endif
#include <webgpu/webgpu_cpp.h>
#if defined(__EMSCRIPTEN__)
// We need to polyfill some Dawn extensions that are not in Emscripten.
#include "WebGPUWasmPolyfill.h"
#endif
#include <cstdint>
#include <vector>
@@ -66,9 +61,9 @@ public:
// either returns a valid surface or panics
[[nodiscard]] virtual wgpu::Surface createSurface(void* nativeWindow, uint64_t flags) = 0;
// either returns a valid adapter or panics
[[nodiscard]] virtual wgpu::Adapter requestAdapter(wgpu::Surface const& surface);
[[nodiscard]] wgpu::Adapter requestAdapter(wgpu::Surface const& surface);
// either returns a valid device or panics
[[nodiscard]] virtual wgpu::Device requestDevice(wgpu::Adapter const& adapter);
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
struct Configuration {
wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined;

View File

@@ -1,40 +0,0 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWASM_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWASM_H
#include <backend/platforms/WebGPUPlatform.h>
namespace filament::backend {
class WebGPUPlatformWasm : public WebGPUPlatform {
public:
wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override;
wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override;
wgpu::Adapter requestAdapter(wgpu::Surface const& surface) override;
wgpu::Device requestDevice(wgpu::Adapter const& adapter) override;
static wgpu::Device sDevice;
protected:
std::vector<wgpu::RequestAdapterOptions> getAdapterOptions() override;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWASM_H

View File

@@ -1,70 +0,0 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPU_WASM_POLYFILL_H
#define TNT_FILAMENT_BACKEND_WEBGPU_WASM_POLYFILL_H
#if defined(__EMSCRIPTEN__)
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace wgpu {
struct Extent2D {
uint32_t width = 0;
uint32_t height = 0;
operator Extent3D() const { return {width, height, 1}; }
};
struct Origin2D {
uint32_t x = 0;
uint32_t y = 0;
operator Origin3D() const { return {x, y, 0}; }
};
// b/508270158
enum class ComponentSwizzle : uint32_t {
Undefined = 0,
Zero = 1,
One = 2,
R = 3,
G = 4,
B = 5,
A = 6,
};
struct TextureComponentSwizzle {
ComponentSwizzle r = ComponentSwizzle::Undefined;
ComponentSwizzle g = ComponentSwizzle::Undefined;
ComponentSwizzle b = ComponentSwizzle::Undefined;
ComponentSwizzle a = ComponentSwizzle::Undefined;
};
struct DawnTogglesDescriptor : public ChainedStruct {
uint32_t enabledToggleCount = 0;
const char* const* enabledToggles = nullptr;
};
struct DawnAdapterPropertiesPowerPreference : public ChainedStructOut {
PowerPreference powerPreference = PowerPreference::Undefined;
};
} // namespace wgpu
#endif // __EMSCRIPTEN__
#endif // TNT_FILAMENT_BACKEND_WEBGPU_WASM_POLYFILL_H

View File

@@ -30,8 +30,6 @@
#include "backend/platforms/WebGPUPlatformLinux.h"
#elif defined(WIN32)
#include "backend/platforms/WebGPUPlatformWindows.h"
#elif defined(__EMSCRIPTEN__)
#include "backend/platforms/WebGPUPlatformWasm.h"
#endif
#endif
@@ -154,8 +152,6 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
return new WebGPUPlatformLinux();
#elif defined(WIN32)
return new WebGPUPlatformWindows();
#elif defined(__EMSCRIPTEN__)
return new WebGPUPlatformWasm();
#else
return nullptr;
#endif
@@ -213,4 +209,3 @@ void PlatformFactory::destroy(Platform** platform) noexcept {
}
} // namespace filament::backend

View File

@@ -19,10 +19,6 @@
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>

View File

@@ -64,22 +64,6 @@
using namespace std::chrono_literals;
// https://issues.chromium.org/issues/507581790
// Manual polyfill pending upstream fix.
#if defined(__EMSCRIPTEN__)
#include <emscripten/em_js.h>
EM_JS(void, wgpuRenderPassEncoderSetImmediates, (WGPURenderPassEncoder passEncoder,
uint32_t offset, void const * data, size_t size), {
const encoder = WebGPU.Internals.jsObjects[passEncoder];
if (encoder && encoder.setImmediates) {
const buffer = HEAPU8.slice(data, data + size);
encoder.setImmediates(offset, buffer);
}
})
#endif
namespace filament::backend {
namespace {
@@ -163,10 +147,8 @@ void WebGPUDriver::terminate() {
}
void WebGPUDriver::tick(int) {
#if !defined(__EMSCRIPTEN__)
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
#endif
}
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -216,9 +198,7 @@ void WebGPUDriver::finish(int /* dummy */) {
// processed. Note that blocking with mReadPixelMapsCounter.waitForAllToFinish will only
// deadlock since we could not advance the counter.
while (!mReadPixelMapsCounter.isIdle()) {
#if !defined(__EMSCRIPTEN__)
mAdapter.GetInstance().ProcessEvents();
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
@@ -802,7 +782,7 @@ FenceStatus WebGPUDriver::fenceWait(FenceHandle fenceHandle, uint64_t const time
if (!fence) {
return FenceStatus::ERROR;
}
using namespace std::chrono;
auto now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
@@ -820,15 +800,15 @@ FenceStatus WebGPUDriver::fenceWait(FenceHandle fenceHandle, uint64_t const time
state = fence->getState();
return bool(state);
}, until);
if (status == FenceStatus::ERROR) {
return FenceStatus::ERROR;
}
if (status == FenceStatus::TIMEOUT_EXPIRED) {
return FenceStatus::TIMEOUT_EXPIRED;
}
auto duration_ns = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(steady_clock::now() - now)
.count());
@@ -857,11 +837,7 @@ bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
}
bool WebGPUDriver::isTextureSwizzleSupported() {
#if defined(__EMSCRIPTEN__)
return false;
#else
return mDevice.HasFeature(wgpu::FeatureName::TextureComponentSwizzle);
#endif
}
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
@@ -883,7 +859,7 @@ bool WebGPUDriver::isTextureFormatFilterable(TextureFormat format) {
if (isFp32ColorFormat(format)) {
return mDevice.HasFeature(wgpu::FeatureName::Float32Filterable);
}
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
isDepthFormat(format) || isStencilFormat(format)) {
return false;
}
@@ -1348,8 +1324,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
const uint8_t mipLevel = colorInfos[i].level;
const uint32_t arrayLayer = colorInfos[i].layer;
customColorViews[customColorViewCount] =
colorTexture->makeAttachmentTextureView(mipLevel, arrayLayer,
renderTarget->getLayerCount());
colorTexture->makeAttachmentTextureView(mipLevel, arrayLayer, renderTarget->getLayerCount());
if (msaaSidecarsRequired) {
const wgpu::TextureView msaaSidecarView{
colorTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
@@ -1508,12 +1483,7 @@ void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
} else if (std::holds_alternative<bool>(value)) {
data = std::get<bool>(value) ? 1 : 0;
}
#if defined(__EMSCRIPTEN__)
wgpuRenderPassEncoderSetImmediates(mRenderPassEncoder.Get(), index * sizeof(uint32_t), &data,
sizeof(uint32_t));
#else
mRenderPassEncoder.SetImmediates(index * sizeof(uint32_t), &data, sizeof(uint32_t));
#endif
}
void WebGPUDriver::insertEventMarker(char const* string) {

View File

@@ -168,10 +168,8 @@ private:
wgpu::TextureFormat::Undefined //
}; // 32 : 328
};
#if !defined(__EMSCRIPTEN__)
static_assert(sizeof(RenderPipelineKey) == 360,
"RenderPipelineKey must not have implicit padding.");
#endif
static_assert(std::is_trivially_copyable<RenderPipelineKey>::value,
"RenderPipelineKey must be a trivially copyable POD for fast hashing.");

View File

@@ -28,33 +28,6 @@
#include <array>
#include <cstring>
// https://issues.chromium.org/issues/507581790
// Manual polyfill pending upstream fix.
#if defined(__EMSCRIPTEN__)
#include <emscripten/em_js.h>
EM_JS(WGPUPipelineLayout, filamentCreatePipelineLayoutWithImmediateData,
(WGPUDevice devicePtr, WGPUPipelineLayoutDescriptor const* descriptor), {
const bglCount = HEAPU32[(descriptor >> 2) + 3];
const bglPtr = HEAPU32[(descriptor >> 2) + 4];
const bgls = [];
for (let i = 0; i < bglCount; ++i) {
bgls.push(WebGPU.getJsObject(HEAPU32[(bglPtr >> 2) + i]));
}
const desc = {
"label": WebGPU.makeStringFromOptionalStringView(descriptor + 4),
"bindGroupLayouts": bgls,
"immediateSize": HEAPU32[(descriptor >> 2) + 5],
};
const device = WebGPU.getJsObject(devicePtr);
const ptr = _emwgpuCreatePipelineLayout(0);
WebGPU.Internals.jsObjectInsert(ptr, device.createPipelineLayout(desc));
return ptr;
})
#endif
namespace filament::backend {
WebGPUPipelineLayoutCache::WebGPUPipelineLayoutCache(wgpu::Device const& device)
@@ -99,28 +72,13 @@ wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
wgpu::Limits supportedLimits{};
mDevice.GetLimits(&supportedLimits);
uint32_t immediateSize = supportedLimits.maxImmediateSize;
if (immediateSize == 0) {
// Fallback: WebGPU implementations that support 'chromium-experimental-immediate-data'
// might not expose 'maxImmediateSize' via JS limits yet, so we assume 64.
immediateSize = 64;
}
const wgpu::PipelineLayoutDescriptor descriptor{
.label = wgpu::StringView(request.label.c_str_safe()),
.bindGroupLayoutCount = request.bindGroupLayoutCount,
.bindGroupLayouts = request.bindGroupLayouts.data(),
.immediateSize = immediateSize,
.immediateSize = supportedLimits.maxImmediateSize,
};
const wgpu::PipelineLayout layout =
#if defined(__EMSCRIPTEN__)
wgpu::PipelineLayout::Acquire(
filamentCreatePipelineLayoutWithImmediateData(mDevice.Get(),
reinterpret_cast<WGPUPipelineLayoutDescriptor const*>(&descriptor)));
#else
mDevice.CreatePipelineLayout(&descriptor);
#endif
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(layout)
<< "Failed to create pipeline layout " << descriptor.label << ".";
return layout;

View File

@@ -76,10 +76,8 @@ private:
uint8_t bindGroupLayoutCount{ 0 }; // 1 :32
uint8_t padding[7]{ 0 }; // 7 :33
};
#if !defined(__EMSCRIPTEN__)
static_assert(sizeof(PipelineLayoutKey) == 40,
"PipelineLayoutKey must not have implicit padding.");
#endif
static_assert(std::is_trivially_copyable<PipelineLayoutKey>::value,
"PipelineLayoutKey must be a trivially copyable POD for fast hashing.");

View File

@@ -75,10 +75,6 @@ namespace {
.label = label.data()
};
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
#if !defined(__EMSCRIPTEN__)
// TODO: We don't really need to wait for compilation info in production. It's helpful only
// for debugging.
const wgpu::Instance instance = device.GetAdapter().GetInstance();
// Synchronously compile the shader module.
@@ -147,7 +143,6 @@ namespace {
PANIC_POSTCONDITION("Timed out creating/compiling shader %s", descriptor.label.data);
break;
}
#endif
FILAMENT_CHECK_POSTCONDITION(shaderModule) << "Failed to create " << descriptor.label;
return shaderModule;
}

View File

@@ -81,9 +81,7 @@ void WebGPUQueueManager::finish() {
// This is similar to draining a work queue. We currently have no other way to force the "last"
// callback to be called.
while (mLatestSubmissionState->getStatus() == FenceStatus::TIMEOUT_EXPIRED) {
#if !defined(__EMSCRIPTEN__)
mDevice.GetAdapter().GetInstance().ProcessEvents();
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}

View File

@@ -392,9 +392,6 @@ WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureForm
case wgpu::TextureFormat::R16Snorm:
case wgpu::TextureFormat::RG16Snorm:
case wgpu::TextureFormat::RGBA16Snorm:
// Formats not available on WASM
#if !defined(__EMSCRIPTEN__)
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
@@ -403,7 +400,6 @@ WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureForm
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
case wgpu::TextureFormat::OpaqueYCbCrAndroid:
#endif
return ScalarSampleType::F32;
case wgpu::TextureFormat::Depth16Unorm:
case wgpu::TextureFormat::Depth24Plus:

View File

@@ -67,12 +67,10 @@ template<typename WebGPUPrintable>
}
}
#if !defined(__EMSCRIPTEN__)
[[nodiscard]] static inline std::string_view powerPreferenceToString(
const wgpu::DawnAdapterPropertiesPowerPreference powerPreference) {
return powerPreferenceToString(powerPreference.powerPreference);
}
#endif
[[nodiscard]] constexpr std::string_view backendTypeToString(const wgpu::BackendType backendType) {
switch (backendType) {
@@ -233,7 +231,6 @@ template<typename WebGPUPrintable>
case wgpu::TextureFormat::R16Snorm: return "R16Snorm";
case wgpu::TextureFormat::RG16Snorm: return "RG16Snorm";
case wgpu::TextureFormat::RGBA16Snorm: return "RGBA16Snorm";
#if !defined(__EMSCRIPTEN__)
case wgpu::TextureFormat::R8BG8Biplanar420Unorm: return "R8BG8Biplanar420Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm: return "R10X6BG10X6Biplanar420Unorm";
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm: return "R8BG8A8Triplanar420Unorm";
@@ -242,7 +239,6 @@ template<typename WebGPUPrintable>
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm: return "R10X6BG10X6Biplanar422Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm: return "R10X6BG10X6Biplanar444Unorm";
case wgpu::TextureFormat::OpaqueYCbCrAndroid: return "OpaqueYCbCrAndroid";
#endif
default: return "Unknown";
}
}

View File

@@ -387,12 +387,9 @@ wgpu::Texture WebGPUSwapChain::getCurrentTexture() {
}
void WebGPUSwapChain::present(DriverBase& driver) {
#if !defined(__EMSCRIPTEN__)
if (!isHeadless()) {
mSurface.Present();
}
#endif
if (mFrameScheduled.callback) {
driver.scheduleCallback(mFrameScheduled.handler,
[callback = mFrameScheduled.callback]() {

View File

@@ -17,15 +17,11 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
#define TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
#include <webgpu/webgpu_cpp.h>
#include "DriverBase.h"
#include <backend/Platform.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <memory>

View File

@@ -41,14 +41,6 @@ namespace filament::backend {
namespace {
bool supportsTransientAttachment(wgpu::Device const& device) {
#if !defined(__EMSCRIPTEN__)
return device.HasFeature(wgpu::FeatureName::TransientAttachments);
#else
return false;
#endif
}
[[nodiscard]] constexpr wgpu::StringView getUserTextureLabel(const SamplerType target) {
// TODO will be helpful to get more useful info than this
switch (target) {
@@ -217,9 +209,9 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS,
supportsTransientAttachment(device)) },
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false,
supportsTransientAttachment(device)) },
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mDimension{ toWebGPUTextureViewDimension(samplerType) },
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) },
@@ -299,7 +291,10 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
mDefaultTextureView = makeTextureView(mDefaultMipLevel, levelCount, 0,
mDefaultBaseArrayLayer, src->getViewDimension());
} else {
wgpu::TextureViewDescriptor viewDesc{
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = mTexture.GetFormat(),
.dimension = src->getViewDimension(),
@@ -308,12 +303,6 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
.baseArrayLayer = 0,
.arrayLayerCount = mDefaultBaseArrayLayer,
};
// b/508270158
#if !defined(__EMSCRIPTEN__)
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
viewDesc.nextInChain = &swizzleDesc,
#endif
mDefaultTextureView = mTexture.CreateView(&viewDesc);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView)
<< "Failed to create swizzled Texture view";
@@ -338,18 +327,15 @@ WebGPUTexture::WebGPUTexture(const WebGPUTexture* src,
mDefaultBaseArrayLayer{ 0 },
mMsaaSidecarTexture{src->mMsaaSidecarTexture},
mSwizzle{ composeSwizzle(src->getSwizzle(), nextSwizzle) } {
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
wgpu::TextureViewDescriptor viewDesc{
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = mTexture.GetFormat(),
.dimension = src->getViewDimension(),
};
// b/508270158
#if !defined(__EMSCRIPTEN__)
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
viewDesc.nextInChain = &swizzleDesc,
#endif
mDefaultTextureView = mTexture.CreateView(&viewDesc);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView) << "Failed to create swizzled Texture view";
}

View File

@@ -20,9 +20,6 @@
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>

View File

@@ -21,10 +21,6 @@
#include <backend/DriverEnums.h>
#if defined(__EMSCRIPTEN__)
#include <backend/platforms/WebGPUWasmPolyfill.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <string_view>
@@ -601,7 +597,7 @@ namespace filament::backend {
}
wgpu::TextureUsage transientAttachmentNeeded{ wgpu::TextureUsage::None };
bool const useTransientAttachment =
const bool useTransientAttachment {
deviceSupportsTransientAttachments &&
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
@@ -612,12 +608,9 @@ namespace filament::backend {
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1)};
if (useTransientAttachment) {
#if !defined(__EMSCRIPTEN__)
transientAttachmentNeeded |= wgpu::TextureUsage::TransientAttachment;
#endif
}
// A texture that is a blit destination or render attachment will often need to be

View File

@@ -57,29 +57,20 @@ namespace {
constexpr uint32_t MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE = 12u;
constexpr std::array REQUIRED_FEATURES = {
wgpu::FeatureName::TransientAttachments,
// Qualcomm 500 and 600 GPUs do not support this so it is not part of core webgpu spec. To
// support such devices, we will either need Filament to not attempt this, or find another
// workaround. https://github.com/gpuweb/gpuweb/issues/2648
wgpu::FeatureName::RG11B10UfloatRenderable,
// necessary for blit conversions of formats like RGBA32Float...
wgpu::FeatureName::Float32Filterable,
// Unsupported on WASM
#if !defined(__EMSCRIPTEN__)
wgpu::FeatureName::TransientAttachments,
#endif
};
constexpr std::array OPTIONAL_FEATURES = {
wgpu::FeatureName::CoreFeaturesAndLimits,
wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8,
// Unsupported on WASM
#if !defined(__EMSCRIPTEN__)
wgpu::FeatureName::TextureComponentSwizzle,
#endif
};
enum class LimitToValidate : uint8_t {
@@ -275,21 +266,12 @@ void printInstanceDetails(wgpu::Instance const& instance) {
#endif
dawnTogglesDescriptor.enabledToggleCount = toggles.size();
dawnTogglesDescriptor.enabledToggles = toggles.data();
#if defined(__EMSCRIPTEN__)
constexpr std::array<wgpu::InstanceFeatureName, 0> features;
#else
constexpr std::array features = {
wgpu::InstanceFeatureName::TimedWaitAny
};
#endif
const wgpu::InstanceFeatureName features[] = {wgpu::InstanceFeatureName::TimedWaitAny};
wgpu::InstanceDescriptor instanceDescriptor{
.nextInChain = &dawnTogglesDescriptor,
.requiredFeatureCount = features.size(),
.requiredFeatures = features.data(),
.requiredFeatures = features,
};
instanceDescriptor.requiredFeatureCount = 1;
wgpu::Instance instance = wgpu::CreateInstance(&instanceDescriptor);
FILAMENT_CHECK_POSTCONDITION(instance != nullptr) << "Unable to create WebGPU instance.";
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
@@ -369,8 +351,6 @@ struct AdapterDetails final {
: info(std::move(info)),
powerPreference(powerPreference),
adapter(std::move(adapter)) {}
AdapterDetails(AdapterDetails&& other) noexcept = default;
AdapterDetails& operator=(AdapterDetails&& other) noexcept {
adapter = std::exchange(other.adapter, nullptr);
info = std::exchange(other.info, {});
@@ -393,10 +373,8 @@ struct AdapterDetails final {
[[nodiscard]] std::string toString(AdapterDetails const& details) {
std::stringstream out;
out << adapterInfoToString(details.info);
#if !defined(__EMSCRIPTEN__)
out << " power preference " << powerPreferenceToString(details.powerPreference);
#endif
out << adapterInfoToString(details.info)
<< " power preference " << powerPreferenceToString(details.powerPreference);
return out.str();
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <backend/platforms/WebGPUPlatformWasm.h>
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
#include <emscripten/html5.h>
#include <vector>
#include <cstdint>
extern "C" WGPUDevice emscripten_webgpu_get_device(void);
namespace filament::backend {
wgpu::Device WebGPUPlatformWasm::sDevice = nullptr;
std::vector<wgpu::RequestAdapterOptions> WebGPUPlatformWasm::getAdapterOptions() {
std::vector<wgpu::RequestAdapterOptions> requests;
requests.reserve(2);
requests.emplace_back(wgpu::RequestAdapterOptions{
.powerPreference = wgpu::PowerPreference::HighPerformance,
.forceFallbackAdapter = false,
.backendType = wgpu::BackendType::WebGPU,
});
requests.emplace_back(wgpu::RequestAdapterOptions{
.powerPreference = wgpu::PowerPreference::LowPower,
.forceFallbackAdapter = false,
.backendType = wgpu::BackendType::WebGPU,
});
return requests;
}
wgpu::Adapter WebGPUPlatformWasm::requestAdapter(wgpu::Surface const& surface) { return nullptr; }
wgpu::Device WebGPUPlatformWasm::requestDevice(wgpu::Adapter const& adapter) {
if (sDevice == nullptr) {
WGPUDevice device = emscripten_webgpu_get_device();
FILAMENT_CHECK_POSTCONDITION(device != nullptr)
<< "WebGPU device not initialized. Call Filament.initWebGPU() first.";
sDevice = wgpu::Device::Acquire(device);
}
return sDevice;
}
wgpu::Extent2D WebGPUPlatformWasm::getSurfaceExtent(void* nativeWindow) const {
const char* selector = static_cast<const char*>(nativeWindow);
int width = 0;
int height = 0;
emscripten_get_canvas_element_size(selector, &width, &height);
return wgpu::Extent2D{
.width = static_cast<uint32_t>(width),
.height = static_cast<uint32_t>(height),
};
}
wgpu::Surface WebGPUPlatformWasm::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvasSource{};
canvasSource.selector = static_cast<const char*>(nativeWindow);
const wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &canvasSource,
.label = "wasm_surface",
};
wgpu::Surface surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface.Get() != nullptr)
<< "Unable to create Wasm-backed surface.";
return surface;
}
} // namespace filament::backend

View File

@@ -138,7 +138,7 @@ constexpr size_t CONFIG_MINSPEC_UBO_SIZE = 16384;
// https://crbug.com/1348363 Lighting looks wrong with D3D11 but not OpenGL
// Note that __EMSCRIPTEN__ is not defined when running matc, but that's okay because we're
// actually using a specification constant.
#if defined(__EMSCRIPTEN__) && !defined(FILAMENT_SUPPORTS_WEBGPU)
#if defined(__EMSCRIPTEN__)
constexpr size_t CONFIG_MAX_INSTANCES = 8;
#else
constexpr size_t CONFIG_MAX_INSTANCES = 64;

View File

@@ -51,7 +51,7 @@ public:
virtual ~Listener() noexcept;
};
using ChangeCallback = std::function<void(utils::Slice<const Entity>)>;
using ChangeCallback = std::function<void(Slice<const Entity>)>;
/**
* Registers a callback to be triggered when entities are destroyed.
@@ -105,10 +105,7 @@ public:
// Return whether the given Entity has been destroyed (false) or not (true).
// Thread safe.
bool isAlive(Entity const e) const noexcept {
assert(getIndex(e) < RAW_INDEX_COUNT);
return (!e.isNull()) && (getGeneration(e) == mGens[getIndex(e)]);
}
bool isAlive(Entity e) const noexcept;
// Registers a listener to be called when an entity is destroyed. Thread safe.
// If the listener is already registered, this method has no effect.
@@ -118,12 +115,8 @@ public:
void unregisterListener(Listener* l) noexcept;
/* no user serviceable parts below */
// current generation of the given index. Use for debugging and testing.
uint8_t getGenerationForIndex(size_t const index) const noexcept {
return mGens[index];
}
/* no user serviceable parts below */
// singleton, can't be copied
EntityManager(const EntityManager& rhs) = delete;
@@ -157,9 +150,6 @@ private:
static Entity::Type makeIdentity(Entity::Type const g, Entity::Type const i) noexcept {
return (g << GENERATION_SHIFT) | (i & INDEX_MASK);
}
// stores the generation of each index.
uint8_t* const mGens;
};
} // namespace utils

View File

@@ -18,21 +18,21 @@
#include "EntityManagerImpl.h"
#include <utils/Entity.h>
#include <cassert>
#include <cstddef>
#include <mutex>
#include <new>
#include <utility>
namespace utils {
EntityManager::Listener::~Listener() noexcept = default;
EntityManager::EntityManager()
: mGens(new uint8_t[RAW_INDEX_COUNT]) {
// initialize all the generations to 0
std::fill_n(mGens, RAW_INDEX_COUNT, 0);
}
EntityManager::EntityManager() = default;
EntityManager::~EntityManager() {
delete [] mGens;
}
EntityManager::~EntityManager() = default;
EntityManager& EntityManager::get() noexcept {
// note: we leak the EntityManager because it's more important that it survives everything else
@@ -41,11 +41,11 @@ EntityManager& EntityManager::get() noexcept {
return *instance;
}
void EntityManager::create(size_t n, Entity* entities) {
void EntityManager::create(size_t const n, Entity* entities) {
static_cast<EntityManagerImpl *>(this)->create(n, entities);
}
void EntityManager::destroy(size_t n, Entity* entities) noexcept {
void EntityManager::destroy(size_t const n, Entity* entities) noexcept {
static_cast<EntityManagerImpl *>(this)->destroy(n, entities);
}
@@ -84,4 +84,8 @@ void EntityManager::dumpActiveEntities(utils::io::ostream& out) const {
#endif
bool EntityManager::isAlive(Entity const e) const noexcept {
return static_cast<EntityManagerImpl const *>(this)->isAlive(e);
}
} // namespace utils

View File

@@ -40,6 +40,7 @@
#include <deque>
#include <memory>
#include <mutex> // for std::lock_guard
#include <new>
#include <utility>
#include <vector>
@@ -49,15 +50,38 @@ static constexpr size_t MIN_FREE_INDICES = 1024;
class UTILS_PRIVATE EntityManagerImpl : public EntityManager {
public:
friend class EntityManager;
using EntityManager::getGeneration;
using EntityManager::getIndex;
using EntityManager::makeIdentity;
using EntityManager::create;
using EntityManager::destroy;
EntityManagerImpl() noexcept
: mGens(new(std::nothrow) uint8_t[RAW_INDEX_COUNT]) {
// initialize all the generations to 0
std::fill_n(mGens, RAW_INDEX_COUNT, 0);
}
~EntityManagerImpl() noexcept {
delete [] mGens;
}
bool isAlive(Entity const e) const noexcept {
assert(getIndex(e) < RAW_INDEX_COUNT);
if (e.isNull()) {
return false;
}
std::lock_guard const lock(mFreeListLock);
bool const alive = (getGeneration(e) == mGens[getIndex(e)]);
return alive;
}
UTILS_NOINLINE
size_t getEntityCount() const noexcept {
std::lock_guard<Mutex> const lock(mFreeListLock);
std::lock_guard const lock(mFreeListLock);
if (mCurrentIndex < RAW_INDEX_COUNT) {
return (mCurrentIndex - 1) - mFreeList.size();
} else {
@@ -66,13 +90,13 @@ public:
}
UTILS_NOINLINE
void create(size_t n, Entity* entities) {
void create(size_t const n, Entity* entities) {
Entity::Type index{};
auto& freeList = mFreeList;
uint8_t const* const gens = mGens;
// this must be thread-safe, acquire the free-list mutex
std::lock_guard<Mutex> const lock(mFreeListLock);
std::lock_guard const lock(mFreeListLock);
Entity::Type currentIndex = mCurrentIndex;
for (size_t i = 0; i < n; i++) {
if (UTILS_UNLIKELY(currentIndex >= RAW_INDEX_COUNT || freeList.size() >= MIN_FREE_INDICES)) {
@@ -98,11 +122,11 @@ public:
}
UTILS_NOINLINE
void destroy(size_t n, Entity* entities) noexcept {
void destroy(size_t const n, Entity* entities) noexcept {
auto& freeList = mFreeList;
uint8_t* const gens = mGens;
std::unique_lock<Mutex> lock(mFreeListLock);
std::unique_lock lock(mFreeListLock);
for (size_t i = 0; i < n; i++) {
if (!entities[i]) {
// behave like free(), ok to free null Entity.
@@ -110,19 +134,15 @@ public:
}
// it's an error to delete an Entity twice...
assert(isAlive(entities[i]));
bool const isAlive = getGeneration(entities[i]) == mGens[getIndex(entities[i])];
assert(isAlive);
// ... deleting a dead Entity will corrupt the internal state, so we protect ourselves
// against it. We don't guarantee anything about external state -- e.g. the listeners
// will be called.
if (isAlive(entities[i])) {
if (UTILS_LIKELY(isAlive)) {
Entity::Type const index = getIndex(entities[i]);
freeList.push_back(index);
// The generation update doesn't require the lock because it's only used for isAlive()
// and entities work as weak references -- it just means that isAlive() could return
// true a little longer than expected in some other threads.
// We do need a memory fence though, it is provided by the mFreeListLock.unlock() below.
gens[index]++;
#if FILAMENT_UTILS_TRACK_ENTITIES
@@ -151,22 +171,22 @@ public:
}
void registerListener(Listener* l) noexcept {
std::lock_guard<Mutex> const lock(mListenerLock);
std::lock_guard const lock(mListenerLock);
mListeners.insert(l);
}
void unregisterListener(Listener* l) noexcept {
std::lock_guard<Mutex> const lock(mListenerLock);
std::lock_guard const lock(mListenerLock);
mListeners.erase(l);
}
void registerChangeCallback(void const* token, ChangeCallback callback) noexcept {
std::lock_guard<Mutex> const lock(mListenerLock);
std::lock_guard const lock(mListenerLock);
mChangeCallbacks.push_back({token, std::move(callback)});
}
void unregisterChangeCallback(void const* token) noexcept {
std::lock_guard<Mutex> const lock(mListenerLock);
std::lock_guard const lock(mListenerLock);
mChangeCallbacks.erase(
std::remove_if(mChangeCallbacks.begin(), mChangeCallbacks.end(),
[token](auto const& info) { return info.token == token; }),
@@ -174,7 +194,7 @@ public:
}
void flushNotifications() noexcept {
std::unique_lock<Mutex> lock(mFreeListLock);
std::unique_lock lock(mFreeListLock);
if (mDirtyCount > 0) {
Entity localBuffer[MAX_DIRTY_COUNT];
assert_invariant(mDirtyCount <= MAX_DIRTY_COUNT);
@@ -208,7 +228,7 @@ public:
private:
std::vector<ChangeCallback> getChangeCallbacks() const noexcept {
std::lock_guard<Mutex> const lock(mListenerLock);
std::lock_guard const lock(mListenerLock);
std::vector<ChangeCallback> result;
result.reserve(mChangeCallbacks.size());
for (auto const& info : mChangeCallbacks) {
@@ -217,7 +237,7 @@ private:
return result;
}
void triggerChangeCallbacks(Entity const* entities, size_t n) const noexcept {
void triggerChangeCallbacks(Entity const* entities, size_t const n) const noexcept {
auto const callbacks = getChangeCallbacks();
Slice const slice(entities, n);
for (auto const& callback : callbacks) {
@@ -226,7 +246,7 @@ private:
}
FixedCapacityVector<Listener*> getListeners() const noexcept {
std::lock_guard<Mutex> const lock(mListenerLock);
std::lock_guard const lock(mListenerLock);
tsl::robin_set<Listener*> const& listeners = mListeners;
FixedCapacityVector<Listener*> result(listeners.size());
result.resize(result.capacity()); // unfortunately this memset()
@@ -234,22 +254,20 @@ private:
return result; // the c++ standard guarantees a move
}
uint32_t mCurrentIndex = 1;
// stores indices that got freed
mutable Mutex mFreeListLock;
std::deque<Entity::Type> mFreeList;
mutable Mutex mListenerLock;
tsl::robin_set<Listener*> mListeners;
static constexpr size_t MAX_DIRTY_COUNT = 16;
struct CallbackInfo {
void const* token;
ChangeCallback callback;
};
std::vector<CallbackInfo> mChangeCallbacks;
static constexpr size_t MAX_DIRTY_COUNT = 16;
mutable Mutex mFreeListLock;
uint32_t mCurrentIndex = 1;
std::deque<Entity::Type> mFreeList; // stores indices that got freed
uint8_t* const mGens; // stores the generation of each index.
mutable Mutex mListenerLock;
tsl::robin_set<Listener*> mListeners;
std::vector<CallbackInfo> mChangeCallbacks;
Entity mDirtyEntities[MAX_DIRTY_COUNT];
size_t mDirtyCount = 0;

View File

@@ -17,9 +17,6 @@ function(build_material MAT_FILE TARGET_DIR MAT_NAME OUT_LIST)
if (NOT CMAKE_BUILD_TYPE MATCHES Release)
set(MATC_FLAGS -g ${MATC_FLAGS})
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
set(MATC_FLAGS -a webgpu ${MATC_FLAGS})
endif()
set(mat_src "${CMAKE_CURRENT_SOURCE_DIR}/${MAT_FILE}")
set(output_path "${SERVER_DIR}/${TARGET_DIR}/${MAT_NAME}.filamat")
add_custom_command(
@@ -90,7 +87,7 @@ function(add_envmap SOURCE TARGET TARGET_DIR OUT_LIST)
WORKING_DIRECTORY "${SERVER_DIR}/${TARGET_DIR}"
MAIN_DEPENDENCY ${source_envmap}
DEPENDS cmgen)
set(${OUT_LIST} ${${OUT_LIST}} ${target_skybox} ${target_skybox_tiny} ${target_envmap} PARENT_SCOPE)
endfunction()

View File

@@ -39,10 +39,6 @@ set(LOPTS "${LOPTS} -s FULL_ES3")
set(LOPTS "${LOPTS} -s MIN_WEBGL_VERSION=2")
set(LOPTS "${LOPTS} -s MAX_WEBGL_VERSION=2")
if (FILAMENT_SUPPORTS_WEBGPU)
set(LOPTS "${LOPTS} --use-port=emdawnwebgpu")
endif()
foreach (JS_FILENAME ${EXTERN_POSTJS_SRC})
set(LOPTS "${LOPTS} --extern-post-js ${JS_FILENAME}")
endforeach()

View File

@@ -80,49 +80,36 @@ Filament.loadClassExtensions = function() {
/// options ::argument:: optional WebGL 2.0 context configuration
/// ::retval:: an instance of [Engine]
Filament.Engine.create = function (canvas, options, config) {
if (!canvas.id) {
canvas.id = 'filament-canvas-' + Math.random().toString(36).substr(2, 9);
}
const canvasId = '#' + canvas.id;
const defaults = {
majorVersion: 2,
minorVersion: 0,
antialias: false,
depth: true,
alpha: false
};
options = Object.assign(defaults, options);
const backend = (options && options.backend !== undefined) ?
options.backend : Filament.Backend.DEFAULT;
// Create the WebGL 2.0 context.
const ctx = canvas.getContext("webgl2", options);
if (backend !== Filament.Backend.WEBGPU) {
const defaults = {
majorVersion: 2,
minorVersion: 0,
antialias: false,
depth: true,
alpha: false
};
const glOptions = Object.assign(defaults, options);
// Enable all desired extensions by calling getExtension on each one.
ctx.getExtension('WEBGL_compressed_texture_s3tc');
ctx.getExtension('WEBGL_compressed_texture_s3tc_srgb');
ctx.getExtension('WEBGL_compressed_texture_astc');
ctx.getExtension('WEBGL_compressed_texture_etc');
// Create the WebGL 2.0 context.
const ctx = canvas.getContext("webgl2", glOptions);
// Enable all desired extensions by calling getExtension on each one.
ctx.getExtension('WEBGL_compressed_texture_s3tc');
ctx.getExtension('WEBGL_compressed_texture_s3tc_srgb');
ctx.getExtension('WEBGL_compressed_texture_astc');
ctx.getExtension('WEBGL_compressed_texture_etc');
// These transient globals are used temporarily during Engine construction.
window.filament_glOptions = glOptions;
window.filament_glContext = ctx;
}
// These transient globals are used temporarily during Engine construction.
window.filament_glOptions = options;
window.filament_glContext = ctx;
// Register the GL context with emscripten and create the Engine.
const defaultConfig = Filament.Engine.createDefaultConfig();
const finalConfig = Object.assign(defaultConfig, config);
const engine = Filament.Engine._create(backend, finalConfig);
const engine = Filament.Engine._create(finalConfig);
// Annotate the engine with the GL context to support multiple canvases.
if (backend !== Filament.Backend.WEBGPU) {
engine.context = window.filament_glContext;
engine.handle = window.filament_contextHandle;
}
engine.canvasId = canvasId;
engine.context = window.filament_glContext;
engine.handle = window.filament_contextHandle;
// Ensure that we do not pollute the global namespace.
delete window.filament_glOptions;
@@ -132,13 +119,6 @@ Filament.loadClassExtensions = function() {
return engine;
};
Filament.Engine.prototype.createSwapChain = function() {
if (this.canvasId) {
return this._createSwapChainForCanvas(this.canvasId);
}
return this._createSwapChain();
};
Filament.Engine.prototype.execute = function() {
window.filament_contextHandle = this.handle;
this._execute();

View File

@@ -26,22 +26,6 @@ import * as glm from "gl-matrix";
export as namespace Filament;
export function getSupportedFormatSuffix(desired: string): void;
/**
* Asynchronously initializes the WebGPU adapter and device.
* This must be awaited before initializing the Filament Engine with the WebGPU backend.
*/
export function initWebGPU(): Promise<void>;
export enum Backend {
DEFAULT,
OPENGL,
VULKAN,
METAL,
WEBGPU,
NOOP,
}
export function init(assets: string[], onready?: (() => void) | null): void;
export function fetch(assets: string[], onDone?: (() => void) | null, onFetched?: ((name: string) => void) | null): void;
export function clearAssetCache(): void;
@@ -558,7 +542,7 @@ interface Filamesh {
}
export class Engine {
public static create(canvas: HTMLCanvasElement, options?: { backend?: Backend }): Engine;
public static create(canvas: HTMLCanvasElement, contextOptions?: object): Engine;
public static destroy(engine: Engine): void;
public execute(): void;
public createCamera(entity: Entity): Camera;

View File

@@ -422,17 +422,15 @@ enum_<Engine::Config::ShaderLanguage>("ShaderLanguage")
/// Engine ::core class:: Central manager and resource owner.
class_<Engine>("Engine")
.class_function("_create", (Engine* (*)(backend::Backend, Engine::Config)) [] (backend::Backend backend, Engine::Config config) {
if (backend == backend::Backend::DEFAULT || backend == backend::Backend::OPENGL) {
EM_ASM_INT({
const options = window.filament_glOptions;
const context = window.filament_glContext;
const handle = GL.registerContext(context, options);
window.filament_contextHandle = handle;
GL.makeContextCurrent(handle);
});
}
return Engine::create(backend, nullptr, nullptr, &config);
.class_function("_create", (Engine* (*)(Engine::Config)) [] (Engine::Config config) {
EM_ASM_INT({
const options = window.filament_glOptions;
const context = window.filament_glContext;
const handle = GL.registerContext(context, options);
window.filament_contextHandle = handle;
GL.makeContextCurrent(handle);
});
return Engine::create(Engine::Backend::DEFAULT, nullptr, nullptr, &config);
}, allow_raw_pointers())
// Create a default Engine configuration. This is for internal use to ensure that engine
@@ -505,17 +503,9 @@ class_<Engine>("Engine")
/// createSwapChain ::method::
/// ::retval:: an instance of [SwapChain]
.function("_createSwapChain", (SwapChain* (*)(Engine*)) []
.function("createSwapChain", (SwapChain* (*)(Engine*)) []
(Engine* engine) { return engine->createSwapChain(nullptr); },
allow_raw_pointers())
.function("_createSwapChainForCanvas", (SwapChain* (*)(Engine*, std::string)) []
(Engine* engine, std::string canvasId) {
// Allocate on the heap because nativeWindow is passed asynchronously through the
// driver command buffer to the backend thread.
std::string* persistentCanvasId = new std::string(canvasId);
return engine->createSwapChain((void*)persistentCanvasId->c_str());
},
allow_raw_pointers())
/// destroySwapChain ::method::
/// swapChain ::argument:: an instance of [SwapChain]
.function("destroySwapChain", (void (*)(Engine*, SwapChain*)) []
@@ -2157,7 +2147,7 @@ class_<Ktx2Provider>("gltfio$Ktx2Provider")
class_<WebpProvider>("gltfio$WebpProvider")
.constructor(EMBIND_LAMBDA(WebpProvider, (Engine* engine), {
return WebpProvider { createWebpProvider(engine) };
}))
}))
.class_function("isWebpSupported", &isWebpSupported);
class_<AssetLoader>("gltfio$AssetLoader")

View File

@@ -452,7 +452,6 @@ enum_<ktxreader::Ktx2Reader::Result>("Ktx2Reader$Result")
.value("OPENGL", backend::Backend::OPENGL)
.value("VULKAN", backend::Backend::VULKAN)
.value("METAL", backend::Backend::METAL)
.value("WEBGPU", backend::Backend::WEBGPU)
.value("NOOP", backend::Backend::NOOP);
}

View File

@@ -14,53 +14,6 @@
* limitations under the License.
*/
// ---------------
// WebGPU Initialization
// ---------------
/// initWebGPU ::function:: Asynchronously initializes the WebGPU adapter and device.
/// This must be awaited before initializing the Filament Engine with the WebGPU backend.
/// ::retval:: Promise that resolves when WebGPU is ready.
Filament.initWebGPU = async function() {
if (!navigator.gpu) {
throw new Error("WebGPU is not supported by this browser.");
}
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
throw new Error("No appropriate WebGPU adapter found.");
}
const requiredFeatures = [];
const optionalFeatures = [
'immediates',
'chromium-experimental-immediate-data',
'rg11b10ufloat-renderable',
'float32-filterable',
'float32-blendable',
'depth-clip-control',
'depth32float-stencil8',
'texture-compression-bc',
'texture-compression-etc2',
'texture-compression-astc'
];
for (const feature of optionalFeatures) {
if (adapter.features.has(feature)) {
requiredFeatures.push(feature);
}
}
const requiredLimits = {};
for (const key in adapter.limits) {
requiredLimits[key] = adapter.limits[key];
}
if (adapter.limits.maxImmediateSize !== undefined) {
requiredLimits.maxImmediateSize = adapter.limits.maxImmediateSize;
} else if (requiredFeatures.includes('chromium-experimental-immediate-data')) {
requiredLimits.maxImmediateSize = 64;
}
const device = await adapter.requestDevice({ requiredFeatures, requiredLimits });
Filament.preinitializedWebGPUDevice = device;
};
// ---------------
// Buffer Wrappers
// ---------------

View File

@@ -66,12 +66,7 @@ Filament.init = (assets, onready) => {
// Emscripten creates a global function called "Filament" that returns a promise that
// resolves to a module. Here we replace the function with the module. Note that our
// TypeScript bindings assume that Filament is a namespace, not a function.
const moduleConfig = {};
if (Filament.preinitializedWebGPUDevice) {
moduleConfig.preinitializedWebGPUDevice = Filament.preinitializedWebGPUDevice;
}
Filament(moduleConfig).then(module => {
Filament().then(module => {
// Merge our extension functions into the emscripten module, not the other
// way around, because Emscripten potentially replaces the HEAPU8 views in