spreed/rspack.config.js
Dimitris Kazakos 98a6ecddac feat(virtual-bg): implement a new, GPU-only pipeline for the virtual background effect
For the inference part, the old Selfie Segmentation using tflite has been replaced by the modern MediaPipe vision tasks API, which runs on GPU if available (otherwise gracefully falls back to wasm/CPU).

For compositing (applying the mask), a new custom WebGL2 compositor performs all work directly on GPU (completely avoiding CPU-GPU back & forth); if WebGL2 is not available, the old canvas compositing is used.

This change introduces significant gains in CPU usage during video calls that use blurring/backgrounds, since the CPU is now mostly idle and all work is done on GPU.

As a bonus, virtual backgrounds are now also available on Safari (WebGL compositor only).

The old JitsiStreamBackgroundEffect class has been replaced by the new VideoStreamBackgroundEffect.

Signed-off-by: Dimitris Kazakos <nemphys@gmail.com>
2025-10-14 18:56:22 +03:00

221 lines
5.6 KiB
JavaScript

/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
const browserslistConfig = require('@nextcloud/browserslist-config')
const { defineConfig } = require('@rspack/cli')
const { CopyRspackPlugin } = require('@rspack/core')
const { CssExtractRspackPlugin, LightningCssMinimizerRspackPlugin, DefinePlugin, ProgressPlugin, SwcJsMinimizerRspackPlugin } = require('@rspack/core')
const NodePolyfillPlugin = require('@rspack/plugin-node-polyfill')
const path = require('node:path')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = defineConfig((env) => {
const appName = process.env.npm_package_name
const appVersion = process.env.npm_package_version
const mode = (env.development && 'development') || (env.production && 'production') || process.env.NODE_ENV || 'production'
const isDev = mode === 'development'
process.env.NODE_ENV = mode
console.info('Building', appName, appVersion, '\n')
return {
target: 'web',
mode,
devtool: isDev ? 'cheap-source-map' : 'source-map',
entry: {
'admin-settings': path.join(__dirname, 'src', 'mainAdminSettings.js'),
collections: path.join(__dirname, 'src', 'collections.js'),
main: path.join(__dirname, 'src', 'main.js'),
recording: path.join(__dirname, 'src', 'mainRecording.js'),
'files-sidebar': [
path.join(__dirname, 'src', 'mainFilesSidebar.js'),
path.join(__dirname, 'src', 'mainFilesSidebarLoader.js'),
],
'public-share-auth-sidebar': path.join(__dirname, 'src', 'mainPublicShareAuthSidebar.js'),
'public-share-sidebar': path.join(__dirname, 'src', 'mainPublicShareSidebar.js'),
flow: path.join(__dirname, 'src', 'flow.js'),
deck: path.join(__dirname, 'src', 'deck.js'),
maps: path.join(__dirname, 'src', 'maps.js'),
search: path.join(__dirname, 'src', 'search.js'),
icons: path.join(__dirname, 'src', 'icons.css'),
},
output: {
path: path.resolve('./js'),
filename: `${appName}-[name].js?v=[contenthash]`,
chunkFilename: `${appName}-[name].js?v=[contenthash]`,
// Set publicPath via __webpack_public_path__
publicPath: 'auto',
// We are working with .wasm and .tflite files as resources on a public path: it must be with the original name in the output folder's root
assetModuleFilename: '[name][ext]?v=[contenthash]',
// We are working with .wasm files as resources on a public path: disabling default wasm loading as source
wasmLoading: false,
enabledWasmLoadingTypes: [],
clean: true,
devtoolNamespace: appName,
// Make sure sourcemaps have a proper path and do not leak local paths
// Source: @nextcloud/webpack-vue-config
devtoolModuleFilenameTemplate(info) {
const rootDir = process.cwd()
const rel = path.relative(rootDir, info.absoluteResourcePath)
return `webpack:///${appName}/${rel}`
},
},
devServer: {
hot: true,
host: '127.0.0.1',
port: 3000,
client: {
overlay: false,
},
devMiddleware: {
writeToDisk: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
optimization: {
chunkIds: 'named',
splitChunks: {
automaticNameDelimiter: '-',
cacheGroups: {
defaultVendors: {
reuseExistingChunk: true,
},
},
},
minimize: !isDev,
minimizer: [
new SwcJsMinimizerRspackPlugin({
minimizerOptions: {
targets: browserslistConfig,
},
}),
new LightningCssMinimizerRspackPlugin({
minimizerOptions: {
targets: browserslistConfig,
},
}),
],
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
experimentalInlineMatchResource: true,
},
},
{
test: /\.css$/,
use: [
{
loader: CssExtractRspackPlugin.loader,
},
'css-loader',
],
},
{
test: /\.scss$/,
use: [
{
loader: CssExtractRspackPlugin.loader,
},
'css-loader',
'sass-loader',
],
},
{
test: /\.ts$/,
exclude: [/node_modules/],
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
},
},
env: {
targets: browserslistConfig,
},
},
type: 'javascript/auto',
},
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: 'asset',
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.wasm$/i,
type: 'asset/resource',
},
{
test: /\.tflite$/i,
type: 'asset/resource',
},
{
resourceQuery: /raw/,
type: 'asset/source',
},
],
},
plugins: [
new ProgressPlugin(),
new VueLoaderPlugin(),
new NodePolyfillPlugin(),
new DefinePlugin({
IS_DESKTOP: false,
__IS_DESKTOP__: false,
appName: JSON.stringify(appName),
appVersion: JSON.stringify(appVersion),
}),
new CssExtractRspackPlugin({
filename: '../css/talk-[name].css',
chunkFilename: '../css/chunks/[id].chunk.css',
ignoreOrder: true,
}),
// Bundle wasm and tflite files required for virtual background feature
new CopyRspackPlugin({
patterns: [
{
from: path.resolve(__dirname, 'node_modules', '@mediapipe/tasks-vision', 'wasm'),
to: path.resolve(__dirname, 'js'),
},
{
from: path.resolve(__dirname, 'src', 'utils', 'media', 'effects', 'virtual-background', 'vendor', 'models', 'selfie_segmenter.tflite'),
to: path.resolve(__dirname, 'js'),
},
],
}),
],
resolve: {
extensions: ['*', '.ts', '.js', '.vue'],
symlinks: false,
fallback: {
fs: false,
},
},
cache: true,
}
})