Skip to content

Commit

Permalink
perf(troika-three-text): parallelize SDF generation with multiple wor…
Browse files Browse the repository at this point in the history
…ker threads

Since the SDF generation worker no longer contains expensive font parsing state,
it is lightweight enough to spawn in multiple workers for parallelization.
Currently uses a max of 4 worker threads, which are terminated after an idle
timeout, for 3-4x overall performance increase in the SDF generation phase.
  • Loading branch information
lojjic committed Sep 16, 2021
1 parent 33b8455 commit c2bf886
Showing 1 changed file with 46 additions and 46 deletions.
92 changes: 46 additions & 46 deletions packages/troika-three-text/src/TextBuilder.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Color, DataTexture, LinearFilter, RGBAFormat } from 'three'
import { defineWorkerModule, ThenableWorkerModule, Thenable } from 'troika-worker-utils'
import { defineWorkerModule, terminateWorker, ThenableWorkerModule, Thenable } from 'troika-worker-utils'
import { createSDFGenerator } from './worker/SDFGenerator.js'
import { createTypesetter } from './worker/Typesetter.js'
import { createGlyphSegmentsIndex } from './worker/GlyphSegmentsIndex.js'
Expand Down Expand Up @@ -353,52 +353,52 @@ const typesetterWorkerModule = /*#__PURE__*/defineWorkerModule({
}
})

// Fan-out to multiple worker threads: (TODO? needs analysis.)
// let generateSDFInWorker = function(...args) {
// const threadCount = 2
// const workers = []
//
// let callNum = 0
// generateSDFInWorker = function(...args) {
// const workerIdx = (callNum++) % threadCount
// if (!workers[workerIdx]) {
// workers[workerIdx] = defineWorkerModule({
// name: 'SDFGenerator_' + workerIdx,
// workerId: 'TroikaTextSDF_' + workerIdx,
// dependencies: [
// CONFIG,
// createGlyphSegmentsIndex,
// createSDFGenerator
// ],
// init(config, createGlyphSegmentsIndex, createSDFGenerator) {
// const {sdfExponent, sdfMargin} = config
// return createSDFGenerator(createGlyphSegmentsIndex, { sdfExponent, sdfMargin })
// },
// getTransferables(result) {
// return [result.textureData.buffer]
// }
// })
// }
// return workers[workerIdx](...args)
// }
// return generateSDFInWorker(...args)
// }

const generateSDFInWorker = /*#__PURE__*/defineWorkerModule({
name: 'SDFGenerator',
dependencies: [
CONFIG,
createGlyphSegmentsIndex,
createSDFGenerator
],
init(config, createGlyphSegmentsIndex, createSDFGenerator) {
const {sdfExponent, sdfMargin} = config
return createSDFGenerator(createGlyphSegmentsIndex, { sdfExponent, sdfMargin })
},
getTransferables(result) {
return [result.textureData.buffer]
/**
* SDF generator function wrapper that fans out requests to a number of worker
* threads for parallelism
*/
const generateSDFInWorker = /*#__PURE__*/function() {
const threadCount = 4 //how many workers to spawn
const idleTimeout = 2000 //workers will be terminated after being idle this many milliseconds
const threads = {}
let callNum = 0
return function(...args) {
const workerId = 'TroikaTextSDFGenerator_' + ((callNum++) % threadCount)
let thread = threads[workerId]
if (!thread) {
thread = threads[workerId] = {
workerModule: defineWorkerModule({
name: workerId,
workerId,
dependencies: [
CONFIG,
createGlyphSegmentsIndex,
createSDFGenerator
],
init(config, createGlyphSegmentsIndex, createSDFGenerator) {
const {sdfExponent, sdfMargin} = config
return createSDFGenerator(createGlyphSegmentsIndex, { sdfExponent, sdfMargin })
},
getTransferables(result) {
return [result.textureData.buffer]
}
}),
requests: 0,
idleTimer: null
}
}

thread.requests++
clearTimeout(thread.idleTimer)
return thread.workerModule(...args)
.then(result => {
if (--thread.requests === 0) {
thread.idleTimer = setTimeout(() => { terminateWorker(workerId) }, idleTimeout)
}
return result
})
}
})
}()

const typesetInWorker = /*#__PURE__*/defineWorkerModule({
name: 'Typesetter',
Expand Down

0 comments on commit c2bf886

Please sign in to comment.