mirror of
https://github.com/GuerillaStudio/souvenir.git
synced 2024-11-08 10:51:53 +00:00
perf(encoder): add worker pool
This commit is contained in:
parent
3229a00604
commit
57d4a8dd52
7 changed files with 160 additions and 112 deletions
36
package-lock.json
generated
36
package-lock.json
generated
|
@ -3898,6 +3898,11 @@
|
|||
"loader-utils": "^0.2.16"
|
||||
}
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.6.1.tgz",
|
||||
"integrity": "sha512-iMmD/pY4q0+V+f8o4twE9JPeqfNuX+gJAaIPB3B0W1lFkBOtTxBo6B0HxHPgGhzQA8jego7EWopcYq/UDJO2KA=="
|
||||
},
|
||||
"get-port": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
|
||||
|
@ -5444,14 +5449,19 @@
|
|||
"os-tmpdir": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
|
||||
"integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
|
||||
"p-event": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz",
|
||||
"integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==",
|
||||
"requires": {
|
||||
"p-try": "^2.0.0"
|
||||
"p-timeout": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
|
||||
|
@ -5478,10 +5488,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
|
||||
"integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ=="
|
||||
"p-timeout": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
|
||||
"integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
|
||||
"requires": {
|
||||
"p-finally": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"pako": {
|
||||
"version": "1.0.10",
|
||||
|
@ -6406,11 +6419,6 @@
|
|||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true
|
||||
},
|
||||
"promises-progress": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/promises-progress/-/promises-progress-1.0.3.tgz",
|
||||
"integrity": "sha512-qeVptRZbEqo/JhWVmaAGIG14QnAXXyLj7EI+HKhduqKCl3mMVslMeqxC3tPyxy3N7ffqwbe56vrxqweyKHe1Yg=="
|
||||
},
|
||||
"proto-list": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.4",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"generic-pool": "^3.6.1",
|
||||
"gif-writer": "^0.9.3",
|
||||
"objectFitPolyfill": "^2.1.1",
|
||||
"p-limit": "^2.2.0",
|
||||
"p-event": "^4.0.0",
|
||||
"postcss-modules": "^1.4.1",
|
||||
"promises-progress": "^1.0.3",
|
||||
"vue": "^2.6.6",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
|
|
79
src/services/encode-core.js
Normal file
79
src/services/encode-core.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
|
||||
import {
|
||||
GifWriter,
|
||||
MedianCutColorReducer,
|
||||
IndexedColorImage
|
||||
} from 'gif-writer'
|
||||
|
||||
import { calcProgress } from '/services/util.js'
|
||||
|
||||
class OutputStream {
|
||||
constructor () {
|
||||
this.buffer = []
|
||||
}
|
||||
|
||||
writeByte (b) {
|
||||
this.buffer.push(b)
|
||||
}
|
||||
|
||||
writeBytes (bb) {
|
||||
Array.prototype.push.apply(this.buffer, bb)
|
||||
}
|
||||
}
|
||||
|
||||
export function write (indexedColorImages, imageWidth, imageHeight, delay) {
|
||||
const emitter = new EventEmitter()
|
||||
|
||||
// yup, this is the browser nextTick implementation we are waiting for :facepalm:
|
||||
Promise.resolve().then(() => {
|
||||
emitter.emit('progress', 0)
|
||||
|
||||
const outputStream = new OutputStream()
|
||||
const writer = new GifWriter(outputStream)
|
||||
|
||||
writer.writeHeader()
|
||||
|
||||
writer.writeLogicalScreenInfo({
|
||||
width: imageWidth,
|
||||
height: imageHeight
|
||||
})
|
||||
|
||||
writer.writeLoopControlInfo(0)
|
||||
|
||||
indexedColorImages.forEach((indexedColorImage, index, { length }) => {
|
||||
writer.writeTableBasedImageWithGraphicControl(indexedColorImage, { delayTimeInMS: delay })
|
||||
emitter.emit('progress', calcProgress(0, 0.99, (index + 1) / length))
|
||||
})
|
||||
|
||||
writer.writeTrailer()
|
||||
emitter.emit('progress', 1)
|
||||
|
||||
emitter.emit('done', outputStream.buffer)
|
||||
})
|
||||
|
||||
return emitter
|
||||
}
|
||||
|
||||
export function convertImageDataToIndexedColorImage (imageData, paletteSize) {
|
||||
const reducer = new MedianCutColorReducer(imageData, paletteSize)
|
||||
|
||||
const paletteData = reducer.process()
|
||||
|
||||
const data = Array.from(imageData.data)
|
||||
const indexedColorImageData = []
|
||||
|
||||
for (let index = 0, length = data.length; index < length; index += 4) {
|
||||
const [r, g, b] = data.slice(index, index + 4) // r,g,b,a
|
||||
indexedColorImageData.push(reducer.map(r, g, b))
|
||||
}
|
||||
|
||||
return new IndexedColorImage(
|
||||
{
|
||||
width: imageData.width,
|
||||
height: imageData.height
|
||||
},
|
||||
indexedColorImageData,
|
||||
paletteData
|
||||
)
|
||||
}
|
|
@ -1,31 +1,49 @@
|
|||
import { GifWriter } from 'gif-writer'
|
||||
import pLimit from 'p-limit'
|
||||
import genericPool from 'generic-pool'
|
||||
import pEvent from 'p-event'
|
||||
import { write } from '/services/encode-core.js'
|
||||
import { promisesProgress, calcProgress } from '/services/util.js'
|
||||
|
||||
onmessage = handleMessage
|
||||
|
||||
const workerPool = genericPool.createPool({
|
||||
create () {
|
||||
return new Worker('/services/quantize-color.worker.js')
|
||||
},
|
||||
destroy (worker) {
|
||||
worker.terminate()
|
||||
}
|
||||
}, {
|
||||
min: 0,
|
||||
max: 4
|
||||
})
|
||||
|
||||
async function handleMessage (event) {
|
||||
const { imageDataList, imageWidth, imageHeight, paletteSize, delayTime } = event.data
|
||||
|
||||
const outputStream = new OutputStream()
|
||||
const writer = new GifWriter(outputStream)
|
||||
|
||||
postProgressMessage(0)
|
||||
|
||||
writer.writeHeader()
|
||||
|
||||
writer.writeLogicalScreenInfo({
|
||||
width: imageWidth,
|
||||
height: imageHeight
|
||||
})
|
||||
|
||||
writer.writeLoopControlInfo(0)
|
||||
|
||||
console.time('quantization')
|
||||
|
||||
const limit = pLimit(8)
|
||||
|
||||
const promises = imageDataList
|
||||
.map(imageData => limit(() => convertImageDataToIndexedColorImage(imageData, paletteSize)))
|
||||
.map(async imageData => {
|
||||
const worker = await workerPool.acquire()
|
||||
|
||||
const indexedColorImage = await new Promise((resolve, reject) => {
|
||||
worker.onerror = reject
|
||||
worker.onmessageerror = reject
|
||||
worker.onmessage = event => {
|
||||
resolve(event.data)
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
imageData,
|
||||
paletteSize
|
||||
})
|
||||
})
|
||||
|
||||
await workerPool.release(worker)
|
||||
return indexedColorImage
|
||||
})
|
||||
|
||||
const progressPromises = promisesProgress(promises, function (value) {
|
||||
postProgressMessage(calcProgress(0, 0.9, value))
|
||||
|
@ -35,65 +53,17 @@ async function handleMessage (event) {
|
|||
|
||||
console.timeEnd('quantization')
|
||||
|
||||
indexedColorImages.forEach((indexedColorImage, index, { length }) => {
|
||||
writer.writeTableBasedImageWithGraphicControl(indexedColorImage, { delayTimeInMS: delayTime })
|
||||
postProgressMessage(calcProgress(0.9, 1, (index + 1) / length))
|
||||
})
|
||||
console.time('write')
|
||||
|
||||
writer.writeTrailer()
|
||||
const writing = write(indexedColorImages, imageWidth, imageHeight, delayTime)
|
||||
|
||||
postDoneMessage(outputStream.buffer)
|
||||
}
|
||||
writing.on('progress', value => postProgressMessage(calcProgress(0.9, 1, value)))
|
||||
|
||||
class OutputStream {
|
||||
constructor () {
|
||||
this.buffer = []
|
||||
}
|
||||
const buffer = await pEvent(writing, 'done')
|
||||
|
||||
writeByte (b) {
|
||||
this.buffer.push(b)
|
||||
}
|
||||
console.timeEnd('write')
|
||||
|
||||
writeBytes (bb) {
|
||||
Array.prototype.push.apply(this.buffer, bb)
|
||||
}
|
||||
}
|
||||
|
||||
function promisesProgress (promises, progress) {
|
||||
let complete = 0
|
||||
|
||||
return promises.map(p => {
|
||||
return p.then(x => {
|
||||
progress(++complete / promises.length)
|
||||
return p
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function convertImageDataToIndexedColorImage (imageData, paletteSize) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker('/services/quantize-color.worker.js')
|
||||
|
||||
worker.onerror = reject
|
||||
worker.onmessageerror = reject
|
||||
worker.onmessage = event => {
|
||||
resolve(event.data)
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
imageData,
|
||||
paletteSize
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function calcProgress (from, to, value) {
|
||||
return from + ((to - from) * value)
|
||||
}
|
||||
|
||||
|
||||
function calcProgress2 (from, to, steps, current) {
|
||||
return from + ((to - from) / steps * current)
|
||||
postDoneMessage(buffer)
|
||||
}
|
||||
|
||||
function postProgressMessage (value) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { MedianCutColorReducer, IndexedColorImage } from 'gif-writer'
|
||||
import { convertImageDataToIndexedColorImage } from '/services/encode-core.js'
|
||||
|
||||
onmessage = handleMessage
|
||||
|
||||
|
@ -9,26 +9,3 @@ function handleMessage (event) {
|
|||
|
||||
postMessage(indexedColorImage)
|
||||
}
|
||||
|
||||
function convertImageDataToIndexedColorImage (imageData, paletteSize) {
|
||||
const reducer = new MedianCutColorReducer(imageData, paletteSize)
|
||||
|
||||
const paletteData = reducer.process()
|
||||
|
||||
const data = Array.from(imageData.data)
|
||||
const indexedColorImageData = []
|
||||
|
||||
for (let index = 0, length = data.length; index < length; index += 4) {
|
||||
const [r, g, b, a] = data.slice(index, index + 4) // r,g,b,a
|
||||
indexedColorImageData.push(reducer.map(r, g, b))
|
||||
}
|
||||
|
||||
return new IndexedColorImage(
|
||||
{
|
||||
width: imageData.width,
|
||||
height: imageData.height
|
||||
},
|
||||
indexedColorImageData,
|
||||
paletteData
|
||||
)
|
||||
}
|
14
src/services/util.js
Normal file
14
src/services/util.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
export function calcProgress (from, to, value) {
|
||||
return from + ((to - from) * value)
|
||||
}
|
||||
|
||||
export function promisesProgress (promises, progress) {
|
||||
let complete = 0
|
||||
|
||||
return promises.map(p => {
|
||||
return p.then(x => {
|
||||
progress(++complete / promises.length)
|
||||
return p
|
||||
})
|
||||
})
|
||||
}
|
|
@ -31,7 +31,7 @@ export default new Vuex.Store({
|
|||
},
|
||||
mutations: {
|
||||
updateWelcomed (state, welcome) {
|
||||
state.welcomed = welcome;
|
||||
state.welcomed = welcome
|
||||
},
|
||||
startCamera (state, mediaStream) {
|
||||
state.mediaStream = mediaStream
|
||||
|
|
Loading…
Reference in a new issue