tonic-image-builder
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -121,1 +121,69 @@ # WebGLUtil | ||
that function. | ||
## TransformShader(shaderString, variableDict, config) | ||
This function can transform shader programs before they are compiled into | ||
a WebGL program in one of several ways. | ||
### String replacement | ||
The variableDict can contain key value mappings and this function will look | ||
for the keys (which must be surrounded by "${" and "}") in the shader string, | ||
and replace them with the values from the dictionary. For example, if the | ||
variableDict contains the mapping `"MAX_COUNT" -> "7"`, then this function will | ||
look for instances of the string `"${MAX_COUNT}"` in the file and replace them | ||
with the value `"7"`. | ||
String replacement happens before any other kind of shader transformations, | ||
and the result of string replacement is then passed on to the next level of | ||
processing, loop unrolling. | ||
### Loop unrolling | ||
This function can unroll properly annotated loops within a shader source file. | ||
Insert a comment just before and after the loop and then pass | ||
`'inlineLoops': true` in the config argument to get loops unrolled before the | ||
shader source is compiled, but after string replacement has occurred. | ||
The comment preceeding the loop code in the shader source must take the form | ||
`"//@INLINE_LOOP (<variableName>, <minLoopIndex>, <maxLoopIndex>)"`, | ||
where `<minLoopIndex>` is the first loop variable value, and `<maxLoopIndex>` | ||
is the last loop index variable (but is not inclusive). The comment after | ||
the loop code in the shader must take the form `"//@INLINE_LOOP"` to indicate | ||
the end of the block. | ||
Following is an example of an annotated loop and how it would be unrolled: | ||
GLSL loop code: | ||
``` | ||
//@INLINE_LOOP (loopIdx, 0, 3) | ||
for (int loopIdx = 0; loopIdx < 3; ++loopIdx) { | ||
if (loopIdx == someVariable) { | ||
gl_FragColor = texture2D(someSampler[loopIdx], someTexCoord); | ||
} | ||
} | ||
//@INLINE_LOOP | ||
``` | ||
Unrolled loop: | ||
``` | ||
if (0 == someVariable) { | ||
gl_FragColor = texture2D(someSampler[0], someTexCoord); | ||
} | ||
if (1 == someVariable) { | ||
gl_FragColor = texture2D(someSampler[1], someTexCoord); | ||
} | ||
if (2 == someVariable) { | ||
gl_FragColor = texture2D(someSampler[2], someTexCoord); | ||
} | ||
``` | ||
### Shader tranformation debugging | ||
In order to get the system to print out the transformed shader to the console | ||
log before compiling, pass `'debug': true` to the `TransformShader` function | ||
within the `config` argument. |
@@ -42,2 +42,6 @@ var CanvasOffscreenBuffer = require('../../util/CanvasOffscreenBuffer'), | ||
CompositeImageBuilder.prototype.getDimensions = function() { | ||
return this.metadata.dimensions; | ||
} | ||
CompositeImageBuilder.prototype.updateOffsetMap = function(query) { | ||
@@ -44,0 +48,0 @@ var layers = this.metadata.layers, |
@@ -109,2 +109,8 @@ var CanvasOffscreenBuffer = require('../../util/CanvasOffscreenBuffer'), | ||
FloatImageImageBuilder.prototype.getDimensions = function() { | ||
return this.dimensions; | ||
} | ||
// ---------------------------------------------------------------------------- | ||
FloatImageImageBuilder.prototype.getCategories = function() { | ||
@@ -111,0 +117,0 @@ var categories = []; |
@@ -1,3 +0,4 @@ | ||
var Composite = require('./Composite'), | ||
DataProber = require('./DataProber'), | ||
var Composite = require('./Composite'), | ||
DataProber = require('./DataProber'), | ||
MagicLens = require('./MagicLens'), | ||
PixelOperator = require('./PixelOperator'), | ||
@@ -11,2 +12,3 @@ QueryDataModel = require('./QueryDataModel'), | ||
DataProber, | ||
MagicLens, | ||
PixelOperator, | ||
@@ -13,0 +15,0 @@ QueryDataModel, |
var CanvasOffscreenBuffer = require('../../util/CanvasOffscreenBuffer'), | ||
WebGlUtil = require('../../util/WebGl'), | ||
vec4 = require('gl-matrix/src/gl-matrix/vec4.js'), | ||
vec2 = require('gl-matrix/src/gl-matrix/vec2.js'), | ||
merge = require('mout/src/object/merge'), | ||
@@ -10,3 +11,4 @@ texParameter = [ | ||
pixelStore = [ [ 'UNPACK_FLIP_Y_WEBGL', true ] ], | ||
align1PixelStore = [ [ 'UNPACK_FLIP_Y_WEBGL', true ], [ 'UNPACK_ALIGNMENT', 1 ] ]; | ||
align1PixelStore = [ [ 'UNPACK_FLIP_Y_WEBGL', true ], [ 'UNPACK_ALIGNMENT', 1 ] ], | ||
EMPTY_LAYER_BUFFER = new Float32Array([ 0.0 ]); | ||
@@ -21,3 +23,2 @@ export default function GPUCompositor(queryDataModel, imageBuilder, colorHelper) { | ||
this.numLayers = this.metadata.layers; | ||
this.lutData = new Uint8Array(256 * 4); | ||
@@ -62,2 +63,12 @@ this.defaultIntensityData = new Uint8Array([255]); | ||
var maxTextureUnits = this.gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS), | ||
simultaneousLayers = (maxTextureUnits - 2) / 2; | ||
this.shaderLayers = simultaneousLayers < this.numLayers ? simultaneousLayers : this.numLayers | ||
this.lutData = [] | ||
for (var i = 0; i < this.shaderLayers; ++i) { | ||
this.lutData.push(new Uint8Array(256 * 4)); | ||
} | ||
// Set up GL resources | ||
@@ -73,3 +84,5 @@ this.glConfig = { | ||
vertexShader: require('../../util/WebGl/shaders/vertex/basic.c'), | ||
fragmentShader: require('./shaders/fragment/addLayerColor.c'), | ||
fragmentShader: WebGlUtil.TransformShader(require('./shaders/fragment/addLayerColor.c'), | ||
{'SIMULTANEOUS_LAYERS': this.shaderLayers}, | ||
{'inlineLoops': true}), | ||
mapping: 'default' | ||
@@ -79,3 +92,5 @@ }, | ||
vertexShader: require('../../util/WebGl/shaders/vertex/basic.c'), | ||
fragmentShader: require('./shaders/fragment/addLitLayerColor.c'), | ||
fragmentShader: WebGlUtil.TransformShader(require('./shaders/fragment/addLitLayerColor.c'), | ||
{'SIMULTANEOUS_LAYERS': this.shaderLayers}, | ||
{'inlineLoops': true}), | ||
mapping: 'default' | ||
@@ -116,4 +131,2 @@ }, | ||
{ id: 'intensityTexture', pixelStore: align1PixelStore, texParameter }, | ||
{ id: 'colorByTexture', pixelStore, texParameter }, | ||
{ id: 'lutTexture', pixelStore, texParameter }, | ||
{ id: 'ping', pixelStore, texParameter }, | ||
@@ -147,2 +160,7 @@ { id: 'pong', pixelStore, texParameter }, | ||
for (var i = 0; i < this.shaderLayers; ++i) { | ||
this.glConfig.resources.textures.push({ id: 'layerColorSampler_' + i, pixelStore: align1PixelStore, texParameter }); | ||
this.glConfig.resources.textures.push({ id: 'lutSampler_' + i, pixelStore: [[ 'UNPACK_ALIGNMENT', 1 ]], texParameter }); | ||
} | ||
this.glResources = WebGlUtil.createGLResources(this.gl, this.glConfig); | ||
@@ -201,2 +219,98 @@ | ||
GPUCompositor.prototype.getAndUseCurrentColorProgram = function() { | ||
var currentProgram = this.glResources.programs.colorProgram; | ||
// Using the coloring shader program | ||
if (this.hasNormal) { | ||
currentProgram = this.glResources.programs.lightColorProgram; | ||
} | ||
this.gl.useProgram(currentProgram); | ||
return currentProgram; | ||
} | ||
// -------------------------------------------------------------------------- | ||
GPUCompositor.prototype.uploadLayerTextures = function(minIdx, maxIdx) { | ||
var layerColorUnits = [], | ||
lutUnits = [], | ||
texCount = 2, | ||
bufferViewSizeList = [], | ||
bufferViewList = [], | ||
ranges = [], | ||
alphas = [], | ||
currentLutIndex = 0; | ||
for (var activeLayerIdx = minIdx; activeLayerIdx <= maxIdx; ++activeLayerIdx) { | ||
var lut = this.colorHelper.getLayerLut(activeLayerIdx), | ||
colorByName = this.colorHelper.getLayerColorByName(activeLayerIdx), | ||
range = this.metadata.ranges[colorByName]; | ||
if (this.colorHelper.getLayerVisible(activeLayerIdx)) { | ||
var layerBufferView = this.colorHelper.getLayerFloatData(activeLayerIdx); | ||
if (layerBufferView) { | ||
bufferViewList.push(layerBufferView); | ||
bufferViewSizeList.push([ this.width, this.height ]); | ||
} else { | ||
bufferViewList.push(new Float32Array([ this.findLayerConstantValue(activeLayerIdx) ])); | ||
bufferViewSizeList.push([ 1, 1 ]); | ||
} | ||
ranges.push(range[0]); | ||
ranges.push(range[1]); | ||
} else { | ||
// We only need these as placeholders so that the indices match up in the shader | ||
bufferViewList.push(EMPTY_LAYER_BUFFER); | ||
bufferViewSizeList.push([ 1, 1 ]); | ||
ranges.push(-1.0); | ||
ranges.push(-1.0); | ||
} | ||
alphas.push(this.colorHelper.getLayerAlpha(activeLayerIdx)); | ||
this.sampleLookupTable(lut, colorByName, range, currentLutIndex++); | ||
// Set up array of texture units to use for layer color textures | ||
layerColorUnits.push(texCount); | ||
lutUnits.push(texCount + this.shaderLayers); | ||
texCount += 1; | ||
} | ||
var currentProgram = this.getAndUseCurrentColorProgram(); | ||
// Set up the color by textures | ||
var colorByLoc = this.gl.getUniformLocation(currentProgram, "layerColorSampler"); | ||
this.gl.uniform1iv(colorByLoc, layerColorUnits); | ||
for (var i = 0; i < layerColorUnits.length; ++i) { | ||
this.gl.activeTexture(this.gl.TEXTURE0 + layerColorUnits[i]); | ||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.glResources.textures["layerColorSampler_" + i]); | ||
var lbvw = bufferViewSizeList[i][0]; | ||
var lbvh = bufferViewSizeList[i][1]; | ||
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, lbvw, lbvh, 0, this.gl.LUMINANCE, this.gl.FLOAT, bufferViewList[i]); | ||
} | ||
// Set up the lut samplers | ||
var lutLoc = this.gl.getUniformLocation(currentProgram, "lutSampler"); | ||
this.gl.uniform1iv(lutLoc, lutUnits); | ||
for (var i = 0; i < lutUnits.length; ++i) { | ||
this.gl.activeTexture(this.gl.TEXTURE0 + lutUnits[i]); | ||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.glResources.textures["lutSampler_" + i]); | ||
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, 256, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.lutData[i]); | ||
} | ||
// Set up the ranges | ||
var rangeLoc = this.gl.getUniformLocation(currentProgram, "layerRange"); | ||
this.gl.uniform2fv(rangeLoc, ranges); | ||
// Set up the alphas | ||
var alphaLoc = this.gl.getUniformLocation(currentProgram, "layerAlpha"); | ||
this.gl.uniform1fv(alphaLoc, alphas); | ||
} | ||
// -------------------------------------------------------------------------- | ||
GPUCompositor.prototype.render = function() { | ||
@@ -211,2 +325,9 @@ if (!this.orderData) { | ||
if (this.shaderLayers >= this.numLayers) { | ||
this.uploadLayerTextures(0, this.numLayers - 1); | ||
var colorProgram = this.getAndUseCurrentColorProgram(); | ||
var offsetLoc = this.gl.getUniformLocation(colorProgram, "orderOffset"); | ||
this.gl.uniform1i(offsetLoc, 0); | ||
} | ||
// Clear the ping pong fbo | ||
@@ -218,3 +339,2 @@ this.pingPong.clearFbo(); | ||
while (layerIdx--) { | ||
var orderLayerArray = this.extractLayerData(this.orderData, layerIdx, 1), | ||
@@ -230,13 +350,18 @@ lightingLayerArray = this.extractLayerData(this.intensityData, layerIdx, 1); | ||
for (var activeLayerIdx = 0; activeLayerIdx < this.numLayers; ++activeLayerIdx) { | ||
if (this.colorHelper.getLayerVisible(activeLayerIdx)) { | ||
var layerBufferView = this.colorHelper.getLayerFloatData(activeLayerIdx); | ||
if (layerBufferView) { | ||
this.layerBufferViewSize = [ this.width, this.height ]; | ||
} else { | ||
this.layerBufferViewSize = [ 1, 1 ]; | ||
this.defaultLayerBufferView[0] = this.findLayerConstantValue(activeLayerIdx); | ||
layerBufferView = this.defaultLayerBufferView; | ||
if (this.shaderLayers >= this.numLayers) { | ||
this.drawColorPass(orderLayerArray, lightingLayerArray); | ||
} else { | ||
for (var batchIndex = 0; batchIndex < this.numLayers; batchIndex += this.shaderLayers) { | ||
var endIndex = batchIndex + (this.shaderLayers - 1); | ||
if (endIndex >= this.numLayers) { | ||
endIndex = this.numLayers - 1; | ||
} | ||
this.drawColorPass(activeLayerIdx, orderLayerArray, lightingLayerArray, layerBufferView); | ||
this.uploadLayerTextures(batchIndex, endIndex); | ||
var colorProgram = this.getAndUseCurrentColorProgram(); | ||
var offsetLoc = this.gl.getUniformLocation(colorProgram, "orderOffset"); | ||
this.gl.uniform1i(offsetLoc, batchIndex); | ||
this.drawColorPass(orderLayerArray, lightingLayerArray); | ||
} | ||
@@ -255,3 +380,4 @@ } | ||
outputSize: [this.width, this.height], | ||
builder: this.imageBuilder | ||
builder: this.imageBuilder, | ||
arguments: this.queryDataModel.getQuery() | ||
}; | ||
@@ -276,3 +402,3 @@ | ||
GPUCompositor.prototype.sampleLookupTable = function(lut, colorBy, range) { | ||
GPUCompositor.prototype.sampleLookupTable = function(lut, colorBy, range, index) { | ||
@@ -286,6 +412,6 @@ function affine(value, inMin, inMax, outMin, outMax) { | ||
var color = lut.getColor(scalarValue); | ||
this.lutData[(i*4)] = color[0] * 255; | ||
this.lutData[(i*4)+1] = color[1] * 255; | ||
this.lutData[(i*4)+2] = color[2] * 255; | ||
this.lutData[(i*4)+3] = color[3] * 255; | ||
this.lutData[index][(i*4)] = color[0] * 255; | ||
this.lutData[index][(i*4)+1] = color[1] * 255; | ||
this.lutData[index][(i*4)+2] = color[2] * 255; | ||
this.lutData[index][(i*4)+3] = color[3] * 255; | ||
} | ||
@@ -362,3 +488,3 @@ } | ||
GPUCompositor.prototype.drawColorPass = function(activeLayerIdx, layerOrderData, layerLightingData, layerColorBy) { | ||
GPUCompositor.prototype.drawColorPass = function(layerOrderData, layerLightingData) { | ||
// Draw to the color fbo on this pass | ||
@@ -379,18 +505,2 @@ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.glResources.framebuffers.colorFbo); | ||
// Send uniform specifying the layer we are actively targeting this time around | ||
var activeLayerLoc = this.gl.getUniformLocation(currentProgram, "activeLayer"); | ||
this.gl.uniform1i(activeLayerLoc, activeLayerIdx); | ||
var colorByName = this.colorHelper.getLayerColorByName(activeLayerIdx); | ||
var curRange = this.metadata.ranges[colorByName]; | ||
var rangeLoc = this.gl.getUniformLocation(currentProgram, "activeLayerRange"); | ||
this.gl.uniform2fv(rangeLoc, curRange); | ||
var lut = this.colorHelper.getLayerLut(activeLayerIdx); | ||
this.sampleLookupTable(lut, colorByName, curRange); | ||
var layerAlpha = this.colorHelper.getLayerAlpha(activeLayerIdx); | ||
var alphaLoc = this.gl.getUniformLocation(currentProgram, "activeLayerAlpha"); | ||
this.gl.uniform1f(alphaLoc, layerAlpha); | ||
var texCount = 0; | ||
@@ -438,20 +548,2 @@ | ||
// Set up the color by texture | ||
var colorByLoc = this.gl.getUniformLocation(currentProgram, "layerColorSampler"); | ||
this.gl.uniform1i(colorByLoc, texCount); | ||
this.gl.activeTexture(this.gl.TEXTURE0 + texCount); | ||
texCount += 1; | ||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.glResources.textures.colorByTexture); | ||
var lbvw = this.layerBufferViewSize[0]; | ||
var lbvh = this.layerBufferViewSize[1]; | ||
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, lbvw, lbvh, 0, this.gl.LUMINANCE, this.gl.FLOAT, layerColorBy); | ||
// Set up the lookup texture (contains alphas to multiply each layer color) | ||
var lutLoc = this.gl.getUniformLocation(currentProgram, "lutSampler"); | ||
this.gl.uniform1i(lutLoc, texCount); | ||
this.gl.activeTexture(this.gl.TEXTURE0 + texCount); | ||
texCount += 1; | ||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.glResources.textures.lutTexture); | ||
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, 256, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.lutData); | ||
// Draw the rectangle. | ||
@@ -496,2 +588,2 @@ this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); | ||
this.lightProperties = merge(this.lightProperties, lightProps); | ||
} | ||
} |
@@ -18,3 +18,3 @@ var Monologue = require('monologue.js'), | ||
this.useIntensity = true; | ||
this.useNormals = true; | ||
this.useNormals = false; | ||
this.pipelineModel = pipelineModel; | ||
@@ -37,3 +37,11 @@ this.lookupTableManager = lookupTableManager; | ||
this.compositor = this.compositors[1]; | ||
this.controlWidgets = [ "LookupTableManagerWidget", "LightPropertiesWidget", "CompositeControl", "QueryDataModelWidget" ]; | ||
this.controlWidgets = [ "LookupTableManagerWidget", "CompositeControl", "QueryDataModelWidget" ]; | ||
if (this.metadata.light.indexOf('normal') >= 0) { | ||
this.controlWidgets = [ "LookupTableManagerWidget", "LightPropertiesWidget", "CompositeControl", "QueryDataModelWidget" ]; | ||
if (this.metadata.light.indexOf('intensity') < 0) { | ||
this.useNormals = true; | ||
} | ||
} | ||
this.resetOpacities(); | ||
@@ -71,2 +79,8 @@ | ||
// ---------------------------------------------------------------------------- | ||
MultiColorBySortedCompositeImageBuilder.prototype.getDimensions = function() { | ||
return this.metadata.dimensions; | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -73,0 +87,0 @@ |
@@ -22,2 +22,8 @@ var CanvasOffscreenBuffer = require('../../util/CanvasOffscreenBuffer'), | ||
PixelOperationImageBuilder.prototype.getDimensions = function() { | ||
return this.dataSize; | ||
} | ||
// ---------------------------------------------------------------------------- | ||
PixelOperationImageBuilder.prototype.setOperation = function(expression) { | ||
@@ -24,0 +30,0 @@ this.operation = expression; |
@@ -27,2 +27,8 @@ var Monologue = require('monologue.js'), | ||
// ---------------------------------------------------------------------------- | ||
QueryDataModelImageBuilder.prototype.getDimensions = function() { | ||
return [ this.lastQueryImage.width, this.lastQueryImage.height ]; | ||
} | ||
QueryDataModelImageBuilder.prototype.update = function() { | ||
@@ -29,0 +35,0 @@ this.queryDataModel.fetchData(); |
@@ -24,2 +24,6 @@ var Monologue = require('monologue.js'), | ||
WebGlImageBuidler.prototype.getDimensions = function() { | ||
return this.compositePipeline.dimensions; | ||
} | ||
// Update the composite pipeline query | ||
@@ -26,0 +30,0 @@ // Sample query: "BACADAGBHBIB" means color layers B, C, and D by field A, |
@@ -66,2 +66,8 @@ var Monologue = require('monologue.js'), | ||
WebGLSortedVolumeImageBuilder.prototype.getDimensions = function() { | ||
return this.metadata.dimensions; | ||
} | ||
// -------------------------------------------------------------------------- | ||
WebGLSortedVolumeImageBuilder.prototype.update = function() { | ||
@@ -68,0 +74,0 @@ if(this.useIntensity) { |
@@ -241,1 +241,50 @@ // Show GL informations | ||
} | ||
//---------------------------------------------------------------------------- | ||
export function TransformShader(shaderString, variableDict, config) { | ||
// First do all the variable replacements | ||
for (var vname in variableDict) { | ||
var value = variableDict[vname], | ||
r = RegExp("\\$\\{" + vname + "\\}", 'g'); | ||
shaderString = shaderString.replace(r, value); | ||
} | ||
// Now check if any loops need to be inlined | ||
if (config.inlineLoops) { | ||
var loopRegex = /\/\/@INLINE_LOOP([\s\S]+?)(?=\/\/@INLINE_LOOP)\/\/@INLINE_LOOP/, | ||
match = shaderString.match(loopRegex); | ||
while (match) { | ||
var entireMatch = match[0], | ||
capture = match[1], | ||
infoRegex = /^\s*\(([^\),]+)\s*,\s*([^\),]+)\s*,\s*([^\)]+)\)/, | ||
infoRegexMatch = capture.match(infoRegex), | ||
loopVariableName = infoRegexMatch[1], | ||
loopMin = infoRegexMatch[2], | ||
loopCount = infoRegexMatch[3], | ||
forLoop = capture.replace(infoRegex, ''), | ||
loopContentsRegex = /^\s*[^\{]+\{([\s\S]+?)\s*\}\s*$/, | ||
forLoopMatch = forLoop.match(loopContentsRegex), | ||
loopBody = forLoopMatch[1], | ||
unrolledContents = "", | ||
loopBodyReplacer = new RegExp(loopVariableName, 'g'); | ||
for (var i = loopMin; i < loopCount; ++i) { | ||
unrolledContents += loopBody.replace(loopBodyReplacer, i); | ||
unrolledContents += '\n'; | ||
} | ||
shaderString = shaderString.replace(loopRegex, unrolledContents); | ||
match = shaderString.match(loopRegex); | ||
} | ||
} | ||
if (config.debug) { | ||
console.log("Transformed shader string:"); | ||
console.log(shaderString); | ||
} | ||
return shaderString; | ||
} |
{ | ||
"name": "tonic-image-builder", | ||
"description": "JavaScript library used to gather image generation based on various input data.", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"license": "BSD-3-Clause", | ||
@@ -6,0 +6,0 @@ "main": "./src/index.js", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2163025
160
12928