Skip to content

Commit

Permalink
Add Debugger Module (#722)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Sep 13, 2018
1 parent 71ed1aa commit d9ad285
Show file tree
Hide file tree
Showing 8 changed files with 496 additions and 4 deletions.
3 changes: 2 additions & 1 deletion aliases.js
Expand Up @@ -26,7 +26,8 @@ const ALIASES = {
'luma.gl-imageprocessing': path.resolve(__dirname, './modules/imageprocessing/src'),
'luma.gl-glfx': path.resolve(__dirname, './modules/glfx/src'),
'luma.gl-io': path.resolve(__dirname, './modules/io/src'),
'loaders.gl': path.resolve(__dirname, './modules/loaders.gl/src')
'loaders.gl': path.resolve(__dirname, './modules/loaders.gl/src'),
'@luma.gl/debug': path.resolve(__dirname, './modules/debug/src')
};

if (module.require) {
Expand Down
39 changes: 39 additions & 0 deletions modules/debug/package.json
@@ -0,0 +1,39 @@
{
"name": "@luma.gl/debug",
"private": true,
"version": "0.0.1",
"description": "Debug utilities for luma.gl",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/uber/luma.gl"
},
"keywords": [
"webgl",
"glsl",
"debug",
"3d"
],
"main": "dist/es5/index.js",
"module": "dist/esm/index.js",
"esnext": "dist/es6/index.js",
"files": [
"src",
"dist"
],
"sideEffects": false,
"browser": {
"fs": false
},
"scripts": {
"clean": "rm -fr dist && mkdir -p dist/es5 dist/esm dist/es6",
"build": "npm run clean && npm run build-es6 && npm run build-esm && npm run build-es5",
"build-es6": "BABEL_ENV=es6 babel src --config-file ../../babel.config.js --out-dir dist/es6 --source-maps --ignore 'node_modules/'",
"build-esm": "BABEL_ENV=esm babel src --config-file ../../babel.config.js --out-dir dist/esm --source-maps --ignore 'node_modules/'",
"build-es5": "BABEL_ENV=es5 babel src --config-file ../../babel.config.js --out-dir dist/es5 --source-maps --ignore 'node_modules/'"
},
"dependencies": {
"luma.gl": "^6.1.0-alpha.3",
"glsl-transpiler": "1.5.9"
}
}
83 changes: 83 additions & 0 deletions modules/debug/src/compile-shader.js
@@ -0,0 +1,83 @@
import Compiler from 'glsl-transpiler';

const normalize = source =>
source
// prepr does not like #define without value
.replace(/^(#define \w+) *$/gm, ($0, $1) => `${$1} 1`);

const compileVS = Compiler({
uniform: name => `uniforms.${name}`,
attribute: name => `attributes.${name}`
});

const compileFS = Compiler({
uniform: name => `uniforms.${name}`,
attribute: name => `attributes.${name}`,
varying: name => `varyings.${name}`
});

// @returns JavaScript function of the transpiled shader
export function compileVertexShader(name, source) {
source = normalize(source);

const compiledSource = compileVS(source);
const {compiler} = compileVS;

// TODO - input validation?
const stats = {
attributes: compiler.attributes,
uniforms: compiler.uniforms,
varyings: compiler.varyings,
functions: compiler.functions
};
compiler.reset();

return evalScript(
`function vs(uniforms, attributes) {
var gl_Position;
${compiledSource}
/* End of shader code */
main();
return {
gl_Position,
varyings: {${Object.keys(stats.varyings).join(', ')}}
};
}`,
name
);
}

// @returns JavaScript function of the transpiled shader
export function compileFragmentShader(name, source) {
source = normalize(source);

const compiledSource = compileFS(source);
const {compiler} = compileFS;

compiler.reset();

return evalScript(
`function fs(uniforms, varyings) {
var gl_FragColor;
var isDiscarded = false;
function discard() {
isDiscarded = true;
}
${compiledSource}
/* End of shader code */
main();
return {
gl_FragColor,
isDiscarded
};
}`,
name
);
}

/* eslint-disable no-eval */
function evalScript(value, name) {
const script = `(function() { return ${value}; })()
//# sourceURL=${name}.js`;
return eval(script);
}
185 changes: 185 additions & 0 deletions modules/debug/src/debug-context.js
@@ -0,0 +1,185 @@
import GL from 'luma.gl/constants';
import drawModel from './draw-model';

/* Draws WebGL style wireframe in a 2d canvas */
export default class DebugContext {
constructor(sourceCanvas) {
this.sourceCanvas = sourceCanvas;

const container = sourceCanvas.offsetParent;
/* global window */
const containerStyle = window.getComputedStyle(container);
if (containerStyle.position === 'static') {
container.style.position = 'relative';
}

this.canvas = this._createCanvas(container);
this.context = this.canvas.getContext('2d');

this._draw = this._draw.bind(this);
}

clear(opts = {}) {
const {canvas, context} = this;
const {clientWidth: width, clientHeight: height} = this.sourceCanvas;

canvas.width = width;
canvas.height = height;
context.clearRect(0, 0, width, height);

for (const name in opts) {
context[name] = opts[name];
}
}

drawModel(model, opts) {
drawModel(Object.assign({}, opts, {
model,
draw: this._draw
}));
}

/* eslint-disable complexity */
_draw({drawMode, indices, positions, colors}) {
this.positions = positions.map(this._clipspaceToScreen, this);
this.colors = colors.map(this._rgbaToColor, this);

switch (drawMode) {
case GL.POINTS: {
for (let i = 0; i < indices.length; i++) {
this._drawPoint(indices[i]);
}
break;
}

case GL.LINES: {
for (let i = 0; i < indices.length - 1; i += 2) {
this._drawLine(indices[i], indices[i] + 1);
}
break;
}

case GL.LINE_STRIP: {
for (let i = 0; i < indices.length - 1; i++) {
this._drawLine(indices[i], indices[i + 1]);
}
break;
}

case GL.LINE_LOOP: {
for (let i = 0; i < indices.length; i++) {
this._drawLine(i === 0 ? indices[indices.length - 1] : indices[i - 1], indices[i]);
}
break;
}

case GL.TRIANGLES: {
for (let i = 0; i < indices.length - 2; i += 3) {
this._drawTriangle(indices[i], indices[i + 1], indices[i + 2]);
}
break;
}

case GL.TRIANGLE_STRIP: {
for (let i = 0; i < indices.length - 2; i++) {
this._drawTriangle(indices[i], indices[i + 1], indices[i + 2]);
}
break;
}

case GL.TRIANGLE_FAN: {
for (let i = 1; i < indices.length - 1; i++) {
this._drawTriangle(indices[0], indices[i], indices[i + 1]);
}
break;
}

default:
throw new Error('unknown draw mode');
}
}
/* eslint-enable complexity */

_createCanvas(container) {
const canvas = document.createElement('canvas');
container.append(canvas);
Object.assign(canvas.style, {
position: 'absolute',
left: '0px',
top: '0px',
pointerEvents: 'none'
});
return canvas;
}

_clipspaceToScreen(position) {
const {width, height} = this.canvas;
const [x, y, z, w] = position;

const depth = z / w;

if (depth < -1 || depth > 1) {
return null;
}

return [(x / w + 1) * width / 2, (1 - y / w) * height / 2];
}

_rgbaToColor(color) {
if (Array.isArray(color)) {
const rgb = color.slice(0, 3).map(x => (x * 255) | 0);
const a = Number.isFinite(color[3]) ? color[3] : 1;
return `rgba(${rgb.join(',')},${a.toFixed(2)})`;
}
return color;
}

_drawPoint(i) {
const {context, positions, colors} = this;
const p = positions[i];
const color = colors[i];

if (!p) {
return;
}
if (color) {
context.fillStyle = color;
}
context.fillRect(p[0] - 0.5, p[1] - 0.5, 1, 1);
}

_drawLine(i0, i1) {
const {context, positions, colors} = this;
const p0 = positions[i0];
const p1 = positions[i1];
const color0 = colors[i0];
const color1 = colors[i1];

if (!p0 || !p1) {
return;
}

context.beginPath();
if (color0 !== color1) {
// Use gradient
const gradient = context.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
gradient.addColorStop(0, color0);
gradient.addColorStop(1, color1);

context.strokeStyle = gradient;
} else if (color0) {
// Single color
context.strokeStyle = color0;
}

context.moveTo(p0[0], p0[1]);
context.lineTo(p1[0], p1[1]);
context.stroke();
}

_drawTriangle(i0, i1, i2) {
this._drawLine(i0, i1);
this._drawLine(i1, i2);
this._drawLine(i2, i0);
}
}

0 comments on commit d9ad285

Please sign in to comment.