diff --git a/web/ci.yml b/web/ci.yml deleted file mode 100644 index a78e80a1a56bcecd67128fc96194b6b0ed77a7ee..0000000000000000000000000000000000000000 --- a/web/ci.yml +++ /dev/null @@ -1,19 +0,0 @@ -Global: - tool: build_submitter - -Default: - profile: [buildProduction] - -Profiles: - - - profile: - name: buildProduction - env: cmc_standard - command: export NODE_ENV=production && sh scripts/build.sh - release: true - - - profile: - name: buildDevelopment - env: cmc_standard - command: export NODE_ENV=development && sh scripts/build.sh - release: true diff --git a/web/demo/index.es6 b/web/demo/index.es6 new file mode 100644 index 0000000000000000000000000000000000000000..1095b4a8a6dfe93e4e0d5d5e01c7cbacee81706f --- /dev/null +++ b/web/demo/index.es6 @@ -0,0 +1,457 @@ +import 'babel-polyfill'; +import VConsole from 'vconsole'; +import Graph from '../src/executor/loader'; +import IO from '../src/feed/imageFeed'; +import Logger from '../tools/logger'; +window.log = new Logger(); +// var vConsole = new VConsole(); +// 统计参数 +window.badCases = []; +// import Utils from '../src/utils/utils'; +// 获取map表 +// import Map from '../test/data/map'; + +// import demoPic from './bbt1.jpg'; +// import demoPic2 from './bbt2.jpg'; +// import demoPic3 from './bbt3.jpg'; +// import demoPic4 from './bbt4.jpg'; +// import demoPic5 from './bbt5.jpg'; +// import testOutput from './data.json'; + +// 后处理测试用例 +// let tempPic = [demoPic, demoPic2, demoPic3, demoPic4, demoPic5]; +/** + * @file model demo 入口文件 + * @author wangqun@baidu.com + * + */ +// 'http://mms-xr.cdn.bcebos.com/paddle/mnist/model.json' +// 模型输出shape +const outputShapes = { + '608': { + from: [19, 19, 25, 1], + to: [19, 19, 5, 5] + }, + '320': { + from: [10, 10, 25, 1], + to: [10, 10, 5, 5] + }, + '320fused': { + from: [10, 10, 25, 1], + to: [10, 10, 5, 5] + }, + 'separate': { + from: [10, 10, 25, 1], + to: [10, 10, 5, 5] + } +}; +// 模型feed数据 +const feedShape = { + '608': { + fw: 608, + fh: 608 + }, + '320': { + fw: 320, + fh: 320 + }, + '320fused': { + fw: 320, + fh: 320 + }, + 'separate': { + fw: 320, + fh: 320 + } +}; +// 模型路径 +const modelPath = { + '608': 'faceModel', + '320': 'facemodel320', + '320fused': 'facemodelfused', + 'separate': 'separablemodel' +}; +const modelType = 'separate'; +const path = modelPath[modelType]; +// 统计参数 +let loaded = false; +let model = {}; +window.statistic = []; +const {fw, fh} = feedShape[modelType]; +// 第一遍执行比较慢 所以预热一下 +async function preheat() { + const io = new IO(); + let feed = io.process({ + input: video, + params: { + gapFillWith: '#000', // 缩放后用什么填充不足方形部分 + targetSize: { + height: fw, + width: fh + }, + targetShape: [1, 3, fh, fw], // 目标形状 为了兼容之前的逻辑所以改个名 + // shape: [3, 608, 608], // 预设tensor形状 + mean: [117.001, 114.697, 97.404], // 预设期望 + // std: [0.229, 0.224, 0.225] // 预设方差 + } + }); + const MODEL_URL = `/${path}/model.json`; + const MODEL_CONFIG = { + dir: `/${path}/`, // 存放模型的文件夹 + main: 'model.json', // 主文件 + }; + loaded = true; + const graphModel = new Graph(); + log.start('加载模型'); + model = await graphModel.loadGraphModel(MODEL_CONFIG, { + multipart: true, + dataType: 'binary', + binaryOption: { + fileCount: 1, // 切成了多少文件 + getFileName(i) { // 获取第i个文件的名称 + return 'chunk_0.dat'; + } + }, + feed + }); + log.end('加载模型'); + let inst = model.execute({ + input: feed + }); +} +async function run(input) { + // const input = document.getElementById('mobilenet'); + log.start('总耗时'); + const io = new IO(); + log.start('预处理'); + let feed = io.process({ + input: input, + params: { + gapFillWith: '#000', // 缩放后用什么填充不足方形部分 + targetSize: { + height: fw, + width: fh + }, + targetShape: [1, 3, fh, fw], // 目标形状 为了兼容之前的逻辑所以改个名 + // shape: [3, 608, 608], // 预设tensor形状 + mean: [117.001, 114.697, 97.404], // 预设期望 + // std: [0.229, 0.224, 0.225] // 预设方差 + } + }); + log.end('预处理'); + if (!loaded) { + const MODEL_URL = `/${path}/model.json`; + const MODEL_CONFIG = { + dir: `/${path}/`, // 存放模型的文件夹 + main: 'model.json', // 主文件 + }; + loaded = true; + const graphModel = new Graph(); + log.start('加载模型'); + model = await graphModel.loadGraphModel(MODEL_CONFIG, { + multipart: true, + dataType: 'binary', + binaryOption: { + fileCount: 1, // 切成了多少文件 + getFileName(i) { // 获取第i个文件的名称 + return 'chunk_0.dat'; + } + }, + feed + }); + log.end('加载模型'); + } + + log.start('运行耗时'); + let inst = model.execute({ + input: feed + }); + + // 其实这里应该有个fetch的执行调用或者fetch的输出 + let result = await inst.read(); + log.end('后处理-读取数据'); + // console.dir(['result', result]); + log.start('后处理-形状调整'); + const newData = []; + let newIndex = -1; + const [w, h, c, b] = outputShapes[modelType].from; + // c channel + for (let i = 0; i < c; i++) { + // height channel + for (let j = 0; j < h; j++) { + // width channel + for (let k = 0; k < w; k++) { + // position: (0, 0, 0, 0) + const index = j * (c * h) + k * c + i; + // const index = j * (i * k) + k * i + i; + newData[++newIndex] = result[index]; + } + } + } + log.end('后处理-形状调整'); + log.start('后处理-画框'); + testRun(newData, input); + log.end('后处理-画框'); + log.end('后处理'); + log.end('总耗时'); +} +var image = ''; + +function selectImage(file) { + if (!file.files || !file.files[0]) { + return; + } + let reader = new FileReader(); + reader.onload = function (evt) { + let img = document.getElementById('image'); + img.src = evt.target.result; + img.onload = function() { + log.during('每次执行的时间间隔'); + run(img); + }; + image = evt.target.result; + } + reader.readAsDataURL(file.files[0]); +} +// selectImage +document.getElementById("uploadImg").onchange = function () { + selectImage(this); +}; + +/* 后处理图片 by zhangmiao06 */ +let preTestRun = (index) => { + let img = document.getElementById('image'); + img.src = tempPic[index]; + img.onload = function() { + testRun(testOutput.data[index], img); + }; +}; +let testRun = (data, img) => { + // console.log('ori', data); + const {from, to} = outputShapes[modelType]; + // let shape = [1, 25, 19, 19]; + let shape = [].concat(from).reverse(); + // 1.从一维数组到1*25*19*19 + let formatData = reshapeMany({ + data: data, + reshapeShape: shape + }); + // console.log('一维到多维', formatData); + // 2.从1*25*19*19 到 19*19*25*1 + let formatData2 = transpose({ + data: formatData, + shape: shape, + transposeShape: [2, 3, 1, 0] + }); + // console.log('transpose', formatData2); + // 3.从19*19*25*1到19*19*5*5 + let formatData3 = reshape({ + data: formatData2, + shape: from, + reshapeShape: to + }); + // console.log('reshape', formatData3); + // 4.运算 + let finalData = handleFinal(formatData3, shape, img); + // console.log('final', finalData); + // 5.处理画布 + // handleCanvas(finalData, img); + handleDiv(finalData, img); +}; + +// sigmoid +let sigmoid = (x) => { + if (x < -100) { + return 0.0; + } + return 1 / (1 + Math.exp(-x)); +} + +// transpose +let transpose = (data) => { + let shape = data.shape; + let transposeShape = data.transposeShape; + let formatData = data.data; + let formatData2 = []; + for(let n = 0; n < shape[transposeShape[0]]; n++) { + let nData = []; + for(let c = 0; c < shape[transposeShape[1]]; c++) { + let cData = []; + for(let row = 0; row < shape[transposeShape[2]]; row++) { + let rowData = []; + for(let col = 0; col < shape[transposeShape[3]]; col++) { + let tempArr = [n, c, row, col]; + let newN = n; + let newC = c; + let newW = row; + let newH = col; + transposeShape.forEach((item, index)=> { + switch(item) { + case 0: + newN = tempArr[index]; + break; + case 1: + newC = tempArr[index]; + break; + case 2: + newW = tempArr[index]; + break; + case 3: + newH = tempArr[index]; + } + }); + rowData.push(formatData[newN][newC][newW][newH]); + } + cData.push(rowData); + } + nData.push(cData); + } + formatData2.push(nData); + } + return formatData2; +}; + +// reshape +let reshape = (data) =>{ + let formatData2 = data.data; + let shape = data.shape; + let reshapeShape = data.reshapeShape; + // 1.变成一维 + let tempData = reshapeOne({ + data: formatData2, + shape: shape + }); + // 2.变成多维 + let formatData3 = reshapeMany({ + data: tempData, + reshapeShape: reshapeShape + }); + return formatData3; +}; + +// 变成一维 +let reshapeOne = (data) => { + let formatData2 = data.data; + let shape = data.shape; + let tempData = []; + for(let n = 0; n < shape[0]; n++) { + for(let c = 0; c < shape[1]; c++) { + for(let row = 0; row < shape[2]; row++) { + for(let col = 0; col < shape[3]; col++) { + tempData.push(formatData2[n][c][row][col]); + } + } + } + } + return tempData; +}; + +// 变成多维 +let reshapeMany = (data) => { + let tempData = data.data; + let reshapeShape = data.reshapeShape; + let formatData3 = []; + for(let n = 0; n < reshapeShape[0]; n++) { + let nData = []; + for(let c = 0; c < reshapeShape[1]; c++) { + let cData = []; + for(let row = 0; row < reshapeShape[2]; row++) { + let rowData = []; + for(let col = 0; col < reshapeShape[3]; col++) { + let tempN = n * reshapeShape[1] * reshapeShape[2] * reshapeShape[3]; + let tempC = c * reshapeShape[2] * reshapeShape[3]; + let tempRow = row * reshapeShape[3]; + rowData.push(tempData[tempN + tempC + tempRow + col]); + } + cData.push(rowData); + } + nData.push(cData); + } + formatData3.push(nData); + } + return formatData3; +}; +let calSize = (img) => { + let w1 = img.width; + let h1 = img.height; + let wh1 = Math.max(w1, h1); + // let factor = 608.0 / wh1; + let factor = fw / wh1; + let width = Math.round(w1 * factor); + let height = Math.round(h1 * factor); + return [w1, h1, width, height]; +}; +// 处理运算 +let handleFinal = (formatData3, shape, img) => { + let finalData = []; + let c = shape[2]; + let [w1, h1, width, height] = calSize(img); + let factorX = Math.max(width, height) / width; + let factorY = Math.max(width, height) / height; + + let maxProb = 0.0; + let anchors = [[1.603231, 2.094468], [6.041143, 7.080126], [2.882459, 3.518061], [4.266906, 5.178857], [9.041765, 10.66308]]; + + for(let i = 0; i < shape[2]; i++) { + for(let j = 0; j < shape[3]; j++) { + for(let k = 0; k < anchors.length; k++) { + let [a1, a2, a3, a4, prob] = formatData3[i][j][k]; + prob = sigmoid(prob); + if (prob > maxProb && prob >= 0.5) { + let ctx = (j + sigmoid(a1)) / c * factorX; + let cty = (i + sigmoid(a2)) / c * factorY; + let col = Math.exp(a3) * anchors[k][0] / c * factorX; + let row = Math.exp(a4) * anchors[k][1] / c * factorY; + let x = (ctx - (col / 2)); + let y = (cty - (row / 2)); + finalData.push([x * w1, y * h1, col * w1, row * h1, prob]); + } + } + } + } + return finalData; +}; + +// 处理画布 +let handleCanvas = (finalData, img) => { + let myCanvas = document.getElementById('myCanvas'); + let [w1, h1, width, height] = calSize(img); + myCanvas.width = w1; + myCanvas.height = h1; + let ctx = myCanvas.getContext("2d"); + ctx.drawImage(img, 0, 0, w1, h1); + + finalData.forEach((demoArr,index) => { + let [demoLeft, demoTop, demoWidth, demoHeight, prob] = demoArr; + ctx.beginPath(); + ctx.strokeStyle="red"; + ctx.moveTo(demoLeft, demoTop); + ctx.lineTo(demoLeft + demoWidth, demoTop); + ctx.lineTo(demoLeft + demoWidth, demoTop + demoHeight); + ctx.lineTo(demoLeft, demoTop + demoHeight); + ctx.closePath(); + ctx.stroke(); + }); +}; +let handleDiv = (finalData, img) => { + if (finalData.length < 1) { + return false; + } + let myCanvas = document.getElementById('myDiv'); + let maxIndex = 0; + if (finalData.length > 1) { + for(let i = 1; i < finalData.length; i++) { + if (finalData[i].prob > finalData[maxIndex].prob) { + maxIndex = i; + } + } + } + let [demoLeft, demoTop, demoWidth, demoHeight, prob] = finalData[maxIndex]; + myCanvas.style.width = demoWidth; + myCanvas.style.height = demoHeight; + myCanvas.style.left = demoLeft; + myCanvas.style.top = demoTop; +}; +// preTestRun(0); + +// run(document.getElementById('pic')); diff --git a/web/demo/index.html b/web/demo/index.html new file mode 100644 index 0000000000000000000000000000000000000000..c418dffd78d16c4f77b81da8827da1733023608c --- /dev/null +++ b/web/demo/index.html @@ -0,0 +1,39 @@ + + +
+ +原图片
+画布
+ ++ + +
+ +tips
+ + + + + diff --git a/web/package.json b/web/package.json index 1fdcb34cca0a4b431b86bce2044fe975111bbd4a..d2c45041a395470c9883568f620402eb16aa3135 100644 --- a/web/package.json +++ b/web/package.json @@ -1,18 +1,16 @@ { - "name": "paddle-web", + "name": "paddle-web-demo", "version": "1.0.0", "description": "paddle", "main": "index.js", "scripts": { "server": "parcel ./src/index.html", "testDemo": "parcel ./demo/index.html", + "testSDemo": "parcel ./demo/index.html --port 8125 --https", + "testVideoDemo": "parcel ./demo/videoDemo.html --port 8123 --https", "unit": "parcel ./test/unitTest.html", "test": "echo \"Error: no test specified\" && exit 1" }, - "repository": { - "type": "git", - "url": "https://github.com/PaddlePaddle/paddle-mobile" - }, "devDependencies": { "babel-core": "^6.26.3", "babel-plugin-transform-class-properties": "^6.24.1", @@ -24,12 +22,13 @@ "babel-preset-stage-0": "^6.24.1", "babel-runtime": "^6.26.0", "parcel-bundler": "^1.10.3", - "axios": ">=0.18.1" + "axios": "^0.17.1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { - "js-file-download": "^0.4.5" + "js-file-download": "^0.4.5", + "vconsole": "^3.3.2" } } diff --git a/web/src/executor/camera.es6 b/web/src/executor/camera.es6 new file mode 100644 index 0000000000000000000000000000000000000000..1050c14bf30dc2bfa9b111a77a274dee6b37df67 --- /dev/null +++ b/web/src/executor/camera.es6 @@ -0,0 +1,142 @@ +/** + * @file 视频流类 + * @author zhangmiao06 + */ +import $ from 'webpack-zepto'; +export default class Camera { + constructor(option) { + this.option = option; + this.video = option.videoDom; + // 标志是否可以切换摄像头 + this.haveDevice = false; + // 设置视频流宽度 + if (option.width) { + this.video.width = option.width; + } + else if (option.height) { + this.video.height = option.height; + } + else { + this.video.width = window.innerWidth; + } + this.deviceInfos = []; + if(navigator.mediaDevices) { + this.haveDevice = true; + } + } + + // 访问用户媒体设备的兼容方法 + run(deviceId) { + if (window.stream) { + window.stream.getTracks().forEach(function (track) { + track.stop(); + }); + } + + let constraints = { + video: {} + }; + const success = this.success.bind(this); + const error = this.error.bind(this); + if (this.deviceInfos.length) { + constraints.video.deviceId= {exact: deviceId || this.deviceInfos[0]}; + } + + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + // 最新的标准API + navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error); + } + else if (navigator.webkitGetUserMedia) { + // webkit核心浏览器 + navigator.webkitGetUserMedia(constraints, success, error); + } + else if (navigator.mozGetUserMedia) { + // firfox浏览器 + navigator.mozGetUserMedia(constraints, success, error); + } + else if (navigator.getUserMedia) { + // 旧版API + navigator.getUserMedia(constraints, success, error); + } + else { + console.log('您的浏览器不支持获取视频流~'); + } + } + + success(stream) { + const domElement = this.video; + // make stream available to console + window.stream = stream; + // 旧的浏览器可能没有srcObject + const URL = window.URL || window.webkitURL || window.mozURL || window.msURL; + if ('srcObject' in domElement) { + try { + domElement.srcObject = stream; + } catch (error) { + domElement.src = URL.createObjectURL(stream) || stream; + } + } else { + // 防止再新的浏览器里使用它,应为它已经不再支持了 + domElement.src = URL.createObjectURL(stream) || stream; + } + domElement.addEventListener('loadeddata', () => { + // 设置视频流高度 + if (this.option.height) { + domElement.width = $(domElement).width(); + } + else { + domElement.height = $(domElement).height(); + } + domElement.play(); + }, false); + } + + error(error) { + alert(`访问用户媒体设备失败${error.name}, ${error.message}`); + } + // 处理摄像头列表 + gotDevices(deviceInfos) { + const ua = navigator.userAgent; + const isIos = /iphone|ipod|ipad/ig.test(ua); + + let delt = -1; + const range = deviceInfos.length; + let start = range - 1; + let end = - 1; + // ios机型camare顺序相反 + if (isIos) { + delt = 1; + start = 0; + end = range; + } + for (let i = start; i !== end; i += delt) { + const deviceInfo = deviceInfos[i]; + if (deviceInfo.kind === 'videoinput') { + this.deviceInfos.push(deviceInfos[i]); + } + } + } + + get curVideo() { + return this.video; + } + getDevices() { + return new Promise((resolve, reject)=> { + if (this.haveDevice) { + if (this.deviceInfos.length) { + resolve(this.deviceInfos); + } + else { + navigator.mediaDevices.enumerateDevices() + .then(this.gotDevices.bind(this)) + .then(()=> { + resolve(this.deviceInfos); + }); + } + } + else { + resolve([]); + } + }); + } +} diff --git a/web/src/executor/executor.es6 b/web/src/executor/executor.es6 index dc9b689f1017575c92bff2b04d156a35f32b2cac..a00dc9b7fde5789d52f9bf127c0e21690e5783d3 100644 --- a/web/src/executor/executor.es6 +++ b/web/src/executor/executor.es6 @@ -10,7 +10,7 @@ export default class GraphExecutor { constructor(model) { this.inputs = model.inputs; this.outputs = model.outputs; - this.attrs = model.attrs; + this.attrs = model.attrs || model['sub-attrs']; this.type = model.type; this.finish = false; this.next = null; @@ -53,7 +53,7 @@ export default class GraphExecutor { else if (this.type === 'fetch') { return this.inputs.X; } - return null; + return this.inputs.Input || this.inputs.X; } get outputsName() { @@ -68,29 +68,31 @@ export default class GraphExecutor { return this.outputs.Y; } else { - return this.outputs.Out; + return this.outputs.Out || this.outputs.Output; } } /** * 将输入数据和具体op进行关联,触发执行具体每一个op - * @param inputs * @param runtime + * @param isRendered */ - execute(runtime) { + execute(runtime, isRendered) { // console.log(inputs, outputs); if (this.type !== 'feed') { - let time = +Date.now(); - runtime.run(this.type, this.opData); + // let time = +Date.now(); + log.start(this.opData.iLayer + '-' + this.type); + runtime.run(this.type, this.opData, isRendered); + log.end(this.opData.iLayer + '-' + this.type); // if (runtime.gpu.frameBufferIsComplete().isComplete) { // var result = runtime.read(); // let res = Array.prototype.slice.call(result); // fileDownload(res, "result.csv"); // } - let length = statistic.length; - statistic[length - 1].type = this.type; - statistic[length - 1].runTime = +Date.now() - time; + // let length = statistic.length; + // statistic[length - 1].type = this.type; + // statistic[length - 1].runTime = +Date.now() - time; // if (this.type === 'scale') { // console.log('时间是:' + (+Date.now() - start)); // } diff --git a/web/src/executor/loader.es6 b/web/src/executor/loader.es6 index de7341aa8050b4ee90ff135bd9b9f1b95df8f333..8be4ab4db01245f1c366528e8b40c733c7da1df3 100644 --- a/web/src/executor/loader.es6 +++ b/web/src/executor/loader.es6 @@ -26,6 +26,8 @@ export default class GraphModel { this.feedOp = null; this.feedItem = null; this.isExecuted = false; + // 网络层数 + this.iLayer = 0; // fetch xhr jsonp this.params = {type: 'fetch'}; // 设置分片加载model @@ -36,22 +38,41 @@ export default class GraphModel { this.binaryOption = loadOptions.binaryOption; } } - // op runner - this.inst = Runtime.init({ - 'width_raw_canvas': 512, - 'height_raw_canvas': 512 - }); - if (this.loadOptions === null) { + + if (!this.loadOptions) { this.loadOptions = {}; + } else { + // op runner + this.inst = Runtime.init(); + factory.setWebglVersion(this.inst.getWebglVersion()); + // this.fetchJson(this.modelGonfig.dir + 'x.json').then(data => { + // const [b, c, h, w] = [1, 3, 320, 320]; + // const size = data.length; + // const total = 3 * 320 * 320; + // this.testData = new Float32Array(total); + // for (let i = 0; i < size; i++) { + // let j = i / (c * w) | 0; + // let k = i % (c * w); + // let b1 = j / h | 0; + // let h1 = j % h; + // let c1 = k % c; + // let w1 = k / c | 0; + // let l = b1 * (c * h * w) + c1 * (h * w) + h1 * (w) + w1; + // this.testData[i] = data[l]; + // } + // }); } } fetchOneChunk(path) { - console.time(path) - return fetch(path).then(request => { - console.timeEnd(path); + return this.fetch(path).then(request => { return request.arrayBuffer(); }) } + fetchJson(path) { + return this.fetch(path).then(request => { + return request.json(); + }) + } fetchAllData() { // todo 兼容一下json的模式 let counts = this.binaryOption.fileCount; @@ -61,8 +82,6 @@ export default class GraphModel { this.fetchOneChunk(this.modelGonfig.dir + this.binaryOption.getFileName(i)) ); } - // 1个文件 - // let chunkArray = [this.fetchOneChunk('/faceModel/mergedData.dat')]; console.time('加载时间'); return Promise.all(chunkArray).then(chunks => { console.timeEnd('加载时间'); @@ -109,13 +128,24 @@ export default class GraphModel { marker += len; }); } + + fetch(path, params) { + params = params || this.params; + let method = params.method || 'get'; + let mode = params.mode || 'cors'; + let myHeaders = new Headers(); + return fetch(path, { + method: method, + mode: mode, + credentials: 'include', + headers: myHeaders + }); + } + fetchModel(params) { params = params || this.params; const path = this.modelGonfig.dir + this.modelGonfig.main; - let URL_SCHEME_REGEX = /^https?:\/\//; let load = null; - let method = params.method || 'get'; - let mode = params.mode || 'cors'; // jsonp请求方式 if (params && params.type === 'jsonp') { let json; @@ -139,14 +169,8 @@ export default class GraphModel { } // 原生fetch else if (params.type === 'fetch') { - let myHeaders = new Headers(); load = new Promise((resolve, reject) => { - fetch(path, { - method: method, - mode: mode, - credentials: "include", - headers: myHeaders - }) + this.fetch(path, params) .then(response => response.json()) .then(responseData => resolve(responseData)) .then(err => reject(err)) @@ -161,23 +185,13 @@ export default class GraphModel { } async load() { let that = this; - console.time('生成op数据之前') - console.time('fetchModel'); const artifacts = this.handler = await this.fetchModel(); - console.timeEnd('fetchModel'); if (this.multipart === true) { - console.time('6个文件准备好op数据'); await this.fetchAllData() .then(() => this.traverse(artifacts.vars)); - console.timeEnd('6个文件准备好op数据'); } - console.time('createOpsMap'); const opsMap = this.createOpsMap(artifacts.ops, artifacts.vars); - console.timeEnd('createOpsMap'); - console.time('constructOpsMap'); this.weightMap = this.constructOpsMap(opsMap); - console.timeEnd('constructOpsMap'); - console.timeEnd('生成op数据之前') // 生成op数据 this.weightMap.forEach(op => { const type = op.type; @@ -192,23 +206,27 @@ export default class GraphModel { const opData = new OpData(op.type, tensor.inputs, tensor.outputs, tensor.attrs); const name = opData.name; const fsCode = factory.buildShader(name, opData.data); - opData.fshader = this.inst.createFragmentShader(fsCode); + opData.fsCode = fsCode; + opData.program = this.inst.createProgram(fsCode, opData.tensor['out']); opData.renderData = opConfs[name].map(elem => { let item = Object.assign({}, elem); const tensorData = opData.tensor[item.tensor]; if (item.type === 'texture') { item.data = tensorData.data; - if (this.feedOp.id === op.id) { + if (this.feedOp.id === op.id && item.tensor === 'origin') { item.shape = tensorData.shape; this.feedItem = item; } item['width_texture'] = tensorData['width_texture']; item['height_texture'] = tensorData['height_texture']; + item['channel'] = tensorData['channel']; } else if (item.type === 'uniform') { item.data = tensorData[item.variable]; } return item; }); + // console.timeEnd('opData.renderData'); + opData.iLayer = this.iLayer++; op.opData = opData; // delete op.inputs; // delete op.outputs; @@ -218,7 +236,7 @@ export default class GraphModel { if (executor.type === 'fetch') { return; } - executor.execute(this.inst); + executor.execute(this.inst, this.isExecuted); if (executor.next) { const id = executor.next; const next = this.getTensor(id); @@ -245,13 +263,12 @@ export default class GraphModel { } let start = +Date.now(); this.execute_(executor[0]); - console.log('总的执行时间是' + (+Date.now() - start)); this.isExecuted = true; return this.inst; } updateFeed() { this.feedItem.data = this.feed.input[0].data; - Utils.img2texture(this.feedItem); + // Utils.img2texture(this.feedItem); } /** * predict enter @@ -282,6 +299,7 @@ export default class GraphModel { input[key] = io.fromPixels(data, pixel); } else if ((key === 'Input') && (inputName === 'image' || inputName === 'x')) { + // that.feed.input[0].data = that.testData; input[key] = that.feed.input; that.feedOp = executor; } @@ -289,6 +307,7 @@ export default class GraphModel { input[key] = that.getTensorAttr(input[key][0]); } }); + // console.log(input); const tensor = { inputs: input, outputs: output, diff --git a/web/src/executor/postProcess.es6 b/web/src/executor/postProcess.es6 new file mode 100644 index 0000000000000000000000000000000000000000..5e4d1672e3061a03de994b915c31f56d4239f896 --- /dev/null +++ b/web/src/executor/postProcess.es6 @@ -0,0 +1,262 @@ +/* eslint-disable */ + +/* 后处理图片 by zhangmiao06 */ +// let preTestRun = index => { +// let img = document.getElementById('image'); +// img.src = tempPic[index]; +// img.onload = function () { +// testRun(testOutput.data[index], img); +// }; +// }; + +import models from '../utils/models'; + +const isSimilar = (r1, r2, threshold = 5) => { + return Math.max(Math.abs(r1[0] - r2[0]), Math.abs(r1[1] - r2[1])) < threshold; + // return Math.abs((r1[0] + r1[1] + r1[2] + r1[3]) - (r2[0] + r2[1] + r2[2] + r2[3])) < threshold; +} + +// sigmoid +let sigmoid = (x) => { + if (x < -100) { + return 0.0; + } + + return 1 / (1 + Math.exp(-x)); +}; + +// transpose +let transpose = (data) => { + let shape = data.shape; + let transposeShape = data.transposeShape; + let formatData = data.data; + let formatData2 = []; + for (let n = 0; n < shape[transposeShape[0]]; n++) { + let nData = []; + for (let c = 0; c < shape[transposeShape[1]]; c++) { + let cData = []; + for (let row = 0; row < shape[transposeShape[2]]; row++) { + let rowData = []; + for (let col = 0; col < shape[transposeShape[3]]; col++) { + let tempArr = [n, c, row, col]; + let newN = n; + let newC = c; + let newW = row; + let newH = col; + transposeShape.forEach((item, index) => { + switch (item) { + case 0: + newN = tempArr[index]; + break; + case 1: + newC = tempArr[index]; + break; + case 2: + newW = tempArr[index]; + break; + case 3: + newH = tempArr[index]; + } + }); + rowData.push(formatData[newN][newC][newW][newH]); + } + cData.push(rowData); + } + nData.push(cData); + } + formatData2.push(nData); + } + return formatData2; +}; + +// reshape +const reshape = (data) => { + let formatData2 = data.data; + let shape = data.shape; + let reshapeShape = data.reshapeShape; + // 1.变成一维 + let tempData = reshapeOne({ + data: formatData2, + shape: shape + }); + // 2.变成多维 + let formatData3 = reshapeMany({ + data: tempData, + reshapeShape: reshapeShape + }); + return formatData3; +}; + +// 变成一维 +const reshapeOne = (data) => { + let formatData2 = data.data; + let shape = data.shape; + let tempData = []; + for (let n = 0; n < shape[0]; n++) { + for (let c = 0; c < shape[1]; c++) { + for (let row = 0; row < shape[2]; row++) { + for (let col = 0; col < shape[3]; col++) { + tempData.push(formatData2[n][c][row][col]); + } + } + } + } + return tempData; +}; + +// 变成多维 +const reshapeMany = data => { + let tempData = data.data; + let reshapeShape = data.reshapeShape; + let formatData3 = []; + for (let n = 0; n < reshapeShape[0]; n++) { + let nData = []; + for (let c = 0; c < reshapeShape[1]; c++) { + let cData = []; + for (let row = 0; row < reshapeShape[2]; row++) { + let rowData = []; + for (let col = 0; col < reshapeShape[3]; col++) { + let tempN = n * reshapeShape[1] * reshapeShape[2] * reshapeShape[3]; + let tempC = c * reshapeShape[2] * reshapeShape[3]; + let tempRow = row * reshapeShape[3]; + rowData.push(tempData[tempN + tempC + tempRow + col]); + } + cData.push(rowData); + } + nData.push(cData); + } + formatData3.push(nData); + } + return formatData3; +}; + +export default class PostProcess { + constructor(options) { + this.modelConfig = models[options.modelName]; + this.count = 0; + this.lastRect = [0, 0, 0, 0] + } + + run(data, img, callback) { + let {from, to} = this.modelConfig.outputShapes; + let shape = [].concat(from).reverse(); + // 1.从一维数组到1*25*19*19 + let formatData = reshapeMany({ + data: data, + reshapeShape: shape + }); + // console.log('一维到多维', formatData); + // 2.从1*25*19*19 到 19*19*25*1 + let formatData2 = transpose({ + data: formatData, + shape: shape, + transposeShape: [2, 3, 1, 0] + }); + // console.log('transpose', formatData2); + // 3.从19*19*25*1到19*19*5*5 + let formatData3 = reshape({ + data: formatData2, + // shape: [19, 19, 25, 1], + // reshapeShape: [19, 19, 5, 5] + shape: from, + reshapeShape: to + }); + // console.log('reshape', formatData3); + // 4.运算 + let finalData = this.handleFinal(formatData3, shape, img); + // console.log('final', finalData); + // 5.处理画布 + // finalData.length && handleCanvas(finalData, img); + this.handleDiv(finalData, img, callback); + } + + calSize(img) { + let w1 = img.width; + let h1 = img.height; + let wh1 = Math.max(w1, h1); + let factor = this.modelConfig.feedShape.fw / wh1; + // let factor = 608.0 / wh1; + let width = Math.round(w1 * factor); + let height = Math.round(h1 * factor); + return [w1, h1, width, height]; + } + + // 处理运算 + handleFinal(formatData3, shape, img) { + let finalData = []; + let c = shape[2]; + let [w1, h1, width, height] = this.calSize(img); + let factorX = Math.max(width, height) / width; + let factorY = Math.max(width, height) / height; + + let maxProb = 0.0; + let anchors = [[1.603231, 2.094468], [6.041143, 7.080126], [2.882459, 3.518061], [4.266906, 5.178857], [9.041765, 10.66308]]; + + for (let i = 0; i < shape[2]; i++) { + for (let j = 0; j < shape[3]; j++) { + for (let k = 0; k < anchors.length; k++) { + let [a1, a2, a3, a4, prob] = formatData3[i][j][k]; + prob = sigmoid(prob); + if (prob > maxProb && prob >= 0.5) { + let ctx = (j + sigmoid(a1)) / c * factorX; + let cty = (i + sigmoid(a2)) / c * factorY; + let col = Math.exp(a3) * anchors[k][0] / c * factorX; + let row = Math.exp(a4) * anchors[k][1] / c * factorY; + let x = (ctx - (col / 2)); + let y = (cty - (row / 2)); + finalData.push([x * w1, y * h1, col * w1, row * h1, prob]); + } + + } + } + } + return finalData; + } + + handleDiv(finalData, img, callback) { + if (finalData.length < 1) { + callback(); + return false; + } + let maxIndex = 0; + if (finalData.length > 1) { + for (let i = 1; i < finalData.length; i++) { + if (finalData[i].prob > finalData[maxIndex].prob) { + maxIndex = i; + } + + } + } + + let [demoLeft, demoTop, demoWidth, demoHeight] = finalData[maxIndex]; + if (!isSimilar(this.lastRect, [demoLeft, demoTop, demoWidth, demoHeight])) { + callback([demoWidth, demoHeight,demoLeft, demoTop]); + }; + this.lastRect = [demoLeft, demoTop, demoWidth, demoHeight]; + } + + // 处理画布 + handleCanvas(finalData, img) { + let myCanvas = document.getElementById('myCanvas'); + let [w1, h1, width, height] = calSize(img); + myCanvas.width = w1; + myCanvas.height = h1; + let ctx = myCanvas.getContext('2d'); + // ctx.drawImage(img, 0, 0, w1, h1); + + // finalData.forEach((demoArr, index) => { + // let [demoLeft, demoTop, demoWidth, demoHeight, prob] = demoArr; + let [demoLeft, demoTop, demoWidth, demoHeight, prob] = finalData[0]; + ctx.beginPath(); + ctx.lineWidth = 4; + ctx.strokeStyle = 'red'; + ctx.moveTo(demoLeft, demoTop); + ctx.lineTo(demoLeft + demoWidth, demoTop); + ctx.lineTo(demoLeft + demoWidth, demoTop + demoHeight); + ctx.lineTo(demoLeft, demoTop + demoHeight); + ctx.closePath(); + ctx.stroke(); + // }); + } +} + diff --git a/web/src/executor/runner.es6 b/web/src/executor/runner.es6 new file mode 100644 index 0000000000000000000000000000000000000000..edb70b6f841805b422586ca5e6fba968506e2a62 --- /dev/null +++ b/web/src/executor/runner.es6 @@ -0,0 +1,153 @@ +/** + * @file Runner 整个流程封装一下 + * @author hantian(hantianjiao@baidu.com) + * 使用方法: + * const runner = new Runner({ + * modelName: 'separate' // '608' | '320' | '320fused' | 'separate' + * }); + * runner.preheat().then(r => { + * r.run(document.getElementById('test')); + * }); + */ + +import IO from '../feed/ImageFeed'; +import DataFeed from '../feed/dataFeed'; +import Graph from './loader'; +import PostProcess from './postProcess'; +import models from '../utils/models'; +import Logger from '../../tools/logger'; +window.log = new Logger(); + +export default class Runner { + // 加载模型&预热 + constructor(options) { + this.modelConfig = models[options.modelName]; + this.flags = { + isRunning: false, + isPreheating: false, + runVideoPaused: false + }; + this.buffer = new Float32Array(); + this.io = new IO(); + this.postProcess = new PostProcess(options); + } + + // 预热 用用空数据跑一遍 + async preheat() { + this.flags.isPreheating = true; + let {fh, fw} = this.modelConfig.feedShape; + let path = this.modelConfig.modelPath; + let feed = [{ + data: new Float32Array(3 * fh * fw), + name: 'image', + shape: [1, 3, fh, fw] + }]; + const MODEL_URL = `/${path}/model.json`; + const MODEL_CONFIG = { + dir: `/${path}/`, // 存放模型的文件夹 + // dir: `https://graph.baidu.com/mms/graph/static/asset/dll/${path}/`, // rd测试地址 + // dir: `/src/view/common/lib/paddle/dist/${path}/`, // 本地测试地址 + main: 'model.json' // 主文件 + }; + const graphModel = new Graph(); + this.model = await graphModel.loadGraphModel(MODEL_CONFIG, { + multipart: true, + dataType: 'binary', + binaryOption: { + fileCount: 1, // 切成了多少文件 + getFileName(i) { // 获取第i个文件的名称 + return 'chunk_0.dat'; + } + }, + feed + }); + this.model.execute({ + input: feed + }); + this.flags.isPreheating = false; + return this; + } + + // 跑一遍 + async run(input, callback) { + this.flags.isRunning = true; + let {fh, fw} = this.modelConfig.feedShape; + let path = this.modelConfig.modelPath; + if (!this.model) { + console.warn('It\'s better to preheat the model before running.'); + await this.preheat(); + } + log.start('总耗时'); // eslint-disable-line + log.start('预处理'); // eslint-disable-line + let feed; + if (typeof input === 'string') { + const dfIO = new DataFeed(); + feed = await dfIO.process({ + input: `/${path}/${input}`, + shape: [1, 3, fh, fw] + }); + } + else { + feed = this.io.process({ + input: input, + params: { + gapFillWith: '#000', // 缩放后用什么填充不足方形部分 + targetSize: { + height: fw, + width: fh + }, + targetShape: [1, 3, fh, fw], // 目标形状 为了兼容之前的逻辑所以改个名 + // shape: [3, 608, 608], // 预设tensor形状 + mean: [117.001, 114.697, 97.404] // 预设期望 + // std: [0.229, 0.224, 0.225] // 预设方差 + } + }); + } + log.end('预处理'); // eslint-disable-line + log.start('运行耗时'); // eslint-disable-line + let inst = this.model.execute({ + input: feed + }); + let result = await inst.read(); + log.end('后处理-读取数据'); // eslint-disable-line + const newData = []; + let newIndex = -1; + const [w, h, c, b] = this.modelConfig.outputShapes.from; + // c channel + for (let i = 0; i < c; i++) { + // height channel + for (let j = 0; j < h; j++) { + // width channel + for (let k = 0; k < w; k++) { + // position: (0, 0, 0, 0) + const index = j * (c * h) + k * c + i; + // const index = j * (i * k) + k * i + i; + newData[++newIndex] = result[index]; + } + } + } + this.postProcess.run(newData, input, callback); + log.end('后处理'); // eslint-disable-line + this.flags.isRunning = false; + log.end('总耗时'); // eslint-disable-line + } + + // 传入获取图片的function + async runStream(getMedia, callback) { + await this.run(getMedia(), callback); + if (!this.flags.runVideoPaused) { + setTimeout(async () => { + await this.runStream(getMedia, callback); + }, 0); + } + } + + stopStream() { + this.flags.runVideoPaused = true; + } + + startStream(getMedia, callback) { + this.flags.runVideoPaused = false; + this.runStream(getMedia, callback); + } +} diff --git a/web/src/factory/fshader/factory.es6 b/web/src/factory/fshader/factory.es6 index 0b218fc4589e5a306a1bbdcf26b8eabb4772a295..4be6cfb0a057bd5c387d0abf46df0f2fe664f157 100644 --- a/web/src/factory/fshader/factory.es6 +++ b/web/src/factory/fshader/factory.es6 @@ -6,6 +6,15 @@ import ops from './ops'; export default class Factory { constructor(opts) { this.defaultOpts = Object.assign({}, opts); + this.webglVersion = 2; + this.texture2d = 'texture'; + } + + setWebglVersion(vs = 0) { + this.webglVersion = vs; + if (vs === 1) { + this.texture2d = 'texture2D'; + } } buildShader(opName, data) { @@ -13,12 +22,16 @@ export default class Factory { result = this.buildPrefix(opName); result += this.buildCommon(opName); result += this.buildOp(opName); + data.texture2d = this.texture2d; result = this.populateData(result, data); return result; } buildPrefix(opName) { - return ops.common.prefix; + if (this.webglVersion === 1) { + return ops.common.prefix; + } + return ops.common.prefix2; } buildCommon(opName) { diff --git a/web/src/factory/fshader/ops.es6 b/web/src/factory/fshader/ops.es6 index dfdf6e653567b46d315e391f7ea8b7a38e2cecb9..94c6fd220ba424dad0b8e69be1b0d64d37832951 100644 --- a/web/src/factory/fshader/ops.es6 +++ b/web/src/factory/fshader/ops.es6 @@ -2,18 +2,28 @@ import common_params from '../../shader/atom/common_params'; import common_func from '../../shader/atom/common_func'; import prefix from '../../shader/atom/prefix'; +import prefix2 from '../../shader/atom/prefix2'; import suffix from '../../shader/atom/suffix'; import ivec56 from '../../shader/atom/type_ivec56'; import conv2d_params from '../../shader/conv2d/params'; import conv2d_func from '../../shader/conv2d/main'; import conv2d_conf from '../../shader/conv2d/conf'; +import conv2d_depthwise_params from '../../shader/conv2d_depthwise/params'; +import conv2d_depthwise_func from '../../shader/conv2d_depthwise/main'; +import conv2d_depthwise_conf from '../../shader/conv2d_depthwise/conf'; import dynamic_params from '../../shader/dynamic/params'; import dynamic_func from '../../shader/dynamic/main'; import dynamic_conf from '../../shader/dynamic/conf'; import pool2d_params from '../../shader/pool2d/params'; import pool2d_func from '../../shader/pool2d/main'; import pool2d_conf from '../../shader/pool2d/conf'; +import pool2d_max_params from '../../shader/pool2d_max/params'; +import pool2d_max_func from '../../shader/pool2d_max/main'; +import pool2d_max_conf from '../../shader/pool2d_max/conf'; +import pool2d_winograd_params from '../../shader/pool2d_winograd/params'; +import pool2d_winograd_func from '../../shader/pool2d_winograd/main'; +import pool2d_winograd_conf from '../../shader/pool2d_winograd/conf'; import elementwise_add_params from '../../shader/elementwise_add/params'; import elementwise_add_func from '../../shader/elementwise_add/main'; import elementwise_add_conf from '../../shader/elementwise_add/conf'; @@ -27,12 +37,21 @@ import batchnorm_params from '../../shader/batchnorm/params'; import batchnorm_func from '../../shader/batchnorm/main'; import batchnorm_conf from '../../shader/batchnorm/conf'; +import conv2d_elementwise_add_params from '../../shader/conv2d_elementwise_add/params'; +import conv2d_elementwise_add_func from '../../shader/conv2d_elementwise_add/main'; +import conv2d_elementwise_add_conf from '../../shader/conv2d_elementwise_add/conf'; + +import conv2d_elementwise_add_winograd_params from '../../shader/conv2d_elementwise_add_winograd/params'; +import conv2d_elementwise_add_winograd_func from '../../shader/conv2d_elementwise_add_winograd/main'; +import conv2d_elementwise_add_winograd_conf from '../../shader/conv2d_elementwise_add_winograd/conf'; + import getArrayIndexFromTensorPos from '../../shader/atom/getArrayIndexFromTensorPos'; import getArrayIndexFromTexturePos from '../../shader/atom/getArrayIndexFromTexturePos'; import getTensorPosFromArrayIndex from '../../shader/atom/getTensorPosFromArrayIndex'; import getTexturePosFromArrayIndex from '../../shader/atom/getTexturePosFromArrayIndex'; import getValueFromTexturePos from '../../shader/atom/getValueFromTexturePos'; import getValueFromTensorPos from '../../shader/atom/getValueFromTensorPos'; +import getValueFromTensorPosPacked from '../../shader/atom/getValueFromTensorPosPacked'; import moveTexture2PosToReal from '../../shader/atom/moveTexture2PosToReal'; import getPixelsFromTexturePos from '../../shader/atom/getPixelsFromTexturePos'; import getRangePowSumFromArrayIndex from '../../shader/atom/getRangePowSumFromArrayIndex'; @@ -51,6 +70,7 @@ export default { params: common_params, func: common_func, prefix, + prefix2, suffix, ivec56 }, @@ -60,6 +80,21 @@ export default { func: conv2d_func, confs: conv2d_conf }, + conv2d_depthwise: { + params: conv2d_depthwise_params, + func: conv2d_depthwise_func, + confs: conv2d_depthwise_conf + }, + conv2d_elementwise_add: { + params: conv2d_elementwise_add_params, + func: conv2d_elementwise_add_func, + confs: conv2d_elementwise_add_conf + }, + conv2d_elementwise_add_winograd: { + params: conv2d_elementwise_add_winograd_params, + func: conv2d_elementwise_add_winograd_func, + confs: conv2d_elementwise_add_winograd_conf + }, dynamic: { params: dynamic_params, func: dynamic_func, @@ -70,6 +105,16 @@ export default { func: pool2d_func, confs: pool2d_conf }, + pool2d_max: { + params: pool2d_max_params, + func: pool2d_max_func, + confs: pool2d_max_conf + }, + pool2d_winograd: { + params: pool2d_winograd_params, + func: pool2d_winograd_func, + confs: pool2d_winograd_conf + }, elementwise_add: { params: elementwise_add_params, func: elementwise_add_func, @@ -108,6 +153,7 @@ export default { getTexturePosFromArrayIndex, getValueFromTexturePos, getValueFromTensorPos, + getValueFromTensorPosPacked, moveTexture2PosToReal, getPixelsFromTexturePos, getRangeSumFromArrayIndex, diff --git a/web/src/feed/ImageFeed.es6 b/web/src/feed/ImageFeed.es6 index 59c2bd66ab4b6dc060afc00afaa6d3122db1b500..fcd1820f41d54c316f4b7cc7d666062bb1357215 100644 --- a/web/src/feed/ImageFeed.es6 +++ b/web/src/feed/ImageFeed.es6 @@ -30,7 +30,10 @@ export default class imageFeed { ...inputs.params }; let output = []; - + if (!this.result) { + const [b, c, h, w] = params.targetShape; + this.result = new Float32Array(h * w * 3); + } output = this.fromPixels(input, params); return output; }; @@ -74,21 +77,27 @@ export default class imageFeed { */ allReshapeToRGB(imageData, opt, scaleSize) { const {sw, sh} = scaleSize; - const {width, height} = opt; + const [b, c, h, w] = opt.targetShape; let data = imageData.data; let mean = opt.mean; let dataLength = data.length; - let result = new Float32Array(dataLength * 3 / 4); - let offsetR = 0; - let offsetG = dataLength / 4; - let offsetB = dataLength / 2; - for (let i = 0; i < data.length; i += 4) { - result[offsetR++] = (data[i] - mean[0]) / 256; - result[offsetG++] = (data[i + 1] - mean[1]) / 256; - result[offsetB++] = (data[i + 2] - mean[2]) / 256; - // result.push((data[i] - mean[0]) / 256); // red - // result.push((data[i + 1] - mean[1]) / 256); // green - // result.push((data[i + 2] - mean[2]) / 256); // blue + // let result = new Float32Array(dataLength * 3); + let result = this.result; + // let offsetR = 0; + // let offsetG = dataLength / 4; + // let offsetB = dataLength / 2; + let offset = 0; + let size = h * w; + // h w c + for (let i = 0; i < h; ++i) { + let iw = i * w; + for (let j = 0; j < w; ++j) { + let iwj = iw + j; + for (let k = 0; k < c; ++k) { + let a = iwj * 4 + k; + result[offset++] = (data[a] - mean[k]) / 256; + } + } } return result; }; @@ -156,8 +165,10 @@ export default class imageFeed { else { this.fromPixels2DContext.drawImage( image, 0, 0, sw, sh); + // currentPic = this.fromPixels2DContext.canvas.toDataURL(); } - document.getElementById('p-c').appendChild(this.fromPixels2DContext.canvas);// test only, demele me + // window.currentPic = this.fromPixels2DContext.canvas;// test only, demele me + // document.getElementById('p-c').appendChild(this.fromPixels2DContext.canvas);// test only, demele me return {sw: targetWidth, sh: targetHeight}; } diff --git a/web/src/feed/dataFeed.es6 b/web/src/feed/dataFeed.es6 new file mode 100644 index 0000000000000000000000000000000000000000..c582d9c85374244ee80de6c0554c391447ba9f70 --- /dev/null +++ b/web/src/feed/dataFeed.es6 @@ -0,0 +1,42 @@ +/** + * @file 直接数据输入 + * @author hantianjiao@baidu.com + */ + +export default class dataFeed { + toFloat32Array(data) { + for (let i = 0; i < data.length; i++) { + this.f32Arr[i] = data[i]; + } + } + + getLengthFromShape(shape) { + return shape.reduce((a, b) => a * b); + } + + loadData() { + return fetch(this.dataPath).then(res => res.json()); + } + + getOutput() { + return this.loadData().then(data => { + this.toFloat32Array(data); + return [{ + data: this.f32Arr, + shape: this.shape, + name: 'x' + }]; + }); + } + + async process(input) { + this.len = this.getLengthFromShape(input.shape); + if (!this.f32Arr || this.len > this.f32Arr.length) { + this.f32Arr = new Float32Array(this.len); + } + this.shape = input.shape; + this.dataPath = input.input; + let output = await this.getOutput(); + return output; + } +} \ No newline at end of file diff --git a/web/src/feed/io.es6 b/web/src/feed/io.es6 index 032f192687e58e0fe67e9ef78e606fec71d8d77c..644f66dfb6292f4dcf957226e97bc0c259fa82a8 100644 --- a/web/src/feed/io.es6 +++ b/web/src/feed/io.es6 @@ -3,6 +3,7 @@ * @file io,loader相关输入输出 * @author wangqun@baidu.com */ + export default class io { constructor() { this.fromPixels2DContext = document.createElement('canvas').getContext('2d'); diff --git a/web/src/gpu/gpu.es6 b/web/src/gpu/gpu.es6 index f2c1c4c5ea912b0d99d8f0bea2d7c600d213ed5c..e59b7aa1142e800478e1d179527ee2daf2b31292 100644 --- a/web/src/gpu/gpu.es6 +++ b/web/src/gpu/gpu.es6 @@ -1,33 +1,83 @@ /* eslint-disable */ import VSHADER from '../shader/v_shader'; +import VSHADER2 from '../shader/v_shader2'; /** * @file gpu运算 * @author yangmingming */ +const CONF = { + alpha: false, + antialias: false, + premultipliedAlpha: false, + preserveDrawingBuffer: false, + depth: false, + stencil: false, + failIfMajorPerformanceCaveat: true +}; +const MAX_WAIT = 100; export default class gpu { constructor(opts = {}) { + // 版本, 默认webgl version 2.0 + this.version = 2; this.opts = opts; opts.width_raw_canvas = Number(opts.width_raw_canvas) || 512; opts.height_raw_canvas = Number(opts.height_raw_canvas) || 512; - let canvas = opts.el ? opts.el : document.createElement('canvas'); - canvas.width = opts.width_raw_canvas; - canvas.height = opts.height_raw_canvas; - this.gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); - this.gl.viewport(0, 0, canvas.width, canvas.height); - // Attempt to activate the extension, returns null if unavailable - this.textureFloat = this.gl.getExtension('OES_texture_float'); - // this.setOutProps(); + const canvas = opts.el ? opts.el : document.createElement('canvas'); + canvas.addEventListener('webglcontextlost', evt => { + evt.preventDefault(); + console.log('webgl context is lost~'); + }, false); + let gl = canvas.getContext('webgl2', CONF); + if (!!gl) { + // 开启float32 + this.version = 2; + this.textureFloat = gl.getExtension('EXT_color_buffer_float'); + this.internalFormat = gl.R32F; + this.textureFormat = gl.RED; + this.downloadInternalFormat = gl.RGBA32F; + } else { + gl = canvas.getContext('webgl', CONF) || canvas.getContext('experimental-webgl', CONF); + this.version = 1; + this.internalFormat = gl.RGBA; + this.textureFormat = gl.RGBA; + this.downloadInternalFormat = gl.RGBA; + if (!gl) { + this.version = 0; + alert('当前环境创建webgl context失败'); + } else { + // 开启扩展 + this.textureFloat = gl.getExtension('OES_texture_float'); + console.log('float extension is started or not? ' + !!this.textureFloat); + } + } + // 关闭相关功能 + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.STENCIL_TEST); + gl.disable(gl.BLEND); + gl.disable(gl.DITHER); + gl.disable(gl.POLYGON_OFFSET_FILL); + gl.disable(gl.SAMPLE_COVERAGE); + gl.enable(gl.SCISSOR_TEST); + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + this.gl = gl; this.initCache(); - console.log('float extension is started or not? ' + !!this.textureFloat); - console.log('WebGl版本是 ' + this.gl.getParameter(this.gl.SHADING_LANGUAGE_VERSION)); + // 同步查看次数 + this.waits = 0; + + console.log('WebGl版本是 ' + this.version); + console.log('MAX_TEXTURE_SIZE is ' + gl.getParameter(gl.MAX_TEXTURE_SIZE)); + console.log('MAX_TEXTURE_IMAGE_UNITS is ' + gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)); + } + + getWebglVersion() { + return this.version; } initCache() { // 运行次数 this.times = 0; const gl = this.gl; - // 缓存每个op的texture - // this.textures = []; // 顶点数据 let vertices = new Float32Array([ -1.0, 1.0, 0.0, 1.0, @@ -40,7 +90,7 @@ export default class gpu { // shader this.vertexShader = null; // 生成vertextShader - this.initShader(VSHADER); + this.initShader(this.version === 2 ? VSHADER2 : VSHADER); this.fragmentShader = null; // 上一个texture this.prevTexture = null; @@ -49,27 +99,18 @@ export default class gpu { // 帧缓存 this.frameBuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); - // 计算texture cache, 最多3个 - this.cacheTextures = [gl.createTexture(), gl.createTexture(), gl.createTexture()]; + // 计算texture cache + this.cacheTextures = {}; + this.uniformLocations = {}; // texture buffer - this.textureBuffer = [gl.createTexture(), gl.createTexture()]; - // program - this.programs = [gl.createProgram(), gl.createProgram()]; - this.program = this.programs[0]; - this.textureBufferIndex = 0; - for (let i = 0; i < 2; i++) { - gl.bindTexture(gl.TEXTURE_2D, this.textureBuffer[i]); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindTexture(gl.TEXTURE_2D, null); - } + this.outTextures = []; + // pbo + this.pbo = gl.createBuffer(); } - runVertexShader() { + runVertexShader(program) { const gl = this.gl; - let aPosition = gl.getAttribLocation(this.program, 'position'); + let aPosition = gl.getAttribLocation(program, 'position'); // Turn on the position attribute gl.enableVertexAttribArray(aPosition); // Bind the position buffer. @@ -82,6 +123,7 @@ export default class gpu { this.height_shape_out = opts.height_shape || 1; this.width_texture_out = opts.width_texture || 1; this.height_texture_out = opts.height_texture || 1; + this.channel = opts.channel || 0; this.total_shape = opts.total_shape || 0; } @@ -89,27 +131,63 @@ export default class gpu { return (this.textureFloat !== null); } - attachShader(fshader) { + createProgram(fshader, out) { const gl = this.gl; - let index = this.textureBufferIndex % 2; - const program = this.programs[index]; + const program = gl.createProgram(); + gl.attachShader(program, this.vertexShader); + gl.attachShader(program, fshader); + gl.linkProgram(program); + // 生成output的texture缓存 + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + gl.texImage2D(gl.TEXTURE_2D, // Target, matches bind above. + 0, // Level of detail. + this.downloadInternalFormat, // Internal format. + out.width_texture, + out.height_texture, + 0, // Always 0 in OpenGL ES. + gl.RGBA, // Format for each pixel. + gl.FLOAT, // Data type for each chanel. + null); + gl.bindTexture(gl.TEXTURE_2D, null); + this.outTextures.push(texture); + return program; + } + + setProgram(program, isRendered) { + const gl = this.gl; + gl.useProgram(program); this.program = program; - if (this.times < 2) { - gl.attachShader(program, this.vertexShader); + if (!isRendered) { + this.runVertexShader(program); } + } + + attachShader(fshader) { + const gl = this.gl; + // let index = this.textureBufferIndex % 2; + // const program = this.programs[index]; + // this.program = program; + const program = this.program; + // if (this.times < 2) { + // gl.attachShader(program, this.vertexShader); + // } this.textureBufferIndex = (this.textureBufferIndex + 1) >= 2 ? 0 : 1; + if (!!this.fragmentShader) { + gl.detachShader(program, this.fragmentShader); + } this.gl.attachShader(program, fshader); + this.fragmentShader = fshader; gl.linkProgram(program); - gl.useProgram(program); - if (this.times++ < 2) { + if (this.times++ === 0) { + gl.useProgram(program); this.runVertexShader(); } - if (!!this.fragmentShader) { - const cache = this.programs[(index + 1) % 2]; - gl.detachShader(cache, this.fragmentShader); - // gl.deleteShader(this.fragmentShader); - } - this.fragmentShader = fshader; } create(vshaderCode, fshaderCode) { @@ -185,10 +263,11 @@ export default class gpu { * @param {WebGLTexture} texture 材质 * @returns {WebGLFramebuffer} The framebuffer */ - attachFrameBuffer(opts = {}) { + attachFrameBuffer(iLayer) { this.prevTexture = this.currentTexture; - this.currentTexture = this.textureBuffer[this.textureBufferIndex % 2]; + // this.currentTexture = this.textureBuffer[this.textureBufferIndex % 2]; // this.textureBufferIndex = (this.textureBufferIndex + 1) >= 2 ? 0 : 1; + this.currentTexture = this.outTextures[iLayer]; const gl = this.gl; gl.framebufferTexture2D(gl.FRAMEBUFFER, // The target is always a FRAMEBUFFER. gl.COLOR_ATTACHMENT0, // We are providing the color buffer. @@ -199,8 +278,14 @@ export default class gpu { gl.viewport( 0, 0, - opts.width_texture_out || this.width_texture_out, - opts.height_texture_out || this.height_texture_out + this.width_texture_out, + this.height_texture_out + ); + gl.scissor( + 0, + 0, + this.width_texture_out, + this.height_texture_out ); return this.frameBuffer; } @@ -243,66 +328,53 @@ export default class gpu { return {isComplete: value, message: message}; } - /** - * 更新材质 - * @param {WebGLTexture} texture 材质对象 - * @param {number} type 材质类型. FLOAT, UNSIGNED_BYTE, etc. - * @param {Float32Array[]} data 材质数据 - */ - refreshTexture(texture, type, data) { - const gl = this.gl; - // Bind the texture so the following methods effect it. - gl.bindTexture(gl.TEXTURE_2D, texture); - - // Replace the texture data - gl.texSubImage2D(gl.TEXTURE_2D, // Target, matches bind above. - 0, // Level of detail. - 0, // xOffset - 0, // yOffset - this.opts.width_raw_canvas, // Width - normalized to s. - this.opts.height_raw_canvas, // Height - normalized to t. - gl.RGBA, // Format for each pixel. - type, // Data type for each chanel. - data); // Image data in the described format. - - // Unbind the texture. - gl.bindTexture(gl.TEXTURE_2D, null); - - return texture; - } - /** * 初始化材质 * @param {int} index 材质索引 * @param {string} tSampler 材质名称 * @param {Object} bufferData 数据 + * @param {boolean} isRendered 是否已运行过 */ - initTexture(index, item) { + initTexture(index, item, iLayer, isRendered) { const gl = this.gl; let texture; if (!item.data) { texture = this.prevTexture; } else { // texture = gl.createTexture(); - texture = this.cacheTextures[index]; - // this.textures.push(texture); + if (isRendered && (iLayer > 0 || (iLayer === 0 && item.tensor !== 'origin'))) { + const tData = this.cacheTextures['' + iLayer]; + texture = tData[item.variable + '_' + item.tensor]; + } else { + texture = gl.createTexture(); + if (index === 0) { + this.cacheTextures['' + iLayer] = this.cacheTextures['' + iLayer] || {}; + } + this.cacheTextures['' + iLayer][item.variable + '_' + item.tensor] = texture; + } } gl.activeTexture(gl[`TEXTURE${index}`]); gl.bindTexture(gl.TEXTURE_2D, texture); - if (item.data) { + if (item.data && (!isRendered || (isRendered && iLayer === 0 && item.tensor === 'origin'))) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, item.width_texture || this.opts.width_raw_canvas, - item.height_texture || this.opts.height_raw_canvas, 0, - gl.RGBA, gl.FLOAT, item.data, 0); + gl.texImage2D(gl.TEXTURE_2D, 0, this.internalFormat, item.width_texture, + item.height_texture, 0, + this.textureFormat, gl.FLOAT, item.data, 0); } } - getUniformLoc(name) { + getUniformLoc(name, ilayer, isRendered) { + if (isRendered) { + return this.uniformLocations['' + ilayer][name]; + } let loc = this.gl.getUniformLocation(this.program, name); if (loc === null) throw `getUniformLoc ${name} err`; + // 缓存 + this.uniformLocations['' + ilayer] = this.uniformLocations['' + ilayer] || {}; + this.uniformLocations['' + ilayer][name] = loc; return loc; } @@ -330,16 +402,16 @@ export default class gpu { return texture; } - render(data = []) { + render(data = [], iLayer = 0, isRendered = false) { const gl = this.gl; let textureIndex = 0; - // 输入数据 data.forEach(item => { if (item.type === 'texture') { - this.initTexture(textureIndex, item); - gl.uniform1i(this.getUniformLoc(item.variable + '_' + item.tensor), textureIndex++); - } else if (item.type === 'uniform') { - gl[item.setter](this.getUniformLoc(item.variable + '_' + item.tensor), item.data); + this.initTexture(textureIndex, item, iLayer, isRendered); + gl.uniform1i(this.getUniformLoc(item.variable + '_' + item.tensor, iLayer, isRendered), textureIndex++); + } + else if (item.type === 'uniform') { + gl[item.setter](this.getUniformLoc(item.variable + '_' + item.tensor, iLayer, isRendered), item.data); } }); // gl.clearColor(.0, .0, .0, 1); @@ -347,25 +419,127 @@ export default class gpu { gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } + createPBO() { + const gl2 = this.gl; + const buffer = this.pbo; + gl2.bindBuffer(gl2.PIXEL_PACK_BUFFER, buffer); + const sizeBytes = 4 * 4 * this.width_texture_out * this.height_texture_out; + gl2.bufferData(gl2.PIXEL_PACK_BUFFER, sizeBytes, gl2.STREAM_READ); + gl2.readPixels(0, 0, this.width_texture_out, this.height_texture_out, gl2.RGBA, gl2.FLOAT, 0); + gl2.bindBuffer(gl2.PIXEL_PACK_BUFFER, null); + return buffer; + } + + downloadFoat32TensorFromBuffer(buffer) { + const gl2 = this.gl; + const size = 4 * this.width_texture_out * this.height_texture_out; + const pixels = new Float32Array(size); + gl2.bindBuffer(gl2.PIXEL_PACK_BUFFER, buffer); + gl2.getBufferSubData(gl2.PIXEL_PACK_BUFFER, 0, pixels); + gl2.bindBuffer(gl2.PIXEL_PACK_BUFFER, null); + log.start('后处理-readloop'); + // let result = []; + // let offset = 0; + // for (let h = 0; h < this.height_texture_out; h++) { + // // 纪录第1和2行数据 + // let temp1 = []; + // let temp2 = []; + // for (let w = 0; w < this.width_texture_out; w++) { + // temp1.push(pixels[offset]); + // temp1.push(pixels[offset + 1]); + // temp2.push(pixels[offset + 2]); + // temp2.push(pixels[offset + 3]); + // offset += 4; + // } + // result = result.concat(temp1); + // result = result.concat(temp2); + // } + let result = []; + for (let i = 0; i < this.width_texture_out * this.height_texture_out; i++) { + result.push(pixels[4 * i]); + } + // const result = Array.prototype.slice.call(pixels); + // console.dir(['result', result]); + log.end('后处理-readloop'); + return result; + } + + getWebglError(status) { + const gl2 = this.gl; + switch (status) { + case gl2.NO_ERROR: + return 'NO_ERROR'; + case gl2.INVALID_ENUM: + return 'INVALID_ENUM'; + case gl2.INVALID_VALUE: + return 'INVALID_VALUE'; + case gl2.INVALID_OPERATION: + return 'INVALID_OPERATION'; + case gl2.INVALID_FRAMEBUFFER_OPERATION: + return 'INVALID_FRAMEBUFFER_OPERATION'; + case gl2.OUT_OF_MEMORY: + return 'OUT_OF_MEMORY'; + case gl2.CONTEXT_LOST_WEBGL: + return 'CONTEXT_LOST_WEBGL'; + default: + return `Unknown error code ${status}`; + } + } + + createAndWaitForFence() { + const gl2 = this.gl; + const isFenceEnabled = (gl2.fenceSync !== null); + let isFencePassed = () => true; + if (isFenceEnabled) { + const sync = gl2.fenceSync(gl2.SYNC_GPU_COMMANDS_COMPLETE, 0); + gl2.flush(); + isFencePassed = () => { + const status = gl2.clientWaitSync(sync, 0, 0); + return status === gl2.ALREADY_SIGNALED || + status === gl2.CONDITION_SATISFIED; + }; + } + return new Promise(resolve => { + this.pollItem(isFencePassed, resolve); + }); + } + + pollItem(isDone, resolveFn) { + const fn = () => { + if (isDone()) { + resolveFn(); + return; + } + setTimeout(fn, 1); + }; + fn(); + } + compute() { let gl = this.gl; + log.start('后处理-readinside'); + const tt = +Date.now(); let pixels = new Float32Array(this.width_texture_out * this.height_texture_out * 4); // gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + const tt2 = +Date.now(); gl.readPixels(0, 0, this.width_texture_out, this.height_texture_out, gl.RGBA, gl.FLOAT, pixels, 0); - + // console.log('本次读取数据时间是' + (+Date.now() - tt2)+ ',' + (tt2 - tt)); + log.end('后处理-readinside'); + log.start('后处理-readloop'); let result = []; for (let i = 0; i < this.width_texture_out * this.height_texture_out; i++) { result.push(pixels[4 * i]); } + log.end('后处理-readloop'); return result; } dispose() { const gl = this.gl; - this.cacheTextures.forEach(texture => { - gl.deleteTexture(texture); - }); - this.cacheTextures = []; + // this.cacheTextures.forEach(texture => { + // gl.deleteTexture(texture); + // }); + this.cacheTextures = {}; this.programs.forEach(program => { gl.detachShader(program, this.vertexShader); gl.deleteShader(this.vertexShader); diff --git a/web/src/ops/dummy.js b/web/src/ops/dummy.js deleted file mode 100644 index 08686bd2ab158167e9811b94b1b862ca6996da41..0000000000000000000000000000000000000000 --- a/web/src/ops/dummy.js +++ /dev/null @@ -1,12 +0,0 @@ - -module.exports.create = function (args, scope, gl) { - const output = scope[args.outputs.Out[0]] - return { - inferShape() { - output.dim = output.dim.forEach(d => d === -1 ? 1 : d) - }, - compute() { - console.log(output) - } - } -} diff --git a/web/src/ops/feed.js b/web/src/ops/feed.js deleted file mode 100644 index 7e132c540367eadf35e05372d6ada8da703ea636..0000000000000000000000000000000000000000 --- a/web/src/ops/feed.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports.create = function (args, scope, gl) { - const output = scope[args.outputs.Out[0]] - const input = scope[args.inputs.X[0]] - return { - inferShape() { - output.dim = output.dim.map(d => d === -1 ? 1 : d) - }, - compute() { - console.log(input) - console.log(output) - } - } -} diff --git a/web/src/ops/fetch.js b/web/src/ops/fetch.js deleted file mode 100644 index ab7d4c308615db5e5c28e769af6b1ae02a2af79a..0000000000000000000000000000000000000000 --- a/web/src/ops/fetch.js +++ /dev/null @@ -1,12 +0,0 @@ - -module.exports.create = function (args, scope, gl) { - // const output = scope[args.outputs.Out[0]] - return { - inferShape() { - // output.dim = output.dim.forEach(d => d === -1 ? 1 : d) - }, - compute() { - console.log(`${args.type} is going to be implemented`) - } - } -} diff --git a/web/src/package.json b/web/src/package.json deleted file mode 100644 index 49f0c29c77e624142e9223ade6790408f39d6986..0000000000000000000000000000000000000000 --- a/web/src/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "paddel-web", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC" -} diff --git a/web/src/runtime/runtime.es6 b/web/src/runtime/runtime.es6 index f95a6a29fde9a30c10dd9e5758681a0e9e8731d2..9969fd8f0834624e94fbf7d620fa7b0144e679af 100644 --- a/web/src/runtime/runtime.es6 +++ b/web/src/runtime/runtime.es6 @@ -1,5 +1,6 @@ /* eslint-disable */ import Gpu from '../gpu/gpu'; +import getMaxUniforms from '../test/getMaxUniforms'; /** * @file gpu运行时 * @author yangmingming @@ -20,10 +21,15 @@ export default { } }, - run(opName, opData) { - let time = +Date.now(); - let start = time; - let timeObj = {}; + getWebglVersion() { + return this.gpu.getWebglVersion(); + }, + + run(opName, opData, isRendered) { + // console.dir(['fscode', opData.fsCode]); + // let time = +Date.now(); + // let start = time; + // let timeObj = {}; if (!opData.isPass) { console.log('跳过当前op:' + opName); return this; @@ -32,19 +38,20 @@ export default { const gpu = this.gpu; gpu.setOutProps(opData.tensor['out']); // 生成帧缓存材质 - gpu.makeTexure(WebGLRenderingContext.FLOAT, null); - let end = +Date.now(); + gpu.attachFrameBuffer(opData.iLayer); + // let end = +Date.now(); let bufferStatus = gpu.frameBufferIsComplete(); if (bufferStatus.isComplete) { - start = +Date.now(); - timeObj['buferstatus-time'] = start - end; - gpu.attachShader(opData.fshader); - end = +Date.now(); - timeObj['createshader-time'] = end - start; - timeObj['jsTime'] = end - time; - statistic.push(timeObj); + // start = +Date.now(); + // timeObj['buferstatus-time'] = start - end; + // gpu.attachShader(opData.fshader); + gpu.setProgram(opData.program, isRendered); + // end = +Date.now(); + // timeObj['createshader-time'] = end - start; + // timeObj['jsTime'] = end - time; + // statistic.push(timeObj); // 开始计算 - this.gpu.render(opData.renderData); + this.gpu.render(opData.renderData, opData.iLayer, isRendered); return this; } else { return bufferStatus.message; @@ -54,12 +61,34 @@ export default { /** * 读取op计算结果, 并返回数据 */ - read() { - return this.gpu.compute(); + read2() { + let bufferStatus = this.gpu.frameBufferIsComplete(); + if (bufferStatus.isComplete) { + + return this.gpu.compute(); + } + return null; + }, + + async read() { + const pbo = this.gpu.createPBO(); + await this.gpu.createAndWaitForFence(); + log.end('运行耗时'); + log.start('后处理'); + // 其实这里应该有个fetch的执行调用或者fetch的输出 + log.start('后处理-读取数据'); + // 开始读数据 + return this.gpu.downloadFoat32TensorFromBuffer(pbo); }, - createFragmentShader(fsCode) { - return this.gpu.initShader(fsCode, 'fragment'); + createProgram(fsCode, outTensor) { + const fshader = this.gpu.initShader(fsCode, 'fragment'); + const program = this.gpu.createProgram(fshader, outTensor); + // test uniforms的个数 + // const maxUniforms = getMaxUniforms(this.gpu.gl, program); + // alert(maxUniforms.maxFragmentShader); + // console.table(maxUniforms.uniforms); + return program; }, // 释放资源 diff --git a/web/src/shader/atom/common_params.es6 b/web/src/shader/atom/common_params.es6 index a99019f223b47827dcbfd0009bb36c27c8c05c10..f4462ddced602ac708cda7b46a97c53b21b73b91 100644 --- a/web/src/shader/atom/common_params.es6 +++ b/web/src/shader/atom/common_params.es6 @@ -4,25 +4,6 @@ * @author yangmingming */ export default ` - // varying变量 - // 顶点shader透传的材质坐标 - varying vec2 vCoord; - // 扩展shader的ivec类型 - // struct ivec5 { - // int x; - // int y; - // int z; - // int w; - // int u; - // }; - // struct ivec6 { - // int x; - // int y; - // int z; - // int w; - // int u; - // int v; - // }; // dynamic的input数据 const float multi_value = float(MULTI_VALUE); const float bias_value = float(BIAS_VALUE); @@ -33,4 +14,5 @@ export default ` const int width_texture_out = WIDTH_TEXTURE_OUT; const int height_texture_out = HEIGHT_TEXTURE_OUT; const int channel_out = CHANNEL_OUT; + const int offset_y_out = OFFSET_Y_OUT; `; diff --git a/web/src/shader/atom/getArrayIndexFromTensorPos.es6 b/web/src/shader/atom/getArrayIndexFromTensorPos.es6 index b0d17e4f3b9f23655eb4751a61c5277300884813..3b28fb839d500af1d5314768dc304c0acdd7104f 100644 --- a/web/src/shader/atom/getArrayIndexFromTensorPos.es6 +++ b/web/src/shader/atom/getArrayIndexFromTensorPos.es6 @@ -4,11 +4,6 @@ * @author yangmingming */ export default ` -// TENSOR_TYPE, tensor坐标的类型,ivec4 -// TENSOR_NAME, tensor name - -// 获取tensor坐标对应数组中的索引 -// uniform int numbers_shape_TENSOR_NAME[LENGTH_SHAPE_TENSOR_NAME]; int getArrayIndexFromTensorPos_TENSOR_NAME(TENSOR_TYPE tensorPos) { int index = 0; diff --git a/web/src/shader/atom/getArrayIndexFromTexturePos.es6 b/web/src/shader/atom/getArrayIndexFromTexturePos.es6 index 1cff8a703e44296127864de402ffe6b047ac4b0a..c48576d5e25df725097e6862b163feff0385a1cc 100644 --- a/web/src/shader/atom/getArrayIndexFromTexturePos.es6 +++ b/web/src/shader/atom/getArrayIndexFromTexturePos.es6 @@ -3,11 +3,7 @@ * @file 公共方法 * @author yangmingming */ -// TEXTURE_NAME, texture name -// WIDTH_TEXTURE_NAME_VALUE, texture的宽度 -// 获取材质元素在数组中的索引 -// const int width_TEXTURE_NAME = WIDTH_TEXTURE_NAME_VALUE; export default ` int getArrayIndexFromTexturePos_TEXTURE_NAME(vec3 pos) { int x = int(floor(pos.x)); diff --git a/web/src/shader/atom/getPixelsFromTexturePos.es6 b/web/src/shader/atom/getPixelsFromTexturePos.es6 index 052bc93759b6eaea78dd3563455d998e0b3d3a8b..683b85e9be479bc1d8c20085126797416e2dbf12 100644 --- a/web/src/shader/atom/getPixelsFromTexturePos.es6 +++ b/web/src/shader/atom/getPixelsFromTexturePos.es6 @@ -3,9 +3,7 @@ * @file 公共方法 * @author yangmingming */ -// TEXTURE_NAME, tensor name // 获取材质中的像素 -// uniform sampler2D TEXTURE_NAME; export default ` -#define getPixelsFromTexturePos_TEXTURE_NAME(pos) texture2D(TEXTURE_NAME, pos) +#define getPixelsFromTexturePos_TEXTURE_NAME(pos) TEXTURE2D(TEXTURE_NAME, pos) `; diff --git a/web/src/shader/atom/getValueFromTensorPos.es6 b/web/src/shader/atom/getValueFromTensorPos.es6 index 5a5239705339583eaa94a54f83c8e16972657dbc..b07d07fa4d1e677c1c54a1d5a6653b1814e20595 100644 --- a/web/src/shader/atom/getValueFromTensorPos.es6 +++ b/web/src/shader/atom/getValueFromTensorPos.es6 @@ -5,15 +5,28 @@ */ export default ` float getValueFromTensorPos_TENSOR_NAME(int r, int g, int b, int a) { - vec4 pixels = texture2D(texture_TENSOR_NAME, vec2((float(a * channel_TENSOR_NAME + g) + 0.5) / float(width_texture_TENSOR_NAME), (float(r * height_shape_TENSOR_NAME + b) + 0.5) / float(height_texture_TENSOR_NAME))); + vec4 pixels = TEXTURE2D(texture_TENSOR_NAME, + vec2( + (float(a * channel_TENSOR_NAME + g) + 0.5) / float(width_texture_TENSOR_NAME), + (float(r * height_shape_TENSOR_NAME + b) + 0.5) / float(height_texture_TENSOR_NAME) + ) + ); return pixels.r; } -float getValueFromTensorPos_TENSOR_NAME(ivec4 pos) { - float offset = 0.5; - float width = float(pos.a * channel_TENSOR_NAME + pos.g) + offset; - float height = float(pos.r * height_shape_TENSOR_NAME + pos.b) + offset; - vec4 pixels = texture2D(texture_TENSOR_NAME, vec2(width / float(width_texture_TENSOR_NAME), height / float(height_texture_TENSOR_NAME))); +float getValueFromTensorPosLimit_TENSOR_NAME(int r, int g, int b, int a) { + float halfW = ceil(float(width_shape_TENSOR_NAME) / 2.0); + int x = int(mod(float(a), halfW)); + int offsetY = 0; + if (a > x) { + offsetY = height_shape_TENSOR_NAME; + } + vec4 pixels = TEXTURE2D(texture_TENSOR_NAME, + vec2( + (float(x * channel_TENSOR_NAME + g) + 0.5) / float(width_texture_TENSOR_NAME), + (float(r * 2 * height_shape_TENSOR_NAME + b + offsetY) + 0.5) / float(height_texture_TENSOR_NAME) + ) + ); return pixels.r; } `; diff --git a/web/src/shader/atom/getValueFromTensorPosPacked.es6 b/web/src/shader/atom/getValueFromTensorPosPacked.es6 new file mode 100644 index 0000000000000000000000000000000000000000..5b64a7aaed32f50c4a5831e4971788bf531a0af7 --- /dev/null +++ b/web/src/shader/atom/getValueFromTensorPosPacked.es6 @@ -0,0 +1,26 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +float getValueFromTensorPosPacked_TENSOR_NAME(int r, int g, int b, int a) { + int y = b / 2; + int yOffset = int(mod(float(b), 2.0)); + int x = a / 2; + int xOffset = int(mod(float(a), 2.0)); + int height = height_shape_TENSOR_NAME + offset_y_TENSOR_NAME; + vec4 pixels = TEXTURE2D(texture_TENSOR_NAME, vec2((float(x) + 0.5) / float(width_texture_TENSOR_NAME), (float(g * height / 2 + y) + 0.5) / float(height_texture_TENSOR_NAME))); + int index = 0; + if (xOffset == 0 && yOffset == 0) { + return pixels[0]; + } + else if (xOffset == 1 && yOffset == 0) { + return pixels[1]; + } + else if (xOffset == 0 && yOffset == 1) { + return pixels[2]; + } + return pixels[3]; +} +`; diff --git a/web/src/shader/atom/getValueFromTexturePos.es6 b/web/src/shader/atom/getValueFromTexturePos.es6 index f68f3f4a385326cf1574b9cbfae6512f30c8fd2d..f810a04bd8f4358c5f8c78aa830d8db376692536 100644 --- a/web/src/shader/atom/getValueFromTexturePos.es6 +++ b/web/src/shader/atom/getValueFromTexturePos.es6 @@ -8,7 +8,7 @@ // uniform sampler2D TEXTURE_NAME; export default ` float getValueFromTexturePos_TEXTURE_NAME(vec3 pos) { - vec4 pixels = texture2D(TEXTURE_NAME, pos.xy); + vec4 pixels = TEXTURE2D(TEXTURE_NAME, pos.xy); int d = int(pos.z); if (d == 0) { return pixels.r; diff --git a/web/src/shader/atom/prefix.es6 b/web/src/shader/atom/prefix.es6 index 54a1742088c915dc27f66459bac15f0c8426e1a9..0d028a6bba963b26cb45e94b1dccf8082c3f4a99 100644 --- a/web/src/shader/atom/prefix.es6 +++ b/web/src/shader/atom/prefix.es6 @@ -11,4 +11,8 @@ export default ` precision mediump float; precision mediump int; #endif + + void setOutput(float result) { + gl_FragColor.r = result; + } `; diff --git a/web/src/shader/atom/prefix2.es6 b/web/src/shader/atom/prefix2.es6 new file mode 100644 index 0000000000000000000000000000000000000000..e17ad09fa52e54f5c8f565d9efb8285845d4bf31 --- /dev/null +++ b/web/src/shader/atom/prefix2.es6 @@ -0,0 +1,22 @@ +/* eslint-disable */ +/** + * @file 预设条件, webgl 2.0版本 + * @author yangmingming + */ +export default `#version 300 es + +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + precision highp int; +#else + precision mediump float; + precision mediump int; +#endif + +// 顶点shader透传的材质坐标 + in vec2 vCoord; + out vec4 outColor; + void setOutput(float result) { + outColor.r = result; + } +`; diff --git a/web/src/shader/atom/suffix.es6 b/web/src/shader/atom/suffix.es6 index 97eb819ffd0b06262886782bf11ad7588558996d..840ef29de4e2680f7f696d65cb32e41da3b130c2 100644 --- a/web/src/shader/atom/suffix.es6 +++ b/web/src/shader/atom/suffix.es6 @@ -14,4 +14,29 @@ ivec4 getOutputTensorPos() { int b = int(outCoord.y / float(height_shape_out)); return ivec4(b, c, y, x); } + +ivec4 getOutputTensorPosLimit() { + // 获取原始长度 + vec2 outCoord = vCoord.xy * _2d_shape_texture_out; + float offsetY = floor(outCoord.y / float(height_shape_out)); + int x = int(outCoord.x / float(channel_out)); + if (mod(offsetY, 2.0) > 0.0) { + x += int(ceil(float(width_shape_out) / 2.0)); + } + int y = int(mod(outCoord.y, float(height_shape_out))); + int c = int(mod(outCoord.x, float(channel_out))); + int b = int(outCoord.y / float(2 * height_shape_out)); + return ivec4(b, c, y, x); +} + +ivec4 getOutputPackedTensorPos() { + // 获取原始长度 + vec2 outCoord = vCoord.xy * _2d_shape_texture_out; + int height = height_shape_out + offset_y_out; + int x = int(outCoord.x); + int c = int(outCoord.y / float(height / 2)); + int y = int(mod(outCoord.y, float(height / 2))); + int b = 0; + return ivec4(b, c, y, x); +} `; diff --git a/web/src/shader/batchnorm/conf.es6 b/web/src/shader/batchnorm/conf.es6 index 7cdfd95e6392ff5543e36df3bf9248c263ed6ef3..b64fd4a80b4ff49d413397dd7e9dc99bcf3c4f1f 100644 --- a/web/src/shader/batchnorm/conf.es6 +++ b/web/src/shader/batchnorm/conf.es6 @@ -32,6 +32,7 @@ export default { 'WIDTH_TEXTURE_OUT', 'HEIGHT_TEXTURE_OUT', 'CHANNEL_OUT', + 'OFFSET_Y_OUT', 'EPSILON', 'WIDTH_TEXTURE_SCALE', diff --git a/web/src/shader/batchnorm/main.es6 b/web/src/shader/batchnorm/main.es6 index ae702250ff075278d14e7c69efe57a926ec51d86..e964a02841111752b53460170a4d064d9c008e03 100644 --- a/web/src/shader/batchnorm/main.es6 +++ b/web/src/shader/batchnorm/main.es6 @@ -13,6 +13,6 @@ void main(void) { vec4 scale = getPixelsFromTexturePos_texture_scale(vec2((float(int(oPos.g)) + 0.5) / float(width_texture_scale), 0.0)); float x = (o - scale[3]) / sqrt(scale[2] + epsilon); float res = scale[0] * x + scale[1]; - gl_FragColor.r = res; + setOutput(res); } `; diff --git a/web/src/shader/conv2d/conf.es6 b/web/src/shader/conv2d/conf.es6 index c66c40a9d36ee7fdb0ad0539da041efb1e5b819c..bcd2e5313afee15deb97ced7f728c957469106a0 100644 --- a/web/src/shader/conv2d/conf.es6 +++ b/web/src/shader/conv2d/conf.es6 @@ -38,6 +38,7 @@ export default { 'WIDTH_TEXTURE_OUT', 'HEIGHT_TEXTURE_OUT', 'CHANNEL_OUT', + 'OFFSET_Y_OUT', 'STRIDE_HORIZONTAL', 'STRIDE_VERTICAL', diff --git a/web/src/shader/conv2d/main.es6 b/web/src/shader/conv2d/main.es6 index e6513899af5b8220442dbf831248ed1237d58ed6..ffa8e5fedce6e3f4a68677f2eac7e02146ec9a25 100644 --- a/web/src/shader/conv2d/main.es6 +++ b/web/src/shader/conv2d/main.es6 @@ -6,7 +6,7 @@ export default ` // start函数 void main(void) { - ivec4 oPos = getOutputTensorPos(); + ivec4 oPos = getOutputTensorPosLIMIT_OUT(); int x = oPos.a; int c = oPos.g; int y = oPos.b; @@ -35,14 +35,14 @@ export default ` } // channel计算 for (int j = 0; j < channel_filter; j++) { - float f = getValueFromTensorPos_filter(c, j, fy, fx); - float o = getValueFromTensorPos_origin(b, oTensorChannel + j, oy, ox); + float f = getValueFromTensorPosLIMIT_FILTER_filter(c, j, fy, fx); + float o = getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + j, oy, ox); res += f * o; } ox += dilation_h; } oy += dilation_v; } - gl_FragColor.r = res; + setOutput(res); } `; diff --git a/web/src/shader/conv2d_depthwise/conf.es6 b/web/src/shader/conv2d_depthwise/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..467320904b3c6d8c5d2fb8c2f6c5085a57340877 --- /dev/null +++ b/web/src/shader/conv2d_depthwise/conf.es6 @@ -0,0 +1,67 @@ +/* eslint-disable */ +/** + * @file conv2d的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'filter' + } + } + ], + conf: [ + 'LENGTH_SHAPE_FILTER', + 'WIDTH_SHAPE_FILTER', + 'HEIGHT_SHAPE_FILTER', + 'WIDTH_TEXTURE_FILTER', + 'HEIGHT_TEXTURE_FILTER', + 'CHANNEL_FILTER', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + 'OFFSET_Y_OUT', + + 'STRIDE_HORIZONTAL', + 'STRIDE_VERTICAL', + 'PAD_LEFT', + 'PAD_TOP', + 'DILATION_HORIZONTAL', + 'DILATION_VERTICAL', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'filter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/conv2d_depthwise/main.es6 b/web/src/shader/conv2d_depthwise/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..870607c6488df35a8fb4226fa844d8922d65d101 --- /dev/null +++ b/web/src/shader/conv2d_depthwise/main.es6 @@ -0,0 +1,42 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author yangmingming + */ +export default ` + // start函数 + void main(void) { + ivec4 oPos = getOutputTensorPosLIMIT_OUT(); + int x = oPos.a; + int c = oPos.g; + int y = oPos.b; + int b = oPos.r; + float res = 0.0; + + int top = y * stride_v - padTop; + int left = x * stride_h - padLeft; + for (int fy = 0; fy < height_shape_filter; fy++) { + int oy = top + fy * dilation_v; + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + continue; + } + for (int fx = 0; fx < width_shape_filter; fx++) { + int ox = left + fx * dilation_h; + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + continue; + } + // b默认是0 + float f = getValueFromTensorPosLIMIT_FILTER_filter(c, 0, fy, fx); + float o = getValueFromTensorPosLIMIT_ORIGIN_origin(b, c, oy, ox); + res += f * o; + } + } + setOutput(res); + } +`; diff --git a/web/src/shader/conv2d_depthwise/params.es6 b/web/src/shader/conv2d_depthwise/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..ae408cceebde5fa5e5c8eddf5e9108f955d4d2b0 --- /dev/null +++ b/web/src/shader/conv2d_depthwise/params.es6 @@ -0,0 +1,43 @@ +/* eslint-disable */ +/** + * @file 参数文件 + * @author yangmingming + */ +export default ` + // conv2d的input数据 + + // 常量 + // 卷积核 + const int length_shape_filter = LENGTH_SHAPE_FILTER; + const int width_shape_filter = WIDTH_SHAPE_FILTER; + const int height_shape_filter = HEIGHT_SHAPE_FILTER; + const int width_texture_filter = WIDTH_TEXTURE_FILTER; + const int height_texture_filter = HEIGHT_TEXTURE_FILTER; + const int channel_filter = CHANNEL_FILTER; + + // 输入数据 + const int width_shape_origin = WIDTH_SHAPE_ORIGIN; + const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; + const int length_shape_origin = LENGTH_SHAPE_ORIGIN; + const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; + const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; + const int channel_origin = CHANNEL_ORIGIN; + + // 计算相关 + // 拆分步长 + const int stride_h = STRIDES_X; + const int stride_v = STRIDES_Y; + // padding的数目 + const int padLeft = PADDINGS_X; + const int padTop = PADDINGS_Y; + // dilation膨胀系数 + const int dilation_h = DILATIONS_X; + const int dilation_v = DILATIONS_Y; + + // uniform变量 + // 卷积核 + uniform sampler2D texture_filter; + + // 输入数据 + uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/conv2d_elementwise_add/conf.es6 b/web/src/shader/conv2d_elementwise_add/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..7d3a58e2940c4cad6018d830d0858e1f4625c980 --- /dev/null +++ b/web/src/shader/conv2d_elementwise_add/conf.es6 @@ -0,0 +1,77 @@ +/* eslint-disable */ +/** + * @file conv2d-elementwise_add的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'filter' + } + } + ], + conf: [ + 'LENGTH_SHAPE_FILTER', + 'WIDTH_SHAPE_FILTER', + 'HEIGHT_SHAPE_FILTER', + 'WIDTH_TEXTURE_FILTER', + 'HEIGHT_TEXTURE_FILTER', + 'CHANNEL_FILTER', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + 'OFFSET_Y_OUT', + + 'WIDTH_SHAPE_COUNTER', + + 'STRIDE_HORIZONTAL', + 'STRIDE_VERTICAL', + 'PAD_LEFT', + 'PAD_TOP', + 'DILATION_HORIZONTAL', + 'DILATION_VERTICAL', + 'GROUPS', + 'AXIS', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'filter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'counter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/conv2d_elementwise_add/main.es6 b/web/src/shader/conv2d_elementwise_add/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..90607a350fc49d33ed6b5e887b45447ba352145c --- /dev/null +++ b/web/src/shader/conv2d_elementwise_add/main.es6 @@ -0,0 +1,53 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author yangmingming + */ +export default ` + // start函数 + void main(void) { + ivec4 oPos = getOutputTensorPosLIMIT_OUT(); + int x = oPos.a; + int c = oPos.g; + int y = oPos.b; + int b = oPos.r; + int addAxis = oPos[axis]; + float res = getValueFromCounter(addAxis); + + // 获取output的坐标 + int oTensorChannel = (c / (channel_out / groups)) * channel_filter; + int oy = y * stride_v - padTop; + for (int fy = 0; fy < height_shape_filter; fy++) { + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + oy += dilation_v; + continue; + } + int ox = x * stride_h - padLeft; + for (int fx = 0; fx < width_shape_filter; fx++) { + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + ox += dilation_h; + continue; + } + // channel计算 + for (int j = 0; j < channel_filter; j++) { + float f = getValueFromTensorPosLIMIT_FILTER_filter(c, j, fy, fx); + float o = getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + j, oy, ox); + res += f * o; + } + ox += dilation_h; + } + oy += dilation_v; + } + setOutput(ACTIVE_FUNCTION(res, multi_value, bias_value)); + // outColor.r = float(b); + // outColor.g = float(c); + // outColor.b = float(y); + // outColor.a = float(x); + } +`; diff --git a/web/src/shader/conv2d_elementwise_add/params.es6 b/web/src/shader/conv2d_elementwise_add/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..e4a4f6aed6eb1e910dd09d0d601de326a6bcef6c --- /dev/null +++ b/web/src/shader/conv2d_elementwise_add/params.es6 @@ -0,0 +1,54 @@ +/* eslint-disable */ +/** + * @file 参数文件 + * @author yangmingming + */ +export default ` + // 卷积核 + const int length_shape_filter = LENGTH_SHAPE_FILTER; + const int width_shape_filter = WIDTH_SHAPE_FILTER; + const int height_shape_filter = HEIGHT_SHAPE_FILTER; + const int width_texture_filter = WIDTH_TEXTURE_FILTER; + const int height_texture_filter = HEIGHT_TEXTURE_FILTER; + const int channel_filter = CHANNEL_FILTER; + + // 输入数据 + const int width_shape_origin = WIDTH_SHAPE_ORIGIN; + const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; + const int length_shape_origin = LENGTH_SHAPE_ORIGIN; + const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; + const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; + const int channel_origin = CHANNEL_ORIGIN; + + // 计算相关 + // 拆分步长 + const int stride_h = STRIDES_X; + const int stride_v = STRIDES_Y; + // padding的数目 + const int padLeft = PADDINGS_X; + const int padTop = PADDINGS_Y; + // dilation膨胀系数 + const int dilation_h = DILATIONS_X; + const int dilation_v = DILATIONS_Y; + // groups + const int groups = GROUPS; + + // 加法 + const int axis = AXIS; + + // uniform变量 + // 卷积核 + uniform sampler2D texture_filter; + + // 输入数据 + uniform sampler2D texture_origin; + + // 加法 + uniform sampler2D texture_counter; + // 加法用到的函数 + float getValueFromCounter(int index) { + float xPos = float(index) / float(WIDTH_SHAPE_COUNTER); + vec4 pixels = TEXTURE2D(texture_counter, vec2(xPos, 0.5)); + return pixels.r; + } +`; diff --git a/web/src/shader/conv2d_elementwise_add_winograd/conf.es6 b/web/src/shader/conv2d_elementwise_add_winograd/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..61bd86fdee4684497dd4e57791494691fb2e431f --- /dev/null +++ b/web/src/shader/conv2d_elementwise_add_winograd/conf.es6 @@ -0,0 +1,72 @@ +/* eslint-disable */ +/** + * @file conv2d-elementwise_add的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'filter' + } + } + ], + conf: [ + 'LENGTH_SHAPE_FILTER', + 'WIDTH_SHAPE_FILTER', + 'HEIGHT_SHAPE_FILTER', + 'WIDTH_TEXTURE_FILTER', + 'HEIGHT_TEXTURE_FILTER', + 'CHANNEL_FILTER', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + 'OFFSET_Y_OUT', + + 'TOTAL_SHAPE_COUNTER', + + 'PAD_LEFT', + 'PAD_TOP', + 'AXIS', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'filter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'counter', + variable: 'data', + setter: 'uniform1fv', + type: 'uniform' + } + ] +}; diff --git a/web/src/shader/conv2d_elementwise_add_winograd/main.es6 b/web/src/shader/conv2d_elementwise_add_winograd/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..bf8da439aaa5681214ac323d61b15a2c67a275b1 --- /dev/null +++ b/web/src/shader/conv2d_elementwise_add_winograd/main.es6 @@ -0,0 +1,98 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author yangmingming + */ +export default ` + // start函数 + void main(void) { + ivec4 oPos = getOutputPackedTensorPos(); + int x = oPos.a; + int c = oPos.g; + int y = oPos.b; + int b = oPos.r; + // b = 0; + // c = 1; + // y = 0; + // x = 0; + int addAxis = oPos[axis]; + float res = getValueFromCounter(addAxis); + // 输出结果 + vec4 v4 = vec4(res); + + float I[16]; + float B[16]; + float T[16]; + float f[16]; + for (int cl = 0; cl < channel_filter; cl++) { + // 获取output的坐标 + int oy = 2*y - padTop; + // 计算输入 4 * 4矩阵 和filter + for (int fy = 0; fy < 4; fy++) { + int ox = 2*x - padLeft; + int index = fy * 4; + for (int fx = 0; fx < 4; fx++) { + if (oy < 0 || oy >= height_shape_origin || ox >= width_shape_origin || ox < 0) { + I[index + fx] = 0.0; + } else { + I[index + fx] = getValueFromTensorPos_origin(b, cl, oy, ox); + } + f[index + fx] = getValueFromTensorPos_filter(c, cl, fy, fx); + ox += 1; + } + oy += 1; + } + // input转化 + float tmp1 = I[2] - I[10]; + float tmp2 = I[9] - I[1]; + B[0] = I[0] - I[8] - tmp1; + B[1] = tmp1 - tmp2; + B[2] = tmp1 + tmp2; + B[3] = I[3] - I[11] + tmp2; + tmp1 = I[6] + I[10]; + tmp2 = I[5] + I[9]; + B[4] = I[4] + I[8] - tmp1; + B[5] = tmp1 + tmp2; + B[6] = tmp1 - tmp2; + B[7] = I[7] + I[11] - tmp2; + tmp1 = I[10] - I[6]; + tmp2 = I[5] - I[9]; + B[8] = I[8] - I[4] - tmp1; + B[9] = tmp1 - tmp2; + B[10] = tmp1 + tmp2; + B[11] = tmp2 - I[7] + I[11]; + tmp1 = I[14] - I[6]; + tmp2 = I[5] - I[13]; + B[12] = I[12] - I[4] - tmp1; + B[13] = tmp1 - tmp2; + B[14] = tmp1 + tmp2; + B[15] = tmp2 - I[7] + I[15]; + // 点乘 + for (int i = 0; i < 16; i++) { + T[i] = B[i] * f[i]; + } + // final output + tmp1 = T[1] + T[5] + T[9]; + tmp2 = T[2] + T[6] + T[10]; + v4[0] += T[0] + T[4] + T[8] + tmp1 + tmp2; + v4[1] += T[3] + T[7] + T[11] + tmp1 - tmp2; + tmp1 = T[5] - T[9] + T[13]; + tmp2 = T[6] - T[10] + T[14]; + v4[2] += T[4] - T[8] + T[12] + tmp1 + tmp2; + v4[3] += T[7] - T[11] + T[15] + tmp1 - tmp2; + } + outColor.r = ACTIVE_FUNCTION(v4[0], multi_value, bias_value); + outColor.g = ACTIVE_FUNCTION(v4[1], multi_value, bias_value); + outColor.b = ACTIVE_FUNCTION(v4[2], multi_value, bias_value); + outColor.a = ACTIVE_FUNCTION(v4[3], multi_value, bias_value); + // outColor = v4; + // outColor.r = I[0]; + // outColor.g = I[1]; + // outColor.b = I[2]; + // outColor.a = I[3]; + // outColor.r = float(b); + // outColor.g = float(c); + // outColor.b = float(y); + // outColor.a = float(x); + } +`; diff --git a/web/src/shader/conv2d_elementwise_add_winograd/params.es6 b/web/src/shader/conv2d_elementwise_add_winograd/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..288ad211acef0e21a184977a4b3a178c93f91a16 --- /dev/null +++ b/web/src/shader/conv2d_elementwise_add_winograd/params.es6 @@ -0,0 +1,47 @@ +/* eslint-disable */ +/** + * @file 参数文件 + * @author yangmingming + */ +export default ` + // 卷积核 + const int length_shape_filter = LENGTH_SHAPE_FILTER; + const int width_shape_filter = WIDTH_SHAPE_FILTER; + const int height_shape_filter = HEIGHT_SHAPE_FILTER; + const int width_texture_filter = WIDTH_TEXTURE_FILTER; + const int height_texture_filter = HEIGHT_TEXTURE_FILTER; + const int channel_filter = CHANNEL_FILTER; + + // 输入数据 + const int width_shape_origin = WIDTH_SHAPE_ORIGIN; + const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; + const int length_shape_origin = LENGTH_SHAPE_ORIGIN; + const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; + const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; + const int channel_origin = CHANNEL_ORIGIN; + + // 计算相关 + // padding的数目 + const int padLeft = PADDINGS_X; + const int padTop = PADDINGS_Y; + + // 加法 + const int axis = AXIS; + uniform float data_counter[TOTAL_SHAPE_COUNTER]; + + // uniform变量 + // 卷积核 + uniform sampler2D texture_filter; + + // 输入数据 + uniform sampler2D texture_origin; + // 加法用到的函数 + float getValueFromCounter(int index) { + for (int i = 0; i < TOTAL_SHAPE_COUNTER; i++) { + if (i == index) { + return data_counter[i]; + } + } + return 0.0; + } +`; diff --git a/web/src/shader/dynamic/conf.es6 b/web/src/shader/dynamic/conf.es6 index 2541ecf7555417ceb65f1b131e3404c484f715b6..eddd1418ca3879fab2627c085914e207192a01e5 100644 --- a/web/src/shader/dynamic/conf.es6 +++ b/web/src/shader/dynamic/conf.es6 @@ -18,6 +18,7 @@ export default { 'WIDTH_TEXTURE_OUT', 'HEIGHT_TEXTURE_OUT', 'CHANNEL_OUT', + 'OFFSET_Y_OUT', 'MULTI_VALUE', 'BIAS_VALUE', diff --git a/web/src/shader/dynamic/main.es6 b/web/src/shader/dynamic/main.es6 index f747a477d774953f1b9de2fde5346e9cc457701f..54c068ff9b5dad6c67dcf0382589d946e88308c7 100644 --- a/web/src/shader/dynamic/main.es6 +++ b/web/src/shader/dynamic/main.es6 @@ -9,6 +9,6 @@ void main(void) { // 输出数据 float o = getPixelsFromTexturePos_texture_origin(vCoord).r; float res = ACTIVE_FUNCTION(o, multi_value, bias_value); - gl_FragColor.r = res; + setOutput(res); } `; diff --git a/web/src/shader/elementwise_add/conf.es6 b/web/src/shader/elementwise_add/conf.es6 index a1cef63cad015f4921a3a43ec98b9a87bd76946a..bfd3fb24c21c856a00bb389f10067b205202ab2c 100644 --- a/web/src/shader/elementwise_add/conf.es6 +++ b/web/src/shader/elementwise_add/conf.es6 @@ -33,6 +33,7 @@ export default { 'WIDTH_TEXTURE_OUT', 'HEIGHT_TEXTURE_OUT', 'CHANNEL_OUT', + 'OFFSET_Y_OUT', 'AXIS', 'MULTI_VALUE', diff --git a/web/src/shader/elementwise_add/main.es6 b/web/src/shader/elementwise_add/main.es6 index 5e9b1bd12579c52d0dfc2a12ade27a2a870984b3..2ec3c47cb0553ddca29e28abef35b484f46c04af 100644 --- a/web/src/shader/elementwise_add/main.es6 +++ b/web/src/shader/elementwise_add/main.es6 @@ -7,11 +7,11 @@ export default ` // start函数 void main(void) { // 输出数据 - ivec4 oPos = getOutputTensorPos(); + ivec4 oPos = getOutputTensorPosLIMIT_OUT(); int index = oPos[axis]; float o = getPixelsFromTexturePos_texture_origin(vCoord).r; float c = getValueFromCounter(index); float res = ACTIVE_FUNCTION(o + c, multi_value, bias_value); - gl_FragColor.r = res; + setOutput(res); } `; diff --git a/web/src/shader/mul/conf.es6 b/web/src/shader/mul/conf.es6 index 25b657575793debfb79e6abb30826ffcbd5d563b..1d0227904979b89ac6e3aacadf6d7fad66dce904 100644 --- a/web/src/shader/mul/conf.es6 +++ b/web/src/shader/mul/conf.es6 @@ -37,7 +37,8 @@ export default { 'HEIGHT_SHAPE_OUT', 'WIDTH_TEXTURE_OUT', 'HEIGHT_TEXTURE_OUT', - 'CHANNEL_OUT' + 'CHANNEL_OUT', + 'OFFSET_Y_OUT' ], input: [ { diff --git a/web/src/shader/mul/main.es6 b/web/src/shader/mul/main.es6 index 4628b96ec37d24d00442aed660323fdf990cf934..bdec1041a98d3482a8cbb9362d96f2e8281596e9 100644 --- a/web/src/shader/mul/main.es6 +++ b/web/src/shader/mul/main.es6 @@ -13,6 +13,6 @@ void main(void) { float o = getValueFromTensorPos_origin(out_pos[0], out_pos[1], out_pos[2], j); res += c * o; } - gl_FragColor.r = res; + setOutput(res); } `; diff --git a/web/src/shader/pool2d/conf.es6 b/web/src/shader/pool2d/conf.es6 index 9d2ca4d0db414b10e39cb94a13e44498e84f3144..ebb6070400b588e4dedd130d5627504ebecc0a18 100644 --- a/web/src/shader/pool2d/conf.es6 +++ b/web/src/shader/pool2d/conf.es6 @@ -29,6 +29,7 @@ export default { 'WIDTH_TEXTURE_OUT', 'HEIGHT_TEXTURE_OUT', 'CHANNEL_OUT', + 'OFFSET_Y_OUT', 'STRIDES_X', 'STRIDES_Y', diff --git a/web/src/shader/pool2d/main.es6 b/web/src/shader/pool2d/main.es6 index df295618a41259162d4ec118fd4d0b0ce8b94c42..113de0b586c7c2bf7333e4fbfe1abef0d1034af2 100644 --- a/web/src/shader/pool2d/main.es6 +++ b/web/src/shader/pool2d/main.es6 @@ -7,7 +7,7 @@ export default ` void main(void) { float res = (-1.0 / exp(-20.0)); // 获取output的坐标 - ivec4 out_pos = getOutputTensorPos(); + ivec4 out_pos = getOutputTensorPosLIMIT_OUT(); // X、Y方向的移动步长 int count_pool = 0; int oy_base = out_pos[2] * stride_v - padTop; @@ -29,7 +29,7 @@ void main(void) { continue; } // origin数据 - float curr = getValueFromTensorPos_origin(out_pos[0], out_pos[1], oy, ox); + float curr = getValueFromTensorPosLIMIT_ORIGIN_origin(out_pos[0], out_pos[1], oy, ox); if (type_pool == 1) { if (curr > res) { res = curr; @@ -44,6 +44,6 @@ void main(void) { if (type_pool != 1) { res = res / float(count_pool); } - gl_FragColor.r = res; + setOutput(res); } `; diff --git a/web/src/shader/pool2d_avg/conf.es6 b/web/src/shader/pool2d_avg/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..fe45ac64502413d525f3deff20352eab260db1ee --- /dev/null +++ b/web/src/shader/pool2d_avg/conf.es6 @@ -0,0 +1,47 @@ +/* eslint-disable */ +/** + * @file pool2d_avg的配置文件 + * @author yangmingming zhangmiao06 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + } + ], + conf: [ + 'KSIZE_X', + 'KSIZE_Y', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + 'OFFSET_Y_OUT', + + 'STRIDES_X', + 'STRIDES_Y', + 'PADDING_X', + 'PADDING_Y' + ], + input: [ + // texture类型,若添加from: 'prev', 表示读取上一个op的产出 + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/pool2d_avg/main.es6 b/web/src/shader/pool2d_avg/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..b9f157e402d23a1ed388ea5963fa1cf26a396e37 --- /dev/null +++ b/web/src/shader/pool2d_avg/main.es6 @@ -0,0 +1,40 @@ +/* eslint-disable */ +/** + * @file pool2d_avg主函数 + * @author yangmingming zhangmiao06 + */ +export default ` +// start函数 +void main(void) { + float res = 0.0; + // 获取output的坐标 + ivec4 out_pos = getOutputTensorPos(); + // X、Y方向的移动步长 + int oy_base = out_pos[2] * stride_v - padTop; + int ox_base = out_pos[3] * stride_h - padLeft; + for (int fy = 0; fy < height_shape_pool; fy++) { + int oy = oy_base + fy; + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + continue; + } + for (int fx = 0; fx < width_shape_pool; fx++) { + int ox = ox_base + fx; + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + continue; + } + // origin数据 + float curr = getValueFromTensorPos_origin(out_pos[0], out_pos[1], oy, ox); + res += curr; + // 在平均池化模式忽略填充值(exclusive默认为true) + } + } + res = res / float(height_shape_pool * width_shape_pool); + setOutput(res); +} +`; diff --git a/web/src/shader/pool2d_avg/params.es6 b/web/src/shader/pool2d_avg/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..17b55948e0e49827b67ef324ecedabde7a3d3075 --- /dev/null +++ b/web/src/shader/pool2d_avg/params.es6 @@ -0,0 +1,30 @@ +/* eslint-disable */ +/** + * @file pool2d_avg参数文件 + * @author yangmingming zhangmiao06 + */ +export default ` +// 常量 +// 池化大小 +const int width_shape_pool = KSIZE_X; +const int height_shape_pool = KSIZE_Y; +// 输入数据 +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; + +// 计算相关 +// 拆分步长 +const int stride_h = STRIDES_X; +const int stride_v = STRIDES_Y; +// padding的数目 +const int padLeft = PADDINGS_X; +const int padTop = PADDINGS_Y; + + +// uniform变量 +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/pool2d_max/conf.es6 b/web/src/shader/pool2d_max/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..722e7df28a066321358e758f6fb681749fa28339 --- /dev/null +++ b/web/src/shader/pool2d_max/conf.es6 @@ -0,0 +1,47 @@ +/* eslint-disable */ +/** + * @file pool2d的配置文件 + * @author yangmingming zhangmiao06 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + } + ], + conf: [ + 'KSIZE_X', + 'KSIZE_Y', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + 'OFFSET_Y_OUT', + + 'STRIDES_X', + 'STRIDES_Y', + 'PADDING_X', + 'PADDING_Y' + ], + input: [ + // texture类型,若添加from: 'prev', 表示读取上一个op的产出 + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/pool2d_max/main.es6 b/web/src/shader/pool2d_max/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..2555efc95d2aa46c43df4dc14c290bb8f8ed41b9 --- /dev/null +++ b/web/src/shader/pool2d_max/main.es6 @@ -0,0 +1,45 @@ +/* eslint-disable */ +/** + * @file pool2d主函数 + */ +export default ` +// start函数 +void main(void) { + float res = (-1.0 / exp(-20.0)); + // 获取output的坐标 + ivec4 out_pos = getOutputTensorPosLIMIT_OUT(); + int b = out_pos[0]; + int c = out_pos[1]; + int y = out_pos[2]; + int x = out_pos[3]; + // X、Y方向的移动步长 + int oy_base = out_pos[2] * stride_v - padTop; + int ox_base = out_pos[3] * stride_h - padLeft; + for (int fy = 0; fy < height_shape_pool; fy++) { + int oy = oy_base + fy; + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + continue; + } + for (int fx = 0; fx < width_shape_pool; fx++) { + int ox = ox_base + fx; + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + continue; + } + // origin数据 + float curr = getValueFromTensorPosLIMIT_ORIGIN_origin(out_pos[0], out_pos[1], oy, ox); + res = max(res, curr); + } + } + setOutput(res); + // outColor.r = float(b); + // outColor.g = float(c); + // outColor.b = float(y); + // outColor.a = float(x); +} +`; diff --git a/web/src/shader/pool2d_max/params.es6 b/web/src/shader/pool2d_max/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..1ddd6befc81b088fd80d0c3cc248238e92a0b53e --- /dev/null +++ b/web/src/shader/pool2d_max/params.es6 @@ -0,0 +1,29 @@ +/* eslint-disable */ +/** + * @file pool2d参数文件 + */ +export default ` +// 常量 +// 池化大小 +const int width_shape_pool = KSIZE_X; +const int height_shape_pool = KSIZE_Y; +// 输入数据 +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; + +// 计算相关 +// 拆分步长 +const int stride_h = STRIDES_X; +const int stride_v = STRIDES_Y; +// padding的数目 +const int padLeft = PADDINGS_X; +const int padTop = PADDINGS_Y; + + +// uniform变量 +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/pool2d_winograd/conf.es6 b/web/src/shader/pool2d_winograd/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..3c3925a3b45a04428f33e5d0a7c221bdc49b8935 --- /dev/null +++ b/web/src/shader/pool2d_winograd/conf.es6 @@ -0,0 +1,50 @@ +/* eslint-disable */ +/** + * @file pool2d的配置文件 + * @author yangmingming zhangmiao06 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPosPacked', + conf: { + TENSOR_NAME: 'origin' + } + } + ], + conf: [ + 'KSIZE_X', + 'KSIZE_Y', + 'TYPE_POOL', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + 'OFFSET_X_ORIGIN', + 'OFFSET_Y_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + 'OFFSET_Y_OUT', + + 'STRIDES_X', + 'STRIDES_Y', + 'PADDING_X', + 'PADDING_Y' + ], + input: [ + // texture类型,若添加from: 'prev', 表示读取上一个op的产出 + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/pool2d_winograd/main.es6 b/web/src/shader/pool2d_winograd/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..0112654fdd379e600899247504029251d3a8ca40 --- /dev/null +++ b/web/src/shader/pool2d_winograd/main.es6 @@ -0,0 +1,63 @@ +/* eslint-disable */ +/** + * @file pool2d主函数 + */ +export default ` +// start函数 +void main(void) { + float res = (-1.0 / exp(-20.0)); + // 获取output的坐标 + ivec4 out_pos = getOutputTensorPos(); + // int b = out_pos[0]; + // int c = out_pos[1]; + // int y = out_pos[2]; + // int x = out_pos[3]; + // X、Y方向的移动步长 + int count_pool = 0; + int oy_base = out_pos[2] * stride_v - padTop; + int ox_base = out_pos[3] * stride_h - padLeft; + // int offset = 0; + // vec4 v4 = texture(texture_origin, vec2((float(0) + 0.5) / float(width_texture_origin), (float(1 * height_shape_origin / 2 + 0) + 0.5) / float(height_texture_origin))); + for (int fy = 0; fy < height_shape_pool; fy++) { + int oy = oy_base + fy; + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + continue; + } + for (int fx = 0; fx < width_shape_pool; fx++) { + int ox = ox_base + fx; + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + continue; + } + // origin数据 + float curr = getValueFromTensorPosPacked_origin(out_pos[0], out_pos[1], oy, ox); + // y = oy; + // x = ox; + // v4[offset++] = curr; + if (type_pool == 1) { + if (curr > res) { + res = curr; + } + } else { + res += curr; + // 在平均池化模式忽略填充值(exclusive默认为true) + count_pool++; + } + } + } + if (type_pool != 1) { + res = res / float(count_pool); + } + setOutput(res); + // outColor = v4; + // outColor.r = float(b); + // outColor.g = float(c); + // outColor.b = float(y); + // outColor.a = float(x); +} +`; diff --git a/web/src/shader/pool2d_winograd/params.es6 b/web/src/shader/pool2d_winograd/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..4310c0f7b2968b7bcdc935d1b51393ef1c0e961a --- /dev/null +++ b/web/src/shader/pool2d_winograd/params.es6 @@ -0,0 +1,33 @@ +/* eslint-disable */ +/** + * @file pool2d参数文件 + */ +export default ` +// 常量 +// 池化大小 +const int width_shape_pool = KSIZE_X; +const int height_shape_pool = KSIZE_Y; +const int type_pool = TYPE_POOL; +// 输入数据 +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; +const int offset_x_origin = OFFSET_X_ORIGIN; +const int offset_y_origin = OFFSET_Y_ORIGIN; + + +// 计算相关 +// 拆分步长 +const int stride_h = STRIDES_X; +const int stride_v = STRIDES_Y; +// padding的数目 +const int padLeft = PADDINGS_X; +const int padTop = PADDINGS_Y; + + +// uniform变量 +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/softmax/conf.es6 b/web/src/shader/softmax/conf.es6 index ff04060e3b116dd46dbf0af3646c2eb248574c6e..07a7bbab414196b9f49eea6e6534d40b433756ab 100644 --- a/web/src/shader/softmax/conf.es6 +++ b/web/src/shader/softmax/conf.es6 @@ -15,7 +15,8 @@ export default { conf: [ 'WIDTH_TEXTURE_ORIGIN', 'HEIGHT_TEXTURE_ORIGIN', - 'TOTAL_SHAPE_ORIGIN' + 'TOTAL_SHAPE_ORIGIN', + 'OFFSET_Y_OUT' ], input: [ { diff --git a/web/src/shader/v_shader2.es6 b/web/src/shader/v_shader2.es6 new file mode 100644 index 0000000000000000000000000000000000000000..529dc6110283b53bcae93914112f53967dced65d --- /dev/null +++ b/web/src/shader/v_shader2.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 顶点文件,webgl 2.0 + * @author yangmingming + */ +export default `#version 300 es +in vec4 position; +out vec2 vCoord; + +void main() { + vCoord.x = (position.x + 1.0) / 2.0; + vCoord.y = (position.y + 1.0) / 2.0; + gl_Position = position; +} +`; diff --git a/web/src/test/getMaxUniforms.es6 b/web/src/test/getMaxUniforms.es6 new file mode 100644 index 0000000000000000000000000000000000000000..9fd0f710a8f31fb27b0b6e50c6e5f4e3076eb4df --- /dev/null +++ b/web/src/test/getMaxUniforms.es6 @@ -0,0 +1,59 @@ +/** + * @file 获取当前环境的max uniform变量 + * @author yangmingming + */ +// uniform变量类型 +const enums = { + 0x8B50: 'FLOAT_VEC2', + 0x8B51: 'FLOAT_VEC3', + 0x8B52: 'FLOAT_VEC4', + 0x8B53: 'INT_VEC2', + 0x8B54: 'INT_VEC3', + 0x8B55: 'INT_VEC4', + 0x8B56: 'BOOL', + 0x8B57: 'BOOL_VEC2', + 0x8B58: 'BOOL_VEC3', + 0x8B59: 'BOOL_VEC4', + 0x8B5A: 'FLOAT_MAT2', + 0x8B5B: 'FLOAT_MAT3', + 0x8B5C: 'FLOAT_MAT4', + 0x8B5E: 'SAMPLER_2D', + 0x8B60: 'SAMPLER_CUBE', + 0x1400: 'BYTE', + 0x1401: 'UNSIGNED_BYTE', + 0x1402: 'SHORT', + 0x1403: 'UNSIGNED_SHORT', + 0x1404: 'INT', + 0x1405: 'UNSIGNED_INT', + 0x1406: 'FLOAT' +}; +export default function(gl, program) { + // max fragment shader, 安卓是256, 桌面chrome浏览器是1024 + const result = { + attributes: [], + uniforms: [], + attributeCount: 0, + uniformCount: 0, + maxVertexShader: gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS), + maxFragmentShader: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS) + }; + const activeUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + const activeAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + // Loop through active uniforms + for (let i = 0; i < activeUniforms; i++) { + const uniform = gl.getActiveUniform(program, i); + uniform.typeName = enums[uniform.type]; + result.uniforms.push(uniform); + result.uniformCount += uniform.size; + } + + // Loop through active attributes + for (let i = 0; i < activeAttributes; i++) { + const attribute = gl.getActiveAttrib(program, i); + attribute.typeName = enums[attribute.type]; + result.attributes.push(attribute); + result.attributeCount += attribute.size; + } + + return result; +}; diff --git a/web/src/utils/models.es6 b/web/src/utils/models.es6 new file mode 100644 index 0000000000000000000000000000000000000000..80c1270441e87ec0ea5555b322fc195a053ecf74 --- /dev/null +++ b/web/src/utils/models.es6 @@ -0,0 +1,46 @@ +export default { + '608': { + modelPath: 'faceModel', + feedShape: { + fw: 608, + fh: 608 + }, + outputShapes: { + from: [19, 19, 25, 1], + to: [19, 19, 5, 5] + } + }, + '320': { + modelPath: 'facemodel320', + feedShape: { + fw: 320, + fh: 320 + }, + outputShapes: { + from: [10, 10, 25, 1], + to: [10, 10, 5, 5] + } + }, + '320fused': { + modelPath: 'facemodelfused', + feedShape: { + fw: 320, + fh: 320 + }, + outputShapes: { + from: [10, 10, 25, 1], + to: [10, 10, 5, 5] + } + }, + 'separate': { + modelPath: 'separablemodel', + feedShape: { + fw: 320, + fh: 320 + }, + outputShapes: { + from: [10, 10, 25, 1], + to: [10, 10, 5, 5] + } + } +}; diff --git a/web/src/utils/opData.es6 b/web/src/utils/opData.es6 index ae9d21f3b85fd2780bd729494ff2bef2b84fac3a..80526f4f8b7c7cfb89c047ac374774db56205be1 100644 --- a/web/src/utils/opData.es6 +++ b/web/src/utils/opData.es6 @@ -19,6 +19,9 @@ const tensorAttrs = [ 'height_shape', 'width_texture', 'height_texture', + 'offset_x', + 'offset_y', + 'limit', 'channel', 'total_shape' ]; @@ -30,6 +33,9 @@ const shaderAttrs = { }, pool2d: { 'pooling_type': 'type_pool' + }, + pool2d_winograd: { + 'pooling_type': 'type_pool' } }; // model的名字和paddle web的tensor名字mapping @@ -48,7 +54,8 @@ const tensorName = { // unique behavior const opBehavior = { conv2d: [ - 'needBatch' + 'needBatch', + 'isApplySeparableConv' ], batchnorm: [ 'needBatch', @@ -58,9 +65,15 @@ const opBehavior = { 'broadcast', 'needBatch' ], + conv2d_elementwise_add: [ + 'mergeAttrs', + 'setActiveFunc', + 'needBatch' + ], pool2d: [ 'isMax', 'needBatch', + 'setPacked', 'isGlobalPooling' ], relu: [ @@ -76,10 +89,14 @@ const opBehavior = { 'needBatch' ] }; +const mergeType = 'conv2d-elementwise_add'; export default class OpData { constructor(name, input = {}, output = {}, attrs = {}) { + this.realName = name; this.name = name; this.attrs = attrs; + // 检查是否是融合op + this.checkIsMerge(); // 是否忽略当前当前op, 使用dropout this.isPass = this.checkIsPass(); if (this.isPass) { @@ -133,6 +150,7 @@ export default class OpData { }); // 生成tensor对象 tensorData.forEach(data => { + // console.log(data); if (data) { if (data.notTensor) { this.tensor[data.tensorName] = { @@ -142,11 +160,13 @@ export default class OpData { }; } else { this.tensor[data.tensorName] = new Tensor({ + type: data.name, name: data.tensorName, shape: data.shape, data: data.data, needBatch: data.needBatch || false, - notCompressed: data.notCompressed || false + notCompressed: data.notCompressed || false, + isPacked: data.isPacked || false }); } } @@ -195,15 +215,74 @@ export default class OpData { } } - broadcast(tensorData = []) { - const x = tensorData[0]; - const y = tensorData[1]; - let small = y; - if (x.shape.length - y.shape.length < 0) { - small = x; + mergeAttrs() { + this.attrs = this.attrs.reduce((attrs, item) => { + return Object.assign(attrs, item); + }, {}); + } + + isApplyWinoGrad(tensorData = []) { + const filter = tensorData.filter(item => { + const [b, c, h, w] = item.shape; + return (h === 3) && (w === 3) && (item.tensorName === 'filter'); + }); + // 使用winograd算法 + if (filter && filter.length) { + this.setPacked(tensorData); + this.applyWinograd(tensorData); + this.setOutputPacked(tensorData); + this.name += '_winograd'; } - // face model - small.notTensor = true; + } + + isApplySeparableConv(tensorData = []) { + const groups = this.attrs.groups; + const filter = tensorData.filter(item => { + const [b, c, h, w] = item.shape; + return (b === groups) && (c === 1) && (item.tensorName === 'filter'); + }); + if (filter && filter.length) { + // 可以执行separable conv + this.name += '_depthwise'; + } + } + + setPacked(tensorData = []) { + const isPacked = this.attrs.ispacked; + tensorData.forEach(item => { + if (item.tensorName === 'origin' && isPacked) { + item.isPacked = true; + if (this.name.indexOf('pool') > -1) { + this.name += '_winograd'; + } + } + }); + } + + applyWinograd(tensorData = []) { + tensorData.forEach(item => { + if (item.tensorName === 'filter') { + const [b, c, h, w] = item.shape; + item.shape = [b, c, 4, 4]; + item.data = Utils.applyFilterWinograd(item.data, item.shape); + } + }); + } + + setOutputPacked(tensorData = []) { + tensorData.forEach(item => { + if (item.tensorName === 'out') { + item.isPacked = true; + } + }); + } + + broadcast(tensorData = []) { + tensorData.forEach(item => { + if (item.tensorName === 'counter') { + item.notTensor = true; + } + }); return; // mobilenet model @@ -227,6 +306,9 @@ export default class OpData { isMax(tensorData = []) { const type = this.attrs['pooling_type'] === 'max' ? 1 : 0; this.attrs['pooling_type'] = type; + if (type === 1) { + this.name += '_max'; + } } transToPrelu(tensorData = []) { @@ -240,10 +322,13 @@ export default class OpData { this.name = 'relu'; } - setActiveFunc(tensorData = []) { - this.data['multi_value'] = '0.0'; - this.data['active_function'] = 'softmax'; - + setActiveFunc() { + // 用于融合op + const suffix = this.realName.replace(mergeType + '-', ''); + if (suffix === 'leaky_relu') { + this.data['multi_value'] = this.attrs.alpha; + this.data['active_function'] = 'leakyRelu'; + } } reshape(tensorData = []) { @@ -284,6 +369,16 @@ export default class OpData { tensorData.splice(result[constants[3] + 'Index'], 1, 0); } + checkIsMerge() { + if (this.name.indexOf(mergeType) > -1 + && Object.prototype.toString.apply(this.attrs) === '[object Array]') { + // 第一个融合op + this.name = 'conv2d_elementwise_add'; + return true; + } + return false; + } + checkIsPass() { if (this.name === 'dropout') { if (this.attrs['dropout_implementation'] === 'downgrade_in_infer') { diff --git a/web/src/utils/tensor.es6 b/web/src/utils/tensor.es6 index 3afad8eb2eb9b75be450002929a01486990472f8..492d02ba058b777f7fa35c3fde07b7668ab7f13c 100644 --- a/web/src/utils/tensor.es6 +++ b/web/src/utils/tensor.es6 @@ -7,6 +7,8 @@ import Utils from './utils'; export default class Tensor { constructor(opts = {}) { this.opts = opts; + // 数据存储方式 + this.isPacked = this.isPacked || false; // 设置tensor名字 this.name = opts.name; // tensor的形状 @@ -23,36 +25,44 @@ export default class Tensor { this.shape = shape; } // 获取转换到texture后的信息 - let {zeroNumber, shape: shape_texture} = Utils.getTextureInfoFromTensorShape(shape); + let {offsetX, offsetY, exceedMax, zeroNumber, shape: shape_texture} = Utils.getTextureInfoFromTensorShape(shape, opts.isPacked); this.shape_texture = shape_texture; + this.exceedMax = exceedMax; + this.offsetX = offsetX; + this.offsetY = offsetY; // tensor数据 - let data = []; - if (opts.data && opts.data.length) { + let data; + if (opts.type === 'image' || opts.type === 'x') { + this.data = opts.data; + } + else if (opts.data && opts.data.length) { + data = new Float32Array(opts.data.length); if (!opts.notCompressed) { let b = shape[0]; let c = shape[1]; let h = shape[2]; let w = shape[3]; + for (let i = 0; i < opts.data.length; i++) { - let j = Math.floor(i / (c * w)); - let k = Math.floor(i % (c * w)); - let b1 = Math.floor(j / h); - let h1 = Math.floor(j % h); - let c1 = Math.floor(k % c); - let w1 = Math.floor(k / c); + let j = i / (c * w) | 0; + let k = i % (c * w); + let b1 = j / h | 0; + let h1 = j % h; + let c1 = k % c; + let w1 = k / c | 0; let l = b1 * (c * h * w) + c1 * (h * w) + h1 * (w) + w1; - data.push(opts.data[l]); - data.push(0); - data.push(0); - data.push(0); + data[i] = opts.data[l]; } + this.data = data; } else { // batchnorm的scale this.shape_texture = [4, 1, this.total / 4]; - data = [].concat(opts.data); + // data = [].concat(opts.data); + this.data = new Float32Array(opts.data); } - this.data = new Float32Array(data); + // this.data = new Float32Array(data); + // console.log('this.data.length', this.data.length); // 清理缓存 opts.data = null; } @@ -106,6 +116,18 @@ export default class Tensor { return 0; } + get offset_x() { + return this.offsetX; + } + + get offset_y() { + return this.offsetY; + } + + get limit() { + return this.exceedMax ? 'Limit' : ''; + } + get length_shape() { return this.shape.length || 0; } diff --git a/web/src/utils/utils.es6 b/web/src/utils/utils.es6 index 6c747c097874a7099cd8f925079cdcee47d4c310..f27318e9e5c15c6c360f150779fbe2a8ca9bbd65 100644 --- a/web/src/utils/utils.es6 +++ b/web/src/utils/utils.es6 @@ -64,18 +64,78 @@ export default { return result; }, + applyFilterWinograd(data, shape) { + const [b, c, h, w] = shape; + let offset = 0; + let index = 0; + const result = new Float32Array(b * c * 16); + // h和w是3、3 + const size2D = 9; + for (let i = 0; i < b; i++) { + // let index = i * c * size2D; + for (let j = 0; j < c; j++) { + // index += j * size2D; + const filter = data.subarray(index, index + size2D); + const [f11, f12, f13, f21, f22, f23, f31, f32, f33] = filter; + const square = [ + f11, + 0.5 * f11 + 0.5 * f12 + 0.5 * f13, + 0.5 * f11 - 0.5 * f12 + 0.5 * f13, + f13, + 0.5 * f11 + 0.5 * f21 + 0.5 * f31, + 0.25 * f11 + 0.25 * f12 + 0.25 * f13 + 0.25 * f21 + 0.25 * f22 + 0.25 * f23 + 0.25 * f31 + 0.25 * f32 + 0.25 * f33, + 0.25 * f11 - 0.25 * f12 + 0.25 * f13 + 0.25 * f21 - 0.25 * f22 + 0.25 * f23 + 0.25 * f31 - 0.25 * f32 + 0.25 * f33, + 0.5 * f13 + 0.5 * f23 + 0.5 * f33, + 0.5 * f11 - 0.5 * f21 + 0.5 * f31, + 0.25 * f11 + 0.25 * f12 + 0.25 * f13 - 0.25 * f21 - 0.25 * f22 - 0.25 * f23 + 0.25 * f31 + 0.25 * f32 + 0.25 * f33, + 0.25 * f11 - 0.25 * f12 + 0.25 * f13 - 0.25 * f21 + 0.25 * f22 - 0.25 * f23 + 0.25 * f31 - 0.25 * f32 + 0.25 * f33, + 0.5 * f13 - 0.5 * f23 + 0.5 * f33, + f31, + 0.5 * f31 + 0.5 * f32 + 0.5 * f33, + 0.5 * f31 - 0.5 * f32 + 0.5 * f33, + f33 + ]; + result.set(square, offset); + offset += 16; + index += size2D; + } + } + return result; + }, + /** * 获取texture形状和补0个数 * @param shape {Array} tensor的形状 * @return {{shape: *[], zeroNumber: number}} {Object} texture信息 */ - getTextureInfoFromTensorShape(shape = []) { + getTextureInfoFromTensorShape(shape = [], isPacked = false) { let b = shape[0]; let c = shape[1]; let h = shape[2]; let w = shape[3]; + let height = b * h; + let width = c * w; + let offsetX = 0; + let offsetY = 0; + // 安卓和ios的max texture size是4096, 改造存储空间(2bh, cw / 2) + let exceedMax = false; + if (height > 4096 || width > 4096) { + height *= 2; + width = c * (Math.ceil(w / 2)); + exceedMax = true; + } + if (isPacked) { + // 紧凑布局 + height = b * c * Math.ceil(h / 2); + width = Math.ceil(w / 2); + offsetX = w % 2; + offsetY = h % 2; + } return { - shape: [4, b * h, c * w], + offsetX, + offsetY, + exceedMax, + shape: [4, height, width], zeroNumber: 0 }; }, @@ -107,21 +167,24 @@ export default { const c = shape[1]; const h = shape[2]; const w = shape[3]; - const data = []; + let data = new Float32Array(b * c * h * w * 4); + let offset = 0; for (let i = 0; i < total; i++) { - let j = Math.floor(i / (c * w)); - let k = Math.floor(i % (c * w)); - let b1 = Math.floor(j / h); - let h1 = Math.floor(j % h); - let c1 = Math.floor(k % c); - let w1 = Math.floor(k / c); + let j = (i / (c * w)) | 0; + let k = i % (c * w); + let b1 = j / h | 0; + let h1 = j % h; + let c1 = k % c; + let w1 = k / c | 0; let l = b1 * (c * h * w) + c1 * (h * w) + h1 * (w) + w1; - data.push(renderData.data[l]); - data.push(0); - data.push(0); - data.push(0); + data[offset] = renderData.data[l]; + offset += 4; + // data.push(renderData.data[l]); + // data.push(0); + // data.push(0); + // data.push(0); } - renderData.data = new Float32Array(data); + renderData.data = data; } }; /* eslint-enable */ diff --git a/web/test/testUtils/diff.js b/web/test/testUtils/diff.js deleted file mode 100644 index beb9b6ce23286c262f369b9b708e419839624137..0000000000000000000000000000000000000000 --- a/web/test/testUtils/diff.js +++ /dev/null @@ -1,1055 +0,0 @@ -/*! - - diff v2.0.1 - -Software License Agreement (BSD License) - -Copyright (c) 2009-2015, Kevin Decker