Buckets:
| import { defineWorkerModule, terminateWorker } from 'troika-worker-utils' | |
| import createSDFGenerator from 'webgl-sdf-generator' | |
| const now = () => (self.performance || Date).now() | |
| const mainThreadGenerator = /*#__PURE__*/ createSDFGenerator() | |
| let warned | |
| /** | |
| * Generate an SDF texture image for a single glyph path, placing the result into a webgl canvas at a | |
| * given location and channel. Utilizes the webgl-sdf-generator external package for GPU-accelerated SDF | |
| * generation when supported. | |
| */ | |
| export function generateSDF(width, height, path, viewBox, distance, exponent, canvas, x, y, channel, useWebGL = true) { | |
| // Allow opt-out | |
| if (!useWebGL) { | |
| return generateSDF_JS_Worker(width, height, path, viewBox, distance, exponent, canvas, x, y, channel) | |
| } | |
| // Attempt GPU-accelerated generation first | |
| return generateSDF_GL(width, height, path, viewBox, distance, exponent, canvas, x, y, channel).then( | |
| null, | |
| err => { | |
| // WebGL failed either due to a hard error or unexpected results; fall back to JS in workers | |
| if (!warned) { | |
| console.warn(`WebGL SDF generation failed, falling back to JS`, err) | |
| warned = true | |
| } | |
| return generateSDF_JS_Worker(width, height, path, viewBox, distance, exponent, canvas, x, y, channel) | |
| } | |
| ) | |
| } | |
| const queue = [] | |
| const chunkTimeBudget = 5 // ms | |
| let timer = 0 | |
| function nextChunk() { | |
| const start = now() | |
| while (queue.length && now() - start < chunkTimeBudget) { | |
| queue.shift()() | |
| } | |
| timer = queue.length ? setTimeout(nextChunk, 0) : 0 | |
| } | |
| /** | |
| * WebGL-based implementation executed on the main thread. Requests are executed in time-bounded | |
| * macrotask chunks to allow render frames to execute in between. | |
| */ | |
| const generateSDF_GL = (...args) => { | |
| return new Promise((resolve, reject) => { | |
| queue.push(() => { | |
| const start = now() | |
| try { | |
| mainThreadGenerator.webgl.generateIntoCanvas(...args) | |
| resolve({ timing: now() - start }) | |
| } catch (err) { | |
| reject(err) | |
| } | |
| }) | |
| if (!timer) { | |
| timer = setTimeout(nextChunk, 0) | |
| } | |
| }) | |
| } | |
| 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 | |
| /** | |
| * Fallback JS-based implementation, fanned out to a number of worker threads for parallelism | |
| */ | |
| function generateSDF_JS_Worker(width, height, path, viewBox, distance, exponent, canvas, x, y, channel) { | |
| const workerId = 'TroikaTextSDFGenerator_JS_' + ((callNum++) % threadCount) | |
| let thread = threads[workerId] | |
| if (!thread) { | |
| thread = threads[workerId] = { | |
| workerModule: defineWorkerModule({ | |
| name: workerId, | |
| workerId, | |
| dependencies: [ | |
| createSDFGenerator, | |
| now | |
| ], | |
| init(_createSDFGenerator, now) { | |
| const generate = _createSDFGenerator().javascript.generate | |
| return function (...args) { | |
| const start = now() | |
| const textureData = generate(...args) | |
| return { | |
| textureData, | |
| timing: now() - start | |
| } | |
| } | |
| }, | |
| getTransferables(result) { | |
| return [result.textureData.buffer] | |
| } | |
| }), | |
| requests: 0, | |
| idleTimer: null | |
| } | |
| } | |
| thread.requests++ | |
| clearTimeout(thread.idleTimer) | |
| return thread.workerModule(width, height, path, viewBox, distance, exponent) | |
| .then(({ textureData, timing }) => { | |
| // copy result data into the canvas | |
| const start = now() | |
| // expand single-channel data into rgba | |
| const imageData = new Uint8Array(textureData.length * 4) | |
| for (let i = 0; i < textureData.length; i++) { | |
| imageData[i * 4 + channel] = textureData[i] | |
| } | |
| mainThreadGenerator.webglUtils.renderImageData(canvas, imageData, x, y, width, height, 1 << (3 - channel)) | |
| timing += now() - start | |
| // clean up workers after a while | |
| if (--thread.requests === 0) { | |
| thread.idleTimer = setTimeout(() => { terminateWorker(workerId) }, idleTimeout) | |
| } | |
| return { timing } | |
| }) | |
| } | |
| export function warmUpSDFCanvas(canvas) { | |
| if (!canvas._warm) { | |
| mainThreadGenerator.webgl.isSupported(canvas) | |
| canvas._warm = true | |
| } | |
| } | |
| export const resizeWebGLCanvasWithoutClearing = mainThreadGenerator.webglUtils.resizeWebGLCanvasWithoutClearing | |
Xet Storage Details
- Size:
- 4.38 kB
- Xet hash:
- 40b75ebd5f49898d8a88b645861ff0c793cf0cbb17e03bd42cdaa3527db4f507
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.