mirror of
https://github.com/GuerillaStudio/souvenir.git
synced 2025-01-20 22:30:20 +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"
|
"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": {
|
"get-port": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
|
||||||
|
@ -5444,14 +5449,19 @@
|
||||||
"os-tmpdir": "^1.0.0"
|
"os-tmpdir": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"p-limit": {
|
"p-event": {
|
||||||
"version": "2.2.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz",
|
||||||
"integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
|
"integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==",
|
||||||
"requires": {
|
"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": {
|
"p-locate": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
|
||||||
|
@ -5478,10 +5488,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"p-try": {
|
"p-timeout": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
|
||||||
"integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ=="
|
"integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
|
||||||
|
"requires": {
|
||||||
|
"p-finally": "^1.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"pako": {
|
"pako": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
|
@ -6406,11 +6419,6 @@
|
||||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||||
"dev": true
|
"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": {
|
"proto-list": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.3.4",
|
"@babel/runtime": "^7.3.4",
|
||||||
"eventemitter3": "^3.1.0",
|
"eventemitter3": "^3.1.0",
|
||||||
|
"generic-pool": "^3.6.1",
|
||||||
"gif-writer": "^0.9.3",
|
"gif-writer": "^0.9.3",
|
||||||
"objectFitPolyfill": "^2.1.1",
|
"objectFitPolyfill": "^2.1.1",
|
||||||
"p-limit": "^2.2.0",
|
"p-event": "^4.0.0",
|
||||||
"postcss-modules": "^1.4.1",
|
"postcss-modules": "^1.4.1",
|
||||||
"promises-progress": "^1.0.3",
|
|
||||||
"vue": "^2.6.6",
|
"vue": "^2.6.6",
|
||||||
"vuex": "^3.0.1"
|
"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,79 +1,34 @@
|
||||||
import { GifWriter } from 'gif-writer'
|
import genericPool from 'generic-pool'
|
||||||
import pLimit from 'p-limit'
|
import pEvent from 'p-event'
|
||||||
|
import { write } from '/services/encode-core.js'
|
||||||
|
import { promisesProgress, calcProgress } from '/services/util.js'
|
||||||
|
|
||||||
onmessage = handleMessage
|
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) {
|
async function handleMessage (event) {
|
||||||
const { imageDataList, imageWidth, imageHeight, paletteSize, delayTime } = event.data
|
const { imageDataList, imageWidth, imageHeight, paletteSize, delayTime } = event.data
|
||||||
|
|
||||||
const outputStream = new OutputStream()
|
|
||||||
const writer = new GifWriter(outputStream)
|
|
||||||
|
|
||||||
postProgressMessage(0)
|
postProgressMessage(0)
|
||||||
|
|
||||||
writer.writeHeader()
|
|
||||||
|
|
||||||
writer.writeLogicalScreenInfo({
|
|
||||||
width: imageWidth,
|
|
||||||
height: imageHeight
|
|
||||||
})
|
|
||||||
|
|
||||||
writer.writeLoopControlInfo(0)
|
|
||||||
|
|
||||||
console.time('quantization')
|
console.time('quantization')
|
||||||
|
|
||||||
const limit = pLimit(8)
|
|
||||||
|
|
||||||
const promises = imageDataList
|
const promises = imageDataList
|
||||||
.map(imageData => limit(() => convertImageDataToIndexedColorImage(imageData, paletteSize)))
|
.map(async imageData => {
|
||||||
|
const worker = await workerPool.acquire()
|
||||||
const progressPromises = promisesProgress(promises, function (value) {
|
|
||||||
postProgressMessage(calcProgress(0, 0.9, value))
|
|
||||||
})
|
|
||||||
|
|
||||||
const indexedColorImages = await Promise.all(progressPromises)
|
|
||||||
|
|
||||||
console.timeEnd('quantization')
|
|
||||||
|
|
||||||
indexedColorImages.forEach((indexedColorImage, index, { length }) => {
|
|
||||||
writer.writeTableBasedImageWithGraphicControl(indexedColorImage, { delayTimeInMS: delayTime })
|
|
||||||
postProgressMessage(calcProgress(0.9, 1, (index + 1) / length))
|
|
||||||
})
|
|
||||||
|
|
||||||
writer.writeTrailer()
|
|
||||||
|
|
||||||
postDoneMessage(outputStream.buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
class OutputStream {
|
|
||||||
constructor () {
|
|
||||||
this.buffer = []
|
|
||||||
}
|
|
||||||
|
|
||||||
writeByte (b) {
|
|
||||||
this.buffer.push(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
|
const indexedColorImage = await new Promise((resolve, reject) => {
|
||||||
worker.onerror = reject
|
worker.onerror = reject
|
||||||
worker.onmessageerror = reject
|
worker.onmessageerror = reject
|
||||||
worker.onmessage = event => {
|
worker.onmessage = event => {
|
||||||
|
@ -85,15 +40,30 @@ function convertImageDataToIndexedColorImage (imageData, paletteSize) {
|
||||||
paletteSize
|
paletteSize
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
function calcProgress (from, to, value) {
|
await workerPool.release(worker)
|
||||||
return from + ((to - from) * value)
|
return indexedColorImage
|
||||||
}
|
})
|
||||||
|
|
||||||
|
const progressPromises = promisesProgress(promises, function (value) {
|
||||||
|
postProgressMessage(calcProgress(0, 0.9, value))
|
||||||
|
})
|
||||||
|
|
||||||
function calcProgress2 (from, to, steps, current) {
|
const indexedColorImages = await Promise.all(progressPromises)
|
||||||
return from + ((to - from) / steps * current)
|
|
||||||
|
console.timeEnd('quantization')
|
||||||
|
|
||||||
|
console.time('write')
|
||||||
|
|
||||||
|
const writing = write(indexedColorImages, imageWidth, imageHeight, delayTime)
|
||||||
|
|
||||||
|
writing.on('progress', value => postProgressMessage(calcProgress(0.9, 1, value)))
|
||||||
|
|
||||||
|
const buffer = await pEvent(writing, 'done')
|
||||||
|
|
||||||
|
console.timeEnd('write')
|
||||||
|
|
||||||
|
postDoneMessage(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
function postProgressMessage (value) {
|
function postProgressMessage (value) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MedianCutColorReducer, IndexedColorImage } from 'gif-writer'
|
import { convertImageDataToIndexedColorImage } from '/services/encode-core.js'
|
||||||
|
|
||||||
onmessage = handleMessage
|
onmessage = handleMessage
|
||||||
|
|
||||||
|
@ -9,26 +9,3 @@ function handleMessage (event) {
|
||||||
|
|
||||||
postMessage(indexedColorImage)
|
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: {
|
mutations: {
|
||||||
updateWelcomed (state, welcome) {
|
updateWelcomed (state, welcome) {
|
||||||
state.welcomed = welcome;
|
state.welcomed = welcome
|
||||||
},
|
},
|
||||||
startCamera (state, mediaStream) {
|
startCamera (state, mediaStream) {
|
||||||
state.mediaStream = mediaStream
|
state.mediaStream = mediaStream
|
||||||
|
|
Loading…
Reference in a new issue